Refactor auth and init README
This commit is contained in:
@@ -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
|
||||
```
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
+35
-57
@@ -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
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export default authProvider;
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user