Refactor auth and init README

This commit is contained in:
srosset81
2026-07-10 18:35:35 +02:00
parent 69ebc2b164
commit 85a3c4b771
9 changed files with 233 additions and 180 deletions
+110
View File
@@ -0,0 +1,110 @@
# Refine providers for NextGraph
Data, auth and live providers that can be used in any Refine application
## Usage
> For a quick overview, have a look at our [example app](./examples/antd-app/).
### 1. Define your shapes
Refine relies on the concept of "resources", which are data types, to manage your application in a structured way. NextGraph identifies data types using ShEx shapes which are transformed into "shape types" using [its own tool](https://docs.nextgraph.org/en/reference/shex-orm/).
Create a `/shapes` directory and, within it, create two subdirectories: `/shex` and `/orm`
Within the `/shapes/shex` directory, create a ShEx file with the shape of the data you want to handle.
Here's an example of a shape for an ActivityStreams Note:
````shex
PREFIX as: <https://www.w3.org/ns/activitystreams#>
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
as:Note {
a [as:Note] ;
as:title xsd:string ;
as:content xsd:string ? ;
}
````
### 2. Pass the shape types to the data provider
Go to the `/shapes` directory and run the following command:
```bash
npx rdf-orm build --input ./shex --output ./orm
```
This will generate three files in the `/orm` subdirectory:
- `note.schema.ts`
- `note.shapeTypes.ts`
- `note.typings.ts`
Now within the `shapes` directory, create a `shapeTypes.ts` like this:
```ts
import { ShapeTypes } from "@ng-org/refine-providers";
import { NoteShapeType } from "./orm/note.shapeTypes";
const shapeTypes : ShapeTypes = {
notes: NoteShapeType
}
export default shapeTypes;
```
### 3. Add the providers to Refine
```ts
import { authProvider, dataProvider, liveProvider } from "@ng-org/refine-providers";
import shapeTypes from "./shapes/shapeTypes";
const ngAuthProvider = authProvider();
const ngDataProvider = dataProvider({ shapeTypes, authProvider: ngAuthProvider })
const ngLiveProvider = liveProvider({ shapeTypes })
const App = () => (
<Refine
authProvider={ngAuthProvider}
dataProvider={ngDataProvider}
liveProvider={ngLiveProvider}
options={{ liveMode: "auto", ... }}
...
>
...
```
### 4. Create the React pages
You can now call the various hooks provided by Refine to create, read, update or delete resources.
For `useForm`, `useTable`, `useShow`, you can pass the `Note` generated by NextGraph CLI.
Here's an example of a simple list view:
```ts
import { List, useTable } from "@refinedev/antd";
import { Table } from "antd";
import { Note } from "./shapes/orm/notes.typings";
export const PostList = () => {
const { tableProps } = useTable<Note>();
return (
<List>
<Table {...tableProps} owKey="@graph">
<Table.Column dataIndex="@graph" title="ID" />
<Table.Column dataIndex="title" title="Title" />
<Table.Column dataIndex="content" title="Content" />
</Table>
</List>
);
};
```
## Commands
```
pnpm build
pnpm watch
```
+29 -46
View File
@@ -1,56 +1,40 @@
import {
Refine,
Authenticated,
} from "@refinedev/core";
import {
useNotificationProvider,
ThemedLayout,
ErrorComponent,
RefineThemes,
} from "@refinedev/antd";
import {
DashboardOutlined,
} from "@ant-design/icons";
import routerProvider, {
NavigateToResource,
CatchAllNavigate,
UnsavedChangesNotifier,
DocumentTitleHandler,
} from "@refinedev/react-router";
import { BrowserRouter, Routes, Route, Outlet, Navigate } from "react-router";
import { Refine, Authenticated } from "@refinedev/core";
import { useNotificationProvider, ThemedLayout, ErrorComponent, RefineThemes } from "@refinedev/antd";
import routerProvider, { NavigateToResource, CatchAllNavigate, UnsavedChangesNotifier, DocumentTitleHandler } from "@refinedev/react-router";
import { BrowserRouter, Routes, Route, Outlet } from "react-router";
import { DashboardOutlined } from "@ant-design/icons";
import { App as AntdApp, ConfigProvider } from "antd";
import "@ant-design/v5-patch-for-react-19";
import "@refinedev/antd/dist/reset.css";
import { authProvider, dataProvider, liveProvider } from "@ng-org/refine-providers";
import { authProvider as ngAuthProvider, dataProvider, liveProvider } from "@ng-org/refine-providers";
import dataModels from "./shapes/data-models";
import shapeTypes from "./shapes/shapeTypes";
import AuthPage from "./components/AuthPage";
import { PostList, PostCreate, PostEdit, PostShow } from "../src/pages/posts";
import { DashboardPage } from "../src/pages/dashboard";
import { getSession } from "./utils/initNg";
const authProvider = ngAuthProvider();
const App: React.FC = () => (
<BrowserRouter basename={import.meta.env.MODE === 'production' ? '/nextgraph-refine-app/' : '/'}>
<ConfigProvider theme={RefineThemes.Blue}>
<AntdApp>
<Refine
// authProvider={authProvider}
dataProvider={dataProvider({ dataModels, getSession })}
liveProvider={liveProvider({ dataModels })}
authProvider={authProvider}
dataProvider={dataProvider({ shapeTypes, authProvider })}
liveProvider={liveProvider({ shapeTypes })}
routerProvider={routerProvider}
resources={[
// {
// name: "dashboard",
// list: "/",
// meta: {
// label: "Dashboard",
// icon: <DashboardOutlined />,
// },
// },
{
name: "dashboard",
list: "/",
meta: {
label: "Dashboard",
icon: <DashboardOutlined />,
},
},
{
name: "posts",
list: "/posts",
@@ -73,18 +57,17 @@ const App: React.FC = () => (
<Routes>
<Route
element={
// <Authenticated
// key="authenticated-routes"
// fallback={<CatchAllNavigate to="/login" />}
// >
<Authenticated
key="authenticated-routes"
fallback={<CatchAllNavigate to="/login" />}
>
<ThemedLayout>
<Outlet />
</ThemedLayout>
// </Authenticated>
</Authenticated>
}
>
<Route index element={<Navigate to="/" />} />
{/* <Route index element={<DashboardPage />} /> */}
<Route index element={<DashboardPage />} />
<Route path="/posts">
<Route index element={<PostList />} />
@@ -94,7 +77,7 @@ const App: React.FC = () => (
</Route>
</Route>
{/* <Route
<Route
element={
<Authenticated key="auth-pages" fallback={<Outlet />}>
<NavigateToResource resource="posts" />
@@ -103,15 +86,15 @@ const App: React.FC = () => (
>
<Route path="/login" element={<AuthPage />} />
<Route path="/register" element={<AuthPage />} />
</Route> */}
</Route>
<Route
element={
// <Authenticated key="catch-all">
<Authenticated key="catch-all">
<ThemedLayout>
<Outlet />
</ThemedLayout>
// </Authenticated>
</Authenticated>
}
>
<Route path="*" element={<ErrorComponent />} />
@@ -1,10 +0,0 @@
import { DataModels } from "@ng-org/refine-providers";
import { NoteShapeType } from "./orm/post.shapeTypes";
const dataModels : DataModels = {
posts: {
shapeType: NoteShapeType
}
}
export default dataModels;
@@ -0,0 +1,8 @@
import { ShapeTypes } from "@ng-org/refine-providers";
import { NoteShapeType } from "./orm/post.shapeTypes";
const shapeTypes : ShapeTypes = {
posts: NoteShapeType
}
export default shapeTypes;
-17
View File
@@ -1,17 +0,0 @@
import { ng, init, Session } from "@ng-org/web";
import { initNg } from "@ng-org/orm";
export let session: Session | undefined;
init(
async (event: any) => {
session = event.session;
// The ORM needs to have access to ng,
// the interface to the engine running in WASM.
initNg(ng, session!);
},
true,
[],
);
export const getSession = () => session;
+34 -56
View File
@@ -1,77 +1,45 @@
import {
type AuthProvider,
} from "@refinedev/core";
import { ng, init, Session } from "@ng-org/web";
import { initNg } from "@ng-org/orm";
import { AuthProvider } from "./types";
import {
ng,
init as initNgWeb,
type Session as NextGraphSession,
} from "@ng-org/web";
import { initNg as initNgSignals } from "@ng-org/orm";
const authProvider: () => AuthProvider = () => {
let session: Session | undefined;
export type NgAuthProvider = AuthProvider & {
getSession: () => NextGraphSession | undefined;
};
/** The session with the NextGraph engine or undefined if not loaded. */
export let session: NextGraphSession | undefined;
export let resolveSessionPromise: (
value: NextGraphSession | PromiseLike<NextGraphSession>,
) => void;
export let rejectSessionPromise: (reason?: any) => void;
/** Resolves to the current NextGraph session. */
export let sessionPromise: Promise<NextGraphSession> = new Promise(
(resolve, reject) => {
resolveSessionPromise = resolve;
rejectSessionPromise = reject;
// TODO load the session only when the login method is called
init(
async (event: any) => {
session = event.session;
// The ORM needs to have access to ng,
// the interface to the engine running in WASM.
initNg(ng, session!);
},
true,
[],
);
const authProvider: NgAuthProvider = {
return ({
login: async ({ brokerUrl }) => {
// try {
// await initNgWeb(
// async (event: any) => {
// session = event.session;
// session!.ng ??= ng;
// localStorage.setItem("logged", "true");
// console.log('session', session);
// resolveSessionPromise(session!);
// initNgSignals(ng, session!);
// },
// true,
// [],
// );
// } catch (error) {
// rejectSessionPromise(error);
// }
console.log('login', brokerUrl);
return {
success: true
};
},
register: async ({ brokerUrl }) => {
console.log('brokerUrl', brokerUrl);
console.log('register', brokerUrl);
return {
success: true
};
},
logout: async () => {
localStorage.removeItem("logged");
console.log('logout');
return {
success: true,
redirectTo: "/login",
};
},
onError: async (error) => {
console.log('onError', error);
if (error.response?.status === 401) {
return {
logout: true,
@@ -81,16 +49,26 @@ const authProvider: NgAuthProvider = {
return { error };
},
check: async () => {
return ({ authenticated: localStorage.getItem("logged") === 'true' });
console.log('check');
return {
authenticated: true
};
},
getPermissions: async (params) => params?.permissions,
getIdentity: async () => ({
getPermissions: async (params) => {
console.log('getPermissions', params);
return params?.permissions
},
getIdentity: async () => {
console.log('getIdentity');
return {
id: 1,
name: "Jane Doe",
avatar:
"https://unsplash.com/photos/IWLOvomUmWU/download?force=true&w=640",
}),
getSession : () => session
};
},
getSession : () => session
});
}
export default authProvider;
+7 -7
View File
@@ -4,11 +4,11 @@ import { getObjects } from "@ng-org/orm";
import { addIdToObject, isGraphKey, getSignalObject } from "./utils";
import { DataProviderConfig } from "./types";
const dataProvider = ({ dataModels, getSession }: DataProviderConfig) : DataProvider => ({
const dataProvider = ({ shapeTypes, authProvider }: DataProviderConfig) : DataProvider => ({
getList: async ({ resource, pagination, sorters, filters, meta }) => {
console.log('list', resource, pagination, sorters, filters, meta);
const objects = await getObjects(dataModels[resource].shapeType, "");
const objects = await getObjects(shapeTypes[resource], "");
return {
data: [...objects].map(addIdToObject) as any,
@@ -18,7 +18,7 @@ const dataProvider = ({ dataModels, getSession }: DataProviderConfig) : DataProv
create: async ({ resource, variables, meta }) => {
console.log("create", resource, variables, meta);
const session = getSession();
const session = authProvider.getSession();
const docNuri = await ng.doc_create(
session!.session_id,
@@ -28,7 +28,7 @@ const dataProvider = ({ dataModels, getSession }: DataProviderConfig) : DataProv
undefined
);
const set = await getSignalObject(dataModels[resource].shapeType, [docNuri]);
const set = await getSignalObject(shapeTypes[resource], [docNuri]);
set.add({
"@graph": docNuri,
@@ -46,7 +46,7 @@ const dataProvider = ({ dataModels, getSession }: DataProviderConfig) : DataProv
update: async ({ resource, id, variables, meta }) => {
console.log('update', resource, id, variables, meta);
const set = await getSignalObject(dataModels[resource].shapeType, [`${id}`]);
const set = await getSignalObject(shapeTypes[resource], [`${id}`]);
const oldData = set.first()!;
@@ -71,7 +71,7 @@ const dataProvider = ({ dataModels, getSession }: DataProviderConfig) : DataProv
deleteOne: async ({ resource, id, variables, meta }) => {
console.log('deleteOne', resource, id, variables, meta);
const set = await getSignalObject(dataModels[resource].shapeType, [`${id}`]);
const set = await getSignalObject(shapeTypes[resource], [`${id}`]);
const oldData = set.first()!;
@@ -85,7 +85,7 @@ const dataProvider = ({ dataModels, getSession }: DataProviderConfig) : DataProv
console.log('getOne', resource, id, meta);
// The ID should be a graph URI, but if it's an object key, we can use the subjects filter to the the object
const objects = await getObjects(dataModels[resource].shapeType, isGraphKey(id) ? `${id}` : { subjects: [`${id}`] });
const objects = await getObjects(shapeTypes[resource], isGraphKey(id) ? `${id}` : { subjects: [`${id}`] });
return {
data: addIdToObject([...objects][0])
+4 -4
View File
@@ -3,7 +3,7 @@ import { watch } from "@ng-org/alien-deepsignals"
import { getSignalObject, parsePatches } from "./utils";
import { LiveProviderConfig, UnwatchFn } from "./types";
const liveProvider = ({ dataModels }: LiveProviderConfig) : LiveProvider => ({
const liveProvider = ({ shapeTypes }: LiveProviderConfig) : LiveProvider => ({
subscribe: async ({ channel, params, types, callback, meta }) : Promise<UnwatchFn> => {
console.log('subscribe', channel, params?.subscriptionType, params, types, meta);
@@ -12,11 +12,11 @@ const liveProvider = ({ dataModels }: LiveProviderConfig) : LiveProvider => ({
if (channel.startsWith("resources/")) {
const resource = channel.split('/')[1];
if (dataModels[resource]) {
if (shapeTypes[resource]) {
if (ids?.length > 0) {
// We don't close the subscription because we want to keep listening for changes
const set = await getSignalObject(dataModels[resource].shapeType, ids, false);
const set = await getSignalObject(shapeTypes[resource], ids, false);
const { stopListening } = watch(set, ({ newValue, patches }) => {
console.log('object modified', newValue, patches);
@@ -37,7 +37,7 @@ const liveProvider = ({ dataModels }: LiveProviderConfig) : LiveProvider => ({
} else {
// We don't close the subscription because we want to keep listening for changes
const set = await getSignalObject(dataModels[resource].shapeType, undefined, false);
const set = await getSignalObject(shapeTypes[resource], undefined, false);
const { stopListening } = watch(set, ({ newValue, patches }) => {
console.log('objects list modified', newValue, patches);
+9 -8
View File
@@ -1,19 +1,20 @@
import type { AuthProvider as CoreAuthProvider } from "@refinedev/core";
import type { ShapeType } from "@ng-org/shex-orm";
import type { Session } from "@ng-org/web";
export type DataModel = {
shapeType: ShapeType<any>;
}
export type ShapeTypes = Record<string, ShapeType<any>>;
export type DataModels = Record<string, DataModel>;
export type DataProviderConfig = {
dataModels: DataModels;
export type AuthProvider = CoreAuthProvider & {
getSession: () => Session | undefined;
};
export type DataProviderConfig = {
shapeTypes: ShapeTypes;
authProvider: AuthProvider;
};
export type LiveProviderConfig = {
dataModels: DataModels;
shapeTypes: ShapeTypes;
};
export type UnwatchFn = () => void;