From 57f53c8698586756aa161609eb44535ce7ac6045 Mon Sep 17 00:00:00 2001 From: artahian Date: Fri, 12 Dec 2025 15:00:54 -0800 Subject: [PATCH 1/4] Add mutation and user context examples --- src/server/example/db.ts | 1 + src/server/example/index.ts | 67 +++++++++++++++++++++++++++++++++++-- 2 files changed, 66 insertions(+), 2 deletions(-) diff --git a/src/server/example/db.ts b/src/server/example/db.ts index 140cf56..5d03747 100644 --- a/src/server/example/db.ts +++ b/src/server/example/db.ts @@ -4,6 +4,7 @@ export const dbExampleItems = new Store('exampleItems', { schema: { title: schema.string(), createdAt: schema.date(), + userId: schema.userId(), }, indexes: [] }); diff --git a/src/server/example/index.ts b/src/server/example/index.ts index f25efb9..cd24a18 100644 --- a/src/server/example/index.ts +++ b/src/server/example/index.ts @@ -1,18 +1,81 @@ import z from 'zod'; -import { Module, ObjectId } from 'modelence/server'; +import { AuthError } from 'modelence'; +import { Module, ObjectId, UserInfo, getConfig } from 'modelence/server'; import { dbExampleItems } from './db'; export default new Module('example', { + configSchema: { + itemsPerPage: { + type: 'number', + default: 5, + isPublic: false, + }, + }, + stores: [dbExampleItems], queries: { - getItem: async (args: unknown) => { + getItem: async (args: unknown, { user }: { user: UserInfo | null }) => { + if (!user) { + throw new AuthError('Not authenticated'); + } + const { itemId } = z.object({ itemId: z.string() }).parse(args); const exampleItem = await dbExampleItems.requireOne({ _id: new ObjectId(itemId) }); + + if (exampleItem.userId.toString() !== user.id) { + throw new AuthError('Not authorized'); + } + return { title: exampleItem.title, createdAt: exampleItem.createdAt, }; }, + + getItems: async (_args: unknown, { user }: { user: UserInfo | null }) => { + if (!user) { + throw new AuthError('Not authenticated'); + } + + const itemsPerPage = getConfig('example.itemsPerPage') as number; + const exampleItems = await dbExampleItems.fetch({}, { limit: itemsPerPage }) + return exampleItems.map((item) => ({ + _id: item._id.toString(), + title: item.title, + createdAt: item.createdAt, + })); + } }, + + mutations: { + createItem: async (args: unknown, { user }: { user: UserInfo | null }) => { + if (!user) { + throw new AuthError('Not authenticated'); + } + + const { title } = z.object({ title: z.string() }).parse(args); + + await dbExampleItems.insertOne({ title, createdAt: new Date(), userId: new ObjectId(user.id) }); + }, + + updateItem: async (args: unknown, { user }: { user: UserInfo | null }) => { + if (!user) { + throw new AuthError('Not authenticated'); + } + + const { itemId, title } = z.object({ itemId: z.string(), title: z.string() }).parse(args); + + const exampleItem = await dbExampleItems.requireOne({ _id: new ObjectId(itemId) }); + if (exampleItem.userId.toString() !== user.id) { + throw new AuthError('Not authorized'); + } + + const { modifiedCount } = await dbExampleItems.updateOne({ _id: new ObjectId(itemId) }, { $set: { title } }); + + if (modifiedCount === 0) { + throw new Error('Item not found'); + } + }, + } }); From eef735b7e8d14e2e61310ea3eb99749b7227d082 Mon Sep 17 00:00:00 2001 From: artahian Date: Fri, 12 Dec 2025 15:05:49 -0800 Subject: [PATCH 2/4] Add cron job example --- src/server/example/cron.ts | 9 +++++++++ src/server/example/index.ts | 5 +++++ 2 files changed, 14 insertions(+) create mode 100644 src/server/example/cron.ts diff --git a/src/server/example/cron.ts b/src/server/example/cron.ts new file mode 100644 index 0000000..4461a17 --- /dev/null +++ b/src/server/example/cron.ts @@ -0,0 +1,9 @@ +import { time } from 'modelence'; + +export const dailyTestCron = { + description: 'Daily cron job example', + interval: time.days(1), + handler: async () => { + // This is just an example. Any code written here will run daily. + }, +}; diff --git a/src/server/example/index.ts b/src/server/example/index.ts index cd24a18..9d66e52 100644 --- a/src/server/example/index.ts +++ b/src/server/example/index.ts @@ -2,6 +2,7 @@ import z from 'zod'; import { AuthError } from 'modelence'; import { Module, ObjectId, UserInfo, getConfig } from 'modelence/server'; import { dbExampleItems } from './db'; +import { dailyTestCron } from './cron'; export default new Module('example', { configSchema: { @@ -77,5 +78,9 @@ export default new Module('example', { throw new Error('Item not found'); } }, + }, + + cronJobs: { + dailyTest: dailyTestCron } }); From e951183e851afe73724d0d1fdde26af2c08a6fc8 Mon Sep 17 00:00:00 2001 From: artahian Date: Thu, 18 Dec 2025 17:10:10 -0800 Subject: [PATCH 3/4] Add placeholder to home page --- src/client/components/Page.tsx | 12 +++--- src/client/pages/HomePage.tsx | 63 ++++++++++++++++++------------- src/client/pages/NotFoundPage.tsx | 4 +- 3 files changed, 46 insertions(+), 33 deletions(-) diff --git a/src/client/components/Page.tsx b/src/client/components/Page.tsx index cb13d7f..443d6b7 100644 --- a/src/client/components/Page.tsx +++ b/src/client/components/Page.tsx @@ -7,10 +7,12 @@ import { Link } from 'react-router-dom'; import { useSession } from 'modelence/client'; import LoadingSpinner from '@/client/components/LoadingSpinner'; import { Button } from '@/client/components/ui/Button'; +import { cn } from '@/client/lib/utils'; interface PageProps { children?: React.ReactNode; isLoading?: boolean; + className?: string; } function Header() { @@ -44,10 +46,10 @@ function PageWrapper({ children }: { children: React.ReactNode }) { return
{children}
; } -function PageBody({ children, isLoading = false }: PageProps) { +function PageBody({ children, className, isLoading = false }: PageProps) { return ( -
-
+
+
{isLoading ? (
@@ -60,11 +62,11 @@ function PageBody({ children, isLoading = false }: PageProps) { ); } -export default function Page({ children, isLoading = false }: PageProps) { +export default function Page({ children, className, isLoading = false }: PageProps) { return (
- {children} + {children} ); } diff --git a/src/client/pages/HomePage.tsx b/src/client/pages/HomePage.tsx index cfbc2dd..656d5ca 100644 --- a/src/client/pages/HomePage.tsx +++ b/src/client/pages/HomePage.tsx @@ -1,34 +1,45 @@ import logo from '@/client/assets/modelence.svg'; +import Page from '@/client/components/Page'; export default function HomePage() { return ( -
-
-
- Modelence Logo -
-

Hello, World!

-

Welcome to your new Modelence app

- -
-

- This is your home page placeholder - {' '} - - src/client/pages/HomePage.tsx - -

-
+ +
+ - +
+
+ ); +} + +// TODO: Replace with actual content +function PlaceholderView() { + return ( +
+
+ Modelence Logo +
+

Hello, World!

+

Welcome to your new Modelence app

+ +
+

+ This is your home page placeholder - {' '} + + src/client/pages/HomePage.tsx + +

+
+ +
); diff --git a/src/client/pages/NotFoundPage.tsx b/src/client/pages/NotFoundPage.tsx index c4f7840..d36e9bf 100644 --- a/src/client/pages/NotFoundPage.tsx +++ b/src/client/pages/NotFoundPage.tsx @@ -7,12 +7,12 @@ export default function NotFoundPage() { return (
- + 404 - +

Page not found

From 8b58a7414864737d92beb611568af32d7abc6399 Mon Sep 17 00:00:00 2001 From: artahian Date: Thu, 18 Dec 2025 19:32:41 -0800 Subject: [PATCH 4/4] Add mutation example --- src/client/pages/ExamplePage.tsx | 9 +++++++-- src/client/pages/HomePage.tsx | 1 - 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/client/pages/ExamplePage.tsx b/src/client/pages/ExamplePage.tsx index 3d490b1..71bcbf3 100644 --- a/src/client/pages/ExamplePage.tsx +++ b/src/client/pages/ExamplePage.tsx @@ -1,7 +1,7 @@ import { useCallback } from 'react'; import { useParams } from 'react-router-dom'; -import { useQuery, useQueryClient } from '@tanstack/react-query'; -import { modelenceQuery, createQueryKey } from '@modelence/react-query'; +import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; +import { modelenceQuery, modelenceMutation, createQueryKey } from '@modelence/react-query'; type ExampleItem = { title: string; @@ -17,6 +17,10 @@ export default function ExamplePage() { enabled: !!itemId, }); + const { mutate: createItem, isPending: isCreatingItem } = useMutation({ + ...modelenceMutation('example.createItem'), + }); + const invalidateItem = useCallback(() => { queryClient.invalidateQueries({ queryKey: createQueryKey('example.getItem', { itemId }) }); }, [queryClient, itemId]); @@ -33,6 +37,7 @@ export default function ExamplePage() { )} +
); } diff --git a/src/client/pages/HomePage.tsx b/src/client/pages/HomePage.tsx index 656d5ca..f581ace 100644 --- a/src/client/pages/HomePage.tsx +++ b/src/client/pages/HomePage.tsx @@ -6,7 +6,6 @@ export default function HomePage() {
-
);