diff --git a/.github/workflows/build-lint-test.yml b/.github/workflows/build-lint-test.yml index 0ad71f6c6..002e810cf 100644 --- a/.github/workflows/build-lint-test.yml +++ b/.github/workflows/build-lint-test.yml @@ -22,7 +22,7 @@ jobs: - uses: pnpm/action-setup@v4 name: Install pnpm with: - version: 10.14.0 + version: 10.15.0 - name: Install Node.js uses: actions/setup-node@v4 diff --git a/.github/workflows/bump_publish.yml b/.github/workflows/bump_publish.yml index 66304f60c..e5a558412 100644 --- a/.github/workflows/bump_publish.yml +++ b/.github/workflows/bump_publish.yml @@ -52,7 +52,7 @@ jobs: - uses: pnpm/action-setup@v4 name: Install pnpm with: - version: 10.14.0 + version: 10.15.0 - name: Install Node.js uses: actions/setup-node@v4 diff --git a/apps/api/package.json b/apps/api/package.json index f66e10ede..e1e74c4e4 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -16,29 +16,30 @@ "drizzle-kit": "drizzle-kit" }, "dependencies": { - "@hono/zod-openapi": "^1.0.2", + "@hono/zod-openapi": "^1.1.0", "@hono/zod-validator": "^0.7.2", "@vitnode/core": "workspace:*", "drizzle-kit": "^0.31.4", "drizzle-orm": "^0.44.4", - "hono": "^4.8.10", + "hono": "^4.9.2", "next-intl": "^4.3.4", "react": "^19.1.1", "react-dom": "^19.1.1", - "zod": "^4.0.14" + "use-intl": "^4.3.4", + "zod": "^4.0.17" }, "devDependencies": { - "@hono/node-server": "^1.18.0", - "@react-email/components": "^0.3.3", - "@types/node": "^24.1.0", - "@types/react": "^19.1.9", + "@hono/node-server": "^1.19.0", + "@react-email/components": "^0.5.1", + "@types/node": "^24.3.0", + "@types/react": "^19.1.10", "@types/react-dom": "^19.1.7", "@vitnode/eslint-config": "workspace:*", "dotenv": "^17.2.1", - "eslint": "^9.32.0", - "react-email": "^4.2.7", + "eslint": "^9.33.0", + "react-email": "^4.2.8", "tsc-alias": "^1.8.16", - "tsx": "^4.20.3", + "tsx": "^4.20.4", "typescript": "^5.9.2" } } diff --git a/apps/api/src/locales/@vitnode/core/en.json b/apps/api/src/locales/@vitnode/core/en.json index bec6c674c..cefac4fa4 100644 --- a/apps/api/src/locales/@vitnode/core/en.json +++ b/apps/api/src/locales/@vitnode/core/en.json @@ -132,7 +132,8 @@ }, "password": { "label": "Password", - "required": "Password is required." + "required": "Password is required.", + "reset": "Forgot password?" }, "errors": { "access_denied": { @@ -141,6 +142,36 @@ } }, "submit": "Login" + }, + "reset_password": { + "title": "Reset Password", + "desc": "Please enter your email address below to receive a password reset link.", + "submit": "Send Reset Link", + "confirmation": { + "title": "Check your email", + "desc": "We've sent a password reset link to your email address:", + "check_spam": "If you don't see the email in your inbox, please check your spam folder." + }, + "email": { + "subject": "Reset your password", + "greeting": "Hello {name}!", + "intro": "We received a request to reset your password for your account.", + "button": "Reset Password", + "instructions": "Click the button above to reset your password. This link will expire in 30 minutes for security reasons.", + "no_action": "If you didn't request a password reset, you can safely ignore this email. Your password will remain unchanged.", + "security_note": "For your security, this request was made from IP address: {ip}", + "help": "If you're having trouble with the button above, copy and paste the URL below into your web browser:", + "expire_time": "This link will expire on {date}" + } + }, + "change_password": { + "title": "Change Password", + "desc": "Please enter your new password below.", + "submit": "Change Password", + "success": { + "title": "Password changed successfully", + "desc": "You can now log in with your new password." + } } } }, diff --git a/apps/docs/content/docs/dev/api/meta.json b/apps/docs/content/docs/dev/api/meta.json new file mode 100644 index 000000000..cd6a87808 --- /dev/null +++ b/apps/docs/content/docs/dev/api/meta.json @@ -0,0 +1,5 @@ +{ + "title": "REST API", + "defaultOpen": true, + "pages": ["modules", "..."] +} diff --git a/apps/docs/content/docs/dev/api/modules.mdx b/apps/docs/content/docs/dev/api/modules.mdx new file mode 100644 index 000000000..339011195 --- /dev/null +++ b/apps/docs/content/docs/dev/api/modules.mdx @@ -0,0 +1,58 @@ +--- +title: Modules +description: xxx +--- + +## Usage + +Think of modules as containers for related API endpoints. They help organize your routes logically - perfect for keeping your sanity intact! + +```ts title="plugins/blog/src/api/modules/categories/categories.module.ts" +import { buildModule } from '@vitnode/core/api/lib/module'; + +import { CONFIG_PLUGIN } from '@/config'; + +export const categoriesModule = buildModule({ + pluginId: CONFIG_PLUGIN.id, + name: 'categories', + routes: [], // We'll populate this soon! +}); +``` + +#### Nested Modules + +Want to create a module hierarchy? VitNode's got your back! Nested modules are perfect for complex APIs. + +```ts title="plugins/blog/src/api/modules/categories/categories.module.ts" +import { buildModule } from '@vitnode/core/api/lib/module'; + +import { CONFIG_PLUGIN } from '@/config'; + +import { postsModule } from './posts/posts.module'; // [!code ++] + +export const categoriesModule = buildModule({ + pluginId: CONFIG_PLUGIN.id, + name: 'categories', + routes: [], + modules: [postsModule], // [!code ++] +}); +``` + +This creates a structure: `/api/{plugin_id}/categories/posts/*` + +## Connecting Modules to the API + +```ts title="plugins/blog/src/config.api.ts" +import { buildApiPlugin } from '@vitnode/core/api/lib/plugin'; + +import { CONFIG_PLUGIN } from '@/config'; + +import { categoriesModule } from './api/modules/categories/categories.module'; // [!code ++] + +export const blogApiPlugin = () => { + return buildApiPlugin({ + ...CONFIG_PLUGIN, + modules: [categoriesModule], // [!code ++] + }); +}; +``` diff --git a/apps/docs/content/docs/dev/rest-api.mdx b/apps/docs/content/docs/dev/api/routes.mdx similarity index 76% rename from apps/docs/content/docs/dev/rest-api.mdx rename to apps/docs/content/docs/dev/api/routes.mdx index 966515d59..f631d8779 100644 --- a/apps/docs/content/docs/dev/rest-api.mdx +++ b/apps/docs/content/docs/dev/api/routes.mdx @@ -1,56 +1,9 @@ --- -title: Restful API -description: Learn how to create and organize API routes in your VitNode plugins with modules, handlers, and parameter validation. +title: Routes +description: xxx --- -Building a RESTful API in VitNode is like assembling LEGO blocks - you create modules, add routes to them, and connect everything together. Let's dive into this architectural adventure! - -## Setting Up Your API Structure - - - - -### Create Module - -Think of modules as containers for related API endpoints. They help organize your routes logically - perfect for keeping your sanity intact! - -```ts title="plugins/blog/src/api/modules/categories/categories.module.ts" -import { buildModule } from '@vitnode/core/api/lib/module'; - -import { CONFIG_PLUGIN } from '@/config'; - -export const categoriesModule = buildModule({ - pluginId: CONFIG_PLUGIN.id, - name: 'categories', - routes: [], // We'll populate this soon! -}); -``` - -#### Nested Modules - -Want to create a module hierarchy? VitNode's got your back! Nested modules are perfect for complex APIs. - -```ts title="plugins/blog/src/api/modules/categories/categories.module.ts" -import { buildModule } from '@vitnode/core/api/lib/module'; - -import { CONFIG_PLUGIN } from '@/config'; - -import { postsModule } from './posts/posts.module'; // [!code ++] - -export const categoriesModule = buildModule({ - pluginId: CONFIG_PLUGIN.id, - name: 'categories', - routes: [], - modules: [postsModule], // [!code ++] -}); -``` - -This creates a beautiful structure: `/api/{plugin_id}/categories/posts/*` - - - - -### Craft Routes +## Usage Now for the fun part - creating actual endpoints! Each route is a small but mighty function that handles HTTP requests. @@ -96,29 +49,7 @@ export const getCategoriesRoute = buildRoute({ }); ``` - - - -### Connect Everything - -The final step is connecting your modules to your plugin's API configuration. It's like plugging in the last cable! - -```ts title="plugins/blog/src/config.api.ts" -import { buildApiPlugin } from '@vitnode/core/api/lib/plugin'; - -import { CONFIG_PLUGIN } from '@/config'; - -import { categoriesModule } from './api/modules/categories/categories.module'; // [!code ++] - -export const blogApiPlugin = () => { - return buildApiPlugin({ - ...CONFIG_PLUGIN, - modules: [categoriesModule], // [!code ++] - }); -}; -``` - -Don't forget to add your routes to the module: +### Connecting Routes to Modules ```ts title="plugins/blog/src/api/modules/categories/categories.module.ts" import { buildModule } from '@vitnode/core/api/lib/module'; @@ -133,10 +64,7 @@ export const categoriesModule = buildModule({ }); ``` - - - -## Working with Parameters +## Inputs ### Path Parameters diff --git a/apps/docs/content/docs/dev/captcha/overview.mdx b/apps/docs/content/docs/dev/captcha/overview.mdx index 5d17778e0..057fa5bb2 100644 --- a/apps/docs/content/docs/dev/captcha/overview.mdx +++ b/apps/docs/content/docs/dev/captcha/overview.mdx @@ -156,7 +156,7 @@ export const mutationApi = async ({ captchaToken, // [!code ++] ...input // [!code ++] -}: z.infer & { captchaToken }) => { +}: z.infer & { captchaToken: string }) => { const res = await fetcher(usersModule, { path: '/sign_up', method: 'post', diff --git a/apps/docs/content/docs/dev/email/components/button.mdx b/apps/docs/content/docs/dev/email/components/button.mdx index c78ebc283..150a8a590 100644 --- a/apps/docs/content/docs/dev/email/components/button.mdx +++ b/apps/docs/content/docs/dev/email/components/button.mdx @@ -13,11 +13,11 @@ import buttonPreviewImg from './button-preview.png'; ## Usage ```ts -import { Button } from '@vitnode/core/emails/ui/button'; +import { EmailButton } from '@vitnode/core/emails/ui/button'; ``` ```tsx - +Default ``` ## Props diff --git a/apps/docs/content/docs/dev/email/components/card.mdx b/apps/docs/content/docs/dev/email/components/card.mdx index 04e7130d3..0bd1d3e6e 100644 --- a/apps/docs/content/docs/dev/email/components/card.mdx +++ b/apps/docs/content/docs/dev/email/components/card.mdx @@ -14,29 +14,29 @@ import cardPreviewImg from './card-preview.png'; ```tsx import { - Card, - CardContent, - CardDescription, - CardFooter, - CardHeader, - CardTitle, + EmailCard, + EmailCardContent, + EmailCardDescription, + EmailCardFooter, + EmailCardHeader, + EmailCardTitle, } from '@vitnode/core/emails/ui/card'; import { Text } from '@react-email/components'; ``` ```tsx - - - Card Title - Card Description + + + Card Title + Card Description - + This is the content of the card. You can put any content here. - - + + Footer - - + + ``` diff --git a/apps/docs/content/docs/dev/email/templates.mdx b/apps/docs/content/docs/dev/email/templates.mdx index 52a5f301f..277068984 100644 --- a/apps/docs/content/docs/dev/email/templates.mdx +++ b/apps/docs/content/docs/dev/email/templates.mdx @@ -152,22 +152,69 @@ import DefaultTemplateEmail, { import { createTranslator } from 'use-intl'; // [!code ++] export default function TestTemplateEmail({ - locale, - messages, + i18n, ...props }: DefaultTemplateEmailProps) { - const t = createTranslator({ locale, messages }); // [!code ++] + const t = createTranslator(i18n); // [!code ++] // [!code ++:5] return ( - + {t('@vitnode/blog.title')} ); } -TestTemplateEmail.PreviewProps = - DefaultTemplateEmail.PreviewProps satisfies DefaultTemplateEmailProps; +TestTemplateEmail.PreviewProps = { + ...DefaultTemplateEmail.PreviewProps, + i18n: { + ...DefaultTemplateEmail.PreviewProps.i18n, + // [!code ++:5] + messages: { + '@vitnode/blog': { + title: 'Blog', + }, + }, + }, +} satisfies DefaultTemplateEmailProps; +``` + +### Subject Translation + +You can also translate the subject of the email using the `createTranslator` function. This allows you to have localized subjects for your emails. + +```ts title="src/api/modules/users/routes/test.route.ts" +import { z } from 'zod'; +// [!code ++] +import { createTranslator } from 'use-intl'; +import { buildRoute } from '@vitnode/core/api/lib/route'; +import { UserModel } from '@vitnode/core/api/models/user'; + +import TestTemplateEmail from '@/emails/test-template'; + +export const testRoute = buildRoute({ + handler: async c => { + const user = await new UserModel().getUserById({ + id: 3, + c, + }); + + if (!user) throw new Error('User not found'); + + await c.get('email').send({ + // [!code ++:5] + subject: ({ i18n }) => { + const t = createTranslator(i18n); + + return t('@vitnode/blog.title'); + }, + content: TestTemplateEmail, + user, + }); + + return c.text('test'); + }, +}); ``` ## User in Email diff --git a/apps/docs/content/docs/dev/meta.json b/apps/docs/content/docs/dev/meta.json index 2dc38745c..e288b40e1 100644 --- a/apps/docs/content/docs/dev/meta.json +++ b/apps/docs/content/docs/dev/meta.json @@ -11,12 +11,13 @@ "deployments", "---Framework---", "plugins", - "rest-api", - "fetcher", + "api", "database", - "---UI---", + "---Frontend---", "layouts-and-pages", "admin-page", + "fetcher", + "---UI---", "i18n", "advanced", "---Integrations---", diff --git a/apps/docs/content/docs/ui/auto-form.mdx b/apps/docs/content/docs/ui/auto-form.mdx index bfe6cf10b..f1f721afc 100644 --- a/apps/docs/content/docs/ui/auto-form.mdx +++ b/apps/docs/content/docs/ui/auto-form.mdx @@ -47,9 +47,7 @@ const formSchema = z.object({ }, { id: 'email', - component: props => ( - - ), + component: props => , }, { id: 'user_type', @@ -106,6 +104,57 @@ const formSchema = z.object({ }); ``` +### Labels + +Set field labels using the `label` property in the field definition: + +```tsx +{ + id: 'username', + component: props => ( + + ), +} +``` + +#### Right Labels + +You can also add a label on the right side of the field using the `labelRight` property: + +```tsx +{ + id: 'username', + component: props => ( + + ), +} +``` + +### Descriptions + +Add descriptions to fields using the `description` property: + +```tsx +{ + id: 'username', + component: props => ( + + ), +} +``` + +or using the `describe` method in Zod: + +```ts +const formSchema = z.object({ + username: z.string().describe('This is the username for your application.'), +}); +``` + ### Default Values Set default values for fields using the `default` method: diff --git a/apps/docs/migrations/0001_reflective_trish_tilby.sql b/apps/docs/migrations/0001_reflective_trish_tilby.sql new file mode 100644 index 000000000..894bbcbdc --- /dev/null +++ b/apps/docs/migrations/0001_reflective_trish_tilby.sql @@ -0,0 +1 @@ +ALTER TABLE "core_users_confirm_emails" ADD COLUMN "ipAddress" varchar(40) NOT NULL; \ No newline at end of file diff --git a/apps/docs/migrations/0002_first_the_fallen.sql b/apps/docs/migrations/0002_first_the_fallen.sql new file mode 100644 index 000000000..88a7f1bf8 --- /dev/null +++ b/apps/docs/migrations/0002_first_the_fallen.sql @@ -0,0 +1,2 @@ +ALTER TABLE "core_users_forgot_password" ADD COLUMN "ipAddress" varchar(40) NOT NULL;--> statement-breakpoint +ALTER TABLE "core_users_forgot_password" DROP COLUMN "ip_address"; \ No newline at end of file diff --git a/apps/docs/migrations/meta/0001_snapshot.json b/apps/docs/migrations/meta/0001_snapshot.json new file mode 100644 index 000000000..556d11ac4 --- /dev/null +++ b/apps/docs/migrations/meta/0001_snapshot.json @@ -0,0 +1,1448 @@ +{ + "id": "c7112d01-13cf-45e3-a15a-172c4596a436", + "prevId": "53f6fec6-1c5b-4344-8806-8d2cfc773fe0", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.core_admin_permissions": { + "name": "core_admin_permissions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "roleId": { + "name": "roleId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "protected": { + "name": "protected", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "core_admin_permissions_role_id_idx": { + "name": "core_admin_permissions_role_id_idx", + "columns": [ + { + "expression": "roleId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_admin_permissions_user_id_idx": { + "name": "core_admin_permissions_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_admin_permissions_roleId_core_roles_id_fk": { + "name": "core_admin_permissions_roleId_core_roles_id_fk", + "tableFrom": "core_admin_permissions", + "tableTo": "core_roles", + "columnsFrom": [ + "roleId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "core_admin_permissions_userId_core_users_id_fk": { + "name": "core_admin_permissions_userId_core_users_id_fk", + "tableFrom": "core_admin_permissions", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_admin_sessions": { + "name": "core_admin_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "token": { + "name": "token", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "lastSeen": { + "name": "lastSeen", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "deviceId": { + "name": "deviceId", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "core_admin_sessions_token_idx": { + "name": "core_admin_sessions_token_idx", + "columns": [ + { + "expression": "token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_admin_sessions_user_id_idx": { + "name": "core_admin_sessions_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_admin_sessions_userId_core_users_id_fk": { + "name": "core_admin_sessions_userId_core_users_id_fk", + "tableFrom": "core_admin_sessions", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "core_admin_sessions_deviceId_core_sessions_known_devices_id_fk": { + "name": "core_admin_sessions_deviceId_core_sessions_known_devices_id_fk", + "tableFrom": "core_admin_sessions", + "tableTo": "core_sessions_known_devices", + "columnsFrom": [ + "deviceId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_admin_sessions_token_unique": { + "name": "core_admin_sessions_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_languages": { + "name": "core_languages", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "code": { + "name": "code", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "timezone": { + "name": "timezone", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "'UTC'" + }, + "protected": { + "name": "protected", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "default": { + "name": "default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "time24": { + "name": "time24", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "core_languages_code_idx": { + "name": "core_languages_code_idx", + "columns": [ + { + "expression": "code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_languages_name_idx": { + "name": "core_languages_name_idx", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_languages_code_unique": { + "name": "core_languages_code_unique", + "nullsNotDistinct": false, + "columns": [ + "code" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_languages_words": { + "name": "core_languages_words", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "languageCode": { + "name": "languageCode", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "pluginCode": { + "name": "pluginCode", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tableName": { + "name": "tableName", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "variable": { + "name": "variable", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "core_languages_words_lang_code_idx": { + "name": "core_languages_words_lang_code_idx", + "columns": [ + { + "expression": "languageCode", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_languages_words_languageCode_core_languages_code_fk": { + "name": "core_languages_words_languageCode_core_languages_code_fk", + "tableFrom": "core_languages_words", + "tableTo": "core_languages", + "columnsFrom": [ + "languageCode" + ], + "columnsTo": [ + "code" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_logs": { + "name": "core_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "ipAddress": { + "name": "ipAddress", + "type": "varchar(45)", + "primaryKey": false, + "notNull": true + }, + "method": { + "name": "method", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true, + "default": "'GET'" + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'localhost'" + }, + "userAgent": { + "name": "userAgent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "statusCode": { + "name": "statusCode", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 500 + }, + "userId": { + "name": "userId", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "test123": { + "name": "test123", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": {}, + "foreignKeys": { + "core_logs_userId_core_users_id_fk": { + "name": "core_logs_userId_core_users_id_fk", + "tableFrom": "core_logs", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_moderators_permissions": { + "name": "core_moderators_permissions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "roleId": { + "name": "roleId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "protected": { + "name": "protected", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "core_moderators_permissions_role_id_idx": { + "name": "core_moderators_permissions_role_id_idx", + "columns": [ + { + "expression": "roleId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_moderators_permissions_user_id_idx": { + "name": "core_moderators_permissions_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_moderators_permissions_roleId_core_roles_id_fk": { + "name": "core_moderators_permissions_roleId_core_roles_id_fk", + "tableFrom": "core_moderators_permissions", + "tableTo": "core_roles", + "columnsFrom": [ + "roleId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "core_moderators_permissions_userId_core_users_id_fk": { + "name": "core_moderators_permissions_userId_core_users_id_fk", + "tableFrom": "core_moderators_permissions", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_roles": { + "name": "core_roles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "protected": { + "name": "protected", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "default": { + "name": "default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "root": { + "name": "root", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "guest": { + "name": "guest", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "color": { + "name": "color", + "type": "varchar(19)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_sessions": { + "name": "core_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "token": { + "name": "token", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "deviceId": { + "name": "deviceId", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "core_sessions_user_id_idx": { + "name": "core_sessions_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_sessions_userId_core_users_id_fk": { + "name": "core_sessions_userId_core_users_id_fk", + "tableFrom": "core_sessions", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "core_sessions_deviceId_core_sessions_known_devices_id_fk": { + "name": "core_sessions_deviceId_core_sessions_known_devices_id_fk", + "tableFrom": "core_sessions", + "tableTo": "core_sessions_known_devices", + "columnsFrom": [ + "deviceId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_sessions_token_unique": { + "name": "core_sessions_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_sessions_known_devices": { + "name": "core_sessions_known_devices", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "publicId": { + "name": "publicId", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "ipAddress": { + "name": "ipAddress", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + }, + "userAgent": { + "name": "userAgent", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "lastSeen": { + "name": "lastSeen", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_sessions_known_devices_ip_address_idx": { + "name": "core_sessions_known_devices_ip_address_idx", + "columns": [ + { + "expression": "ipAddress", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_sessions_known_devices_publicId_unique": { + "name": "core_sessions_known_devices_publicId_unique", + "nullsNotDistinct": false, + "columns": [ + "publicId" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_users": { + "name": "core_users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "nameCode": { + "name": "nameCode", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "password": { + "name": "password", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "newsletter": { + "name": "newsletter", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "avatarColor": { + "name": "avatarColor", + "type": "varchar(6)", + "primaryKey": false, + "notNull": true + }, + "emailVerified": { + "name": "emailVerified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "roleId": { + "name": "roleId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "birthday": { + "name": "birthday", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "ipAddress": { + "name": "ipAddress", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + }, + "language": { + "name": "language", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "'en'" + } + }, + "indexes": { + "core_users_name_code_idx": { + "name": "core_users_name_code_idx", + "columns": [ + { + "expression": "nameCode", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_users_name_idx": { + "name": "core_users_name_idx", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_users_email_idx": { + "name": "core_users_email_idx", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_users_roleId_core_roles_id_fk": { + "name": "core_users_roleId_core_roles_id_fk", + "tableFrom": "core_users", + "tableTo": "core_roles", + "columnsFrom": [ + "roleId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "core_users_language_core_languages_code_fk": { + "name": "core_users_language_core_languages_code_fk", + "tableFrom": "core_users", + "tableTo": "core_languages", + "columnsFrom": [ + "language" + ], + "columnsTo": [ + "code" + ], + "onDelete": "set default", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_users_nameCode_unique": { + "name": "core_users_nameCode_unique", + "nullsNotDistinct": false, + "columns": [ + "nameCode" + ] + }, + "core_users_name_unique": { + "name": "core_users_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + }, + "core_users_email_unique": { + "name": "core_users_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_users_confirm_emails": { + "name": "core_users_confirm_emails", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires": { + "name": "expires", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ipAddress": { + "name": "ipAddress", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "core_users_confirm_emails_userId_core_users_id_fk": { + "name": "core_users_confirm_emails_userId_core_users_id_fk", + "tableFrom": "core_users_confirm_emails", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_users_confirm_emails_token_unique": { + "name": "core_users_confirm_emails_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_users_forgot_password": { + "name": "core_users_forgot_password", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "core_users_forgot_password_userId_core_users_id_fk": { + "name": "core_users_forgot_password_userId_core_users_id_fk", + "tableFrom": "core_users_forgot_password", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_users_forgot_password_userId_unique": { + "name": "core_users_forgot_password_userId_unique", + "nullsNotDistinct": false, + "columns": [ + "userId" + ] + }, + "core_users_forgot_password_token_unique": { + "name": "core_users_forgot_password_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_users_sso": { + "name": "core_users_sso", + "schema": "", + "columns": { + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "providerId": { + "name": "providerId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "providerAccountId": { + "name": "providerAccountId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "core_users_sso_user_id_idx": { + "name": "core_users_sso_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_users_sso_userId_core_users_id_fk": { + "name": "core_users_sso_userId_core_users_id_fk", + "tableFrom": "core_users_sso", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.blog_categories": { + "name": "blog_categories", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "titleSeo": { + "name": "titleSeo", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true, + "default": "''" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "blog_categories_titleSeo_unique": { + "name": "blog_categories_titleSeo_unique", + "nullsNotDistinct": false, + "columns": [ + "titleSeo" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.blog_posts": { + "name": "blog_posts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "titleSeo": { + "name": "titleSeo", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "categoryId": { + "name": "categoryId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "blog_posts_categoryId_blog_categories_id_fk": { + "name": "blog_posts_categoryId_blog_categories_id_fk", + "tableFrom": "blog_posts", + "tableTo": "blog_categories", + "columnsFrom": [ + "categoryId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "blog_posts_titleSeo_unique": { + "name": "blog_posts_titleSeo_unique", + "nullsNotDistinct": false, + "columns": [ + "titleSeo" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/apps/docs/migrations/meta/0002_snapshot.json b/apps/docs/migrations/meta/0002_snapshot.json new file mode 100644 index 000000000..cce646f3e --- /dev/null +++ b/apps/docs/migrations/meta/0002_snapshot.json @@ -0,0 +1,1448 @@ +{ + "id": "e4143cd9-39bd-4bd4-8cd9-e620a0a997b4", + "prevId": "c7112d01-13cf-45e3-a15a-172c4596a436", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.core_admin_permissions": { + "name": "core_admin_permissions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "roleId": { + "name": "roleId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "protected": { + "name": "protected", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "core_admin_permissions_role_id_idx": { + "name": "core_admin_permissions_role_id_idx", + "columns": [ + { + "expression": "roleId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_admin_permissions_user_id_idx": { + "name": "core_admin_permissions_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_admin_permissions_roleId_core_roles_id_fk": { + "name": "core_admin_permissions_roleId_core_roles_id_fk", + "tableFrom": "core_admin_permissions", + "tableTo": "core_roles", + "columnsFrom": [ + "roleId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "core_admin_permissions_userId_core_users_id_fk": { + "name": "core_admin_permissions_userId_core_users_id_fk", + "tableFrom": "core_admin_permissions", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_admin_sessions": { + "name": "core_admin_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "token": { + "name": "token", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "lastSeen": { + "name": "lastSeen", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "deviceId": { + "name": "deviceId", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "core_admin_sessions_token_idx": { + "name": "core_admin_sessions_token_idx", + "columns": [ + { + "expression": "token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_admin_sessions_user_id_idx": { + "name": "core_admin_sessions_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_admin_sessions_userId_core_users_id_fk": { + "name": "core_admin_sessions_userId_core_users_id_fk", + "tableFrom": "core_admin_sessions", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "core_admin_sessions_deviceId_core_sessions_known_devices_id_fk": { + "name": "core_admin_sessions_deviceId_core_sessions_known_devices_id_fk", + "tableFrom": "core_admin_sessions", + "tableTo": "core_sessions_known_devices", + "columnsFrom": [ + "deviceId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_admin_sessions_token_unique": { + "name": "core_admin_sessions_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_languages": { + "name": "core_languages", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "code": { + "name": "code", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "timezone": { + "name": "timezone", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "'UTC'" + }, + "protected": { + "name": "protected", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "default": { + "name": "default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "time24": { + "name": "time24", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "core_languages_code_idx": { + "name": "core_languages_code_idx", + "columns": [ + { + "expression": "code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_languages_name_idx": { + "name": "core_languages_name_idx", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_languages_code_unique": { + "name": "core_languages_code_unique", + "nullsNotDistinct": false, + "columns": [ + "code" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_languages_words": { + "name": "core_languages_words", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "languageCode": { + "name": "languageCode", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "pluginCode": { + "name": "pluginCode", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tableName": { + "name": "tableName", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "variable": { + "name": "variable", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "core_languages_words_lang_code_idx": { + "name": "core_languages_words_lang_code_idx", + "columns": [ + { + "expression": "languageCode", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_languages_words_languageCode_core_languages_code_fk": { + "name": "core_languages_words_languageCode_core_languages_code_fk", + "tableFrom": "core_languages_words", + "tableTo": "core_languages", + "columnsFrom": [ + "languageCode" + ], + "columnsTo": [ + "code" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_logs": { + "name": "core_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "ipAddress": { + "name": "ipAddress", + "type": "varchar(45)", + "primaryKey": false, + "notNull": true + }, + "method": { + "name": "method", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true, + "default": "'GET'" + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'localhost'" + }, + "userAgent": { + "name": "userAgent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "statusCode": { + "name": "statusCode", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 500 + }, + "userId": { + "name": "userId", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "test123": { + "name": "test123", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": {}, + "foreignKeys": { + "core_logs_userId_core_users_id_fk": { + "name": "core_logs_userId_core_users_id_fk", + "tableFrom": "core_logs", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_moderators_permissions": { + "name": "core_moderators_permissions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "roleId": { + "name": "roleId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "protected": { + "name": "protected", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "core_moderators_permissions_role_id_idx": { + "name": "core_moderators_permissions_role_id_idx", + "columns": [ + { + "expression": "roleId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_moderators_permissions_user_id_idx": { + "name": "core_moderators_permissions_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_moderators_permissions_roleId_core_roles_id_fk": { + "name": "core_moderators_permissions_roleId_core_roles_id_fk", + "tableFrom": "core_moderators_permissions", + "tableTo": "core_roles", + "columnsFrom": [ + "roleId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "core_moderators_permissions_userId_core_users_id_fk": { + "name": "core_moderators_permissions_userId_core_users_id_fk", + "tableFrom": "core_moderators_permissions", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_roles": { + "name": "core_roles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "protected": { + "name": "protected", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "default": { + "name": "default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "root": { + "name": "root", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "guest": { + "name": "guest", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "color": { + "name": "color", + "type": "varchar(19)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_sessions": { + "name": "core_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "token": { + "name": "token", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "deviceId": { + "name": "deviceId", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "core_sessions_user_id_idx": { + "name": "core_sessions_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_sessions_userId_core_users_id_fk": { + "name": "core_sessions_userId_core_users_id_fk", + "tableFrom": "core_sessions", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "core_sessions_deviceId_core_sessions_known_devices_id_fk": { + "name": "core_sessions_deviceId_core_sessions_known_devices_id_fk", + "tableFrom": "core_sessions", + "tableTo": "core_sessions_known_devices", + "columnsFrom": [ + "deviceId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_sessions_token_unique": { + "name": "core_sessions_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_sessions_known_devices": { + "name": "core_sessions_known_devices", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "publicId": { + "name": "publicId", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "ipAddress": { + "name": "ipAddress", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + }, + "userAgent": { + "name": "userAgent", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "lastSeen": { + "name": "lastSeen", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_sessions_known_devices_ip_address_idx": { + "name": "core_sessions_known_devices_ip_address_idx", + "columns": [ + { + "expression": "ipAddress", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_sessions_known_devices_publicId_unique": { + "name": "core_sessions_known_devices_publicId_unique", + "nullsNotDistinct": false, + "columns": [ + "publicId" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_users": { + "name": "core_users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "nameCode": { + "name": "nameCode", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "password": { + "name": "password", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "newsletter": { + "name": "newsletter", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "avatarColor": { + "name": "avatarColor", + "type": "varchar(6)", + "primaryKey": false, + "notNull": true + }, + "emailVerified": { + "name": "emailVerified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "roleId": { + "name": "roleId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "birthday": { + "name": "birthday", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "ipAddress": { + "name": "ipAddress", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + }, + "language": { + "name": "language", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "'en'" + } + }, + "indexes": { + "core_users_name_code_idx": { + "name": "core_users_name_code_idx", + "columns": [ + { + "expression": "nameCode", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_users_name_idx": { + "name": "core_users_name_idx", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_users_email_idx": { + "name": "core_users_email_idx", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_users_roleId_core_roles_id_fk": { + "name": "core_users_roleId_core_roles_id_fk", + "tableFrom": "core_users", + "tableTo": "core_roles", + "columnsFrom": [ + "roleId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "core_users_language_core_languages_code_fk": { + "name": "core_users_language_core_languages_code_fk", + "tableFrom": "core_users", + "tableTo": "core_languages", + "columnsFrom": [ + "language" + ], + "columnsTo": [ + "code" + ], + "onDelete": "set default", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_users_nameCode_unique": { + "name": "core_users_nameCode_unique", + "nullsNotDistinct": false, + "columns": [ + "nameCode" + ] + }, + "core_users_name_unique": { + "name": "core_users_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + }, + "core_users_email_unique": { + "name": "core_users_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_users_confirm_emails": { + "name": "core_users_confirm_emails", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires": { + "name": "expires", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ipAddress": { + "name": "ipAddress", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "core_users_confirm_emails_userId_core_users_id_fk": { + "name": "core_users_confirm_emails_userId_core_users_id_fk", + "tableFrom": "core_users_confirm_emails", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_users_confirm_emails_token_unique": { + "name": "core_users_confirm_emails_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_users_forgot_password": { + "name": "core_users_forgot_password", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "ipAddress": { + "name": "ipAddress", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "core_users_forgot_password_userId_core_users_id_fk": { + "name": "core_users_forgot_password_userId_core_users_id_fk", + "tableFrom": "core_users_forgot_password", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_users_forgot_password_userId_unique": { + "name": "core_users_forgot_password_userId_unique", + "nullsNotDistinct": false, + "columns": [ + "userId" + ] + }, + "core_users_forgot_password_token_unique": { + "name": "core_users_forgot_password_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_users_sso": { + "name": "core_users_sso", + "schema": "", + "columns": { + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "providerId": { + "name": "providerId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "providerAccountId": { + "name": "providerAccountId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "core_users_sso_user_id_idx": { + "name": "core_users_sso_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_users_sso_userId_core_users_id_fk": { + "name": "core_users_sso_userId_core_users_id_fk", + "tableFrom": "core_users_sso", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.blog_categories": { + "name": "blog_categories", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "titleSeo": { + "name": "titleSeo", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true, + "default": "''" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "blog_categories_titleSeo_unique": { + "name": "blog_categories_titleSeo_unique", + "nullsNotDistinct": false, + "columns": [ + "titleSeo" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.blog_posts": { + "name": "blog_posts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "titleSeo": { + "name": "titleSeo", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "categoryId": { + "name": "categoryId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "blog_posts_categoryId_blog_categories_id_fk": { + "name": "blog_posts_categoryId_blog_categories_id_fk", + "tableFrom": "blog_posts", + "tableTo": "blog_categories", + "columnsFrom": [ + "categoryId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "blog_posts_titleSeo_unique": { + "name": "blog_posts_titleSeo_unique", + "nullsNotDistinct": false, + "columns": [ + "titleSeo" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/apps/docs/migrations/meta/_journal.json b/apps/docs/migrations/meta/_journal.json index 509512a43..7d21da2ed 100644 --- a/apps/docs/migrations/meta/_journal.json +++ b/apps/docs/migrations/meta/_journal.json @@ -8,6 +8,20 @@ "when": 1754424977539, "tag": "0000_serious_nightcrawler", "breakpoints": true + }, + { + "idx": 1, + "version": "7", + "when": 1754907028203, + "tag": "0001_reflective_trish_tilby", + "breakpoints": true + }, + { + "idx": 2, + "version": "7", + "when": 1754907229544, + "tag": "0002_first_the_fallen", + "breakpoints": true } ] } \ No newline at end of file diff --git a/apps/docs/package.json b/apps/docs/package.json index 51861f904..6b6cdfec4 100644 --- a/apps/docs/package.json +++ b/apps/docs/package.json @@ -10,7 +10,7 @@ "init": "vitnode init", "dev": "vitnode init && next dev --turbopack", "dev:email": "email dev --dir src/emails", - "build": "next build", + "build": "next build --turbopack", "start": "next start", "lint": "eslint .", "lint:fix": "eslint . --fix", @@ -22,44 +22,45 @@ "drizzle-kit": "drizzle-kit" }, "dependencies": { - "@hono/zod-openapi": "^1.0.2", + "@hono/zod-openapi": "^1.1.0", "@hono/zod-validator": "^0.7.2", "@vitnode/blog": "workspace:*", "@vitnode/core": "workspace:*", "babel-plugin-react-compiler": "19.1.0-rc.2", "drizzle-kit": "^0.31.4", "drizzle-orm": "^0.44.4", - "fumadocs-core": "^15.6.7", - "fumadocs-mdx": "^11.7.3", - "fumadocs-ui": "^15.6.7", - "hono": "^4.8.10", - "lucide-react": "^0.536.0", + "fumadocs-core": "^15.6.12", + "fumadocs-mdx": "^11.7.5", + "fumadocs-ui": "^15.6.12", + "hono": "^4.9.2", + "lucide-react": "^0.540.0", "motion": "^12.23.12", - "next": "^15.4.5", + "next": "^15.5.0", "next-intl": "^4.3.4", "react": "^19.1.1", "react-dom": "^19.1.1", - "react-hook-form": "^7.61.1", + "react-hook-form": "^7.62.0", "react-use": "^17.6.0", - "sonner": "^2.0.6" + "sonner": "^2.0.7" }, "devDependencies": { - "@playwright/test": "^1.54.2", - "@react-email/components": "^0.4.0", - "@tailwindcss/postcss": "^4.1.11", + "@playwright/test": "^1.55.0", + "@react-email/components": "^0.5.1", + "@tailwindcss/postcss": "^4.1.12", "@types/mdx": "^2.0.13", - "@types/node": "^24.1.0", - "@types/react": "^19.1.9", + "@types/node": "^24.3.0", + "@types/react": "^19.1.10", "@types/react-dom": "^19.1.7", "@vitnode/eslint-config": "workspace:*", "class-variance-authority": "^0.7.1", - "eslint": "^9.32.0", + "eslint": "^9.33.0", "postcss": "^8.5.6", - "react-email": "^4.2.7", - "shiki": "^3.9.1", - "tailwindcss": "^4.1.11", - "tw-animate-css": "^1.3.6", + "react-email": "^4.2.8", + "shiki": "^3.11.0", + "tailwindcss": "^4.1.12", + "tw-animate-css": "^1.3.7", "typescript": "^5.9.2", - "zod": "^4.0.14" + "use-intl": "^4.3.4", + "zod": "^4.0.17" } } diff --git a/apps/docs/src/app/[locale]/(main)/(plugins)/(vitnode-core)/login/reset-password/page.tsx b/apps/docs/src/app/[locale]/(main)/(plugins)/(vitnode-core)/login/reset-password/page.tsx new file mode 100644 index 000000000..069f6fdd1 --- /dev/null +++ b/apps/docs/src/app/[locale]/(main)/(plugins)/(vitnode-core)/login/reset-password/page.tsx @@ -0,0 +1,27 @@ +import type { Metadata } from 'next/dist/types'; + +import { getTranslations } from 'next-intl/server'; + +import { PasswordResetView } from '@vitnode/core/views/auth/password-reset/password-reset-view'; + +export const generateMetadata = async ({ + params, +}: { + params: Promise<{ locale: string }>; +}): Promise => { + const { locale } = await params; + const t = await getTranslations({ + locale, + namespace: 'core.auth.reset_password', + }); + + return { + title: t('title'), + }; +}; + +export default function Page( + props: React.ComponentProps, +) { + return ; +} diff --git a/apps/docs/src/app/global.css b/apps/docs/src/app/global.css index 40301e73d..cc47703f0 100644 --- a/apps/docs/src/app/global.css +++ b/apps/docs/src/app/global.css @@ -9,7 +9,7 @@ @source "../../node_modules/@vitnode/core/dist/src/views"; :root:not(.dark) { - --background: oklch(0.99 0.01 250); + --background: oklch(0.96 0.01 250); --foreground: oklch(0.18 0.01 250); --card: oklch(1 0 0); --card-foreground: oklch(0.22 0.01 250); diff --git a/apps/docs/src/examples/auto-form.tsx b/apps/docs/src/examples/auto-form.tsx index 87ff240d9..149f2ad67 100644 --- a/apps/docs/src/examples/auto-form.tsx +++ b/apps/docs/src/examples/auto-form.tsx @@ -38,7 +38,7 @@ export default function AutoFormExample() { { id: 'email', component: props => ( - + ), }, { diff --git a/apps/docs/src/examples/input.tsx b/apps/docs/src/examples/input.tsx index c0fe93052..a74d4dd09 100644 --- a/apps/docs/src/examples/input.tsx +++ b/apps/docs/src/examples/input.tsx @@ -29,7 +29,6 @@ export default function InputExample() { ), diff --git a/apps/docs/src/locales/@vitnode/core/en.json b/apps/docs/src/locales/@vitnode/core/en.json index bec6c674c..cefac4fa4 100644 --- a/apps/docs/src/locales/@vitnode/core/en.json +++ b/apps/docs/src/locales/@vitnode/core/en.json @@ -132,7 +132,8 @@ }, "password": { "label": "Password", - "required": "Password is required." + "required": "Password is required.", + "reset": "Forgot password?" }, "errors": { "access_denied": { @@ -141,6 +142,36 @@ } }, "submit": "Login" + }, + "reset_password": { + "title": "Reset Password", + "desc": "Please enter your email address below to receive a password reset link.", + "submit": "Send Reset Link", + "confirmation": { + "title": "Check your email", + "desc": "We've sent a password reset link to your email address:", + "check_spam": "If you don't see the email in your inbox, please check your spam folder." + }, + "email": { + "subject": "Reset your password", + "greeting": "Hello {name}!", + "intro": "We received a request to reset your password for your account.", + "button": "Reset Password", + "instructions": "Click the button above to reset your password. This link will expire in 30 minutes for security reasons.", + "no_action": "If you didn't request a password reset, you can safely ignore this email. Your password will remain unchanged.", + "security_note": "For your security, this request was made from IP address: {ip}", + "help": "If you're having trouble with the button above, copy and paste the URL below into your web browser:", + "expire_time": "This link will expire on {date}" + } + }, + "change_password": { + "title": "Change Password", + "desc": "Please enter your new password below.", + "submit": "Change Password", + "success": { + "title": "Password changed successfully", + "desc": "You can now log in with your new password." + } } } }, diff --git a/package.json b/package.json index d48efe3e8..a5f04dc6d 100644 --- a/package.json +++ b/package.json @@ -18,19 +18,19 @@ "test:e2e": "turbo test:e2e" }, "devDependencies": { - "@types/node": "^24.1.0", + "@types/node": "^24.3.0", "@vitnode/eslint-config": "workspace:*", "prettier": "^3.6.2", "prettier-plugin-tailwindcss": "^0.6.14", - "tsx": "^4.20.3", - "turbo": "^2.5.5", + "tsx": "^4.20.4", + "turbo": "^2.5.6", "typescript": "^5.9.2", - "zod": "^4.0.14" + "zod": "^4.0.17" }, "engines": { "node": ">=22" }, - "packageManager": "pnpm@10.14.0", + "packageManager": "pnpm@10.15.0", "workspaces": [ "apps/*", "packages/*", diff --git a/packages/create-vitnode-app/copy-of-vitnode-app/root/src/app/global.css b/packages/create-vitnode-app/copy-of-vitnode-app/root/src/app/global.css index d90ff72f6..ac899273a 100644 --- a/packages/create-vitnode-app/copy-of-vitnode-app/root/src/app/global.css +++ b/packages/create-vitnode-app/copy-of-vitnode-app/root/src/app/global.css @@ -6,7 +6,7 @@ @source "../../node_modules/@vitnode/core/dist/src/views"; :root:not(.dark) { - --background: oklch(0.99 0.01 250); + --background: oklch(0.96 0.01 250); --foreground: oklch(0.18 0.01 250); --card: oklch(1 0 0); --card-foreground: oklch(0.22 0.01 250); diff --git a/packages/create-vitnode-app/package.json b/packages/create-vitnode-app/package.json index 8ef1048ae..83021b5b8 100644 --- a/packages/create-vitnode-app/package.json +++ b/packages/create-vitnode-app/package.json @@ -28,18 +28,18 @@ "typescript" ], "dependencies": { - "@inquirer/prompts": "^7.8.0", + "@inquirer/prompts": "^7.8.3", "commander": "^14.0.0", "ora": "^8.2.0", "picocolors": "^1.1.1", "validate-npm-package-name": "^6.0.2" }, "devDependencies": { - "@types/node": "^24.1.0", + "@types/node": "^24.3.0", "@types/prompts": "^2.4.9", "@types/validate-npm-package-name": "^4.0.2", "@vitnode/eslint-config": "workspace:*", - "eslint": "^9.32.0", + "eslint": "^9.33.0", "typescript": "^5.9.2" } } diff --git a/packages/create-vitnode-app/src/create/create-package-json.ts b/packages/create-vitnode-app/src/create/create-package-json.ts index 27027c2b2..58a8def28 100644 --- a/packages/create-vitnode-app/src/create/create-package-json.ts +++ b/packages/create-vitnode-app/src/create/create-package-json.ts @@ -10,6 +10,270 @@ import { getAvailablePackageManagers } from '../helpers/get-available-package-ma const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); +type Mode = CreateCliReturn['mode']; + +const writeJson = async (path: string, data: unknown) => + writeFile(path, JSON.stringify(data, null, 2)); + +const paths = (root: string) => ({ + root, + api: join(root, 'apps', 'api'), + web: join(root, 'apps', 'web'), +}); + +const withIf = >(cond: boolean, obj: T) => + (cond ? obj : {}) as Partial; + +const versions = { + typesNode: '^24', + typesReact: '^19.1', + typesReactDom: '^19.1', + typesMdx: '^2.0.13', + typesBun: 'latest', + + turbo: '^2.5.6', + typescript: '^5.9.2', + tsx: '^4.20.4', + tscAlias: '^1.8.16', + eslint: '^9.33.0', + prettier: '^3.6.2', + prettierTailwind: '^0.6.14', + tailwind: '^4.1.12', + tailwindPostcss: '^4.1.12', + postcss: '^8.5.6', + twAnimateCssWeb: '^1.3.7', + twAnimateCssSingle: '^1.3.6', + + react: '^19.1', + reactDom: '^19.1', + nextSingle: '^15.5.0', + nextWebInMonorepo: '^15.4.6', + nextIntl: '^4.3.4', + useIntl: '^4.3.4', + rhf: '^7.62.0', + rhfResolvers: '^5.1.1', + lucide: '^0.540.0', + sonner: '^2.0.7', + dotenv: '^17.2.1', + + drizzleKitSingle: '^0.31.4', + drizzleKitApi: '^0.31.3', + drizzleOrm: '^0.44.4', + + hono: '^4.9.2', + honoZodOpenapi: '^1.1.0', + honoZodValidator: '^0.7.2', + reactEmail: '^4.2.8', + reactEmailComponents: '^0.5.1', + zod: '^4.0.17', + + babelReactCompiler: '19.1.0-rc.2', + cva: '^0.7.1', +}; + +/** + * Shared blocks + */ +const eslintScripts = { + lint: 'eslint .', + 'lint:fix': 'eslint . --fix', +}; + +const dockerDevScript = (appName: string) => + `docker compose -f ./docker-compose.yml -p ${appName}-vitnode-dev up -d`; + +const rootScripts = ( + enableEslint: boolean, + enableDocker: boolean, + appName: string, +) => ({ + 'db:migrate': 'turbo db:migrate', + 'db:push': 'turbo db:push', + init: 'turbo init', + dev: 'turbo dev', + build: 'turbo build', + start: 'turbo start', + ...withIf(enableEslint, eslintScripts), + ...withIf(enableDocker, { 'docker:dev': dockerDevScript(appName) }), +}); + +const apiScripts = ( + pm: string, + eslint: boolean, + docker: boolean, + onlyApi: boolean, + appName: string, +) => ({ + 'db:push': 'vitnode push', + 'db:migrate': 'vitnode migrate', + init: 'vitnode init --api', + ...(pm === 'bun' + ? { + dev: 'vitnode init --api && bun run --hot src/index.ts', + start: 'NODE_ENV=production bun run src/index.ts', + } + : { + dev: 'vitnode init --api && tsx watch src/index.ts', + build: 'tsc && tsc-alias -p tsconfig.json', + start: 'node dist/index.js', + }), + 'dev:email': 'email dev --dir src/emails', + ...withIf(eslint, eslintScripts), + ...withIf(docker && onlyApi, { 'docker:dev': dockerDevScript(appName) }), + 'drizzle-kit': 'drizzle-kit', +}); + +const singleAppScripts = ( + eslint: boolean, + docker: boolean, + appName: string, +) => ({ + 'db:push': 'vitnode push', + 'db:migrate': 'vitnode migrate', + init: 'vitnode init', + dev: 'vitnode init && next dev --turbopack', + 'dev:email': 'email dev --dir src/emails', + build: 'next build', + start: 'next start', + ...withIf(eslint, eslintScripts), + ...withIf(docker, { 'docker:dev': dockerDevScript(appName) }), + 'drizzle-kit': 'drizzle-kit', +}); + +const webScripts = (eslint: boolean) => ({ + init: 'vitnode init --web', + dev: 'vitnode init --web && next dev --turbopack', + build: 'next build', + start: 'next start', + ...withIf(eslint, eslintScripts), +}); + +/** + * Dependency builders + */ +const baseDevDeps = (eslint: boolean, includePrettier: boolean) => ({ + '@types/node': versions.typesNode, + '@vitnode/eslint-config': '', // filled with local version dynamically + ...withIf(eslint, { + eslint: versions.eslint, + ...withIf(includePrettier, { + prettier: versions.prettier, + 'prettier-plugin-tailwindcss': versions.prettierTailwind, + }), + }), +}); + +const rootDevDeps = (eslint: boolean) => ({ + ...baseDevDeps(eslint, true), + turbo: versions.turbo, + typescript: versions.typescript, + zod: versions.zod, +}); + +const apiDeps = { + '@hono/zod-openapi': versions.honoZodOpenapi, + '@hono/zod-validator': versions.honoZodValidator, + '@react-email/components': versions.reactEmailComponents, + '@vitnode/core': '', // filled dynamically + 'drizzle-kit': versions.drizzleKitApi, + 'drizzle-orm': versions.drizzleOrm, + hono: versions.hono, + 'next-intl': versions.nextIntl, + react: versions.react, + 'react-dom': versions.reactDom, + 'use-intl': versions.useIntl, + zod: versions.zod, +}; + +const apiDevDeps = (pm: string, eslint: boolean) => ({ + '@hono/node-server': '^1.19.0', + ...(pm === 'bun' ? { '@types/bun': versions.typesBun } : {}), + '@types/node': versions.typesNode, + '@types/react': versions.typesReact, + '@types/react-dom': versions.typesReactDom, + '@vitnode/eslint-config': '', + dotenv: versions.dotenv, + ...withIf(eslint, { + eslint: versions.eslint, + // Prettier in API only when onlyApi + eslint in original code – we'll preserve by passing include later if needed + }), + 'react-email': versions.reactEmail, + 'tsc-alias': versions.tscAlias, + tsx: versions.tsx, + typescript: versions.typescript, +}); + +const singleAppDeps = { + '@hono/zod-openapi': versions.honoZodOpenapi, + '@hono/zod-validator': versions.honoZodValidator, + '@hookform/resolvers': versions.rhfResolvers, + '@react-email/components': versions.reactEmailComponents, + '@vitnode/core': '', + 'babel-plugin-react-compiler': versions.babelReactCompiler, + 'drizzle-kit': versions.drizzleKitSingle, + 'drizzle-orm': versions.drizzleOrm, + hono: versions.hono, + 'lucide-react': versions.lucide, + next: versions.nextSingle, + 'next-intl': versions.nextIntl, + react: versions.react, + 'react-dom': versions.reactDom, + 'react-hook-form': versions.rhf, + sonner: versions.sonner, + 'use-intl': versions.useIntl, + zod: versions.zod, +}; + +const singleAppDevDeps = (eslint: boolean) => ({ + '@tailwindcss/postcss': versions.tailwindPostcss, + '@types/node': versions.typesNode, + '@types/react': versions.typesReact, + '@types/react-dom': versions.typesReactDom, + '@vitnode/eslint-config': '', + ...withIf(eslint, { + eslint: versions.eslint, + prettier: versions.prettier, + 'prettier-plugin-tailwindcss': versions.prettierTailwind, + }), + 'react-email': versions.reactEmail, + turbo: versions.turbo, + tailwindcss: versions.tailwind, + 'tw-animate-css': versions.twAnimateCssSingle, + typescript: versions.typescript, +}); + +const webDeps = { + '@vitnode/core': '', + 'babel-plugin-react-compiler': versions.babelReactCompiler, + 'lucide-react': versions.lucide, + next: versions.nextWebInMonorepo, + 'next-intl': versions.nextIntl, + react: versions.react, + 'react-dom': versions.reactDom, + 'react-hook-form': versions.rhf, + sonner: versions.sonner, +}; + +const webDevDeps = (eslint: boolean) => ({ + '@hookform/resolvers': versions.rhfResolvers, + '@tailwindcss/postcss': versions.tailwindPostcss, + '@types/mdx': versions.typesMdx, + '@types/node': versions.typesNode, + '@types/react': versions.typesReact, + '@types/react-dom': versions.typesReactDom, + '@vitnode/eslint-config': '', + 'class-variance-authority': versions.cva, + ...withIf(eslint, { eslint: versions.eslint }), + postcss: versions.postcss, + tailwindcss: versions.tailwind, + 'tw-animate-css': versions.twAnimateCssWeb, + typescript: versions.typescript, + zod: versions.zod, +}); + +/** + * Main + */ export const createPackageJSON = async ({ appName, packageManager, @@ -22,279 +286,120 @@ export const createPackageJSON = async ({ appName: string; docker?: boolean; eslint: boolean; - mode: CreateCliReturn['mode']; + mode: Mode; monorepo?: boolean; packageManager: string; root: string; }) => { - const availablePackageManagers = await getAvailablePackageManagers(); - const pkg: PackageJSON = JSON.parse( + // Resolve local version of @vitnode/* based on this CLI's package.json + const cliPkg: PackageJSON = JSON.parse( await readFile(join(__dirname, '..', '..', '..', 'package.json'), 'utf-8'), ); - const pkgVitNodeVersion = `^${pkg.version}`; - const monorepoStructure = { - api: join(root, 'apps', 'api'), - web: join(root, 'apps', 'web'), - }; + const vitnodeVersionRange = `^${cliPkg.version}`; - if (mode === 'apiMonorepo' || monorepo) { - const rootPackageJson: PackageJSON = { + const pmVersions = await getAvailablePackageManagers(); + const pmSpec = `${packageManager}@${pmVersions[packageManager]}`; + const p = paths(root); + + const isApiMonorepo = mode === 'apiMonorepo' || !!monorepo; + const isOnlyApi = mode === 'onlyApi'; + const isSingleApp = mode === 'singleApp'; + + // 1) Root package.json (for monorepo/apiMonorepo) + if (isApiMonorepo) { + const rootPkg: PackageJSON = { name: appName, private: true, - scripts: { - 'db:migrate': 'turbo db:migrate', - 'db:push': 'turbo db:push', - init: 'turbo init', - dev: 'turbo dev', - build: 'turbo build', - start: 'turbo start', - ...(eslint - ? { - lint: 'eslint .', - 'lint:fix': 'eslint . --fix', - } - : {}), - ...(docker - ? { - 'docker:dev': `docker compose -f ./docker-compose.yml -p ${appName}-vitnode-dev up -d`, - } - : {}), - }, + scripts: rootScripts(eslint, !!docker, appName), devDependencies: { - '@types/node': '^24', - '@vitnode/eslint-config': pkgVitNodeVersion, - ...(eslint - ? { - 'prettier-plugin-tailwindcss': '^0.6.14', - prettier: '^3.6.2', - } - : {}), - turbo: '^2.5.5', - typescript: '^5.8.3', - zod: '^4.0.14', + ...rootDevDeps(eslint), + '@vitnode/eslint-config': vitnodeVersionRange, }, - packageManager: `${packageManager}@${availablePackageManagers[packageManager]}`, + packageManager: pmSpec, workspaces: ['apps/*', 'plugins/*'], }; - await writeFile( - join(root, 'package.json'), - JSON.stringify(rootPackageJson, null, 2), - ); + await writeJson(join(p.root, 'package.json'), rootPkg); } - const apiPackageJson: PackageJSON = { - name: mode === 'apiMonorepo' || monorepo ? 'api' : appName, + // 2) API package.json (shared by onlyApi and apiMonorepo) + const apiPkg: PackageJSON = { + name: isApiMonorepo ? 'api' : appName, version: '0.1.0', private: true, type: 'module', - scripts: { - 'db:push': 'vitnode push', - 'db:migrate': 'vitnode migrate', - init: 'vitnode init --api', - ...(packageManager === 'bun' - ? { - dev: 'vitnode init --api && bun run --hot src/index.ts', - start: 'NODE_ENV=production bun run src/index.ts', - } - : { - dev: 'vitnode init --api && tsx watch src/index.ts', - build: 'tsc && tsc-alias -p tsconfig.json', - start: 'node dist/index.js', - }), - 'dev:email': 'email dev --dir src/emails', - ...(eslint - ? { - lint: 'eslint .', - 'lint:fix': 'eslint . --fix', - } - : {}), - ...(docker && mode === 'onlyApi' - ? { - 'docker:dev': `docker compose -f ./docker-compose.yml -p ${appName}-vitnode-dev up -d`, - } - : {}), - 'drizzle-kit': 'drizzle-kit', - }, + scripts: apiScripts( + packageManager, + eslint, + !!docker, + mode === 'onlyApi', + appName, + ), dependencies: { - '@hono/zod-openapi': '^1.0.2', - '@hono/zod-validator': '^0.7.2', - '@react-email/components': '^0.4.0', - '@vitnode/core': pkgVitNodeVersion, - 'drizzle-kit': '^0.31.3', - 'drizzle-orm': '^0.44.4', - hono: '^4.8.10', - 'next-intl': '^4.3.1', - react: '^19.1', - 'react-dom': '^19.1', - zod: '^4.0.14', + ...apiDeps, + '@vitnode/core': vitnodeVersionRange, }, devDependencies: { - '@hono/node-server': '^1.18.0', - ...(packageManager === 'bun' + ...apiDevDeps(packageManager, eslint), + '@vitnode/eslint-config': vitnodeVersionRange, + ...(eslint && mode === 'onlyApi' ? { - '@types/bun': 'latest', + prettier: versions.prettier, + 'prettier-plugin-tailwindcss': versions.prettierTailwind, } : {}), - '@types/node': '^24', - '@types/react': '^19.1', - '@types/react-dom': '^19.1', - '@vitnode/eslint-config': pkgVitNodeVersion, - dotenv: '^17.2.1', - ...(eslint - ? { - eslint: '^9.32.0', - ...(mode === 'onlyApi' - ? { - 'prettier-plugin-tailwindcss': '^0.6.14', - prettier: '^3.6.2', - } - : {}), - } - : {}), - 'react-email': '^4.2.7', - 'tsc-alias': '^1.8.16', - tsx: '^4.20.3', - typescript: '^5.8.3', + // TS pipeline pieces when not using Bun for dev + ...(packageManager === 'bun' ? {} : {}), }, }; - if (mode === 'singleApp') { - const packageJson: PackageJSON = { + // 3) Single app (Next.js + API inside one app) + if (isSingleApp) { + const singlePkg: PackageJSON = { name: monorepo ? 'web' : appName, version: '0.1.0', private: true, type: 'module', - scripts: { - 'db:push': 'vitnode push', - 'db:migrate': 'vitnode migrate', - init: 'vitnode init', - dev: 'vitnode init && next dev --turbopack', - 'dev:email': 'email dev --dir src/emails', - build: 'next build', - start: 'next start', - ...(eslint - ? { - lint: 'eslint .', - 'lint:fix': 'eslint . --fix', - } - : {}), - ...(docker - ? { - 'docker:dev': `docker compose -f ./docker-compose.yml -p ${appName}-vitnode-dev up -d`, - } - : {}), - 'drizzle-kit': 'drizzle-kit', - }, + scripts: singleAppScripts(eslint, !!docker, appName), dependencies: { - '@hono/zod-openapi': '^1.0.2', - '@hono/zod-validator': '^0.7.2', - '@hookform/resolvers': '^5.1.1', - '@react-email/components': '^0.4.0', - '@vitnode/core': pkgVitNodeVersion, - 'babel-plugin-react-compiler': '19.1.0-rc.2', - 'drizzle-kit': '^0.31.4', - 'drizzle-orm': '^0.44.4', - hono: '^4.8.10', - 'lucide-react': '^0.536.0', - next: '^15.4.5', - 'next-intl': '^4.3.4', - react: '^19.1', - 'react-dom': '^19.1', - 'react-hook-form': '^7.61.1', - sonner: '^2.0.6', - zod: '^4.0.14', + ...singleAppDeps, + '@vitnode/core': vitnodeVersionRange, }, devDependencies: { - '@tailwindcss/postcss': '^4.1.11', - '@types/node': '^24', - '@types/react': '^19.1', - '@types/react-dom': '^19.1', - '@vitnode/eslint-config': pkgVitNodeVersion, - ...(eslint - ? { - eslint: '^9.32.0', - 'prettier-plugin-tailwindcss': '^0.6.14', - prettier: '^3.6.2', - } - : {}), - 'react-email': '^4.2.7', - turbo: '^2.5.5', - tailwindcss: '^4.1.11', - 'tw-animate-css': '^1.3.6', - typescript: '^5.8.3', + ...singleAppDevDeps(eslint), + '@vitnode/eslint-config': vitnodeVersionRange, }, - packageManager: `${packageManager}@${availablePackageManagers[packageManager]}`, + packageManager: pmSpec, }; - await writeFile( - join(monorepo ? monorepoStructure.web : root, 'package.json'), - JSON.stringify(packageJson, null, 2), - ); - } else if (mode === 'apiMonorepo') { - await writeFile( - join(root, 'apps', 'api', 'package.json'), - JSON.stringify(apiPackageJson, null, 2), - ); - - const webPackageJson: PackageJSON = { + await writeJson(join(monorepo ? p.web : p.root, 'package.json'), singlePkg); + } + + // 4) apiMonorepo: write API + WEB + if (mode === 'apiMonorepo') { + await writeJson(join(p.api, 'package.json'), apiPkg); + + const webPkg: PackageJSON = { name: 'web', version: '0.1.0', private: true, type: 'module', - scripts: { - init: 'vitnode init --web', - dev: 'vitnode init --web && next dev --turbopack', - build: 'next build', - start: 'next start', - ...(eslint - ? { - lint: 'eslint .', - 'lint:fix': 'eslint . --fix', - } - : {}), - }, + scripts: webScripts(eslint), dependencies: { - '@vitnode/core': pkgVitNodeVersion, - 'babel-plugin-react-compiler': '19.1.0-rc.2', - 'lucide-react': '^0.536.0', - next: '^15.4.4', - 'next-intl': '^4.3.4', - react: '^19.1', - 'react-dom': '^19.1', - 'react-hook-form': '^7.61.1', - sonner: '^2.0.6', + ...webDeps, + '@vitnode/core': vitnodeVersionRange, }, devDependencies: { - '@hookform/resolvers': '^5.1.1', - '@tailwindcss/postcss': '^4.1.11', - '@types/mdx': '^2.0.13', - '@types/node': '^24', - '@types/react': '^19.1', - '@types/react-dom': '^19.1', - '@vitnode/eslint-config': pkgVitNodeVersion, - 'class-variance-authority': '^0.7.1', - ...(eslint - ? { - eslint: '^9.32.0', - } - : {}), - postcss: '^8.5.6', - tailwindcss: '^4.1.11', - 'tw-animate-css': '^1.3.6', - typescript: '^5.8.3', - zod: '^4.0.14', + ...webDevDeps(eslint), + '@vitnode/eslint-config': vitnodeVersionRange, }, }; - await writeFile( - join(root, 'apps', 'web', 'package.json'), - JSON.stringify(webPackageJson, null, 2), - ); - } else if (mode === 'onlyApi') { - await writeFile( - join(monorepo ? monorepoStructure.api : root, 'package.json'), - JSON.stringify(apiPackageJson, null, 2), - ); + await writeJson(join(p.web, 'package.json'), webPkg); + } + + // 5) onlyApi: write API (in root or in monorepo structure if requested) + if (isOnlyApi) { + await writeJson(join(monorepo ? p.api : p.root, 'package.json'), apiPkg); } }; diff --git a/packages/eslint/eslint.config.mjs b/packages/eslint/eslint.config.mjs index b6796b8d8..0f8c6c266 100644 --- a/packages/eslint/eslint.config.mjs +++ b/packages/eslint/eslint.config.mjs @@ -1,21 +1,23 @@ // @ts-check + +import { dirname } from 'node:path'; +import { fileURLToPath } from 'node:url'; import eslint from '@eslint/js'; -import tsEslint from 'typescript-eslint'; -import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'; -import perfectionist from 'eslint-plugin-perfectionist'; +import eslintReact from '@eslint-react/eslint-plugin'; import jsxA11y from 'eslint-plugin-jsx-a11y'; +import perfectionist from 'eslint-plugin-perfectionist'; +import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'; import reactPlugin from 'eslint-plugin-react'; -import hooksPlugin from 'eslint-plugin-react-hooks'; import reactCompiler from 'eslint-plugin-react-compiler'; -import eslintReact from '@eslint-react/eslint-plugin'; -import { fileURLToPath } from 'node:url'; -import { dirname } from 'node:path'; +import hooksPlugin from 'eslint-plugin-react-hooks'; +import tsEslint from 'typescript-eslint'; const __dirname = dirname(fileURLToPath(import.meta.url)); export default [ { ignores: [ + 'next-env.d.ts', 'dist', '**/\\(main\\)/\\(plugins\\)/**', '**/\\(auth\\)/\\(plugins\\)/**', diff --git a/packages/eslint/package.json b/packages/eslint/package.json index 5c371afdb..0853b1937 100644 --- a/packages/eslint/package.json +++ b/packages/eslint/package.json @@ -42,16 +42,16 @@ "typescript": "^5.9.2" }, "dependencies": { - "@eslint-react/eslint-plugin": "^1.52.3", - "@eslint/js": "^9.32.0", + "@eslint-react/eslint-plugin": "^1.52.6", + "@eslint/js": "^9.33.0", "eslint-config-prettier": "^10.1.8", "eslint-plugin-jsx-a11y": "^6.10.2", "eslint-plugin-perfectionist": "^4.15.0", - "eslint-plugin-prettier": "^5.5.3", + "eslint-plugin-prettier": "^5.5.4", "eslint-plugin-react": "^7.37.5", "eslint-plugin-react-compiler": "19.1.0-rc.2", "eslint-plugin-react-hooks": "6.0.0-rc1", "prettier-plugin-tailwindcss": "^0.6.14", - "typescript-eslint": "^8.39.0" + "typescript-eslint": "^8.40.0" } } diff --git a/packages/vitnode/package.json b/packages/vitnode/package.json index 58b59172a..c88ef6fdd 100644 --- a/packages/vitnode/package.json +++ b/packages/vitnode/package.json @@ -19,37 +19,37 @@ "core" ], "peerDependencies": { - "@hono/zod-openapi": "1.0.x", + "@hono/zod-openapi": "^1.0.x", "@swc/cli": "0.6.x", "@swc/core": "1.12.x", - "@types/react": "19.1.x", - "@types/react-dom": "19.1.x", - "drizzle-kit": "0.31.x", + "@types/react": "^19.1.x", + "@types/react-dom": "^19.1.x", + "drizzle-kit": "^0.31.x", "drizzle-orm": "^0.44.x", - "hono": "4.8.x", + "hono": "^4.9.x", "motion": "^12.x.x", - "next": "15.4.x", - "next-intl": "4.x.x", - "react": "19.1.x", - "react-dom": "19.1.x", + "next": "^15.5.x", + "next-intl": "^4.x.x", + "react": "^19.1.x", + "react-dom": "^19.1.x", "react-hook-form": "^7.x.x", "typescript": "^5.9.x", - "zod": "4.x.x" + "zod": "^4.x.x" }, "devDependencies": { - "@hono/zod-openapi": "^1.0.2", + "@hono/zod-openapi": "^1.1.0", "@hono/zod-validator": "^0.7.2", "@hookform/resolvers": "^5.2.1", - "@react-email/components": "^0.4.0", + "@react-email/components": "^0.5.1", "@swc/cli": "0.6.0", "@swc/core": "^1.13.3", "@testing-library/dom": "^10.4.1", "@testing-library/react": "^16.3.0", - "@types/node": "^24.1.0", - "@types/nodemailer": "^6.4.17", - "@types/react": "^19.1.9", + "@types/node": "^24.3.0", + "@types/nodemailer": "^7.0.1", + "@types/react": "^19.1.10", "@types/react-dom": "^19.1.7", - "@vitejs/plugin-react": "^4.7.0", + "@vitejs/plugin-react": "^5.0.1", "@vitest/coverage-v8": "^3.2.4", "@vitnode/eslint-config": "workspace:*", "chokidar": "^4.0.3", @@ -57,26 +57,26 @@ "dotenv": "^17.2.1", "drizzle-kit": "^0.31.4", "drizzle-orm": "^0.44.4", - "eslint": "^9.32.0", - "hono": "^4.8.10", + "eslint": "^9.33.0", + "hono": "^4.9.2", "jsdom": "^26.1.0", - "lucide-react": "^0.536.0", - "next": "^15.4.5", + "lucide-react": "^0.540.0", + "next": "^15.5.0", "next-intl": "^4.3.4", "react": "^19.1.1", "react-dom": "^19.1.1", - "react-email": "^4.2.7", - "react-hook-form": "^7.61.1", - "sonner": "^2.0.6", - "tailwindcss": "^4.1.11", + "react-email": "^4.2.8", + "react-hook-form": "^7.62.0", + "sonner": "^2.0.7", + "tailwindcss": "^4.1.12", "tsc-alias": "^1.8.16", "tsup": "^8.5.0", - "tsx": "^4.20.3", - "tw-animate-css": "^1.3.6", + "tsx": "^4.20.4", + "tw-animate-css": "^1.3.7", "typescript": "^5.9.2", "vite-tsconfig-paths": "^5.1.4", "vitest": "^3.2.4", - "zod": "^4.0.14" + "zod": "^4.0.17" }, "bin": { "vitnode": "./dist/scripts/scripts.js" @@ -111,8 +111,8 @@ "dependencies": { "@dnd-kit/core": "^6.3.1", "@hono/swagger-ui": "^0.5.2", - "@react-email/preview-server": "^4.2.7", - "@tanstack/react-query": "^5.84.1", + "@react-email/preview-server": "^4.2.8", + "@tanstack/react-query": "^5.85.5", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.1.1", @@ -121,10 +121,10 @@ "next-themes": "^0.4.6", "nodemailer": "^7.0.5", "postgres": "^3.4.7", - "radix-ui": "^1.4.2", + "radix-ui": "^1.4.3", "rate-limiter-flexible": "^7.2.0", "react-scan": "^0.4.3", - "resend": "^4.7.0", + "resend": "^6.0.1", "tailwind-merge": "^3.3.1", "use-debounce": "^10.0.5", "use-intl": "^4.3.4", diff --git a/packages/vitnode/src/api/models/email.ts b/packages/vitnode/src/api/models/email.ts index f684162cc..1cbe31fed 100644 --- a/packages/vitnode/src/api/models/email.ts +++ b/packages/vitnode/src/api/models/email.ts @@ -44,7 +44,9 @@ export type EmailModelSendArgs = { html?: string; locale?: string; replyTo?: string; - subject: string; + subject: + | ((props: Pick) => string) + | string; // eslint-disable-next-line perfectionist/sort-intersection-types } & (EmailModelSendArgsWithEmail | EmailModelSendArgsWithUser); @@ -98,7 +100,10 @@ export class EmailModel { const htmlContent = html ?? content({ - locale, + i18n: { + locale, + messages, + }, templateProps: { metadata: { ...core.metadata, @@ -106,7 +111,6 @@ export class EmailModel { }, logo: core.email?.logo, }, - messages, user, }); @@ -121,7 +125,10 @@ export class EmailModel { await provider.sendEmail({ html: await render(htmlContent), to: emailTo, - subject, + subject: + typeof subject === 'function' + ? subject({ i18n: { locale, messages } }) + : subject, replyTo, metadata: core.metadata, text: await render(htmlContent, { diff --git a/packages/vitnode/src/api/models/password.ts b/packages/vitnode/src/api/models/password.ts index afe91ec53..d84de7cd5 100644 --- a/packages/vitnode/src/api/models/password.ts +++ b/packages/vitnode/src/api/models/password.ts @@ -32,3 +32,13 @@ export class PasswordModel { }); } } + +export class ForgotPasswordTokenModel { + generateResetToken() { + return crypto.randomBytes(32).toString('base64url'); + } + + hashResetToken(token: string) { + return crypto.createHash('sha256').update(token).digest('hex'); + } +} diff --git a/packages/vitnode/src/api/modules/users/routes/change-password.route.ts b/packages/vitnode/src/api/modules/users/routes/change-password.route.ts new file mode 100644 index 000000000..a42d0a0d1 --- /dev/null +++ b/packages/vitnode/src/api/modules/users/routes/change-password.route.ts @@ -0,0 +1,75 @@ +import { and, eq, gt } from 'drizzle-orm'; +import { HTTPException } from 'hono/http-exception'; +import { z } from 'zod'; + +import { buildRoute } from '@/api/lib/route'; +import { PasswordModel } from '@/api/models/password'; +import { CONFIG_PLUGIN } from '@/config'; +import { core_users, core_users_forgot_password } from '@/database/users'; + +export const zodChangePasswordSchema = z.object({ + password: z.string().min(8).openapi({ + example: 'Test123!', + }), + userId: z.number().openapi({ example: 123456 }), + token: z.string().openapi({ example: 'abcdefg12345' }), +}); + +export const changePasswordRoute = buildRoute({ + ...CONFIG_PLUGIN, + route: { + method: 'post', + description: 'Change user password', + path: '/change-password', + request: { + body: { + required: true, + content: { + 'application/json': { + schema: zodChangePasswordSchema, + }, + }, + }, + }, + responses: { + 201: { + description: 'Password changed', + }, + }, + }, + handler: async c => { + const { password, userId, token } = c.req.valid('json'); + + const [user] = await c + .get('db') + .select() + .from(core_users_forgot_password) + .where( + and( + eq(core_users_forgot_password.userId, userId), + eq(core_users_forgot_password.token, token), + gt(core_users_forgot_password.expiresAt, new Date()), + ), + ) + .limit(1); + + if (!user) { + throw new HTTPException(400, { message: 'Invalid token' }); + } + + const hashPassword = await new PasswordModel().encryptPassword(password); + await Promise.all([ + c + .get('db') + .update(core_users) + .set({ password: hashPassword }) + .where(eq(core_users.id, userId)), + c + .get('db') + .delete(core_users_forgot_password) + .where(eq(core_users_forgot_password.id, user.id)), + ]); + + return c.text('Password changed', 201); + }, +}); diff --git a/packages/vitnode/src/api/modules/users/routes/reset-passowrd.route.ts b/packages/vitnode/src/api/modules/users/routes/reset-passowrd.route.ts new file mode 100644 index 000000000..001a10169 --- /dev/null +++ b/packages/vitnode/src/api/modules/users/routes/reset-passowrd.route.ts @@ -0,0 +1,124 @@ +import { eq } from 'drizzle-orm'; +import { createTranslator } from 'use-intl'; +import { z } from 'zod'; + +import { buildRoute } from '@/api/lib/route'; +import { ForgotPasswordTokenModel } from '@/api/models/password'; +import { CONFIG_PLUGIN } from '@/config'; +import { core_users, core_users_forgot_password } from '@/database/users'; +import ResetPasswordEmailTemplate from '@/emails/reset-password'; +import { CONFIG } from '@/lib/config'; + +export const resetPasswordRoute = buildRoute({ + ...CONFIG_PLUGIN, + route: { + method: 'post', + description: 'Request a password reset', + path: '/reset-password', + withCaptcha: true, + request: { + body: { + required: true, + content: { + 'application/json': { + schema: z.object({ + email: z.email().toLowerCase().openapi({ + example: 'test@test.com', + }), + }), + }, + }, + }, + }, + responses: { + 201: { + description: 'Email sent', + }, + }, + }, + handler: async c => { + const RESPONSE_TEXT = c.text('Email sent', 201); + const { email } = c.req.valid('json'); + const [findUser] = await c + .get('db') + .select({ + email: core_users.email, + id: core_users.id, + language: core_users.language, + }) + .from(core_users) + .where(eq(core_users.email, email)) + .limit(1); + + if (!findUser) { + return RESPONSE_TEXT; + } + + const hashToken = new ForgotPasswordTokenModel().generateResetToken(); + + const [findLastRecord] = await c + .get('db') + .select() + .from(core_users_forgot_password) + .where(eq(core_users_forgot_password.userId, findUser.id)) + .limit(1); + + // If a record will be found with createdAt in the last 5 minutes, skip + if (findLastRecord?.createdAt > new Date(Date.now() - 1000 * 60 * 5)) { + return RESPONSE_TEXT; + } + + const EXPIRES_AT = new Date(Date.now() + 1000 * 60 * 30); // 30 minutes + + if (findLastRecord) { + await c + .get('db') + .update(core_users_forgot_password) + .set({ + createdAt: new Date(), + expiresAt: EXPIRES_AT, + token: hashToken, + ipAddress: c.get('ipAddress'), + }) + .where(eq(core_users_forgot_password.id, findLastRecord.id)); + } else { + await c + .get('db') + .insert(core_users_forgot_password) + .values({ + token: hashToken, + ipAddress: c.get('ipAddress'), + userId: findUser.id, + expiresAt: EXPIRES_AT, + }); + } + + // Send email + const resetUrlNative = new URL( + `login/reset-password?token=${hashToken}&userId=${findUser.id}`, + CONFIG.web.href, + ); + + await c.get('email').send({ + user: { + id: findUser.id, + email: findUser.email, + language: findUser.language, + }, + content: props => + ResetPasswordEmailTemplate({ + ...props, + resetUrl: resetUrlNative.href, + expiryDate: EXPIRES_AT, + userIpAddress: c.get('ipAddress'), + }), + subject: ({ i18n }) => { + const t = createTranslator(i18n); + + return t('core.auth.reset_password.email.subject'); + }, + }); + + return RESPONSE_TEXT; + }, +}); diff --git a/packages/vitnode/src/api/modules/users/routes/test.route.ts b/packages/vitnode/src/api/modules/users/routes/test.route.ts index 83d432794..6bc597294 100644 --- a/packages/vitnode/src/api/modules/users/routes/test.route.ts +++ b/packages/vitnode/src/api/modules/users/routes/test.route.ts @@ -2,7 +2,6 @@ import { z } from 'zod'; import { buildRoute } from '@/api/lib/route'; import { CONFIG_PLUGIN } from '@/config'; -import TestTemplateEmail from '@/emails/test-template'; export const testRoute = buildRoute({ ...CONFIG_PLUGIN, @@ -30,13 +29,6 @@ export const testRoute = buildRoute({ }, }, handler: async c => { - await c.get('email').send({ - to: 'axendeveloper@gmail.com', - subject: 'Test Email', - locale: 'en', - content: TestTemplateEmail, - }); - await c.get('log').warn('This is a test warn log'); return c.text('test'); diff --git a/packages/vitnode/src/api/modules/users/users.module.ts b/packages/vitnode/src/api/modules/users/users.module.ts index 53923ad72..c6f1293f9 100644 --- a/packages/vitnode/src/api/modules/users/users.module.ts +++ b/packages/vitnode/src/api/modules/users/users.module.ts @@ -1,6 +1,8 @@ import { buildModule } from '@/api/lib/module'; import { CONFIG_PLUGIN } from '@/config'; +import { changePasswordRoute } from './routes/change-password.route'; +import { resetPasswordRoute } from './routes/reset-passowrd.route'; import { sessionRoute } from './routes/session.route'; import { signInRoute } from './routes/sign-in.route'; import { signOutRoute } from './routes/sign-out.route'; @@ -11,6 +13,14 @@ import { ssoUserModule } from './sso/sso.module'; export const usersModule = buildModule({ ...CONFIG_PLUGIN, name: 'users', - routes: [sessionRoute, signInRoute, signOutRoute, signUpRoute, testRoute], + routes: [ + sessionRoute, + signInRoute, + signOutRoute, + signUpRoute, + testRoute, + resetPasswordRoute, + changePasswordRoute, + ], modules: [ssoUserModule], }); diff --git a/packages/vitnode/src/app/login/reset-password/page.tsx b/packages/vitnode/src/app/login/reset-password/page.tsx new file mode 100644 index 000000000..e363ee2a5 --- /dev/null +++ b/packages/vitnode/src/app/login/reset-password/page.tsx @@ -0,0 +1,27 @@ +import type { Metadata } from 'next/dist/types'; + +import { getTranslations } from 'next-intl/server'; + +import { PasswordResetView } from '@/views/auth/password-reset/password-reset-view'; + +export const generateMetadata = async ({ + params, +}: { + params: Promise<{ locale: string }>; +}): Promise => { + const { locale } = await params; + const t = await getTranslations({ + locale, + namespace: 'core.auth.reset_password', + }); + + return { + title: t('title'), + }; +}; + +export default function Page( + props: React.ComponentProps, +) { + return ; +} diff --git a/packages/vitnode/src/components/form/auto-form.tsx b/packages/vitnode/src/components/form/auto-form.tsx index 963e4c677..b7a7a227c 100644 --- a/packages/vitnode/src/components/form/auto-form.tsx +++ b/packages/vitnode/src/components/form/auto-form.tsx @@ -28,6 +28,7 @@ export interface ItemAutoFormComponentProps { description?: React.ReactNode; field: ControllerRenderProps; label?: React.ReactNode; + labelRight?: React.ReactNode; otherProps: { enum?: string[]; isOptional?: boolean; @@ -74,6 +75,7 @@ export function AutoForm< captcha, fields, submitButtonProps, + children, ...props }: Omit, 'onSubmit'> & { captcha?: z.infer['captcha']; @@ -185,7 +187,10 @@ export function AutoForm< typeof params.pattern === 'string' ? params.pattern : undefined, - type: params.type === 'string' ? params.type : undefined, + type: + typeof params.type === 'string' + ? params.type + : undefined, }, })} @@ -195,6 +200,8 @@ export function AutoForm< ); })} + {children} + {captcha &&
} {setIsDirty ? ( diff --git a/packages/vitnode/src/components/form/common/label.tsx b/packages/vitnode/src/components/form/common/label.tsx index 49863a893..01257defd 100644 --- a/packages/vitnode/src/components/form/common/label.tsx +++ b/packages/vitnode/src/components/form/common/label.tsx @@ -1,8 +1,26 @@ import { FormLabel } from '@/components/ui/form'; +import { cn } from '@/lib/utils'; export const AutoFormLabel = ({ children, + labelRight, + className, ...props -}: React.ComponentProps) => { - return {children}; +}: React.ComponentProps & { + labelRight?: React.ReactNode; +}) => { + return ( + + {children} + {labelRight && {labelRight}} + + ); }; diff --git a/packages/vitnode/src/components/form/fields/checkbox.tsx b/packages/vitnode/src/components/form/fields/checkbox.tsx index 70f323d8b..d6d01ab63 100644 --- a/packages/vitnode/src/components/form/fields/checkbox.tsx +++ b/packages/vitnode/src/components/form/fields/checkbox.tsx @@ -7,6 +7,7 @@ import { AutoFormLabel } from '../common/label'; export const AutoFormCheckbox = ({ label, + labelRight, description, otherProps: { isOptional }, field, @@ -30,7 +31,9 @@ export const AutoFormCheckbox = ({ {!!(label ?? description) && (
{label && ( - {label} + + {label} + )} {description && {description}} diff --git a/packages/vitnode/src/components/form/fields/combobox-async.tsx b/packages/vitnode/src/components/form/fields/combobox-async.tsx index 7bf66e800..3df0449cf 100644 --- a/packages/vitnode/src/components/form/fields/combobox-async.tsx +++ b/packages/vitnode/src/components/form/fields/combobox-async.tsx @@ -33,6 +33,7 @@ export const AutoFormComboboxAsync = ({ description, placeholder, className, + labelRight, id, otherProps: { isOptional }, searchPlaceholder, @@ -70,7 +71,11 @@ export const AutoFormComboboxAsync = ({ return ( - {label && {label}} + {label && ( + + {label} + + )} diff --git a/packages/vitnode/src/components/form/fields/combobox.tsx b/packages/vitnode/src/components/form/fields/combobox.tsx index d4ebfce73..d44e6e5b7 100644 --- a/packages/vitnode/src/components/form/fields/combobox.tsx +++ b/packages/vitnode/src/components/form/fields/combobox.tsx @@ -32,6 +32,7 @@ export const AutoFormCombobox = ({ className, otherProps: { enum: enumValues = [], isOptional }, labels = [], + labelRight, searchPlaceholder, ...props }: ItemAutoFormComponentProps & @@ -53,7 +54,11 @@ export const AutoFormCombobox = ({ return ( - {label && {label}} + {label && ( + + {label} + + )} diff --git a/packages/vitnode/src/components/form/fields/input.tsx b/packages/vitnode/src/components/form/fields/input.tsx index 7ad2b3f85..3e9beed8f 100644 --- a/packages/vitnode/src/components/form/fields/input.tsx +++ b/packages/vitnode/src/components/form/fields/input.tsx @@ -7,6 +7,7 @@ import { AutoFormLabel } from '../common/label'; export const AutoFormInput = ({ label, + labelRight, description, otherProps: { isOptional, maxLength, minLength, pattern, type }, field, @@ -15,7 +16,11 @@ export const AutoFormInput = ({ Omit, 'value'>) => { return ( - {label && {label}} + {label && ( + + {label} + + )} - {label && {label}} + {label && ( + + {label} + + )} - {label && {label}} + {label && ( + + {label} + + )}