diff --git a/apps/web/Dockerfile b/apps/web/Dockerfile new file mode 100644 index 0000000..63912b9 --- /dev/null +++ b/apps/web/Dockerfile @@ -0,0 +1,50 @@ +FROM oven/bun:latest AS base + +# Install dependencies only when needed +FROM base AS deps +WORKDIR /app +COPY package.json ./ +RUN bun install + +# Rebuild the source code only when needed +FROM base AS builder +WORKDIR /app +COPY --from=deps /app/node_modules ./node_modules +COPY . . + +# Build Next.js +ENV NEXT_TELEMETRY_DISABLED=1 +RUN bun run build + +# # Production image, copy all the files and run next +FROM base AS runner +WORKDIR /app + +ENV NODE_ENV=production +ENV NEXT_TELEMETRY_DISABLED=1 + +# Copy public folder +COPY --from=builder /app/public ./public + +# Set permission for prerender cache +RUN mkdir .next + +# Automatically leverage output traces to reduce image size +COPY --from=builder /app/.next/standalone ./ +COPY --from=builder /app/.next/static ./.next/static + +# Copy migrations and migration script +COPY --from=builder /app/drizzle ./drizzle +COPY --from=builder /app/src/db/migrate.ts ./src/db/migrate.ts +COPY --from=builder /app/src ./src + +RUN bun install drizzle-orm pg dotenv + + +EXPOSE 3000 + +ENV PORT=3000 +ENV HOSTNAME="0.0.0.0" + +# Custom entrypoint to run migrations then start app +CMD ["sh", "-c", "bun --bun run src/db/migrate.ts && bun --bun run server.js"] \ No newline at end of file diff --git a/apps/web/drizzle/0000_initial_schema.sql b/apps/web/drizzle/0000_busy_bill_hollister.sql similarity index 68% rename from apps/web/drizzle/0000_initial_schema.sql rename to apps/web/drizzle/0000_busy_bill_hollister.sql index 60bcecb..82d1321 100644 --- a/apps/web/drizzle/0000_initial_schema.sql +++ b/apps/web/drizzle/0000_busy_bill_hollister.sql @@ -1,41 +1,3 @@ -CREATE TABLE IF NOT EXISTS "user" ( - "id" text PRIMARY KEY NOT NULL, - "name" text, - "email" text NOT NULL, - "emailVerified" timestamp, - "image" text -); -CREATE TABLE IF NOT EXISTS "pastes" ( - "id" text PRIMARY KEY NOT NULL, - "title" text, - "content" text NOT NULL, - "contentType" text DEFAULT 'text/plain', - "syntax" text, - "visibility" text DEFAULT 'public', - "author_id" text, - "session_id" text, - "expires_at" timestamp, - "created_at" timestamp DEFAULT now(), - "updated_at" timestamp DEFAULT now() -); -CREATE TABLE IF NOT EXISTS "tags" ( - "id" text PRIMARY KEY NOT NULL, - "name" text NOT NULL UNIQUE, - "color" text, - "description" text -); -CREATE TABLE IF NOT EXISTS "pastes_to_tags" ( - "paste_id" text NOT NULL, - "tag_id" text NOT NULL, - CONSTRAINT "pastes_to_tags_paste_id_tag_id_pk" PRIMARY KEY("paste_id", "tag_id") -); -CREATE TABLE IF NOT EXISTS "agent_sessions" ( - "id" text PRIMARY KEY NOT NULL, - "title" text, - "agent_name" text, - "user_id" text, - "created_at" timestamp DEFAULT now() -); CREATE TABLE IF NOT EXISTS "account" ( "userId" text NOT NULL, "type" text NOT NULL, @@ -50,19 +12,17 @@ CREATE TABLE IF NOT EXISTS "account" ( "session_state" text, CONSTRAINT "account_provider_providerAccountId_pk" PRIMARY KEY("provider", "providerAccountId") ); -CREATE TABLE IF NOT EXISTS "session" ( - "sessionToken" text PRIMARY KEY NOT NULL, - "userId" text NOT NULL, - "expires" timestamp NOT NULL -); -CREATE TABLE IF NOT EXISTS "verificationToken" ( - "identifier" text NOT NULL, - "token" text NOT NULL, - "expires" timestamp NOT NULL, - CONSTRAINT "verificationToken_identifier_token_pk" PRIMARY KEY("identifier", "token") +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "agent_sessions" ( + "id" text PRIMARY KEY NOT NULL, + "title" text, + "agent_name" text, + "user_id" text, + "created_at" timestamp DEFAULT now() ); +--> statement-breakpoint CREATE TABLE IF NOT EXISTS "authenticator" ( - "credentialID" text PRIMARY KEY NOT NULL UNIQUE, + "credentialID" text NOT NULL, "userId" text NOT NULL, "providerAccountId" text NOT NULL, "credentialPublicKey" text NOT NULL, @@ -70,55 +30,81 @@ CREATE TABLE IF NOT EXISTS "authenticator" ( "credentialDeviceType" text NOT NULL, "credentialBackedUp" boolean NOT NULL, "transports" text, - CONSTRAINT "authenticator_userId_credentialID_pk" PRIMARY KEY("userId", "credentialID") + CONSTRAINT "authenticator_userId_credentialID_pk" PRIMARY KEY("userId", "credentialID"), + CONSTRAINT "authenticator_credentialID_unique" UNIQUE("credentialID") ); -DO $$ BEGIN -ALTER TABLE "pastes" -ADD CONSTRAINT "pastes_author_id_user_id_fk" FOREIGN KEY ("author_id") REFERENCES "user"("id") ON DELETE -set null ON UPDATE no action; -EXCEPTION -WHEN duplicate_object THEN null; -END $$; -DO $$ BEGIN -ALTER TABLE "pastes" -ADD CONSTRAINT "pastes_session_id_agent_sessions_id_fk" FOREIGN KEY ("session_id") REFERENCES "agent_sessions"("id") ON DELETE -set null ON UPDATE no action; -EXCEPTION -WHEN duplicate_object THEN null; -END $$; -DO $$ BEGIN -ALTER TABLE "pastes_to_tags" -ADD CONSTRAINT "pastes_to_tags_paste_id_pastes_id_fk" FOREIGN KEY ("paste_id") REFERENCES "pastes"("id") ON DELETE cascade ON UPDATE no action; -EXCEPTION -WHEN duplicate_object THEN null; -END $$; -DO $$ BEGIN -ALTER TABLE "pastes_to_tags" -ADD CONSTRAINT "pastes_to_tags_tag_id_tags_id_fk" FOREIGN KEY ("tag_id") REFERENCES "tags"("id") ON DELETE cascade ON UPDATE no action; -EXCEPTION -WHEN duplicate_object THEN null; -END $$; -DO $$ BEGIN -ALTER TABLE "agent_sessions" -ADD CONSTRAINT "agent_sessions_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "user"("id") ON DELETE cascade ON UPDATE no action; -EXCEPTION -WHEN duplicate_object THEN null; -END $$; -DO $$ BEGIN +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "pastes" ( + "id" text PRIMARY KEY NOT NULL, + "title" text, + "content" text NOT NULL, + "content_type" text DEFAULT 'text/plain', + "syntax" text, + "visibility" text DEFAULT 'public', + "author_id" text, + "session_id" text, + "expires_at" timestamp, + "created_at" timestamp DEFAULT now(), + "updated_at" timestamp DEFAULT now() +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "pastes_to_tags" ( + "paste_id" text, + "tag_id" text, + CONSTRAINT "pastes_to_tags_paste_id_tag_id_pk" PRIMARY KEY("paste_id", "tag_id") +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "session" ( + "sessionToken" text PRIMARY KEY NOT NULL, + "userId" text NOT NULL, + "expires" timestamp NOT NULL +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "tags" ( + "id" text PRIMARY KEY NOT NULL, + "name" text NOT NULL, + "color" text, + "description" text, + CONSTRAINT "tags_name_unique" UNIQUE("name") +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "user" ( + "id" text PRIMARY KEY NOT NULL, + "name" text, + "email" text NOT NULL, + "emailVerified" timestamp, + "image" text +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "verificationToken" ( + "identifier" text NOT NULL, + "token" text NOT NULL, + "expires" timestamp NOT NULL, + CONSTRAINT "verificationToken_identifier_token_pk" PRIMARY KEY("identifier", "token") +); +--> statement-breakpoint ALTER TABLE "account" -ADD CONSTRAINT "account_userId_user_id_fk" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE cascade ON UPDATE no action; -EXCEPTION -WHEN duplicate_object THEN null; -END $$; -DO $$ BEGIN -ALTER TABLE "session" -ADD CONSTRAINT "session_userId_user_id_fk" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE cascade ON UPDATE no action; -EXCEPTION -WHEN duplicate_object THEN null; -END $$; -DO $$ BEGIN +ADD CONSTRAINT "account_userId_user_id_fk" FOREIGN KEY ("userId") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action; +--> statement-breakpoint +ALTER TABLE "agent_sessions" +ADD CONSTRAINT "agent_sessions_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action; +--> statement-breakpoint ALTER TABLE "authenticator" -ADD CONSTRAINT "authenticator_userId_user_id_fk" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE cascade ON UPDATE no action; -EXCEPTION -WHEN duplicate_object THEN null; -END $$; \ No newline at end of file +ADD CONSTRAINT "authenticator_userId_user_id_fk" FOREIGN KEY ("userId") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action; +--> statement-breakpoint +ALTER TABLE "pastes" +ADD CONSTRAINT "pastes_author_id_user_id_fk" FOREIGN KEY ("author_id") REFERENCES "public"."user"("id") ON DELETE +set null ON UPDATE no action; +--> statement-breakpoint +ALTER TABLE "pastes" +ADD CONSTRAINT "pastes_session_id_agent_sessions_id_fk" FOREIGN KEY ("session_id") REFERENCES "public"."agent_sessions"("id") ON DELETE +set null ON UPDATE no action; +--> statement-breakpoint +ALTER TABLE "pastes_to_tags" +ADD CONSTRAINT "pastes_to_tags_paste_id_pastes_id_fk" FOREIGN KEY ("paste_id") REFERENCES "public"."pastes"("id") ON DELETE cascade ON UPDATE no action; +--> statement-breakpoint +ALTER TABLE "pastes_to_tags" +ADD CONSTRAINT "pastes_to_tags_tag_id_tags_id_fk" FOREIGN KEY ("tag_id") REFERENCES "public"."tags"("id") ON DELETE cascade ON UPDATE no action; +--> statement-breakpoint +ALTER TABLE "session" +ADD CONSTRAINT "session_userId_user_id_fk" FOREIGN KEY ("userId") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action; \ No newline at end of file diff --git a/apps/web/drizzle/meta/0000_snapshot.json b/apps/web/drizzle/meta/0000_snapshot.json new file mode 100644 index 0000000..912af41 --- /dev/null +++ b/apps/web/drizzle/meta/0000_snapshot.json @@ -0,0 +1,611 @@ +{ + "id": "f6b95186-510d-43a2-b8c9-40511244786f", + "prevId": "00000000-0000-0000-0000-000000000000", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.account": { + "name": "account", + "schema": "", + "columns": { + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "providerAccountId": { + "name": "providerAccountId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "token_type": { + "name": "token_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "session_state": { + "name": "session_state", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "account_userId_user_id_fk": { + "name": "account_userId_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "account_provider_providerAccountId_pk": { + "name": "account_provider_providerAccountId_pk", + "columns": [ + "provider", + "providerAccountId" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_sessions": { + "name": "agent_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agent_name": { + "name": "agent_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "agent_sessions_user_id_user_id_fk": { + "name": "agent_sessions_user_id_user_id_fk", + "tableFrom": "agent_sessions", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.authenticator": { + "name": "authenticator", + "schema": "", + "columns": { + "credentialID": { + "name": "credentialID", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "providerAccountId": { + "name": "providerAccountId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "credentialPublicKey": { + "name": "credentialPublicKey", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "counter": { + "name": "counter", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "credentialDeviceType": { + "name": "credentialDeviceType", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "credentialBackedUp": { + "name": "credentialBackedUp", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "transports": { + "name": "transports", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "authenticator_userId_user_id_fk": { + "name": "authenticator_userId_user_id_fk", + "tableFrom": "authenticator", + "tableTo": "user", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "authenticator_userId_credentialID_pk": { + "name": "authenticator_userId_credentialID_pk", + "columns": [ + "userId", + "credentialID" + ] + } + }, + "uniqueConstraints": { + "authenticator_credentialID_unique": { + "name": "authenticator_credentialID_unique", + "nullsNotDistinct": false, + "columns": [ + "credentialID" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pastes": { + "name": "pastes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content_type": { + "name": "content_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'text/plain'" + }, + "syntax": { + "name": "syntax", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "visibility": { + "name": "visibility", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'public'" + }, + "author_id": { + "name": "author_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "pastes_author_id_user_id_fk": { + "name": "pastes_author_id_user_id_fk", + "tableFrom": "pastes", + "tableTo": "user", + "columnsFrom": [ + "author_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "pastes_session_id_agent_sessions_id_fk": { + "name": "pastes_session_id_agent_sessions_id_fk", + "tableFrom": "pastes", + "tableTo": "agent_sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pastes_to_tags": { + "name": "pastes_to_tags", + "schema": "", + "columns": { + "paste_id": { + "name": "paste_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag_id": { + "name": "tag_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "pastes_to_tags_paste_id_pastes_id_fk": { + "name": "pastes_to_tags_paste_id_pastes_id_fk", + "tableFrom": "pastes_to_tags", + "tableTo": "pastes", + "columnsFrom": [ + "paste_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pastes_to_tags_tag_id_tags_id_fk": { + "name": "pastes_to_tags_tag_id_tags_id_fk", + "tableFrom": "pastes_to_tags", + "tableTo": "tags", + "columnsFrom": [ + "tag_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "pastes_to_tags_paste_id_tag_id_pk": { + "name": "pastes_to_tags_paste_id_tag_id_pk", + "columns": [ + "paste_id", + "tag_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "sessionToken": { + "name": "sessionToken", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires": { + "name": "expires", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "session_userId_user_id_fk": { + "name": "session_userId_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tags": { + "name": "tags", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "tags_name_unique": { + "name": "tags_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "emailVerified": { + "name": "emailVerified", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verificationToken": { + "name": "verificationToken", + "schema": "", + "columns": { + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires": { + "name": "expires", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "verificationToken_identifier_token_pk": { + "name": "verificationToken_identifier_token_pk", + "columns": [ + "identifier", + "token" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/apps/web/drizzle/meta/_journal.json b/apps/web/drizzle/meta/_journal.json new file mode 100644 index 0000000..f193d46 --- /dev/null +++ b/apps/web/drizzle/meta/_journal.json @@ -0,0 +1,13 @@ +{ + "version": "7", + "dialect": "postgresql", + "entries": [ + { + "idx": 0, + "version": "7", + "when": 1765959773637, + "tag": "0000_busy_bill_hollister", + "breakpoints": true + } + ] +} \ No newline at end of file