diff --git a/.github/workflows/frontend.yaml b/.github/workflows/frontend.yaml index aa8ab65..c9c598f 100644 --- a/.github/workflows/frontend.yaml +++ b/.github/workflows/frontend.yaml @@ -29,7 +29,7 @@ jobs: - name: Install Dependencies run: bun install - name: Create Env file - run: echo "VITE_BACKEND_URL=${{ secrets.VITE_BACKEND_URL }}" > apps/frontend/.env + run: echo "VITE_BACKEND_URL=${{ secrets.VITE_BACKEND_URL }}\nVITE_CLERK_PUBLISHABLE_KEY=${{secrets.VITE_CLERK_PUBLISHABLE_KEY}}" > apps/frontend/.env - name: Build run: bun run build -F frontend - name: Deploy diff --git a/apps/backend/.env.template b/apps/backend/.env.template index 11d717c..54708eb 100644 --- a/apps/backend/.env.template +++ b/apps/backend/.env.template @@ -1,3 +1,5 @@ SERVER_KEY= DATABASE_URL= -PORT=3000 \ No newline at end of file +PORT=3000 +CLERK_PUBLISHABLE_KEY= +CLERK_SECRET_KEY= \ No newline at end of file diff --git a/apps/backend/drizzle/0002_outstanding_kree.sql b/apps/backend/drizzle/0002_outstanding_kree.sql new file mode 100644 index 0000000..117647f --- /dev/null +++ b/apps/backend/drizzle/0002_outstanding_kree.sql @@ -0,0 +1 @@ +ALTER TABLE "definitions" ADD COLUMN "userId" text; \ No newline at end of file diff --git a/apps/backend/drizzle/0003_past_random.sql b/apps/backend/drizzle/0003_past_random.sql new file mode 100644 index 0000000..c1971e3 --- /dev/null +++ b/apps/backend/drizzle/0003_past_random.sql @@ -0,0 +1 @@ +ALTER TABLE "definitions" ALTER COLUMN "userId" SET NOT NULL; \ No newline at end of file diff --git a/apps/backend/drizzle/meta/0002_snapshot.json b/apps/backend/drizzle/meta/0002_snapshot.json new file mode 100644 index 0000000..6943b7a --- /dev/null +++ b/apps/backend/drizzle/meta/0002_snapshot.json @@ -0,0 +1,416 @@ +{ + "id": "6c9b0184-c5f6-4aea-863a-72f0ddc99c78", + "prevId": "2c7edc58-1de4-4eda-b156-61331723c2c7", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.definitions": { + "name": "definitions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "definitions_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "name": { + "name": "name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'definition'" + }, + "global": { + "name": "global", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "uiObject": { + "name": "uiObject", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "tasks": { + "name": "tasks", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "date", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.runtime_logs": { + "name": "runtime_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "runtime_logs_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "taskId": { + "name": "taskId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "timestamp": { + "name": "timestamp", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "log": { + "name": "log", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "''" + }, + "severity": { + "name": "severity", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'log'" + }, + "runtimeId": { + "name": "runtimeId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "definitionId": { + "name": "definitionId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "date", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "log_runtime_id_idx": { + "name": "log_runtime_id_idx", + "columns": [ + { + "expression": "runtimeId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "runtime_logs_runtimeId_runtimes_id_fk": { + "name": "runtime_logs_runtimeId_runtimes_id_fk", + "tableFrom": "runtime_logs", + "tableTo": "runtimes", + "columnsFrom": [ + "runtimeId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "runtime_logs_definitionId_definitions_id_fk": { + "name": "runtime_logs_definitionId_definitions_id_fk", + "tableFrom": "runtime_logs", + "tableTo": "definitions", + "columnsFrom": [ + "definitionId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.runtimes": { + "name": "runtimes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "runtimes_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "global": { + "name": "global", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "workflowStatus": { + "name": "workflowStatus", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'added'" + }, + "definitionId": { + "name": "definitionId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "date", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "runtimes_definitionId_definitions_id_fk": { + "name": "runtimes_definitionId_definitions_id_fk", + "tableFrom": "runtimes", + "tableTo": "definitions", + "columnsFrom": [ + "definitionId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.runtime_tasks": { + "name": "runtime_tasks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "runtime_tasks_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "taskId": { + "name": "taskId", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "next": { + "name": "next", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "previous": { + "name": "previous", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "exec": { + "name": "exec", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'added'" + }, + "result": { + "name": "result", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "runtimeId": { + "name": "runtimeId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "date", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "task_runtime_id_idx": { + "name": "task_runtime_id_idx", + "columns": [ + { + "expression": "runtimeId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "runtime_tasks_runtimeId_runtimes_id_fk": { + "name": "runtime_tasks_runtimeId_runtimes_id_fk", + "tableFrom": "runtime_tasks", + "tableTo": "runtimes", + "columnsFrom": [ + "runtimeId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "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/backend/drizzle/meta/0003_snapshot.json b/apps/backend/drizzle/meta/0003_snapshot.json new file mode 100644 index 0000000..73c2ba3 --- /dev/null +++ b/apps/backend/drizzle/meta/0003_snapshot.json @@ -0,0 +1,416 @@ +{ + "id": "335c72dd-8fba-47b4-babd-7c47c9f67b14", + "prevId": "6c9b0184-c5f6-4aea-863a-72f0ddc99c78", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.definitions": { + "name": "definitions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "definitions_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "name": { + "name": "name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'definition'" + }, + "global": { + "name": "global", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "uiObject": { + "name": "uiObject", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "tasks": { + "name": "tasks", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "date", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.runtime_logs": { + "name": "runtime_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "runtime_logs_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "taskId": { + "name": "taskId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "timestamp": { + "name": "timestamp", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "log": { + "name": "log", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "''" + }, + "severity": { + "name": "severity", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'log'" + }, + "runtimeId": { + "name": "runtimeId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "definitionId": { + "name": "definitionId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "date", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "log_runtime_id_idx": { + "name": "log_runtime_id_idx", + "columns": [ + { + "expression": "runtimeId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "runtime_logs_runtimeId_runtimes_id_fk": { + "name": "runtime_logs_runtimeId_runtimes_id_fk", + "tableFrom": "runtime_logs", + "tableTo": "runtimes", + "columnsFrom": [ + "runtimeId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "runtime_logs_definitionId_definitions_id_fk": { + "name": "runtime_logs_definitionId_definitions_id_fk", + "tableFrom": "runtime_logs", + "tableTo": "definitions", + "columnsFrom": [ + "definitionId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.runtimes": { + "name": "runtimes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "runtimes_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "global": { + "name": "global", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "workflowStatus": { + "name": "workflowStatus", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'added'" + }, + "definitionId": { + "name": "definitionId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "date", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "runtimes_definitionId_definitions_id_fk": { + "name": "runtimes_definitionId_definitions_id_fk", + "tableFrom": "runtimes", + "tableTo": "definitions", + "columnsFrom": [ + "definitionId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.runtime_tasks": { + "name": "runtime_tasks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "runtime_tasks_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "taskId": { + "name": "taskId", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "next": { + "name": "next", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "previous": { + "name": "previous", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "exec": { + "name": "exec", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'added'" + }, + "result": { + "name": "result", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "runtimeId": { + "name": "runtimeId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "date", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "task_runtime_id_idx": { + "name": "task_runtime_id_idx", + "columns": [ + { + "expression": "runtimeId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "runtime_tasks_runtimeId_runtimes_id_fk": { + "name": "runtime_tasks_runtimeId_runtimes_id_fk", + "tableFrom": "runtime_tasks", + "tableTo": "runtimes", + "columnsFrom": [ + "runtimeId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "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/backend/drizzle/meta/_journal.json b/apps/backend/drizzle/meta/_journal.json index bf11782..f769a35 100644 --- a/apps/backend/drizzle/meta/_journal.json +++ b/apps/backend/drizzle/meta/_journal.json @@ -15,6 +15,20 @@ "when": 1755444310742, "tag": "0001_strange_talisman", "breakpoints": true + }, + { + "idx": 2, + "version": "7", + "when": 1763881462889, + "tag": "0002_outstanding_kree", + "breakpoints": true + }, + { + "idx": 3, + "version": "7", + "when": 1763889013163, + "tag": "0003_past_random", + "breakpoints": true } ] } \ No newline at end of file diff --git a/apps/backend/package.json b/apps/backend/package.json index 250d4f5..40ac36b 100644 --- a/apps/backend/package.json +++ b/apps/backend/package.json @@ -10,6 +10,8 @@ "check-types": "tsc --noEmit" }, "dependencies": { + "@clerk/backend": "^2.27.0", + "@hono/clerk-auth": "^3.0.3", "@orpc/contract": "catalog:", "@orpc/openapi": "catalog:", "@orpc/server": "catalog:", @@ -18,17 +20,17 @@ "@repo/orpc": "workspace:*", "@repo/typescript-config": "workspace:*", "@repo/utils": "workspace:*", - "axios": "^1.9.0", - "dotenv": "^16.5.0", - "drizzle-orm": "^0.43.1", - "hono": "^4.8.5", + "axios": "^1.13.2", + "dotenv": "^17.2.3", + "drizzle-orm": "^0.45.1", + "hono": "^4.11.0", "postgres": "^3.4.7", "zod": "catalog:" }, "devDependencies": { "@types/bun": "latest", - "drizzle-kit": "^0.31.1", - "tsx": "^4.19.4", + "drizzle-kit": "^0.31.8", + "tsx": "^4.21.0", "typescript": "catalog:" } } diff --git a/apps/backend/src/db/schema.ts b/apps/backend/src/db/schema.ts index 9931de0..6bc7fe8 100644 --- a/apps/backend/src/db/schema.ts +++ b/apps/backend/src/db/schema.ts @@ -23,6 +23,7 @@ export const definitionTable = pgTable("definitions", { .notNull(), uiObject: json(), tasks: json().$type>().notNull(), + userId: text().notNull(), createdAt: date().defaultNow().notNull(), }); diff --git a/apps/backend/src/db/seed.ts b/apps/backend/src/db/seed.ts index c065bcd..bac6861 100644 --- a/apps/backend/src/db/seed.ts +++ b/apps/backend/src/db/seed.ts @@ -12,6 +12,7 @@ async function seed() { status: "active", type: "template", uiObject: {}, + userId: "abcd", }, ]); } diff --git a/apps/backend/src/engine/postgres.ts b/apps/backend/src/engine/postgres.ts index 263fd02..126e73a 100644 --- a/apps/backend/src/engine/postgres.ts +++ b/apps/backend/src/engine/postgres.ts @@ -10,7 +10,7 @@ import { definitionTaskList, } from "@repo/engine/types"; import { and, eq } from "drizzle-orm"; -import { processTask } from "@modules/engine"; +import { processTaskInternal } from "@modules/engine"; export class DbPostgresPersistor extends WorkflowPersistor { async getRuntime(runtimeId: number): Promise { @@ -81,7 +81,7 @@ export class DbPostgresPersistor extends WorkflowPersistor { } async nextTaskCaller(runtimeId: number, taskId: string): Promise { - await processTask({ + await processTaskInternal({ runtimeId, taskId, }); diff --git a/apps/backend/src/index.ts b/apps/backend/src/index.ts index 508b6b4..4c91194 100644 --- a/apps/backend/src/index.ts +++ b/apps/backend/src/index.ts @@ -4,6 +4,7 @@ import { CORSPlugin } from "@orpc/server/plugins"; import { ZodToJsonSchemaConverter } from "@orpc/zod"; import { OpenAPIGenerator } from "@orpc/openapi"; import { OpenAPIHandler } from "@orpc/openapi/fetch"; +import { clerkMiddleware } from "@hono/clerk-auth"; const app = new Hono(); @@ -44,12 +45,12 @@ const html = ` `; +app.use("*", clerkMiddleware()); + app.use(async (c) => { const { matched, response } = await openAPIHandler.handle(c.req.raw, { prefix: "/rpc", - context: { - req: c.req, - }, // Provide initial context if needed + context: { honoContext: c }, // Provide initial context if needed }); if (matched) { diff --git a/apps/backend/src/lib/implementor.ts b/apps/backend/src/lib/implementor.ts index 2ecb213..4ff8516 100644 --- a/apps/backend/src/lib/implementor.ts +++ b/apps/backend/src/lib/implementor.ts @@ -1,10 +1,5 @@ import { implement } from "@orpc/server"; import { contractRouter } from "@repo/orpc"; -import type { HonoRequest } from "hono"; - -type HonoContext = { - req?: HonoRequest; - internal?: boolean; -}; +import type { HonoContext } from "./type"; export const contractOpenSpec = implement(contractRouter).$context(); diff --git a/apps/backend/src/lib/procedures.ts b/apps/backend/src/lib/procedures.ts index 1ce6228..b1a8d0d 100644 --- a/apps/backend/src/lib/procedures.ts +++ b/apps/backend/src/lib/procedures.ts @@ -1,72 +1,49 @@ -import { oo } from "@orpc/openapi"; -import { ORPCError, os } from "@orpc/server"; -import type { HonoRequest } from "hono"; - -type HonoContext = { - internal?: boolean; - req?: HonoRequest; -}; +import { ORPCError } from "@orpc/server"; +import { getAuth } from "@hono/clerk-auth"; +import { contractOpenSpec } from "./implementor"; const internalApiKey = process.env.SERVER_KEY; -export const internalAuth = oo.spec( - os.$context().middleware(({ context, next }) => { - if (context?.internal === true) { - return next(); - } - - const apiKey = context?.req?.header("x-api-key"); - - if (!apiKey) { - throw new ORPCError("UNAUTHORIZED", { - message: "No `x-api-key` Header Found", - }); - } +export const privateAuthSpec = contractOpenSpec.use(({ context, next }) => { + if (!context?.honoContext) { + throw new ORPCError("UNAUTHORIZED", { + message: "Unauthorized", + }); + } - if (apiKey !== internalApiKey) { - throw new ORPCError("UNAUTHORIZED", { - message: "Invalid API Key", - }); - } + const auth = getAuth(context.honoContext); - return next(); - }), - { - security: [{ apiKey: [] }], + if (!auth?.userId) { + throw new ORPCError("UNAUTHORIZED", { + message: "Unauthorized", + }); } -); -export const privateAuth = oo.spec( - os.$context().middleware(({ context, next }) => { - if (context?.internal === true) { - return next(); - } + const authClaims = { + userId: auth.userId, + }; - const authHeader = context?.req?.header("authorization")?.split(" ")?.at(1); + return next({ + context: { + auth: authClaims, + }, + }); +}); - if (!authHeader) { - throw new ORPCError("UNAUTHORIZED", { - message: "No Auth Header Found", - }); - } +export const internalAuthSpec = contractOpenSpec.use(({ context, next }) => { + const apiKey = context?.internal?.apiKey; - if (authHeader !== "token") { - throw new ORPCError("UNAUTHORIZED", { - message: "Invalid Token", - }); - } + if (!apiKey) { + throw new ORPCError("UNAUTHORIZED", { + message: "No `x-api-key` Header Found", + }); + } - return next(); - }), - { - security: [{ bearerAuth: [] }], + if (apiKey !== internalApiKey) { + throw new ORPCError("UNAUTHORIZED", { + message: "Invalid API Key", + }); } -); -export const publicProcedures = oo.spec(os.$context(), { - security: [], + return next(); }); - -export const privateProcedures = os.$context().use(privateAuth); - -export const internalProcedures = os.$context().use(internalAuth); diff --git a/apps/backend/src/lib/type.ts b/apps/backend/src/lib/type.ts new file mode 100644 index 0000000..448d50a --- /dev/null +++ b/apps/backend/src/lib/type.ts @@ -0,0 +1,8 @@ +import type { Context } from "hono"; + +export type HonoContext = { + honoContext?: Context; + internal?: { + apiKey: string; + }; +}; diff --git a/apps/backend/src/modules/definition.ts b/apps/backend/src/modules/definition.ts index e8a27c6..a4f5eb4 100644 --- a/apps/backend/src/modules/definition.ts +++ b/apps/backend/src/modules/definition.ts @@ -1,12 +1,13 @@ import { db } from "@db/index"; import { definitionTable } from "@db/schema"; -import { contractOpenSpec } from "@lib/implementor"; import { safeAsync } from "@repo/utils"; import { and, asc, count, desc, eq } from "drizzle-orm"; import { definitionTaskList } from "@repo/engine/types"; +import { privateAuthSpec } from "@lib/procedures"; -export const createDefinition = contractOpenSpec.definition.create.handler( - async ({ input, errors }) => { +export const createDefinition = privateAuthSpec.definition.create.handler( + async ({ input, context, errors }) => { + const userId = context.auth.userId; const parsedTask = definitionTaskList.safeParse(input.tasks); if (!parsedTask.success) { @@ -26,6 +27,7 @@ export const createDefinition = contractOpenSpec.definition.create.handler( status: input.status, type: input.type, uiObject: input.uiObject, + userId: userId, }) .returning({ id: definitionTable.id, @@ -56,8 +58,9 @@ export const createDefinition = contractOpenSpec.definition.create.handler( } ); -export const editDefinition = contractOpenSpec.definition.edit.handler( - async ({ input, errors }) => { +export const editDefinition = privateAuthSpec.definition.edit.handler( + async ({ input, errors, context }) => { + const userId = context.auth.userId; const parsedTask = definitionTaskList.safeParse(input.body.tasks); if (!parsedTask.success) { @@ -68,7 +71,10 @@ export const editDefinition = contractOpenSpec.definition.edit.handler( const definitionResult = await safeAsync( db.query.definition.findFirst({ - where: eq(definitionTable.id, Number(input.params.id)), + where: and( + eq(definitionTable.id, Number(input.params.id)), + eq(definitionTable.userId, userId) + ), columns: { id: true, }, @@ -132,46 +138,52 @@ export const editDefinition = contractOpenSpec.definition.edit.handler( } ); -export const listDefinition = contractOpenSpec.definition.list.handler(async ({ input }) => { - const list = await db.query.definition.findMany({ - offset: (input.page - 1) * input.limit, - limit: input.limit, - where: eq(definitionTable.type, "definition"), - orderBy: [asc(definitionTable.status), desc(definitionTable.createdAt)], - columns: { - id: true, - name: true, - description: true, - status: true, - createdAt: true, - }, - }); - - const totalCount = await db - .select({ - count: count(), - }) - .from(definitionTable) - .where(and(eq(definitionTable.status, "active"), eq(definitionTable.type, "definition"))); - - return { - message: "Definition listed successfully", - data: { - list, - pagination: { - total: totalCount?.at(0)?.count ?? 0, - page: input.page, - size: input.limit, +export const listDefinition = privateAuthSpec.definition.list.handler( + async ({ input, context }) => { + const userId = context.auth.userId; + + const list = await db.query.definition.findMany({ + offset: (input.page - 1) * input.limit, + limit: input.limit, + where: and(eq(definitionTable.type, "definition"), eq(definitionTable.userId, userId)), + orderBy: [asc(definitionTable.status), desc(definitionTable.createdAt)], + columns: { + id: true, + name: true, + description: true, + status: true, + createdAt: true, }, - }, - }; -}); + }); + + const totalCount = await db + .select({ + count: count(), + }) + .from(definitionTable) + .where(and(eq(definitionTable.status, "active"), eq(definitionTable.type, "definition"))); + + return { + message: "Definition listed successfully", + data: { + list, + pagination: { + total: totalCount?.at(0)?.count ?? 0, + page: input.page, + size: input.limit, + }, + }, + }; + } +); + +export const fetchEditDefinition = privateAuthSpec.definition.fetchEdit.handler( + async ({ input, errors, context }) => { + const userId = context.auth.userId; -export const fetchEditDefinition = contractOpenSpec.definition.fetchEdit.handler( - async ({ input, errors }) => { const result = await safeAsync( db.query.definition.findFirst({ - where: eq(definitionTable.id, Number(input.id)), + where: and(eq(definitionTable.id, Number(input.id)), eq(definitionTable.userId, userId)), columns: { id: true, global: true, @@ -202,11 +214,13 @@ export const fetchEditDefinition = contractOpenSpec.definition.fetchEdit.handler } ); -export const deleteDefinition = contractOpenSpec.definition.delete.handler( - async ({ errors, input }) => { +export const deleteDefinition = privateAuthSpec.definition.delete.handler( + async ({ errors, input, context }) => { + const userId = context.auth.userId; + const definitionResult = await safeAsync( db.query.definition.findFirst({ - where: eq(definitionTable.id, Number(input.id)), + where: and(eq(definitionTable.id, Number(input.id)), eq(definitionTable.userId, userId)), columns: { id: true, }, @@ -249,11 +263,13 @@ export const deleteDefinition = contractOpenSpec.definition.delete.handler( } ); -export const fetchDefinition = contractOpenSpec.definition.get.handler( - async ({ input, errors }) => { +export const fetchDefinition = privateAuthSpec.definition.get.handler( + async ({ input, errors, context }) => { + const userId = context.auth.userId; + const result = await safeAsync( db.query.definition.findFirst({ - where: eq(definitionTable.id, Number(input.id)), + where: and(eq(definitionTable.id, Number(input.id)), eq(definitionTable.userId, userId)), columns: { id: true, global: true, diff --git a/apps/backend/src/modules/engine.ts b/apps/backend/src/modules/engine.ts index d464082..5514d4d 100644 --- a/apps/backend/src/modules/engine.ts +++ b/apps/backend/src/modules/engine.ts @@ -1,120 +1,231 @@ import { db } from "@db/index"; import { definitionTable, runtimeTable, runtimeTaskTable } from "@db/schema"; import { DbPostgresPersistor } from "@engine/postgres"; -import { contractOpenSpec } from "@lib/implementor"; +import { internalAuthSpec, privateAuthSpec } from "@lib/procedures"; import { Engine } from "@repo/engine/engine"; import { definitionTaskList } from "@repo/engine/types"; import { safeAsync } from "@repo/utils"; import { and, eq, not } from "drizzle-orm"; -export const startEngine = contractOpenSpec.engine.start.handler(async ({ input, errors }) => { - const definitionResult = await safeAsync( - db.query.definition.findFirst({ - where: and( - eq(definitionTable.id, input.definitionId), - eq(definitionTable.type, "definition"), - eq(definitionTable.status, "active") - ), - }) - ); - - if (!definitionResult.success) { - console.error("Definition findFirst failed", definitionResult.error); - throw errors.INTERNAL_SERVER_ERROR({ - message: "Internal server error", - }); - } +export const startEngine = privateAuthSpec.engine.start.handler( + async ({ input, errors, context }) => { + const userId = context.auth.userId; + + const definitionResult = await safeAsync( + db.query.definition.findFirst({ + where: and( + eq(definitionTable.id, input.definitionId), + eq(definitionTable.type, "definition"), + eq(definitionTable.status, "active"), + eq(definitionTable.userId, userId) + ), + }) + ); + + if (!definitionResult.success) { + console.error("Definition findFirst failed", definitionResult.error); + throw errors.INTERNAL_SERVER_ERROR({ + message: "Internal server error", + }); + } + + if (!definitionResult.data) { + throw errors.BAD_REQUEST({ + message: "Definition not found", + }); + } - if (!definitionResult.data) { - throw errors.BAD_REQUEST({ - message: "Definition not found", + const definitionTasks = definitionTaskList.parse(definitionResult.data.tasks ?? []); + + const dbTaskRecord = definitionTasks.map((i) => ({ + taskId: i.id, + name: i.name, + type: i.type, + next: i.next, + previous: i.previous, + exec: i.type === "FUNCTION" || i.type === "GUARD" ? i.exec : "", + status: i.status ?? "added", + })); + + const definitionGlobalMap = new Map(); + + definitionResult.data.global?.forEach((gVal) => { + definitionGlobalMap.set(gVal.key, gVal.value); }); - } - const definitionTasks = definitionTaskList.parse(definitionResult.data.tasks ?? []); + const transactionResult = await safeAsync( + db.transaction(async (tx) => { + const createdRuntime = await tx + .insert(runtimeTable) + .values({ + global: { + ...Object.fromEntries(definitionGlobalMap.entries()), + ...(input?.globalParams ?? {}), + }, + workflowStatus: "added", + definitionId: input.definitionId, + }) + .returning({ + id: runtimeTable.id, + }); - const dbTaskRecord = definitionTasks.map((i) => ({ - taskId: i.id, - name: i.name, - type: i.type, - next: i.next, - previous: i.previous, - exec: i.type === "FUNCTION" || i.type === "GUARD" ? i.exec : "", - status: i.status ?? "added", - })); + const createdRuntimeId = createdRuntime?.at(0)?.id; - const definitionGlobalMap = new Map(); + if (typeof createdRuntimeId !== "number") { + throw errors.BAD_REQUEST({ + message: "Can't start engine", + }); + } - definitionResult.data.global?.forEach((gVal) => { - definitionGlobalMap.set(gVal.key, gVal.value); - }); + await tx + .insert(runtimeTaskTable) + .values(dbTaskRecord.map((i) => ({ ...i, runtimeId: createdRuntimeId }))); - const transactionResult = await safeAsync( - db.transaction(async (tx) => { - const createdRuntime = await tx - .insert(runtimeTable) - .values({ - global: { - ...Object.fromEntries(definitionGlobalMap.entries()), - ...(input?.globalParams ?? {}), - }, - workflowStatus: "added", - definitionId: input.definitionId, - }) - .returning({ - id: runtimeTable.id, - }); + return { + createdRuntimeId, + }; + }) + ); - const createdRuntimeId = createdRuntime?.at(0)?.id; + if (!transactionResult.success) { + console.error("Transaction failed", transactionResult.error); + throw errors.INTERNAL_SERVER_ERROR({ + message: "Internal server error", + }); + } - if (typeof createdRuntimeId !== "number") { + if (input.autoStart === true) { + const startTaskId = dbTaskRecord.find((i) => i.type === "START")?.taskId; + if (!startTaskId) { throw errors.BAD_REQUEST({ - message: "Can't start engine", + message: "Can't find start task in definition", }); } - await tx - .insert(runtimeTaskTable) - .values(dbTaskRecord.map((i) => ({ ...i, runtimeId: createdRuntimeId }))); - - return { - createdRuntimeId, - }; - }) - ); + await safeAsync( + processTaskInternal({ + runtimeId: transactionResult.data?.createdRuntimeId, + taskId: startTaskId, + }) + ); + } - if (!transactionResult.success) { - console.error("Transaction failed", transactionResult.error); - throw errors.INTERNAL_SERVER_ERROR({ - message: "Internal server error", - }); + return { + data: { + id: transactionResult.data?.createdRuntimeId, + }, + message: "Runtime Engine created successfully", + }; } +); + +export const processTask = privateAuthSpec.engine.process.handler( + async ({ input, errors, context }) => { + const userId = context.auth.userId; + + const runtimeResult = await safeAsync( + db.query.runtime.findFirst({ + where: and( + eq(runtimeTable.id, input.runtimeId), + not(eq(runtimeTable.workflowStatus, "completed")) + ), + columns: { + id: true, + definitionId: true, + }, + }) + ); + + if (!runtimeResult.success) { + console.error(runtimeResult.error); + throw errors.INTERNAL_SERVER_ERROR({ + message: "Internal Server Error", + }); + } + + if (!runtimeResult.data) { + throw errors.BAD_REQUEST({ + message: "Runtime not found", + }); + } + + const definitionResult = await safeAsync( + db.query.definition.findFirst({ + where: and( + eq(definitionTable.id, runtimeResult.data.definitionId), + eq(definitionTable.userId, userId) + ), + columns: { + id: true, + }, + }) + ); - if (input.autoStart === true) { - const startTaskId = dbTaskRecord.find((i) => i.type === "START")?.taskId; - if (!startTaskId) { + if (!definitionResult.success) { + console.error(definitionResult.error); + throw errors.INTERNAL_SERVER_ERROR({ + message: "Internal Server Error", + }); + } + + if (!definitionResult.data) { throw errors.BAD_REQUEST({ - message: "Can't find start task in definition", + message: "Definition not found", }); } - await safeAsync( - processTask({ - runtimeId: transactionResult.data?.createdRuntimeId, - taskId: startTaskId, + const runtimeId = runtimeResult.data.id; + + const taskResult = await safeAsync( + db.query.runtimeTask.findFirst({ + where: and( + eq(runtimeTaskTable.runtimeId, runtimeId), + eq(runtimeTaskTable.taskId, input.taskId), + not(eq(runtimeTaskTable.status, "completed")) + ), + columns: { + id: true, + taskId: true, + }, }) ); - } - return { - data: { - id: transactionResult.data?.createdRuntimeId, - }, - message: "Runtime Engine created successfully", - }; -}); + if (!taskResult.success) { + console.error(taskResult.error); + throw errors.INTERNAL_SERVER_ERROR({ + message: "Internal Server Error", + }); + } + + if (!taskResult.data) { + throw errors.BAD_REQUEST({ + message: "Task not found", + }); + } + + const taskId = taskResult.data.taskId; + + const persistor = new DbPostgresPersistor(); + + const engine = new Engine(persistor); + + try { + await engine.setup(runtimeId); + await engine.process(taskId); + } catch (error) { + console.error(error); + throw errors.INTERNAL_SERVER_ERROR({ + message: "Internal Server Error", + }); + } + + return { + message: "Task processing started successfully", + }; + } +); -export const processTask = contractOpenSpec.engine.process +export const processTaskInternal = internalAuthSpec.engine.processInternal .handler(async ({ input, errors }) => { const runtimeResult = await safeAsync( db.query.runtime.findFirst({ @@ -192,6 +303,8 @@ export const processTask = contractOpenSpec.engine.process }) .callable({ context: { - internal: true, + internal: { + apiKey: process.env.SERVER_KEY!, + }, }, }); diff --git a/apps/backend/src/modules/runtime.ts b/apps/backend/src/modules/runtime.ts index 8d7d480..a213682 100644 --- a/apps/backend/src/modules/runtime.ts +++ b/apps/backend/src/modules/runtime.ts @@ -1,160 +1,174 @@ import { db } from "@db/index"; import { definitionTable, runtimeLogTable, runtimeTable, runtimeTaskTable } from "@db/schema"; -import { contractOpenSpec } from "@lib/implementor"; +import { privateAuthSpec } from "@lib/procedures"; import { safeAsync } from "@repo/utils"; -import { count, desc, eq } from "drizzle-orm"; - -export const listRuntime = contractOpenSpec.runtime.list.handler(async ({ input, errors }) => { - const definitionResult = await safeAsync( - db.query.definition.findFirst({ - where: eq(definitionTable.id, Number(input.definition)), - columns: { - id: true, +import { and, count, desc, eq } from "drizzle-orm"; + +export const listRuntime = privateAuthSpec.runtime.list.handler( + async ({ input, errors, context }) => { + const userId = context.auth.userId; + + const definitionResult = await safeAsync( + db.query.definition.findFirst({ + where: and( + eq(definitionTable.id, Number(input.definition)), + eq(definitionTable.userId, userId) + ), + columns: { + id: true, + }, + }) + ); + + if (!definitionResult.success) { + console.error(definitionResult.error); + throw errors.INTERNAL_SERVER_ERROR({ + message: "Internal server error", + }); + } + + if (!definitionResult.data) { + throw errors.BAD_REQUEST({ + message: "Definition not found", + }); + } + + const runtimeResult = await safeAsync( + Promise.all([ + db.query.runtime.findMany({ + where: eq(runtimeTable.definitionId, Number(input.definition)), + columns: { + id: true, + workflowStatus: true, + createdAt: true, + }, + orderBy: [desc(runtimeTable.createdAt), desc(runtimeTable.id)], + offset: (input.page - 1) * input.limit, + limit: input.limit, + }), + db + .select({ + count: count(), + }) + .from(runtimeTable) + .where(eq(runtimeTable.definitionId, Number(input.definition))), + ]) + ); + + if (!runtimeResult.success) { + console.error(runtimeResult.error); + throw errors.INTERNAL_SERVER_ERROR({ + message: "Internal server error", + }); + } + + if (!runtimeResult.data) { + throw errors.BAD_REQUEST({ + message: "Runtime not found", + }); + } + + const [list, countData] = runtimeResult.data; + + return { + message: "Runtime listed successfully", + data: { + list, + pagination: { + total: countData?.at(0)?.count ?? 0, + page: input.page, + size: input.limit, + }, }, - }) - ); - - if (!definitionResult.success) { - console.error(definitionResult.error); - throw errors.INTERNAL_SERVER_ERROR({ - message: "Internal server error", - }); + }; } +); - if (!definitionResult.data) { - throw errors.BAD_REQUEST({ - message: "Definition not found", - }); - } +export const getRuntime = privateAuthSpec.runtime.get.handler( + async ({ input, errors, context }) => { + const userId = context?.auth?.userId; - const runtimeResult = await safeAsync( - Promise.all([ - db.query.runtime.findMany({ - where: eq(runtimeTable.definitionId, Number(input.definition)), + const runtimeResult = await safeAsync( + db.query.runtime.findFirst({ + where: eq(runtimeTable.id, Number(input.id)), columns: { id: true, workflowStatus: true, createdAt: true, + global: true, + definitionId: true, }, - orderBy: [desc(runtimeTable.createdAt), desc(runtimeTable.id)], - offset: (input.page - 1) * input.limit, - limit: input.limit, - }), - db - .select({ - count: count(), - }) - .from(runtimeTable) - .where(eq(runtimeTable.definitionId, Number(input.definition))), - ]) - ); - - if (!runtimeResult.success) { - console.error(runtimeResult.error); - throw errors.INTERNAL_SERVER_ERROR({ - message: "Internal server error", - }); - } - - if (!runtimeResult.data) { - throw errors.BAD_REQUEST({ - message: "Runtime not found", + }) + ); + + if (!runtimeResult.success) { + console.error(runtimeResult.error); + throw errors.INTERNAL_SERVER_ERROR({ + message: "Internal server error", + }); + } + + if (!runtimeResult.data) { + throw errors.NOT_FOUND({ + message: "Runtime not found", + }); + } + + const definition = await db.query.definition.findFirst({ + columns: { + id: true, + name: true, + status: true, + description: true, + createdAt: true, + }, + where: and( + eq(definitionTable.id, runtimeResult.data.definitionId), + eq(definitionTable.userId, userId) + ), }); - } - const [list, countData] = runtimeResult.data; + if (!definition) { + throw errors.NOT_FOUND({ + message: "Definition not found", + }); + } - return { - message: "Runtime listed successfully", - data: { - list, - pagination: { - total: countData?.at(0)?.count ?? 0, - page: input.page, - size: input.limit, - }, - }, - }; -}); - -export const getRuntime = contractOpenSpec.runtime.get.handler(async ({ input, errors }) => { - const runtimeResult = await safeAsync( - db.query.runtime.findFirst({ - where: eq(runtimeTable.id, Number(input.id)), + const runtimeTasks = await db.query.runtimeTask.findMany({ + where: eq(runtimeTaskTable.runtimeId, runtimeResult.data.id), columns: { id: true, - workflowStatus: true, - createdAt: true, - global: true, - definitionId: true, + taskId: true, + name: true, + result: true, + status: true, + type: true, }, - }) - ); - - if (!runtimeResult.success) { - console.error(runtimeResult.error); - throw errors.INTERNAL_SERVER_ERROR({ - message: "Internal server error", }); - } - if (!runtimeResult.data) { - throw errors.NOT_FOUND({ - message: "Runtime not found", + const runtimeLogs = await db.query.runtimeLog.findMany({ + where: eq(runtimeLogTable.runtimeId, runtimeResult.data.id), + orderBy: [desc(runtimeLogTable.timestamp)], + columns: { + id: true, + log: true, + timestamp: true, + taskId: true, + severity: true, + }, }); - } - const definition = await db.query.definition.findFirst({ - columns: { - id: true, - name: true, - status: true, - description: true, - createdAt: true, - }, - where: eq(definitionTable.id, runtimeResult.data.definitionId), - }); - - if (!definition) { - throw errors.NOT_FOUND({ - message: "Definition not found", - }); + return { + message: "Runtime fetched successfully", + data: { + id: runtimeResult.data.id, + createdAt: runtimeResult.data.createdAt, + workflowStatus: runtimeResult.data.workflowStatus, + global: runtimeResult.data.global, + definition: definition, + runtimeTasks: runtimeTasks, + runtimeLogs: runtimeLogs, + }, + }; } - - const runtimeTasks = await db.query.runtimeTask.findMany({ - where: eq(runtimeTaskTable.runtimeId, runtimeResult.data.id), - columns: { - id: true, - taskId: true, - name: true, - result: true, - status: true, - type: true, - }, - }); - - const runtimeLogs = await db.query.runtimeLog.findMany({ - where: eq(runtimeLogTable.runtimeId, runtimeResult.data.id), - orderBy: [desc(runtimeLogTable.timestamp)], - columns: { - id: true, - log: true, - timestamp: true, - taskId: true, - severity: true, - }, - }); - - return { - message: "Runtime fetched successfully", - data: { - id: runtimeResult.data.id, - createdAt: runtimeResult.data.createdAt, - workflowStatus: runtimeResult.data.workflowStatus, - global: runtimeResult.data.global, - definition: definition, - runtimeTasks: runtimeTasks, - runtimeLogs: runtimeLogs, - }, - }; -}); +); diff --git a/apps/backend/src/router.ts b/apps/backend/src/router.ts index a5b2d15..38adbc2 100644 --- a/apps/backend/src/router.ts +++ b/apps/backend/src/router.ts @@ -7,7 +7,7 @@ import { fetchEditDefinition, listDefinition, } from "@modules/definition"; -import { processTask, startEngine } from "@modules/engine"; +import { processTask, processTaskInternal, startEngine } from "@modules/engine"; import { liveness, readiness } from "@modules/health"; import { getRuntime, listRuntime } from "@modules/runtime"; @@ -19,6 +19,7 @@ export const router = contractOpenSpec.router({ engine: { start: startEngine, process: processTask, + processInternal: processTaskInternal, }, definition: { create: createDefinition, diff --git a/apps/frontend/.env.template b/apps/frontend/.env.template index 43275c7..0a2a748 100644 --- a/apps/frontend/.env.template +++ b/apps/frontend/.env.template @@ -1 +1,2 @@ -VITE_BACKEND_URL=http://localhost:3000 \ No newline at end of file +VITE_BACKEND_URL=http://localhost:3000 +VITE_CLERK_PUBLISHABLE_KEY= \ No newline at end of file diff --git a/apps/frontend/package.json b/apps/frontend/package.json index ecac7c0..ad307c6 100644 --- a/apps/frontend/package.json +++ b/apps/frontend/package.json @@ -10,6 +10,7 @@ "check-types": "tsc --noEmit" }, "dependencies": { + "@clerk/clerk-react": "^5.56.2", "@hookform/resolvers": "^5.2.2", "@monaco-editor/react": "^4.7.0", "@orpc/client": "catalog:", @@ -58,7 +59,7 @@ "embla-carousel-react": "^8.6.0", "input-otp": "^1.4.2", "json-to-ts": "^2.1.0", - "lucide-react": "^0.544.0", + "lucide-react": "^0.561.0", "next-themes": "^0.4.6", "react": "^19", "react-day-picker": "^9.11.0", @@ -72,7 +73,7 @@ "tailwindcss": "^4.1.7", "vaul": "^1.1.2", "vite-tsconfig-paths": "^5.1.4", - "zod": "^4.1.11" + "zod": "catalog:" }, "devDependencies": { "@tanstack/router-plugin": "^1.133.13", diff --git a/apps/frontend/src/components/layout/Layout.tsx b/apps/frontend/src/components/layout/Layout.tsx index 4883ff1..b328f28 100644 --- a/apps/frontend/src/components/layout/Layout.tsx +++ b/apps/frontend/src/components/layout/Layout.tsx @@ -5,6 +5,7 @@ import ContextFactory from "@/contexts/ContextFactory"; import { Button } from "../ui/button"; import type { FC, ReactNode } from "react"; import { Link } from "@tanstack/react-router"; +import { SignedIn, SignedOut, UserButton } from "@clerk/clerk-react"; interface Props { children: ReactNode; @@ -18,7 +19,7 @@ const Layout: FC = ({ children }) => ( -