@proofkit/cli 1.0.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +120 -0
- package/LICENSE.md +21 -0
- package/README.md +19 -0
- package/dist/acorn-AKFTBDM6.js +15 -0
- package/dist/angular-BOQ6FHSU.js +2 -0
- package/dist/babel-ZTOORN7K.js +15 -0
- package/dist/chunk-4LISTI44.js +1 -0
- package/dist/estree-KOJPX4S6.js +36 -0
- package/dist/flow-RCI44GYZ.js +19 -0
- package/dist/glimmer-GV5EF5E4.js +30 -0
- package/dist/graphql-YXQNPQWM.js +29 -0
- package/dist/html-ZAJTRROK.js +22 -0
- package/dist/index.js +300 -0
- package/dist/markdown-Q75DTQI7.js +63 -0
- package/dist/meriyah-32K7GBV5.js +4 -0
- package/dist/postcss-WWYO4PGL.js +54 -0
- package/dist/typescript-M6N7JDNQ.js +20 -0
- package/dist/yaml-LY7PNAYV.js +161 -0
- package/index.d.ts +19 -0
- package/package.json +122 -0
- package/template/extras/_cursor/conditional-rules/nextjs-framework.mdc +51 -0
- package/template/extras/_cursor/conditional-rules/npm.mdc +60 -0
- package/template/extras/_cursor/conditional-rules/pnpm.mdc +65 -0
- package/template/extras/_cursor/conditional-rules/yarn.mdc +60 -0
- package/template/extras/_cursor/rules/cursor-rules.mdc +88 -0
- package/template/extras/_cursor/rules/filemaker-api.mdc +176 -0
- package/template/extras/_cursor/rules/troubleshooting-patterns.mdc +240 -0
- package/template/extras/_cursor/rules/ui-components.mdc +57 -0
- package/template/extras/config/_eslint.js +27 -0
- package/template/extras/config/_prettier.config.js +6 -0
- package/template/extras/config/drizzle-config-mysql.ts +12 -0
- package/template/extras/config/drizzle-config-postgres.ts +12 -0
- package/template/extras/config/drizzle-config-sqlite.ts +12 -0
- package/template/extras/config/fmschema.config.mjs +9 -0
- package/template/extras/config/get-query-client.ts +6 -0
- package/template/extras/config/postcss.config.cjs +7 -0
- package/template/extras/config/query-provider-vite.tsx +19 -0
- package/template/extras/config/query-provider.tsx +21 -0
- package/template/extras/emailProviders/none/email.tsx +24 -0
- package/template/extras/emailProviders/plunk/email.tsx +26 -0
- package/template/extras/emailProviders/plunk/service.ts +4 -0
- package/template/extras/emailProviders/resend/email.tsx +23 -0
- package/template/extras/emailProviders/resend/service.ts +4 -0
- package/template/extras/fmaddon-auth/app/(main)/auth/profile/actions.ts +93 -0
- package/template/extras/fmaddon-auth/app/(main)/auth/profile/page.tsx +27 -0
- package/template/extras/fmaddon-auth/app/(main)/auth/profile/profile-form.tsx +56 -0
- package/template/extras/fmaddon-auth/app/(main)/auth/profile/reset-password-form.tsx +110 -0
- package/template/extras/fmaddon-auth/app/(main)/auth/profile/schema.ts +19 -0
- package/template/extras/fmaddon-auth/app/auth/forgot-password/actions.ts +37 -0
- package/template/extras/fmaddon-auth/app/auth/forgot-password/forgot-form.tsx +41 -0
- package/template/extras/fmaddon-auth/app/auth/forgot-password/page.tsx +21 -0
- package/template/extras/fmaddon-auth/app/auth/forgot-password/schema.ts +5 -0
- package/template/extras/fmaddon-auth/app/auth/login/actions.ts +34 -0
- package/template/extras/fmaddon-auth/app/auth/login/login-form.tsx +64 -0
- package/template/extras/fmaddon-auth/app/auth/login/page.tsx +26 -0
- package/template/extras/fmaddon-auth/app/auth/login/schema.ts +6 -0
- package/template/extras/fmaddon-auth/app/auth/reset-password/actions.ts +50 -0
- package/template/extras/fmaddon-auth/app/auth/reset-password/page.tsx +32 -0
- package/template/extras/fmaddon-auth/app/auth/reset-password/reset-password-form.tsx +59 -0
- package/template/extras/fmaddon-auth/app/auth/reset-password/schema.ts +14 -0
- package/template/extras/fmaddon-auth/app/auth/reset-password/verify-email/actions.ts +45 -0
- package/template/extras/fmaddon-auth/app/auth/reset-password/verify-email/page.tsx +32 -0
- package/template/extras/fmaddon-auth/app/auth/reset-password/verify-email/schema.ts +5 -0
- package/template/extras/fmaddon-auth/app/auth/reset-password/verify-email/verify-email-form.tsx +48 -0
- package/template/extras/fmaddon-auth/app/auth/signup/actions.ts +49 -0
- package/template/extras/fmaddon-auth/app/auth/signup/page.tsx +26 -0
- package/template/extras/fmaddon-auth/app/auth/signup/schema.ts +12 -0
- package/template/extras/fmaddon-auth/app/auth/signup/signup-form.tsx +67 -0
- package/template/extras/fmaddon-auth/app/auth/verify-email/actions.ts +110 -0
- package/template/extras/fmaddon-auth/app/auth/verify-email/email-verification-form.tsx +45 -0
- package/template/extras/fmaddon-auth/app/auth/verify-email/page.tsx +38 -0
- package/template/extras/fmaddon-auth/app/auth/verify-email/resend-button.tsx +35 -0
- package/template/extras/fmaddon-auth/app/auth/verify-email/schema.ts +5 -0
- package/template/extras/fmaddon-auth/components/auth/actions.ts +16 -0
- package/template/extras/fmaddon-auth/components/auth/protect.tsx +17 -0
- package/template/extras/fmaddon-auth/components/auth/redirect.tsx +26 -0
- package/template/extras/fmaddon-auth/components/auth/use-user.ts +59 -0
- package/template/extras/fmaddon-auth/components/auth/user-menu.tsx +51 -0
- package/template/extras/fmaddon-auth/emails/auth-code.tsx +156 -0
- package/template/extras/fmaddon-auth/middleware.ts +45 -0
- package/template/extras/fmaddon-auth/server/auth/utils/email-verification.ts +136 -0
- package/template/extras/fmaddon-auth/server/auth/utils/encryption.ts +51 -0
- package/template/extras/fmaddon-auth/server/auth/utils/index.ts +16 -0
- package/template/extras/fmaddon-auth/server/auth/utils/password-reset.ts +152 -0
- package/template/extras/fmaddon-auth/server/auth/utils/password.ts +67 -0
- package/template/extras/fmaddon-auth/server/auth/utils/redirect.ts +8 -0
- package/template/extras/fmaddon-auth/server/auth/utils/session.ts +192 -0
- package/template/extras/fmaddon-auth/server/auth/utils/user.ts +147 -0
- package/template/extras/prisma/schema/base-planetscale.prisma +24 -0
- package/template/extras/prisma/schema/base.prisma +20 -0
- package/template/extras/prisma/schema/with-auth-planetscale.prisma +77 -0
- package/template/extras/prisma/schema/with-auth.prisma +74 -0
- package/template/extras/src/app/_components/post-tw.tsx +50 -0
- package/template/extras/src/app/_components/post.tsx +54 -0
- package/template/extras/src/app/api/auth/[...nextauth]/route.ts +4 -0
- package/template/extras/src/app/api/trpc/[trpc]/route.ts +34 -0
- package/template/extras/src/app/clerk-auth/layout.tsx +10 -0
- package/template/extras/src/app/clerk-auth/signin/[[...sign-in]]/page.tsx +5 -0
- package/template/extras/src/app/clerk-auth/signup/[[...sign-up]]/page.tsx +5 -0
- package/template/extras/src/app/layout/base.tsx +34 -0
- package/template/extras/src/app/layout/main-shell.tsx +37 -0
- package/template/extras/src/app/layout/with-trpc-tw.tsx +24 -0
- package/template/extras/src/app/layout/with-trpc.tsx +24 -0
- package/template/extras/src/app/layout/with-tw.tsx +20 -0
- package/template/extras/src/app/next-auth/layout.tsx +22 -0
- package/template/extras/src/app/next-auth/signin/page.tsx +82 -0
- package/template/extras/src/app/next-auth/signup/action.ts +24 -0
- package/template/extras/src/app/next-auth/signup/page.tsx +40 -0
- package/template/extras/src/app/next-auth/signup/validation.ts +12 -0
- package/template/extras/src/app/page/base.tsx +6 -0
- package/template/extras/src/app/page/with-auth-trpc-tw.tsx +67 -0
- package/template/extras/src/app/page/with-auth-trpc.tsx +68 -0
- package/template/extras/src/app/page/with-trpc-tw.tsx +53 -0
- package/template/extras/src/app/page/with-trpc.tsx +54 -0
- package/template/extras/src/app/page/with-tw.tsx +37 -0
- package/template/extras/src/components/clerk-auth/clerk-provider.tsx +18 -0
- package/template/extras/src/components/clerk-auth/user-menu-mobile.tsx +36 -0
- package/template/extras/src/components/clerk-auth/user-menu.tsx +24 -0
- package/template/extras/src/components/next-auth/next-auth-provider.tsx +14 -0
- package/template/extras/src/components/next-auth/user-menu-mobile.tsx +31 -0
- package/template/extras/src/components/next-auth/user-menu.tsx +38 -0
- package/template/extras/src/env/with-auth.ts +31 -0
- package/template/extras/src/env/with-clerk.ts +20 -0
- package/template/extras/src/index.module.css +177 -0
- package/template/extras/src/middleware/clerk.ts +20 -0
- package/template/extras/src/middleware/next-auth.ts +5 -0
- package/template/extras/src/pages/_app/base.tsx +14 -0
- package/template/extras/src/pages/_app/with-auth-trpc-tw.tsx +23 -0
- package/template/extras/src/pages/_app/with-auth-trpc.tsx +23 -0
- package/template/extras/src/pages/_app/with-auth-tw.tsx +21 -0
- package/template/extras/src/pages/_app/with-auth.tsx +21 -0
- package/template/extras/src/pages/_app/with-trpc-tw.tsx +16 -0
- package/template/extras/src/pages/_app/with-trpc.tsx +16 -0
- package/template/extras/src/pages/_app/with-tw.tsx +14 -0
- package/template/extras/src/pages/api/auth/[...nextauth].ts +5 -0
- package/template/extras/src/pages/api/trpc/[trpc].ts +19 -0
- package/template/extras/src/pages/index/base.tsx +47 -0
- package/template/extras/src/pages/index/with-auth-trpc-tw.tsx +80 -0
- package/template/extras/src/pages/index/with-auth-trpc.tsx +81 -0
- package/template/extras/src/pages/index/with-trpc-tw.tsx +52 -0
- package/template/extras/src/pages/index/with-trpc.tsx +53 -0
- package/template/extras/src/pages/index/with-tw.tsx +45 -0
- package/template/extras/src/server/api/root.ts +23 -0
- package/template/extras/src/server/api/routers/post/base.ts +40 -0
- package/template/extras/src/server/api/routers/post/with-auth-drizzle.ts +39 -0
- package/template/extras/src/server/api/routers/post/with-auth-prisma.ts +41 -0
- package/template/extras/src/server/api/routers/post/with-auth.ts +37 -0
- package/template/extras/src/server/api/routers/post/with-drizzle.ts +30 -0
- package/template/extras/src/server/api/routers/post/with-prisma.ts +31 -0
- package/template/extras/src/server/api/trpc-app/base.ts +103 -0
- package/template/extras/src/server/api/trpc-app/with-auth-db.ts +133 -0
- package/template/extras/src/server/api/trpc-app/with-auth.ts +130 -0
- package/template/extras/src/server/api/trpc-app/with-db.ts +106 -0
- package/template/extras/src/server/api/trpc-pages/base.ts +122 -0
- package/template/extras/src/server/api/trpc-pages/with-auth-db.ts +160 -0
- package/template/extras/src/server/api/trpc-pages/with-auth.ts +158 -0
- package/template/extras/src/server/api/trpc-pages/with-db.ts +125 -0
- package/template/extras/src/server/data/users.ts +23 -0
- package/template/extras/src/server/db/db-prisma-planetscale.ts +22 -0
- package/template/extras/src/server/db/db-prisma.ts +17 -0
- package/template/extras/src/server/db/index-drizzle/with-mysql.ts +18 -0
- package/template/extras/src/server/db/index-drizzle/with-planetscale.ts +7 -0
- package/template/extras/src/server/db/index-drizzle/with-postgres.ts +18 -0
- package/template/extras/src/server/db/index-drizzle/with-sqlite.ts +19 -0
- package/template/extras/src/server/db/schema-drizzle/base-mysql.ts +34 -0
- package/template/extras/src/server/db/schema-drizzle/base-planetscale.ts +34 -0
- package/template/extras/src/server/db/schema-drizzle/base-postgres.ts +36 -0
- package/template/extras/src/server/db/schema-drizzle/base-sqlite.ts +30 -0
- package/template/extras/src/server/db/schema-drizzle/with-auth-mysql.ts +123 -0
- package/template/extras/src/server/db/schema-drizzle/with-auth-planetscale.ts +117 -0
- package/template/extras/src/server/db/schema-drizzle/with-auth-postgres.ts +130 -0
- package/template/extras/src/server/db/schema-drizzle/with-auth-sqlite.ts +116 -0
- package/template/extras/src/server/next-auth/base.ts +111 -0
- package/template/extras/src/server/next-auth/password.ts +13 -0
- package/template/extras/src/server/next-auth/with-drizzle.ts +83 -0
- package/template/extras/src/server/next-auth/with-prisma.ts +72 -0
- package/template/extras/src/trpc/query-client.ts +25 -0
- package/template/extras/src/trpc/react.tsx +76 -0
- package/template/extras/src/trpc/server.ts +30 -0
- package/template/extras/src/utils/api.ts +68 -0
- package/template/extras/start-database/mysql.sh +54 -0
- package/template/extras/start-database/postgres.sh +55 -0
- package/template/fm-addon/ProofKitAuth/de.xml +518 -0
- package/template/fm-addon/ProofKitAuth/en.xml +518 -0
- package/template/fm-addon/ProofKitAuth/es.xml +518 -0
- package/template/fm-addon/ProofKitAuth/fr.xml +518 -0
- package/template/fm-addon/ProofKitAuth/icon.png +0 -0
- package/template/fm-addon/ProofKitAuth/icon@2x.png +0 -0
- package/template/fm-addon/ProofKitAuth/info.json +11 -0
- package/template/fm-addon/ProofKitAuth/info_de.json +18 -0
- package/template/fm-addon/ProofKitAuth/info_en.json +8 -0
- package/template/fm-addon/ProofKitAuth/info_es.json +18 -0
- package/template/fm-addon/ProofKitAuth/info_fr.json +18 -0
- package/template/fm-addon/ProofKitAuth/info_it.json +18 -0
- package/template/fm-addon/ProofKitAuth/info_ja.json +18 -0
- package/template/fm-addon/ProofKitAuth/info_ko.json +18 -0
- package/template/fm-addon/ProofKitAuth/info_nl.json +18 -0
- package/template/fm-addon/ProofKitAuth/info_pt.json +18 -0
- package/template/fm-addon/ProofKitAuth/info_sv.json +18 -0
- package/template/fm-addon/ProofKitAuth/info_zh.json +18 -0
- package/template/fm-addon/ProofKitAuth/it.xml +518 -0
- package/template/fm-addon/ProofKitAuth/ja.xml +518 -0
- package/template/fm-addon/ProofKitAuth/ko.xml +518 -0
- package/template/fm-addon/ProofKitAuth/nl.xml +518 -0
- package/template/fm-addon/ProofKitAuth/preview.png +0 -0
- package/template/fm-addon/ProofKitAuth/pt.xml +518 -0
- package/template/fm-addon/ProofKitAuth/sv.xml +518 -0
- package/template/fm-addon/ProofKitAuth/template.xml +0 -0
- package/template/fm-addon/ProofKitAuth/zh.xml +518 -0
- package/template/fm-addon/ProofKitWV/de.xml +896 -0
- package/template/fm-addon/ProofKitWV/en.xml +896 -0
- package/template/fm-addon/ProofKitWV/es.xml +896 -0
- package/template/fm-addon/ProofKitWV/fr.xml +896 -0
- package/template/fm-addon/ProofKitWV/icon.png +0 -0
- package/template/fm-addon/ProofKitWV/icon@2x.png +0 -0
- package/template/fm-addon/ProofKitWV/info.json +11 -0
- package/template/fm-addon/ProofKitWV/info_de.json +18 -0
- package/template/fm-addon/ProofKitWV/info_en.json +11 -0
- package/template/fm-addon/ProofKitWV/info_es.json +18 -0
- package/template/fm-addon/ProofKitWV/info_fr.json +18 -0
- package/template/fm-addon/ProofKitWV/info_it.json +18 -0
- package/template/fm-addon/ProofKitWV/info_ja.json +18 -0
- package/template/fm-addon/ProofKitWV/info_ko.json +18 -0
- package/template/fm-addon/ProofKitWV/info_nl.json +18 -0
- package/template/fm-addon/ProofKitWV/info_pt.json +18 -0
- package/template/fm-addon/ProofKitWV/info_sv.json +18 -0
- package/template/fm-addon/ProofKitWV/info_zh.json +18 -0
- package/template/fm-addon/ProofKitWV/it.xml +896 -0
- package/template/fm-addon/ProofKitWV/ja.xml +896 -0
- package/template/fm-addon/ProofKitWV/ko.xml +896 -0
- package/template/fm-addon/ProofKitWV/nl.xml +896 -0
- package/template/fm-addon/ProofKitWV/preview.png +0 -0
- package/template/fm-addon/ProofKitWV/pt.xml +896 -0
- package/template/fm-addon/ProofKitWV/records_de.xml +0 -0
- package/template/fm-addon/ProofKitWV/records_en.xml +0 -0
- package/template/fm-addon/ProofKitWV/records_es.xml +0 -0
- package/template/fm-addon/ProofKitWV/records_fr.xml +0 -0
- package/template/fm-addon/ProofKitWV/records_it.xml +0 -0
- package/template/fm-addon/ProofKitWV/records_ja.xml +0 -0
- package/template/fm-addon/ProofKitWV/records_ko.xml +0 -0
- package/template/fm-addon/ProofKitWV/records_nl.xml +0 -0
- package/template/fm-addon/ProofKitWV/records_pt.xml +0 -0
- package/template/fm-addon/ProofKitWV/records_sv.xml +0 -0
- package/template/fm-addon/ProofKitWV/records_zh.xml +0 -0
- package/template/fm-addon/ProofKitWV/sv.xml +896 -0
- package/template/fm-addon/ProofKitWV/template.xml +0 -0
- package/template/fm-addon/ProofKitWV/zh.xml +896 -0
- package/template/nextjs/README.md +27 -0
- package/template/nextjs/_gitignore +37 -0
- package/template/nextjs/next.config.ts +12 -0
- package/template/nextjs/package.json +50 -0
- package/template/nextjs/postcss.config.cjs +14 -0
- package/template/nextjs/proofkit.json +1 -0
- package/template/nextjs/public/favicon.ico +0 -0
- package/template/nextjs/public/proofkit.png +0 -0
- package/template/nextjs/src/app/(main)/layout.tsx +6 -0
- package/template/nextjs/src/app/(main)/page.tsx +90 -0
- package/template/nextjs/src/app/layout.tsx +39 -0
- package/template/nextjs/src/app/navigation.tsx +12 -0
- package/template/nextjs/src/components/AppLogo.tsx +6 -0
- package/template/nextjs/src/components/AppShell/internal/AppShell.tsx +21 -0
- package/template/nextjs/src/components/AppShell/internal/Header.module.css +40 -0
- package/template/nextjs/src/components/AppShell/internal/Header.tsx +34 -0
- package/template/nextjs/src/components/AppShell/internal/HeaderMobileMenu.tsx +27 -0
- package/template/nextjs/src/components/AppShell/internal/HeaderNavLink.tsx +31 -0
- package/template/nextjs/src/components/AppShell/internal/config.ts +1 -0
- package/template/nextjs/src/components/AppShell/slot-header-center.tsx +13 -0
- package/template/nextjs/src/components/AppShell/slot-header-left.tsx +23 -0
- package/template/nextjs/src/components/AppShell/slot-header-mobile-content.tsx +43 -0
- package/template/nextjs/src/components/AppShell/slot-header-right.tsx +26 -0
- package/template/nextjs/src/config/env.ts +13 -0
- package/template/nextjs/src/config/theme/globals.css +1 -0
- package/template/nextjs/src/config/theme/mantine-theme.ts +22 -0
- package/template/nextjs/src/server/safe-action.ts +3 -0
- package/template/nextjs/src/utils/notification-helpers.ts +32 -0
- package/template/nextjs/tsconfig.json +40 -0
- package/template/pages/nextjs/blank/page.tsx +5 -0
- package/template/pages/nextjs/table/page.tsx +17 -0
- package/template/pages/nextjs/table/table.tsx +18 -0
- package/template/pages/nextjs/table-edit/actions.ts +23 -0
- package/template/pages/nextjs/table-edit/page.tsx +28 -0
- package/template/pages/nextjs/table-edit/schema.ts +4 -0
- package/template/pages/nextjs/table-edit/table.tsx +43 -0
- package/template/pages/nextjs/table-infinite/actions.ts +62 -0
- package/template/pages/nextjs/table-infinite/page.tsx +11 -0
- package/template/pages/nextjs/table-infinite/query.ts +44 -0
- package/template/pages/nextjs/table-infinite/table.tsx +107 -0
- package/template/pages/nextjs/table-infinite-edit/actions.ts +84 -0
- package/template/pages/nextjs/table-infinite-edit/page.tsx +23 -0
- package/template/pages/nextjs/table-infinite-edit/query.ts +81 -0
- package/template/pages/nextjs/table-infinite-edit/schema.ts +4 -0
- package/template/pages/nextjs/table-infinite-edit/table.tsx +130 -0
- package/template/pages/vite-wv/blank/index.tsx +0 -0
- package/template/pages/vite-wv/table/index.tsx +34 -0
- package/template/pages/vite-wv/table-edit/index.tsx +72 -0
- package/template/vite-wv/.vscode/settings.json +11 -0
- package/template/vite-wv/_gitignore +18 -0
- package/template/vite-wv/index.html +13 -0
- package/template/vite-wv/package.json +52 -0
- package/template/vite-wv/pnpm-lock.yaml +2294 -0
- package/template/vite-wv/postcss.config.cjs +14 -0
- package/template/vite-wv/proofkit.json +1 -0
- package/template/vite-wv/scripts/launch-fm.sh +3 -0
- package/template/vite-wv/scripts/upload.js +21 -0
- package/template/vite-wv/src/components/AppLogo.tsx +5 -0
- package/template/vite-wv/src/components/full-screen-loader.tsx +9 -0
- package/template/vite-wv/src/config/env.ts +16 -0
- package/template/vite-wv/src/config/theme/globals.css +1 -0
- package/template/vite-wv/src/config/theme/mantine-theme.ts +22 -0
- package/template/vite-wv/src/main.tsx +42 -0
- package/template/vite-wv/src/routeTree.gen.ts +111 -0
- package/template/vite-wv/src/routes/__root.tsx +21 -0
- package/template/vite-wv/src/routes/index.tsx +63 -0
- package/template/vite-wv/src/routes/secondary.tsx +28 -0
- package/template/vite-wv/src/utils/notification-helpers.ts +32 -0
- package/template/vite-wv/tsconfig.json +14 -0
- package/template/vite-wv/vite.config.ts +18 -0
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import {
|
|
2
|
+
encodeBase32LowerCaseNoPadding,
|
|
3
|
+
encodeHexLowerCase,
|
|
4
|
+
} from "@oslojs/encoding";
|
|
5
|
+
import { sha256 } from "@oslojs/crypto/sha2";
|
|
6
|
+
import { cookies } from "next/headers";
|
|
7
|
+
import { cache } from "react";
|
|
8
|
+
import type { User } from "./user";
|
|
9
|
+
|
|
10
|
+
import { sessionsLayout } from "../db/client";
|
|
11
|
+
import { Tsessions as _Session } from "../db/sessions";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Generate a random session token with sufficient entropy for a session ID.
|
|
15
|
+
* @returns The session token.
|
|
16
|
+
*/
|
|
17
|
+
export function generateSessionToken(): string {
|
|
18
|
+
const bytes = new Uint8Array(20);
|
|
19
|
+
crypto.getRandomValues(bytes);
|
|
20
|
+
const token = encodeBase32LowerCaseNoPadding(bytes);
|
|
21
|
+
return token;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Create a new session for a user and save it to the database.
|
|
26
|
+
* @param token - The session token.
|
|
27
|
+
* @param userId - The ID of the user.
|
|
28
|
+
* @returns The session.
|
|
29
|
+
*/
|
|
30
|
+
export async function createSession(
|
|
31
|
+
token: string,
|
|
32
|
+
userId: string,
|
|
33
|
+
): Promise<Session> {
|
|
34
|
+
const sessionId = encodeHexLowerCase(sha256(new TextEncoder().encode(token)));
|
|
35
|
+
const session: Session = {
|
|
36
|
+
id: sessionId,
|
|
37
|
+
id_user: userId,
|
|
38
|
+
expiresAt: new Date(Date.now() + 1000 * 60 * 60 * 24 * 30),
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// create session in DB
|
|
42
|
+
await sessionsLayout.create({
|
|
43
|
+
fieldData: {
|
|
44
|
+
id: session.id,
|
|
45
|
+
id_user: session.id_user,
|
|
46
|
+
expiresAt: Math.floor(session.expiresAt.getTime() / 1000),
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
return session;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Invalidate a session by deleting it from the database.
|
|
55
|
+
* @param sessionId - The ID of the session to invalidate.
|
|
56
|
+
*/
|
|
57
|
+
export async function invalidateSession(sessionId: string): Promise<void> {
|
|
58
|
+
const fmResult = await sessionsLayout.maybeFindFirst({
|
|
59
|
+
query: { id: `==${sessionId}` },
|
|
60
|
+
});
|
|
61
|
+
if (fmResult === null) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
await sessionsLayout.delete({ recordId: fmResult.data.recordId });
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Validate a session token to make sure it still exists in the database and hasn't expired.
|
|
69
|
+
* @param token - The session token.
|
|
70
|
+
* @returns The session, or null if it doesn't exist.
|
|
71
|
+
*/
|
|
72
|
+
export async function validateSessionToken(
|
|
73
|
+
token: string,
|
|
74
|
+
): Promise<SessionValidationResult> {
|
|
75
|
+
const sessionId = encodeHexLowerCase(sha256(new TextEncoder().encode(token)));
|
|
76
|
+
|
|
77
|
+
const result = await sessionsLayout.maybeFindFirst({
|
|
78
|
+
query: { id: `==${sessionId}` },
|
|
79
|
+
});
|
|
80
|
+
if (result === null) {
|
|
81
|
+
return { session: null, user: null };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const fmResult = result.data.fieldData;
|
|
85
|
+
const recordId = result.data.recordId;
|
|
86
|
+
const session: Session = {
|
|
87
|
+
id: fmResult.id,
|
|
88
|
+
id_user: fmResult.id_user,
|
|
89
|
+
expiresAt: fmResult.expiresAt
|
|
90
|
+
? new Date(fmResult.expiresAt * 1000)
|
|
91
|
+
: new Date(Date.now() + 1000 * 60 * 60 * 24 * 30),
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const user: User = {
|
|
95
|
+
id: session.id_user,
|
|
96
|
+
email: fmResult["proofkit_auth_users::email"],
|
|
97
|
+
emailVerified: Boolean(fmResult["proofkit_auth_users::emailVerified"]),
|
|
98
|
+
username: fmResult["proofkit_auth_users::username"],
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
// delete session if it has expired
|
|
102
|
+
if (Date.now() >= session.expiresAt.getTime()) {
|
|
103
|
+
await sessionsLayout.delete({ recordId });
|
|
104
|
+
return { session: null, user: null };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// extend session if it's going to expire soon
|
|
108
|
+
// You may want to customize this logic to better suit your app's requirements
|
|
109
|
+
if (Date.now() >= session.expiresAt.getTime() - 1000 * 60 * 60 * 24 * 15) {
|
|
110
|
+
session.expiresAt = new Date(Date.now() + 1000 * 60 * 60 * 24 * 30);
|
|
111
|
+
await sessionsLayout.update({
|
|
112
|
+
recordId,
|
|
113
|
+
fieldData: {
|
|
114
|
+
expiresAt: Math.floor(session.expiresAt.getTime() / 1000),
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return { session, user };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Get the current session from the cookie.
|
|
124
|
+
* Wrapped in a React cache to avoid calling the database more than once per request
|
|
125
|
+
* This function can be used in server components, server actions, and route handlers (but importantly not middleware).
|
|
126
|
+
* @returns The session, or null if it doesn't exist.
|
|
127
|
+
*/
|
|
128
|
+
export const getCurrentSession = cache(
|
|
129
|
+
async (): Promise<SessionValidationResult> => {
|
|
130
|
+
const token = (await cookies()).get("session")?.value ?? null;
|
|
131
|
+
if (token === null) {
|
|
132
|
+
return { session: null, user: null };
|
|
133
|
+
}
|
|
134
|
+
const result = await validateSessionToken(token);
|
|
135
|
+
return result;
|
|
136
|
+
},
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Invalidate all sessions for a user by deleting them from the database.
|
|
141
|
+
* @param userId - The ID of the user.
|
|
142
|
+
*/
|
|
143
|
+
export async function invalidateUserSessions(userId: string): Promise<void> {
|
|
144
|
+
const sessions = await sessionsLayout.findAll({
|
|
145
|
+
query: { id_user: `==${userId}` },
|
|
146
|
+
});
|
|
147
|
+
for (const session of sessions) {
|
|
148
|
+
await sessionsLayout.delete({ recordId: session.recordId });
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Set a cookie for a session.
|
|
154
|
+
* @param token - The session token.
|
|
155
|
+
* @param expiresAt - The expiration date of the session.
|
|
156
|
+
*/
|
|
157
|
+
export async function setSessionTokenCookie(
|
|
158
|
+
token: string,
|
|
159
|
+
expiresAt: Date,
|
|
160
|
+
): Promise<void> {
|
|
161
|
+
(await cookies()).set("session", token, {
|
|
162
|
+
httpOnly: true,
|
|
163
|
+
path: "/",
|
|
164
|
+
secure: process.env.NODE_ENV === "production",
|
|
165
|
+
sameSite: "lax",
|
|
166
|
+
expires: expiresAt,
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Delete the session cookie.
|
|
173
|
+
*/
|
|
174
|
+
export async function deleteSessionTokenCookie(): Promise<void> {
|
|
175
|
+
(await cookies()).set("session", "", {
|
|
176
|
+
httpOnly: true,
|
|
177
|
+
path: "/",
|
|
178
|
+
secure: process.env.NODE_ENV === "production",
|
|
179
|
+
sameSite: "lax",
|
|
180
|
+
maxAge: 0,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export interface Session {
|
|
185
|
+
id: string;
|
|
186
|
+
expiresAt: Date;
|
|
187
|
+
id_user: string;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
type SessionValidationResult =
|
|
191
|
+
| { session: Session; user: User }
|
|
192
|
+
| { session: null; user: null };
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { usersLayout } from "../db/client";
|
|
2
|
+
import { Tusers as _User } from "../db/users";
|
|
3
|
+
|
|
4
|
+
export type User = Partial<
|
|
5
|
+
Omit<_User, "id" | "password_hash" | "recovery_code" | "emailVerified">
|
|
6
|
+
> & {
|
|
7
|
+
id: string;
|
|
8
|
+
email: string;
|
|
9
|
+
emailVerified: boolean;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
import { hashPassword, verifyPasswordHash } from "./password";
|
|
13
|
+
|
|
14
|
+
/** An internal helper function to fetch a user from the database. */
|
|
15
|
+
async function fetchUser(userId: string) {
|
|
16
|
+
const { data } = await usersLayout.findOne({
|
|
17
|
+
query: { id: `==${userId}` },
|
|
18
|
+
});
|
|
19
|
+
return data;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Create a new user in the database. */
|
|
23
|
+
export async function createUser(
|
|
24
|
+
email: string,
|
|
25
|
+
password: string
|
|
26
|
+
): Promise<User> {
|
|
27
|
+
const password_hash = await hashPassword(password);
|
|
28
|
+
const { recordId } = await usersLayout.create({
|
|
29
|
+
fieldData: {
|
|
30
|
+
email,
|
|
31
|
+
password_hash,
|
|
32
|
+
emailVerified: 0,
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
const fmResult = await usersLayout.get({ recordId });
|
|
36
|
+
const { fieldData } = fmResult.data[0];
|
|
37
|
+
|
|
38
|
+
const user: User = {
|
|
39
|
+
id: fieldData.id,
|
|
40
|
+
email,
|
|
41
|
+
emailVerified: false,
|
|
42
|
+
username: "",
|
|
43
|
+
};
|
|
44
|
+
return user;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Update a user's password in the database. */
|
|
48
|
+
export async function updateUserPassword(
|
|
49
|
+
userId: string,
|
|
50
|
+
password: string
|
|
51
|
+
): Promise<void> {
|
|
52
|
+
const password_hash = await hashPassword(password);
|
|
53
|
+
const { recordId } = await fetchUser(userId);
|
|
54
|
+
|
|
55
|
+
await usersLayout.update({ recordId, fieldData: { password_hash } });
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export async function updateUserEmailAndSetEmailAsVerified(
|
|
59
|
+
userId: string,
|
|
60
|
+
email: string
|
|
61
|
+
): Promise<void> {
|
|
62
|
+
const { recordId } = await fetchUser(userId);
|
|
63
|
+
await usersLayout.update({
|
|
64
|
+
recordId,
|
|
65
|
+
fieldData: { email, emailVerified: 1 },
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export async function setUserAsEmailVerifiedIfEmailMatches(
|
|
70
|
+
userId: string,
|
|
71
|
+
email: string
|
|
72
|
+
): Promise<boolean> {
|
|
73
|
+
try {
|
|
74
|
+
const {
|
|
75
|
+
data: { recordId },
|
|
76
|
+
} = await usersLayout.findOne({
|
|
77
|
+
query: { id: `==${userId}`, email: `==${email}` },
|
|
78
|
+
});
|
|
79
|
+
await usersLayout.update({ recordId, fieldData: { emailVerified: 1 } });
|
|
80
|
+
return true;
|
|
81
|
+
} catch (error) {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export async function getUserFromEmail(email: string): Promise<User | null> {
|
|
87
|
+
const fmResult = await usersLayout.maybeFindFirst({
|
|
88
|
+
query: { email: `==${email}` },
|
|
89
|
+
});
|
|
90
|
+
if (fmResult === null) return null;
|
|
91
|
+
|
|
92
|
+
const {
|
|
93
|
+
data: { fieldData },
|
|
94
|
+
} = fmResult;
|
|
95
|
+
|
|
96
|
+
const user: User = {
|
|
97
|
+
id: fieldData.id,
|
|
98
|
+
email: fieldData.email,
|
|
99
|
+
emailVerified: Boolean(fieldData.emailVerified),
|
|
100
|
+
username: fieldData.username,
|
|
101
|
+
};
|
|
102
|
+
return user;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Validate a user's email/password combination.
|
|
107
|
+
* @param email - The user's email.
|
|
108
|
+
* @param password - The user's password.
|
|
109
|
+
* @returns The user, or null if the login is invalid.
|
|
110
|
+
*/
|
|
111
|
+
export async function validateLogin(
|
|
112
|
+
email: string,
|
|
113
|
+
password: string
|
|
114
|
+
): Promise<User | null> {
|
|
115
|
+
try {
|
|
116
|
+
const {
|
|
117
|
+
data: { fieldData },
|
|
118
|
+
} = await usersLayout.findOne({
|
|
119
|
+
query: { email: `==${email}` },
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
const validPassword = await verifyPasswordHash(
|
|
123
|
+
fieldData.password_hash,
|
|
124
|
+
password
|
|
125
|
+
);
|
|
126
|
+
if (!validPassword) {
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
const user: User = {
|
|
130
|
+
id: fieldData.id,
|
|
131
|
+
email: fieldData.email,
|
|
132
|
+
emailVerified: Boolean(fieldData.emailVerified),
|
|
133
|
+
username: fieldData.username,
|
|
134
|
+
};
|
|
135
|
+
return user;
|
|
136
|
+
} catch (error) {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export async function checkEmailAvailability(email: string): Promise<boolean> {
|
|
142
|
+
const { data } = await usersLayout.find({
|
|
143
|
+
query: { email: `==${email}` },
|
|
144
|
+
ignoreEmptyResult: true,
|
|
145
|
+
});
|
|
146
|
+
return data.length === 0;
|
|
147
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// This is your Prisma schema file,
|
|
2
|
+
// learn more about it in the docs: https://pris.ly/d/prisma-schema
|
|
3
|
+
|
|
4
|
+
generator client {
|
|
5
|
+
provider = "prisma-client-js"
|
|
6
|
+
previewFeatures = ["driverAdapters"]
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
datasource db {
|
|
10
|
+
provider = "mysql"
|
|
11
|
+
url = env("DATABASE_URL")
|
|
12
|
+
|
|
13
|
+
// If you have enabled foreign key constraints for your database, remove this line.
|
|
14
|
+
relationMode = "prisma"
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
model Post {
|
|
18
|
+
id Int @id @default(autoincrement())
|
|
19
|
+
name String
|
|
20
|
+
createdAt DateTime @default(now())
|
|
21
|
+
updatedAt DateTime @updatedAt
|
|
22
|
+
|
|
23
|
+
@@index([name])
|
|
24
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// This is your Prisma schema file,
|
|
2
|
+
// learn more about it in the docs: https://pris.ly/d/prisma-schema
|
|
3
|
+
|
|
4
|
+
generator client {
|
|
5
|
+
provider = "prisma-client-js"
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
datasource db {
|
|
9
|
+
provider = "sqlite"
|
|
10
|
+
url = env("DATABASE_URL")
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
model Post {
|
|
14
|
+
id Int @id @default(autoincrement())
|
|
15
|
+
name String
|
|
16
|
+
createdAt DateTime @default(now())
|
|
17
|
+
updatedAt DateTime @updatedAt
|
|
18
|
+
|
|
19
|
+
@@index([name])
|
|
20
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// This is your Prisma schema file,
|
|
2
|
+
// learn more about it in the docs: https://pris.ly/d/prisma-schema
|
|
3
|
+
|
|
4
|
+
generator client {
|
|
5
|
+
provider = "prisma-client-js"
|
|
6
|
+
previewFeatures = ["driverAdapters"]
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
datasource db {
|
|
10
|
+
provider = "mysql"
|
|
11
|
+
url = env("DATABASE_URL")
|
|
12
|
+
|
|
13
|
+
// If you have enabled foreign key constraints for your database, remove this line.
|
|
14
|
+
relationMode = "prisma"
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
model Post {
|
|
18
|
+
id Int @id @default(autoincrement())
|
|
19
|
+
name String
|
|
20
|
+
createdAt DateTime @default(now())
|
|
21
|
+
updatedAt DateTime @updatedAt
|
|
22
|
+
|
|
23
|
+
createdBy User @relation(fields: [createdById], references: [id])
|
|
24
|
+
createdById String
|
|
25
|
+
|
|
26
|
+
@@index([name])
|
|
27
|
+
@@index([createdById])
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Necessary for Next auth
|
|
31
|
+
model Account {
|
|
32
|
+
id String @id @default(cuid())
|
|
33
|
+
userId String
|
|
34
|
+
type String
|
|
35
|
+
provider String
|
|
36
|
+
providerAccountId String
|
|
37
|
+
refresh_token String? @db.Text
|
|
38
|
+
access_token String? @db.Text
|
|
39
|
+
expires_at Int?
|
|
40
|
+
token_type String?
|
|
41
|
+
scope String?
|
|
42
|
+
id_token String? @db.Text
|
|
43
|
+
session_state String?
|
|
44
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
45
|
+
|
|
46
|
+
@@unique([provider, providerAccountId])
|
|
47
|
+
@@index([userId])
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
model Session {
|
|
51
|
+
id String @id @default(cuid())
|
|
52
|
+
sessionToken String @unique
|
|
53
|
+
userId String
|
|
54
|
+
expires DateTime
|
|
55
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
56
|
+
|
|
57
|
+
@@index([userId])
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
model User {
|
|
61
|
+
id String @id @default(cuid())
|
|
62
|
+
name String?
|
|
63
|
+
email String? @unique
|
|
64
|
+
emailVerified DateTime?
|
|
65
|
+
image String?
|
|
66
|
+
accounts Account[]
|
|
67
|
+
sessions Session[]
|
|
68
|
+
posts Post[]
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
model VerificationToken {
|
|
72
|
+
identifier String
|
|
73
|
+
token String @unique
|
|
74
|
+
expires DateTime
|
|
75
|
+
|
|
76
|
+
@@unique([identifier, token])
|
|
77
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// This is your Prisma schema file,
|
|
2
|
+
// learn more about it in the docs: https://pris.ly/d/prisma-schema
|
|
3
|
+
|
|
4
|
+
generator client {
|
|
5
|
+
provider = "prisma-client-js"
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
datasource db {
|
|
9
|
+
provider = "sqlite"
|
|
10
|
+
// NOTE: When using mysql or sqlserver, uncomment the @db.Text annotations in model Account below
|
|
11
|
+
// Further reading:
|
|
12
|
+
// https://next-auth.js.org/adapters/prisma#create-the-prisma-schema
|
|
13
|
+
// https://www.prisma.io/docs/reference/api-reference/prisma-schema-reference#string
|
|
14
|
+
url = env("DATABASE_URL")
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
model Post {
|
|
18
|
+
id Int @id @default(autoincrement())
|
|
19
|
+
name String
|
|
20
|
+
createdAt DateTime @default(now())
|
|
21
|
+
updatedAt DateTime @updatedAt
|
|
22
|
+
|
|
23
|
+
createdBy User @relation(fields: [createdById], references: [id])
|
|
24
|
+
createdById String
|
|
25
|
+
|
|
26
|
+
@@index([name])
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Necessary for Next auth
|
|
30
|
+
model Account {
|
|
31
|
+
id String @id @default(cuid())
|
|
32
|
+
userId String
|
|
33
|
+
type String
|
|
34
|
+
provider String
|
|
35
|
+
providerAccountId String
|
|
36
|
+
refresh_token String? // @db.Text
|
|
37
|
+
access_token String? // @db.Text
|
|
38
|
+
expires_at Int?
|
|
39
|
+
token_type String?
|
|
40
|
+
scope String?
|
|
41
|
+
id_token String? // @db.Text
|
|
42
|
+
session_state String?
|
|
43
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
44
|
+
refresh_token_expires_in Int?
|
|
45
|
+
|
|
46
|
+
@@unique([provider, providerAccountId])
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
model Session {
|
|
50
|
+
id String @id @default(cuid())
|
|
51
|
+
sessionToken String @unique
|
|
52
|
+
userId String
|
|
53
|
+
expires DateTime
|
|
54
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
model User {
|
|
58
|
+
id String @id @default(cuid())
|
|
59
|
+
name String?
|
|
60
|
+
email String? @unique
|
|
61
|
+
emailVerified DateTime?
|
|
62
|
+
image String?
|
|
63
|
+
accounts Account[]
|
|
64
|
+
sessions Session[]
|
|
65
|
+
posts Post[]
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
model VerificationToken {
|
|
69
|
+
identifier String
|
|
70
|
+
token String @unique
|
|
71
|
+
expires DateTime
|
|
72
|
+
|
|
73
|
+
@@unique([identifier, token])
|
|
74
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
|
|
5
|
+
import { api } from "~/trpc/react";
|
|
6
|
+
|
|
7
|
+
export function LatestPost() {
|
|
8
|
+
const [latestPost] = api.post.getLatest.useSuspenseQuery();
|
|
9
|
+
|
|
10
|
+
const utils = api.useUtils();
|
|
11
|
+
const [name, setName] = useState("");
|
|
12
|
+
const createPost = api.post.create.useMutation({
|
|
13
|
+
onSuccess: async () => {
|
|
14
|
+
await utils.post.invalidate();
|
|
15
|
+
setName("");
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<div className="w-full max-w-xs">
|
|
21
|
+
{latestPost ? (
|
|
22
|
+
<p className="truncate">Your most recent post: {latestPost.name}</p>
|
|
23
|
+
) : (
|
|
24
|
+
<p>You have no posts yet.</p>
|
|
25
|
+
)}
|
|
26
|
+
<form
|
|
27
|
+
onSubmit={(e) => {
|
|
28
|
+
e.preventDefault();
|
|
29
|
+
createPost.mutate({ name });
|
|
30
|
+
}}
|
|
31
|
+
className="flex flex-col gap-2"
|
|
32
|
+
>
|
|
33
|
+
<input
|
|
34
|
+
type="text"
|
|
35
|
+
placeholder="Title"
|
|
36
|
+
value={name}
|
|
37
|
+
onChange={(e) => setName(e.target.value)}
|
|
38
|
+
className="w-full rounded-full px-4 py-2 text-black"
|
|
39
|
+
/>
|
|
40
|
+
<button
|
|
41
|
+
type="submit"
|
|
42
|
+
className="rounded-full bg-white/10 px-10 py-3 font-semibold transition hover:bg-white/20"
|
|
43
|
+
disabled={createPost.isPending}
|
|
44
|
+
>
|
|
45
|
+
{createPost.isPending ? "Submitting..." : "Submit"}
|
|
46
|
+
</button>
|
|
47
|
+
</form>
|
|
48
|
+
</div>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
|
|
5
|
+
import { api } from "~/trpc/react";
|
|
6
|
+
import styles from "../index.module.css";
|
|
7
|
+
|
|
8
|
+
export function LatestPost() {
|
|
9
|
+
const [latestPost] = api.post.getLatest.useSuspenseQuery();
|
|
10
|
+
|
|
11
|
+
const utils = api.useUtils();
|
|
12
|
+
const [name, setName] = useState("");
|
|
13
|
+
const createPost = api.post.create.useMutation({
|
|
14
|
+
onSuccess: async () => {
|
|
15
|
+
await utils.post.invalidate();
|
|
16
|
+
setName("");
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<div className={styles.showcaseContainer}>
|
|
22
|
+
{latestPost ? (
|
|
23
|
+
<p className={styles.showcaseText}>
|
|
24
|
+
Your most recent post: {latestPost.name}
|
|
25
|
+
</p>
|
|
26
|
+
) : (
|
|
27
|
+
<p className={styles.showcaseText}>You have no posts yet.</p>
|
|
28
|
+
)}
|
|
29
|
+
|
|
30
|
+
<form
|
|
31
|
+
onSubmit={(e) => {
|
|
32
|
+
e.preventDefault();
|
|
33
|
+
createPost.mutate({ name });
|
|
34
|
+
}}
|
|
35
|
+
className={styles.form}
|
|
36
|
+
>
|
|
37
|
+
<input
|
|
38
|
+
type="text"
|
|
39
|
+
placeholder="Title"
|
|
40
|
+
value={name}
|
|
41
|
+
onChange={(e) => setName(e.target.value)}
|
|
42
|
+
className={styles.input}
|
|
43
|
+
/>
|
|
44
|
+
<button
|
|
45
|
+
type="submit"
|
|
46
|
+
className={styles.submitButton}
|
|
47
|
+
disabled={createPost.isPending}
|
|
48
|
+
>
|
|
49
|
+
{createPost.isPending ? "Submitting..." : "Submit"}
|
|
50
|
+
</button>
|
|
51
|
+
</form>
|
|
52
|
+
</div>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { fetchRequestHandler } from "@trpc/server/adapters/fetch";
|
|
2
|
+
import { type NextRequest } from "next/server";
|
|
3
|
+
|
|
4
|
+
import { env } from "~/env";
|
|
5
|
+
import { appRouter } from "~/server/api/root";
|
|
6
|
+
import { createTRPCContext } from "~/server/api/trpc";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* This wraps the `createTRPCContext` helper and provides the required context for the tRPC API when
|
|
10
|
+
* handling a HTTP request (e.g. when you make requests from Client Components).
|
|
11
|
+
*/
|
|
12
|
+
const createContext = async (req: NextRequest) => {
|
|
13
|
+
return createTRPCContext({
|
|
14
|
+
headers: req.headers,
|
|
15
|
+
});
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const handler = (req: NextRequest) =>
|
|
19
|
+
fetchRequestHandler({
|
|
20
|
+
endpoint: "/api/trpc",
|
|
21
|
+
req,
|
|
22
|
+
router: appRouter,
|
|
23
|
+
createContext: () => createContext(req),
|
|
24
|
+
onError:
|
|
25
|
+
env.NODE_ENV === "development"
|
|
26
|
+
? ({ path, error }) => {
|
|
27
|
+
console.error(
|
|
28
|
+
`❌ tRPC failed on ${path ?? "<no-path>"}: ${error.message}`
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
: undefined,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
export { handler as GET, handler as POST };
|