diff --git a/migrations/0001_glamorous_thunderbird.sql b/migrations/0000_hesitant_kylun.sql similarity index 50% rename from migrations/0001_glamorous_thunderbird.sql rename to migrations/0000_hesitant_kylun.sql index e7a22c4ef1..2c76dcc0fd 100644 --- a/migrations/0001_glamorous_thunderbird.sql +++ b/migrations/0000_hesitant_kylun.sql @@ -1,5 +1,5 @@ -CREATE TABLE "event" ( - "id" text PRIMARY KEY NOT NULL, +CREATE TABLE "events" ( + "id" integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY (sequence name "events_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 2147483647 START WITH 1 CACHE 1), "type" text NOT NULL, "timestamp" bigint NOT NULL, "properties" json NOT NULL, diff --git a/migrations/0000_misty_leo.sql b/migrations/0000_misty_leo.sql deleted file mode 100644 index 8854258552..0000000000 --- a/migrations/0000_misty_leo.sql +++ /dev/null @@ -1,12 +0,0 @@ -CREATE TABLE "organization" ( - "id" text PRIMARY KEY NOT NULL, - "stripe_customer_id" text, - "stripe_subscription_id" text, - "stripe_subscription_price_id" text, - "stripe_subscription_status" text, - "stripe_subscription_current_period_end" bigint, - "updated_at" timestamp DEFAULT now() NOT NULL, - "created_at" timestamp DEFAULT now() NOT NULL -); ---> statement-breakpoint -CREATE UNIQUE INDEX "stripe_customer_id_idx" ON "organization" USING btree ("stripe_customer_id"); \ No newline at end of file diff --git a/migrations/meta/0000_snapshot.json b/migrations/meta/0000_snapshot.json index 8d2c1bea6f..4ebacc9d10 100644 --- a/migrations/meta/0000_snapshot.json +++ b/migrations/meta/0000_snapshot.json @@ -1,55 +1,47 @@ { - "id": "013accf9-070a-47cd-9b49-1318a1c8a237", + "id": "5ab333f5-a980-4564-a919-3d15608505d0", "prevId": "00000000-0000-0000-0000-000000000000", "version": "7", "dialect": "postgresql", "tables": { - "public.organization": { - "name": "organization", + "public.events": { + "name": "events", "schema": "", "columns": { "id": { "name": "id", - "type": "text", + "type": "integer", "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "events_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, "notNull": true }, - "stripe_customer_id": { - "name": "stripe_customer_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "stripe_subscription_id": { - "name": "stripe_subscription_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "stripe_subscription_price_id": { - "name": "stripe_subscription_price_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "stripe_subscription_status": { - "name": "stripe_subscription_status", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "stripe_subscription_current_period_end": { - "name": "stripe_subscription_current_period_end", + "timestamp": { + "name": "timestamp", "type": "bigint", "primaryKey": false, - "notNull": false + "notNull": true }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", + "properties": { + "name": "properties", + "type": "json", "primaryKey": false, - "notNull": true, - "default": "now()" + "notNull": true }, "created_at": { "name": "created_at", @@ -57,25 +49,16 @@ "primaryKey": false, "notNull": true, "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" } }, - "indexes": { - "stripe_customer_id_idx": { - "name": "stripe_customer_id_idx", - "columns": [ - { - "expression": "stripe_customer_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - } - }, + "indexes": {}, "foreignKeys": {}, "compositePrimaryKeys": {}, "uniqueConstraints": {}, diff --git a/migrations/meta/0001_snapshot.json b/migrations/meta/0001_snapshot.json deleted file mode 100644 index 6383fb94f8..0000000000 --- a/migrations/meta/0001_snapshot.json +++ /dev/null @@ -1,149 +0,0 @@ -{ - "id": "37608d29-a721-43ec-91e6-c4a356d23b3e", - "prevId": "013accf9-070a-47cd-9b49-1318a1c8a237", - "version": "7", - "dialect": "postgresql", - "tables": { - "public.event": { - "name": "event", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "type": { - "name": "type", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "timestamp": { - "name": "timestamp", - "type": "bigint", - "primaryKey": false, - "notNull": true - }, - "properties": { - "name": "properties", - "type": "json", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.organization": { - "name": "organization", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "stripe_customer_id": { - "name": "stripe_customer_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "stripe_subscription_id": { - "name": "stripe_subscription_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "stripe_subscription_price_id": { - "name": "stripe_subscription_price_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "stripe_subscription_status": { - "name": "stripe_subscription_status", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "stripe_subscription_current_period_end": { - "name": "stripe_subscription_current_period_end", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": { - "stripe_customer_id_idx": { - "name": "stripe_customer_id_idx", - "columns": [ - { - "expression": "stripe_customer_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - } - }, - "enums": {}, - "schemas": {}, - "sequences": {}, - "roles": {}, - "policies": {}, - "views": {}, - "_meta": { - "columns": {}, - "schemas": {}, - "tables": {} - } -} diff --git a/migrations/meta/_journal.json b/migrations/meta/_journal.json index 35730d6981..11751f2659 100644 --- a/migrations/meta/_journal.json +++ b/migrations/meta/_journal.json @@ -5,15 +5,8 @@ { "idx": 0, "version": "7", - "when": 1747031111132, - "tag": "0000_misty_leo", - "breakpoints": true - }, - { - "idx": 1, - "version": "7", - "when": 1747203099634, - "tag": "0001_glamorous_thunderbird", + "when": 1747204609794, + "tag": "0000_hesitant_kylun", "breakpoints": true } ] diff --git a/src/app/api/events/route.ts b/src/app/api/events/route.ts index 30fcee7f07..e1d5964a8c 100644 --- a/src/app/api/events/route.ts +++ b/src/app/api/events/route.ts @@ -1,6 +1,10 @@ import { auth } from '@clerk/nextjs/server'; import { NextRequest, NextResponse } from 'next/server'; +import { eventSchema } from '@/schemas'; +import { db } from '@/db'; +import { eventsTable } from '@/db/schema'; + export async function POST(request: NextRequest) { const { userId } = await auth(); @@ -11,7 +15,14 @@ export async function POST(request: NextRequest) { ); } - console.log(await request.json()); + const payload = await request.json(); + const result = eventSchema.safeParse(payload); - return NextResponse.json({ success: true }); + if (!result.success) { + return NextResponse.json({ success: false }); + } + + const [record] = await db.insert(eventsTable).values(result.data).returning(); + + return NextResponse.json({ success: true, id: record?.id }); } diff --git a/src/db/schema.ts b/src/db/schema.ts index 06f42c482c..d600638eed 100644 --- a/src/db/schema.ts +++ b/src/db/schema.ts @@ -3,33 +3,33 @@ import { pgTable, text, timestamp, - uniqueIndex, json, + integer, } from 'drizzle-orm/pg-core'; -export const organizationSchema = pgTable( - 'organization', - { - id: text('id').primaryKey(), - stripeCustomerId: text('stripe_customer_id'), - stripeSubscriptionId: text('stripe_subscription_id'), - stripeSubscriptionPriceId: text('stripe_subscription_price_id'), - stripeSubscriptionStatus: text('stripe_subscription_status'), - stripeSubscriptionCurrentPeriodEnd: bigint( - 'stripe_subscription_current_period_end', - { mode: 'number' }, - ), - updatedAt: timestamp('updated_at', { mode: 'date' }) - .defaultNow() - .$onUpdate(() => new Date()) - .notNull(), - createdAt: timestamp('created_at', { mode: 'date' }).defaultNow().notNull(), - }, - (table) => [uniqueIndex('stripe_customer_id_idx').on(table.stripeCustomerId)], -); +// export const organizationTable = pgTable( +// 'organizations', +// { +// id: integer().primaryKey().generatedAlwaysAsIdentity(), +// stripeCustomerId: text('stripe_customer_id'), +// stripeSubscriptionId: text('stripe_subscription_id'), +// stripeSubscriptionPriceId: text('stripe_subscription_price_id'), +// stripeSubscriptionStatus: text('stripe_subscription_status'), +// stripeSubscriptionCurrentPeriodEnd: bigint( +// 'stripe_subscription_current_period_end', +// { mode: 'number' }, +// ), +// updatedAt: timestamp('updated_at', { mode: 'date' }) +// .defaultNow() +// .$onUpdate(() => new Date()) +// .notNull(), +// createdAt: timestamp('created_at', { mode: 'date' }).defaultNow().notNull(), +// }, +// (table) => [uniqueIndex('stripe_customer_id_idx').on(table.stripeCustomerId)], +// ); -export const eventSchema = pgTable('event', { - id: text('id').primaryKey(), +export const eventsTable = pgTable('events', { + id: integer().primaryKey().generatedAlwaysAsIdentity(), type: text('type').notNull(), timestamp: bigint('timestamp', { mode: 'number' }).notNull(), properties: json('properties').notNull(), diff --git a/src/schemas/index.ts b/src/schemas/index.ts new file mode 100644 index 0000000000..27671fd376 --- /dev/null +++ b/src/schemas/index.ts @@ -0,0 +1,53 @@ +import { z } from 'zod'; + +export const providerNames = [ + 'anthropic', + 'glama', + 'openrouter', + 'bedrock', + 'vertex', + 'openai', + 'ollama', + 'vscode-lm', + 'lmstudio', + 'gemini', + 'openai-native', + 'mistral', + 'deepseek', + 'unbound', + 'requesty', + 'human-relay', + 'fake-ai', + 'xai', + 'groq', + 'chutes', + 'litellm', +] as const; + +export const eventSchema = z.discriminatedUnion('type', [ + z.object({ + type: z.literal('task_created'), + timestamp: z.number(), + properties: z.object({ + taskId: z.string(), + provider: z.enum(providerNames), + modelId: z.string(), + prompt: z.string(), + mode: z.string(), + }), + }), + z.object({ + type: z.literal('completion'), + timestamp: z.number(), + properties: z.object({ + taskId: z.string(), + provider: z.enum(providerNames), + modelId: z.string(), + inputTokens: z.number(), + outputTokens: z.number(), + cost: z.number(), + }), + }), +]); + +export type Event = z.infer;