diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..ccef9a4 --- /dev/null +++ b/.eslintignore @@ -0,0 +1 @@ +/src/lib/app/db/* diff --git a/.gitignore b/.gitignore index 42208aa..f51e192 100644 --- a/.gitignore +++ b/.gitignore @@ -43,4 +43,7 @@ next-env.d.ts # webstorm .idea +# db /src/api/schema +/drizzle/relations.ts +/drizzle/schema.ts diff --git a/bun.lockb b/bun.lockb index d78215e..4f8bbc8 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/drizzle.config.ts b/drizzle.config.ts new file mode 100644 index 0000000..db882ca --- /dev/null +++ b/drizzle.config.ts @@ -0,0 +1,12 @@ +import 'dotenv/config' +import type { Config } from 'drizzle-kit' + +export default { + schema: './app/db/schema.ts', + out: './drizzle', + dialect: 'postgresql', + schemaFilter: ['public', 'neon_auth'], + dbCredentials: { + url: process.env.NEON_DATABASE_URL! + } +} satisfies Config diff --git a/drizzle/0000_wooden_mercury.sql b/drizzle/0000_wooden_mercury.sql new file mode 100644 index 0000000..4cafc96 --- /dev/null +++ b/drizzle/0000_wooden_mercury.sql @@ -0,0 +1,135 @@ +-- Current sql file was generated after introspecting the database +-- If you want to run this migration please uncomment this code before executing migrations +/* +CREATE SCHEMA "neon_auth"; +--> statement-breakpoint +CREATE TABLE "neon_auth"."invitation" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "organizationId" uuid NOT NULL, + "email" text NOT NULL, + "role" text, + "status" text NOT NULL, + "expiresAt" timestamp with time zone NOT NULL, + "createdAt" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL, + "inviterId" uuid NOT NULL +); +--> statement-breakpoint +CREATE TABLE "neon_auth"."user" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "name" text NOT NULL, + "email" text NOT NULL, + "emailVerified" boolean NOT NULL, + "image" text, + "createdAt" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL, + "updatedAt" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL, + "role" text, + "banned" boolean, + "banReason" text, + "banExpires" timestamp with time zone, + CONSTRAINT "user_email_key" UNIQUE("email") +); +--> statement-breakpoint +CREATE TABLE "neon_auth"."session" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "expiresAt" timestamp with time zone NOT NULL, + "token" text NOT NULL, + "createdAt" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL, + "updatedAt" timestamp with time zone NOT NULL, + "ipAddress" text, + "userAgent" text, + "userId" uuid NOT NULL, + "impersonatedBy" text, + "activeOrganizationId" text, + CONSTRAINT "session_token_key" UNIQUE("token") +); +--> statement-breakpoint +CREATE TABLE "neon_auth"."organization" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "name" text NOT NULL, + "slug" text NOT NULL, + "logo" text, + "createdAt" timestamp with time zone NOT NULL, + "metadata" text, + CONSTRAINT "organization_slug_key" UNIQUE("slug") +); +--> statement-breakpoint +CREATE TABLE "neon_auth"."account" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "accountId" text NOT NULL, + "providerId" text NOT NULL, + "userId" uuid NOT NULL, + "accessToken" text, + "refreshToken" text, + "idToken" text, + "accessTokenExpiresAt" timestamp with time zone, + "refreshTokenExpiresAt" timestamp with time zone, + "scope" text, + "password" text, + "createdAt" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL, + "updatedAt" timestamp with time zone NOT NULL +); +--> statement-breakpoint +CREATE TABLE "neon_auth"."verification" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "identifier" text NOT NULL, + "value" text NOT NULL, + "expiresAt" timestamp with time zone NOT NULL, + "createdAt" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL, + "updatedAt" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL +); +--> statement-breakpoint +CREATE TABLE "neon_auth"."jwks" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "publicKey" text NOT NULL, + "privateKey" text NOT NULL, + "createdAt" timestamp with time zone NOT NULL, + "expiresAt" timestamp with time zone +); +--> statement-breakpoint +CREATE TABLE "neon_auth"."member" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "organizationId" uuid NOT NULL, + "userId" uuid NOT NULL, + "role" text NOT NULL, + "createdAt" timestamp with time zone NOT NULL +); +--> statement-breakpoint +CREATE TABLE "neon_auth"."project_config" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "name" text NOT NULL, + "endpoint_id" text NOT NULL, + "created_at" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL, + "updated_at" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL, + "trusted_origins" jsonb NOT NULL, + "social_providers" jsonb NOT NULL, + "email_provider" jsonb, + "email_and_password" jsonb, + "allow_localhost" boolean NOT NULL, + CONSTRAINT "project_config_endpoint_id_key" UNIQUE("endpoint_id") +); +--> statement-breakpoint +CREATE TABLE "test" ( + "id" uuid PRIMARY KEY NOT NULL, + "user_id" text NOT NULL, + "data" text, + "is_published" boolean DEFAULT false NOT NULL +); +--> statement-breakpoint +ALTER TABLE "test" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint +ALTER TABLE "neon_auth"."invitation" ADD CONSTRAINT "invitation_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "neon_auth"."organization"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "neon_auth"."invitation" ADD CONSTRAINT "invitation_inviterId_fkey" FOREIGN KEY ("inviterId") REFERENCES "neon_auth"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "neon_auth"."session" ADD CONSTRAINT "session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "neon_auth"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "neon_auth"."account" ADD CONSTRAINT "account_userId_fkey" FOREIGN KEY ("userId") REFERENCES "neon_auth"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "neon_auth"."member" ADD CONSTRAINT "member_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "neon_auth"."organization"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "neon_auth"."member" ADD CONSTRAINT "member_userId_fkey" FOREIGN KEY ("userId") REFERENCES "neon_auth"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +CREATE INDEX "invitation_email_idx" ON "neon_auth"."invitation" USING btree ("email" text_ops);--> statement-breakpoint +CREATE INDEX "invitation_organizationId_idx" ON "neon_auth"."invitation" USING btree ("organizationId" uuid_ops);--> statement-breakpoint +CREATE INDEX "session_userId_idx" ON "neon_auth"."session" USING btree ("userId" uuid_ops);--> statement-breakpoint +CREATE UNIQUE INDEX "organization_slug_uidx" ON "neon_auth"."organization" USING btree ("slug" text_ops);--> statement-breakpoint +CREATE INDEX "account_userId_idx" ON "neon_auth"."account" USING btree ("userId" uuid_ops);--> statement-breakpoint +CREATE INDEX "verification_identifier_idx" ON "neon_auth"."verification" USING btree ("identifier" text_ops);--> statement-breakpoint +CREATE INDEX "member_organizationId_idx" ON "neon_auth"."member" USING btree ("organizationId" uuid_ops);--> statement-breakpoint +CREATE INDEX "member_userId_idx" ON "neon_auth"."member" USING btree ("userId" uuid_ops);--> statement-breakpoint +CREATE POLICY "read_published" ON "test" AS PERMISSIVE FOR SELECT TO "authenticated" USING ((is_published = true));--> statement-breakpoint +CREATE POLICY "manage_own" ON "test" AS PERMISSIVE FOR ALL TO "authenticated"; +*/ \ No newline at end of file diff --git a/drizzle/meta/0000_snapshot.json b/drizzle/meta/0000_snapshot.json new file mode 100644 index 0000000..e1fb3d7 --- /dev/null +++ b/drizzle/meta/0000_snapshot.json @@ -0,0 +1,913 @@ +{ + "id": "00000000-0000-0000-0000-000000000000", + "prevId": "", + "version": "7", + "dialect": "postgresql", + "tables": { + "neon_auth.invitation": { + "name": "invitation", + "schema": "neon_auth", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organizationId": { + "name": "organizationId", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "inviterId": { + "name": "inviterId", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "invitation_email_idx": { + "name": "invitation_email_idx", + "columns": [ + { + "expression": "email", + "asc": true, + "nulls": "last", + "opclass": "text_ops", + "isExpression": false + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "invitation_organizationId_idx": { + "name": "invitation_organizationId_idx", + "columns": [ + { + "expression": "organizationId", + "asc": true, + "nulls": "last", + "opclass": "uuid_ops", + "isExpression": false + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "invitation_organizationId_fkey": { + "name": "invitation_organizationId_fkey", + "tableFrom": "invitation", + "tableTo": "organization", + "schemaTo": "neon_auth", + "columnsFrom": [ + "organizationId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "invitation_inviterId_fkey": { + "name": "invitation_inviterId_fkey", + "tableFrom": "invitation", + "tableTo": "user", + "schemaTo": "neon_auth", + "columnsFrom": [ + "inviterId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {}, + "policies": {}, + "isRLSEnabled": false + }, + "neon_auth.user": { + "name": "user", + "schema": "neon_auth", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "emailVerified": { + "name": "emailVerified", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "banned": { + "name": "banned", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "banReason": { + "name": "banReason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "banExpires": { + "name": "banExpires", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_key": { + "columns": [ + "email" + ], + "nullsNotDistinct": false, + "name": "user_email_key" + } + }, + "checkConstraints": {}, + "policies": {}, + "isRLSEnabled": false + }, + "neon_auth.session": { + "name": "session", + "schema": "neon_auth", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "ipAddress": { + "name": "ipAddress", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "userAgent": { + "name": "userAgent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "userId": { + "name": "userId", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "impersonatedBy": { + "name": "impersonatedBy", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "activeOrganizationId": { + "name": "activeOrganizationId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "session_userId_idx": { + "name": "session_userId_idx", + "columns": [ + { + "expression": "userId", + "asc": true, + "nulls": "last", + "opclass": "uuid_ops", + "isExpression": false + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "session_userId_fkey": { + "name": "session_userId_fkey", + "tableFrom": "session", + "tableTo": "user", + "schemaTo": "neon_auth", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "session_token_key": { + "columns": [ + "token" + ], + "nullsNotDistinct": false, + "name": "session_token_key" + } + }, + "checkConstraints": {}, + "policies": {}, + "isRLSEnabled": false + }, + "neon_auth.organization": { + "name": "organization", + "schema": "neon_auth", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "logo": { + "name": "logo", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "organization_slug_uidx": { + "name": "organization_slug_uidx", + "columns": [ + { + "expression": "slug", + "asc": true, + "nulls": "last", + "opclass": "text_ops", + "isExpression": false + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "organization_slug_key": { + "columns": [ + "slug" + ], + "nullsNotDistinct": false, + "name": "organization_slug_key" + } + }, + "checkConstraints": {}, + "policies": {}, + "isRLSEnabled": false + }, + "neon_auth.account": { + "name": "account", + "schema": "neon_auth", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "accountId": { + "name": "accountId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "providerId": { + "name": "providerId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "accessToken": { + "name": "accessToken", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refreshToken": { + "name": "refreshToken", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "idToken": { + "name": "idToken", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "accessTokenExpiresAt": { + "name": "accessTokenExpiresAt", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "refreshTokenExpiresAt": { + "name": "refreshTokenExpiresAt", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "account_userId_idx": { + "name": "account_userId_idx", + "columns": [ + { + "expression": "userId", + "asc": true, + "nulls": "last", + "opclass": "uuid_ops", + "isExpression": false + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "account_userId_fkey": { + "name": "account_userId_fkey", + "tableFrom": "account", + "tableTo": "user", + "schemaTo": "neon_auth", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {}, + "policies": {}, + "isRLSEnabled": false + }, + "neon_auth.verification": { + "name": "verification", + "schema": "neon_auth", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "verification_identifier_idx": { + "name": "verification_identifier_idx", + "columns": [ + { + "expression": "identifier", + "asc": true, + "nulls": "last", + "opclass": "text_ops", + "isExpression": false + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {}, + "policies": {}, + "isRLSEnabled": false + }, + "neon_auth.jwks": { + "name": "jwks", + "schema": "neon_auth", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "publicKey": { + "name": "publicKey", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "privateKey": { + "name": "privateKey", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {}, + "policies": {}, + "isRLSEnabled": false + }, + "neon_auth.member": { + "name": "member", + "schema": "neon_auth", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organizationId": { + "name": "organizationId", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "member_organizationId_idx": { + "name": "member_organizationId_idx", + "columns": [ + { + "expression": "organizationId", + "asc": true, + "nulls": "last", + "opclass": "uuid_ops", + "isExpression": false + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "member_userId_idx": { + "name": "member_userId_idx", + "columns": [ + { + "expression": "userId", + "asc": true, + "nulls": "last", + "opclass": "uuid_ops", + "isExpression": false + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "member_organizationId_fkey": { + "name": "member_organizationId_fkey", + "tableFrom": "member", + "tableTo": "organization", + "schemaTo": "neon_auth", + "columnsFrom": [ + "organizationId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "member_userId_fkey": { + "name": "member_userId_fkey", + "tableFrom": "member", + "tableTo": "user", + "schemaTo": "neon_auth", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {}, + "policies": {}, + "isRLSEnabled": false + }, + "neon_auth.project_config": { + "name": "project_config", + "schema": "neon_auth", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "trusted_origins": { + "name": "trusted_origins", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "social_providers": { + "name": "social_providers", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "email_provider": { + "name": "email_provider", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "email_and_password": { + "name": "email_and_password", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "allow_localhost": { + "name": "allow_localhost", + "type": "boolean", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "project_config_endpoint_id_key": { + "columns": [ + "endpoint_id" + ], + "nullsNotDistinct": false, + "name": "project_config_endpoint_id_key" + } + }, + "checkConstraints": {}, + "policies": {}, + "isRLSEnabled": false + }, + "public.test": { + "name": "test", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "data": { + "name": "data", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_published": { + "name": "is_published", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {}, + "policies": { + "read_published": { + "name": "read_published", + "as": "PERMISSIVE", + "for": "SELECT", + "to": [ + "authenticated" + ], + "using": "(is_published = true)" + }, + "manage_own": { + "name": "manage_own", + "as": "PERMISSIVE", + "for": "ALL", + "to": [ + "authenticated" + ] + } + }, + "isRLSEnabled": true + } + }, + "enums": {}, + "schemas": { + "neon_auth": "neon_auth" + }, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "tables": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json new file mode 100644 index 0000000..a2e5d15 --- /dev/null +++ b/drizzle/meta/_journal.json @@ -0,0 +1,13 @@ +{ + "version": "7", + "dialect": "postgresql", + "entries": [ + { + "idx": 0, + "version": "7", + "when": 1778338864717, + "tag": "0000_wooden_mercury", + "breakpoints": true + } + ] +} \ No newline at end of file diff --git a/package.json b/package.json index 55d7e94..1cbb97d 100644 --- a/package.json +++ b/package.json @@ -7,16 +7,20 @@ "build": "next build", "start": "next start", "format": "prettier --write '**/*.{ts,tsx,md}'", - "lint": "eslint && next lint && tsc --noemit && prettier --check '**/*.{ts,tsx,md,scss}'" + "lint": "eslint && next lint && tsc --noemit && prettier --check '**/*.{ts,tsx,md,scss}'", + "db:pull": "drizzle-kit pull && cp ./drizzle/schema.ts ./src/api/db/schema.ts && cp ./drizzle/relations.ts ./src/api/db/relations.ts", + "db:generate-types": "npx @neondatabase/neon-js gen-types --db-url \"$NEON_DATABASE_URL\" --output src/api/db/types.ts" }, "dependencies": { "@mantine/core": "^7.9.1", "@mantine/form": "^7.16.3", "@mantine/hooks": "^7.16.3", + "@neondatabase/auth": "latest", + "@neondatabase/auth-ui": "^0.2.0-beta", "@neondatabase/neon-js": "^0.2.0-beta.1", - "@neondatabase/serverless": "^1.0.2", + "@neondatabase/serverless": "^1.1.0", "@tabler/icons-react": "^2.47.0", - "fauna": "^1.3.1", + "drizzle-orm": "^0.45.2", "next": "15.1.11", "next-client-cookies": "^2.0.1", "react": "^19.0.0", @@ -24,11 +28,13 @@ "react-router-dom": "^7.13.0" }, "devDependencies": { - "@types/node": "^20", + "@types/node": "^25.6.2", "@types/react": "^19", "@types/react-dom": "^19", "@typescript-eslint/parser": "^6.21.0", "autoprefixer": "^10.0.1", + "dotenv": "^17.4.2", + "drizzle-kit": "^0.31.10", "eslint": "^8", "eslint-config-next": "14.1.0", "postcss": "^8", diff --git a/src/api/_actions/index.ts b/src/api/_actions/index.ts new file mode 100644 index 0000000..3a2c4f5 --- /dev/null +++ b/src/api/_actions/index.ts @@ -0,0 +1,45 @@ +// import { +// get as getTemplate, +// create as createTemplate, +// update as updateTemplate, +// deleteTemplate +// } from './template' +// import { getByUser as getTemplatesByUser, deleteTemplates } from './templates' +// import { +// get as getProject, +// create as createProject, +// update as updateProject, +// deleteProject +// } from './project' +// import { getByUser as getProjectsByUser, deleteProjects } from './projects' +// +// export const actions = { +// template: { +// get: { +// byId: getTemplate +// }, +// add: createTemplate, +// update: updateTemplate, +// delete: deleteTemplate +// }, +// templates: { +// get: { +// byUser: getTemplatesByUser +// }, +// delete: deleteTemplates +// }, +// project: { +// get: { +// byId: getProject +// }, +// add: createProject, +// update: updateProject, +// delete: deleteProject +// }, +// projects: { +// get: { +// byUser: getProjectsByUser +// }, +// delete: deleteProjects +// } +// } diff --git a/src/api/_actions/middleware.ts b/src/api/_actions/middleware.ts new file mode 100644 index 0000000..efd5e96 --- /dev/null +++ b/src/api/_actions/middleware.ts @@ -0,0 +1,22 @@ +// 'use server' +// import { cookies } from 'next/headers' +// +// export async function getToken() { +// const cookieStore = await cookies() +// const token = cookieStore.get('token')?.value +// return token +// } +// +// export const executor = async ( +// callbackPath: string, +// callback: (args: Req) => Res, +// args: Req +// ): Promise => { +// try { +// console.debug(`Entered ${callbackPath}`) +// return await callback.call(undefined, args) +// } catch (error) { +// console.error(error) +// throw error +// } +// } diff --git a/src/api/_actions/project/create.ts b/src/api/_actions/project/create.ts new file mode 100644 index 0000000..a30760e --- /dev/null +++ b/src/api/_actions/project/create.ts @@ -0,0 +1,39 @@ +// 'use server' +// import { Project, Template } from 'api/core/collections' +// import { ProjectManager, TemplateManager } from 'api/_core/managers' +// import { executor, getToken } from 'middleware' +// import { getMaterials } from 'api/utils' +// +// type Req = { +// templateId: Template['id'] +// } +// type Res = Promise +// +// async function handler({ templateId }: Req) { +// const token = await getToken() +// if (!token) return [] +// const templates = new TemplateManager(token) +// +// const template = await templates.getById(templateId) +// const { title, type } = template +// +// const schema = template.schema.map(row => +// row.map(color => ({ color, isBeaded: false })) +// ) +// const materials = getMaterials(schema) +// +// const projects = new ProjectManager(token) +// const userProjects = await projects.addProject({ +// title, +// type, +// schema, +// materials +// }) +// if (!userProjects) throw new Error(`Projects not found`) +// +// return userProjects +// } +// +// export async function create(args: Req) { +// return await executor('actions/project/create', handler, args) +// } diff --git a/src/api/_actions/project/delete.ts b/src/api/_actions/project/delete.ts new file mode 100644 index 0000000..bedbb30 --- /dev/null +++ b/src/api/_actions/project/delete.ts @@ -0,0 +1,24 @@ +// 'use server' +// import { Project } from 'api/core/collections' +// import { ProjectManager } from 'api/_core/managers' +// import { executor, getToken } from 'middleware' +// +// type Req = { +// id: Project['id'] +// } +// type Res = Promise +// +// async function handler({ id }: Req) { +// const token = await getToken() +// if (!token) return [] +// const projects = new ProjectManager(token) +// +// const userProjects = projects.deleteProject(id) +// if (!userProjects) throw new Error(`Projects not found`) +// +// return userProjects +// } +// +// export async function deleteProject(args: Req) { +// return await executor('actions/project/delete', handler, args) +// } diff --git a/src/api/_actions/project/get.ts b/src/api/_actions/project/get.ts new file mode 100644 index 0000000..50fc7c0 --- /dev/null +++ b/src/api/_actions/project/get.ts @@ -0,0 +1,24 @@ +// 'use server' +// import { Project } from 'api/core/collections' +// import { ProjectManager } from 'api/_core/managers' +// import { executor, getToken } from 'middleware' +// +// type Req = { +// id: Project['id'] +// } +// type Res = Promise +// +// async function handler({ id }: Req) { +// const token = await getToken() +// if (!token) return null +// const projects = new ProjectManager(token) +// +// const project = projects.getById(id) +// if (!project) throw new Error(`Project with id ${id} not found`) +// +// return project +// } +// +// export async function get(args: Req) { +// return await executor('actions/project/get', handler, args) +// } diff --git a/src/api/_actions/project/index.ts b/src/api/_actions/project/index.ts new file mode 100644 index 0000000..bf05799 --- /dev/null +++ b/src/api/_actions/project/index.ts @@ -0,0 +1,4 @@ +// export { create } from './create' +// export { deleteProject } from './delete' +// export { get } from './get' +// export { update } from './update' diff --git a/src/api/_actions/project/update.ts b/src/api/_actions/project/update.ts new file mode 100644 index 0000000..6d3ce55 --- /dev/null +++ b/src/api/_actions/project/update.ts @@ -0,0 +1,25 @@ +// 'use server' +// import { Project } from 'api/core/collections' +// import { ProjectManager } from 'api/_core/managers' +// import { executor, getToken } from 'middleware' +// import { getProgress } from 'api/utils' +// +// type Req = Omit +// type Res = Promise +// +// async function handler(data: Req) { +// const token = await getToken() +// if (!token) return [] +// const projects = new ProjectManager(token) +// +// const progress = getProgress(data.schema) +// +// const userProjects = projects.updateProject({ progress, ...data }) +// if (!userProjects) throw new Error(`Projects not found`) +// +// return userProjects +// } +// +// export async function update(args: Req) { +// return await executor('actions/project/update', handler, args) +// } diff --git a/src/api/_actions/projects/delete.ts b/src/api/_actions/projects/delete.ts new file mode 100644 index 0000000..ab407ff --- /dev/null +++ b/src/api/_actions/projects/delete.ts @@ -0,0 +1,24 @@ +// 'use server' +// import { Project } from 'api/core/collections' +// import { ProjectManager } from 'api/_core/managers' +// import { executor, getToken } from 'middleware' +// +// type Req = { +// idList: Project['id'][] +// } +// type Res = Promise +// +// async function handler({ idList }: Req) { +// const token = await getToken() +// if (!token) return [] +// const projects = new ProjectManager(token) +// +// const userProjects = projects.deleteProjects(idList) +// if (!userProjects) throw new Error(`Projects not found`) +// +// return userProjects +// } +// +// export async function deleteProjects(args: Req) { +// return await executor('actions/projects/delete', handler, args) +// } diff --git a/src/api/_actions/projects/get/by-user.ts b/src/api/_actions/projects/get/by-user.ts new file mode 100644 index 0000000..a89e6be --- /dev/null +++ b/src/api/_actions/projects/get/by-user.ts @@ -0,0 +1,22 @@ +// 'use server' +// import { Project } from 'api/core/collections' +// import { ProjectManager } from 'api/_core/managers' +// import { executor, getToken } from 'middleware' +// +// type Req = void +// type Res = Promise +// +// async function handler() { +// const token = await getToken() +// if (!token) return [] +// const projects = new ProjectManager(token) +// +// const userProjects = projects.getUserProjects() +// if (!userProjects) throw new Error(`Projects not found`) +// +// return userProjects +// } +// +// export async function getByUser(args: Req) { +// return await executor('actions/projects/get/by-user', handler, args) +// } diff --git a/src/api/_actions/projects/index.ts b/src/api/_actions/projects/index.ts new file mode 100644 index 0000000..76e765e --- /dev/null +++ b/src/api/_actions/projects/index.ts @@ -0,0 +1,2 @@ +// export { getByUser } from './get/by-user' +// export { deleteProjects } from './delete' diff --git a/src/api/_actions/template/create.ts b/src/api/_actions/template/create.ts new file mode 100644 index 0000000..8fd78ac --- /dev/null +++ b/src/api/_actions/template/create.ts @@ -0,0 +1,26 @@ +// 'use server' +// import { Template } from 'api/core/collections' +// import { TemplateManager } from 'api/_core/managers' +// import { executor, getToken } from 'middleware' +// +// type Req = { +// title: Template['title'] +// type: Template['type'] +// schema: Template['schema'] +// } +// type Res = Promise +// +// async function handler(args: Req) { +// const token = await getToken() +// if (!token) return [] +// const templates = new TemplateManager(token) +// +// const userTemplates = templates.addTemplate(args) +// if (!userTemplates) throw new Error(`Templates not found`) +// +// return userTemplates +// } +// +// export async function create(args: Req) { +// return await executor('actions/template/create', handler, args) +// } diff --git a/src/api/_actions/template/delete.ts b/src/api/_actions/template/delete.ts new file mode 100644 index 0000000..214b18c --- /dev/null +++ b/src/api/_actions/template/delete.ts @@ -0,0 +1,24 @@ +// 'use server' +// import { Template } from 'api/core/collections' +// import { TemplateManager } from 'api/_core/managers' +// import { executor, getToken } from 'middleware' +// +// type Req = { +// id: Template['id'] +// } +// type Res = Promise +// +// async function handler({ id }: Req) { +// const token = await getToken() +// if (!token) return [] +// const templates = new TemplateManager(token) +// +// const userTemplates = templates.deleteTemplate(id) +// if (!userTemplates) throw new Error(`Templates not found`) +// +// return userTemplates +// } +// +// export async function deleteTemplate(args: Req) { +// return await executor('actions/template/delete', handler, args) +// } diff --git a/src/api/_actions/template/get.ts b/src/api/_actions/template/get.ts new file mode 100644 index 0000000..3f7abc3 --- /dev/null +++ b/src/api/_actions/template/get.ts @@ -0,0 +1,24 @@ +// 'use server' +// import { Template } from 'api/core/collections' +// import { TemplateManager } from 'api/_core/managers' +// import { executor, getToken } from 'middleware' +// +// type Req = { +// id: Template['id'] +// } +// type Res = Promise