diff --git a/frontend/drizzle/0025_clammy_dark_beast.sql b/frontend/drizzle/0025_clammy_dark_beast.sql new file mode 100644 index 0000000..44deb42 --- /dev/null +++ b/frontend/drizzle/0025_clammy_dark_beast.sql @@ -0,0 +1,8 @@ +ALTER TABLE "project" ALTER COLUMN "id" SET DATA TYPE text;--> statement-breakpoint +ALTER TABLE "project" ALTER COLUMN "user_id" SET DATA TYPE text;--> statement-breakpoint +ALTER TABLE "session" ALTER COLUMN "user_id" SET DATA TYPE text;--> statement-breakpoint +ALTER TABLE "shortener" ALTER COLUMN "id" SET DATA TYPE text;--> statement-breakpoint +ALTER TABLE "shortener" ALTER COLUMN "user_id" SET DATA TYPE text;--> statement-breakpoint +ALTER TABLE "shortener" ALTER COLUMN "project_id" SET DATA TYPE text;--> statement-breakpoint +ALTER TABLE "user" ALTER COLUMN "id" SET DATA TYPE text;--> statement-breakpoint +ALTER TABLE "visitor" ALTER COLUMN "shortener_id" SET DATA TYPE text; \ No newline at end of file diff --git a/frontend/drizzle/meta/0025_snapshot.json b/frontend/drizzle/meta/0025_snapshot.json new file mode 100644 index 0000000..5749b55 --- /dev/null +++ b/frontend/drizzle/meta/0025_snapshot.json @@ -0,0 +1,465 @@ +{ + "id": "10c8e2dc-3707-4281-913f-2c992280fdd0", + "prevId": "22a24e2f-3a4f-4867-bb4a-a391f6cde519", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.email_verification_token": { + "name": "email_verification_token", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.project": { + "name": "project", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "uuid": { + "name": "uuid", + "type": "uuid", + "primaryKey": false, + "notNull": false, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "qr_background": { + "name": "qr_background", + "type": "varchar(7)", + "primaryKey": false, + "notNull": true, + "default": "'#ffffff'" + }, + "qr_foreground": { + "name": "qr_foreground", + "type": "varchar(7)", + "primaryKey": false, + "notNull": true, + "default": "'#000000'" + }, + "domain_status": { + "name": "domain_status", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "'verified'" + }, + "enable_custom_domain": { + "name": "enable_custom_domain", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "custom_ip": { + "name": "custom_ip", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "custom_domain_id": { + "name": "custom_domain_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "custom_domain": { + "name": "custom_domain", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "qr_corner_square_style": { + "name": "qr_corner_square_style", + "type": "varchar", + "primaryKey": false, + "notNull": true, + "default": "'square'" + }, + "qr_dot_style": { + "name": "qr_dot_style", + "type": "varchar", + "primaryKey": false, + "notNull": true, + "default": "'square'" + }, + "qr_image_base64": { + "name": "qr_image_base64", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.shortener": { + "name": "shortener", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "link": { + "name": "link", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "ios": { + "name": "ios", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "ios_link": { + "name": "ios_link", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "android": { + "name": "android", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "android_link": { + "name": "android_link", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "code": { + "name": "code", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "active": { + "name": "active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "shortener_code_unique": { + "name": "shortener_code_unique", + "nullsNotDistinct": false, + "columns": [ + "code" + ] + } + } + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "uuid": { + "name": "uuid", + "type": "uuid", + "primaryKey": false, + "notNull": false, + "default": "gen_random_uuid()" + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "google_id": { + "name": "google_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "username": { + "name": "username", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "plan": { + "name": "plan", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "'free'" + }, + "stripe_customer_id": { + "name": "stripe_customer_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "qr_background": { + "name": "qr_background", + "type": "varchar(7)", + "primaryKey": false, + "notNull": true, + "default": "'#fff'" + }, + "qr_foreground": { + "name": "qr_foreground", + "type": "varchar(7)", + "primaryKey": false, + "notNull": true, + "default": "'#000'" + }, + "qr_corner_square_style": { + "name": "qr_corner_square_style", + "type": "varchar", + "primaryKey": false, + "notNull": true, + "default": "'square'" + }, + "qr_dot_style": { + "name": "qr_dot_style", + "type": "varchar", + "primaryKey": false, + "notNull": true, + "default": "'square'" + }, + "qr_image_base64": { + "name": "qr_image_base64", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + } + }, + "public.visitor": { + "name": "visitor", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "shortener_id": { + "name": "shortener_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "country_code": { + "name": "country_code", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "country": { + "name": "country", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "city": { + "name": "city", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "device_type": { + "name": "device_type", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "device_vendor": { + "name": "device_vendor", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "os": { + "name": "os", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "browser": { + "name": "browser", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "referer": { + "name": "referer", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "''" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/frontend/drizzle/meta/_journal.json b/frontend/drizzle/meta/_journal.json index 3c5ffac..019496c 100644 --- a/frontend/drizzle/meta/_journal.json +++ b/frontend/drizzle/meta/_journal.json @@ -176,6 +176,13 @@ "when": 1725806107053, "tag": "0024_fancy_dust", "breakpoints": true + }, + { + "idx": 25, + "version": "7", + "when": 1725888846230, + "tag": "0025_clammy_dark_beast", + "breakpoints": true } ] } \ No newline at end of file diff --git a/frontend/src/lib/components/ShortenerCard.svelte b/frontend/src/lib/components/ShortenerCard.svelte index d4cfa3a..bfd3b0e 100644 --- a/frontend/src/lib/components/ShortenerCard.svelte +++ b/frontend/src/lib/components/ShortenerCard.svelte @@ -35,7 +35,7 @@ let deleteDialogOpen = false let deleteShortenerCode = '' - const openDeleteDialog = (code: string | number) => { + const openDeleteDialog = (code: string) => { deleteShortenerCode = code deleteDialogOpen = true } diff --git a/frontend/src/lib/db/schema.ts b/frontend/src/lib/db/schema.ts index 007af44..4a344ad 100644 --- a/frontend/src/lib/db/schema.ts +++ b/frontend/src/lib/db/schema.ts @@ -3,7 +3,6 @@ import { serial, varchar, timestamp, - integer, uuid, boolean, text, @@ -11,7 +10,7 @@ import { import { relations } from 'drizzle-orm' export const user = pgTable('user', { - id: serial('id').primaryKey().notNull(), + id: text('id').primaryKey(), uuid: uuid('uuid').defaultRandom(), email_verified: boolean('email_verified').notNull().default(false), email: varchar('email', { length: 255 }).notNull().unique(), @@ -44,7 +43,7 @@ export const user = pgTable('user', { }) export const shortener = pgTable('shortener', { - id: serial('id').primaryKey().notNull(), + id: text('id').primaryKey(), link: varchar('link', { length: 255 }).notNull(), ios: boolean('ios').notNull().default(false), ios_link: varchar('ios_link', { length: 255 }), @@ -54,16 +53,16 @@ export const shortener = pgTable('shortener', { createdAt: timestamp('created_at', { mode: 'string' }) .defaultNow() .notNull(), - userId: integer('user_id').notNull(), + userId: text('user_id').notNull(), active: boolean('active').notNull().default(true), - projectId: integer('project_id'), + projectId: text('project_id'), }) export const project = pgTable('project', { - id: serial('id').primaryKey().notNull(), + id: text('id').primaryKey(), uuid: uuid('uuid').defaultRandom(), name: varchar('name', { length: 255 }).notNull(), - userId: integer('user_id').notNull(), + userId: text('user_id').notNull(), qr_background: varchar('qr_background', { length: 7 }) .default('#ffffff') .notNull(), @@ -95,7 +94,7 @@ export const project = pgTable('project', { export const visitor = pgTable('visitor', { id: serial('id').primaryKey().notNull(), - shortenerId: integer('shortener_id').notNull(), + shortenerId: text('shortener_id').notNull(), createdAt: timestamp('created_at', { mode: 'date' }) .defaultNow() .notNull(), @@ -117,7 +116,7 @@ export const visitor = pgTable('visitor', { export const session = pgTable('session', { id: varchar('id', { length: 255 }).primaryKey(), - userId: integer('user_id').notNull(), + userId: text('user_id').notNull(), expiresAt: timestamp('expires_at', { withTimezone: true, mode: 'date', @@ -128,7 +127,7 @@ export const emailVerificationToken = pgTable( 'email_verification_token', { id: varchar('id', { length: 255 }).primaryKey().notNull(), - userId: integer('user_id').notNull(), + userId: text('user_id').notNull(), email: varchar('email', { length: 255 }).notNull(), expiresAt: timestamp('expires_at', { withTimezone: true, diff --git a/frontend/src/lib/server/auth.ts b/frontend/src/lib/server/auth.ts index 0994823..916059e 100644 --- a/frontend/src/lib/server/auth.ts +++ b/frontend/src/lib/server/auth.ts @@ -9,7 +9,7 @@ import { env } from '$env/dynamic/private' declare module 'lucia' { interface Register { Lucia: typeof lucia - UserId: number + UserId: string DatabaseUserAttributes: DatabaseUserAttributes } } diff --git a/frontend/src/lib/server/email.ts b/frontend/src/lib/server/email.ts index 290829b..60987fb 100644 --- a/frontend/src/lib/server/email.ts +++ b/frontend/src/lib/server/email.ts @@ -36,7 +36,7 @@ export const sendEmailVerification = async ({ userId, email, }: { - userId: number + userId: string email: string }) => { await db diff --git a/frontend/src/routes/(app)/dashboard/links/+page.server.ts b/frontend/src/routes/(app)/dashboard/links/+page.server.ts index 72fbc0e..db92040 100644 --- a/frontend/src/routes/(app)/dashboard/links/+page.server.ts +++ b/frontend/src/routes/(app)/dashboard/links/+page.server.ts @@ -16,6 +16,7 @@ import { formSchema } from './schema' import type { Actions } from './$types' import { nanoid } from 'nanoid' import { isAlphanumeric } from '$lib/utils' +import { generateId } from 'lucia' export const load = (async (event) => { const user = event.locals.user @@ -44,7 +45,7 @@ export const load = (async (event) => { sortBy = 'latest' } - let project_id: number | undefined + let project_id: string | undefined let selected_project: { value: null | string; label: string } = { value: null, label: 'All', @@ -185,6 +186,7 @@ export const actions: Actions = { ? form.data.custom_code : nanoid(8) await db.insert(shortener).values({ + id: generateId(8), link: form.data.link, projectId: project?.id, userId: user.id, diff --git a/frontend/src/routes/(app)/dashboard/projects/+page.server.ts b/frontend/src/routes/(app)/dashboard/projects/+page.server.ts index b58cd35..bebec6f 100644 --- a/frontend/src/routes/(app)/dashboard/projects/+page.server.ts +++ b/frontend/src/routes/(app)/dashboard/projects/+page.server.ts @@ -5,6 +5,7 @@ import { zod } from 'sveltekit-superforms/adapters' import { formSchema } from './schema' import { fail, type Actions } from '@sveltejs/kit' import { project } from '$lib/db/schema' +import { generateId } from 'lucia' export const load = (async (event) => { const user = event.locals.user @@ -31,6 +32,7 @@ export const actions: Actions = { const user = event.locals.user await db.insert(project).values({ + id: generateId(8), name: form.data.name, userId: user.id, }) diff --git a/frontend/src/routes/(app)/dashboard/projects/[id]/(components)/ShortenerCard.svelte b/frontend/src/routes/(app)/dashboard/projects/[id]/(components)/ShortenerCard.svelte index 481f4d4..49f3366 100644 --- a/frontend/src/routes/(app)/dashboard/projects/[id]/(components)/ShortenerCard.svelte +++ b/frontend/src/routes/(app)/dashboard/projects/[id]/(components)/ShortenerCard.svelte @@ -31,7 +31,7 @@ let deleteDialogOpen = false let deleteShortenerCode = '' - const openDeleteDialog = (code: string | number) => { + const openDeleteDialog = (code: string) => { deleteShortenerCode = code deleteDialogOpen = true } diff --git a/frontend/src/routes/(app)/dashboard/projects/[id]/+page.server.ts b/frontend/src/routes/(app)/dashboard/projects/[id]/+page.server.ts index 55b99ab..ba50997 100644 --- a/frontend/src/routes/(app)/dashboard/projects/[id]/+page.server.ts +++ b/frontend/src/routes/(app)/dashboard/projects/[id]/+page.server.ts @@ -16,6 +16,7 @@ import { formSchema } from './schema' import type { Actions } from './$types' import { nanoid } from 'nanoid' import { isAlphanumeric } from '$lib/utils' +import { generateId } from 'lucia' export const load = (async (event) => { const { project: selectedProject } = await event.parent() @@ -155,6 +156,7 @@ export const actions: Actions = { ? form.data.custom_code : nanoid(8) await db.insert(shortener).values({ + id: generateId(8), link: form.data.link, projectId: project.id, userId: user.id, diff --git a/frontend/src/routes/(auth)/login/google/callback/+server.ts b/frontend/src/routes/(auth)/login/google/callback/+server.ts index 85ff945..47bcaaf 100644 --- a/frontend/src/routes/(auth)/login/google/callback/+server.ts +++ b/frontend/src/routes/(auth)/login/google/callback/+server.ts @@ -3,6 +3,7 @@ import { google, lucia } from '$lib/server/auth' import { db } from '$lib/db' import { user } from '$lib/db/schema' import { eq } from 'drizzle-orm' +import { generateId } from 'lucia' interface GoogleUser { sub: string // Unique identifier for the user @@ -93,6 +94,7 @@ export async function GET(event) { const insertUser = await db .insert(user) .values({ + id: generateId(8), email: googleUser.email, // Using email as username email_verified: true, googleId: googleUser.sub, diff --git a/frontend/src/routes/(auth)/signup/+page.server.ts b/frontend/src/routes/(auth)/signup/+page.server.ts index a7e7873..21e4d08 100644 --- a/frontend/src/routes/(auth)/signup/+page.server.ts +++ b/frontend/src/routes/(auth)/signup/+page.server.ts @@ -10,6 +10,7 @@ import { lucia } from '$lib/server/auth' import { env } from '$env/dynamic/private' import { sendEmailVerification } from '$lib/server/email' import * as argon2 from 'argon2' +import { generateId } from 'lucia' export const load = (async (event) => { return { @@ -52,6 +53,7 @@ export const actions: Actions = { const returnUsers = await db .insert(userSchema) .values({ + id: generateId(8), email: form.data.email, password: hashedPassword, }) diff --git a/frontend/src/routes/api/shortener/+server.ts b/frontend/src/routes/api/shortener/+server.ts index bb6e768..8c47f9f 100644 --- a/frontend/src/routes/api/shortener/+server.ts +++ b/frontend/src/routes/api/shortener/+server.ts @@ -3,6 +3,7 @@ import type { RequestHandler } from './$types' import { db } from '$lib/db' import { shortener } from '$lib/db/schema' import { nanoid } from 'nanoid' +import { generateId } from 'lucia' export const GET: RequestHandler = async () => { return new Response() @@ -10,7 +11,7 @@ export const GET: RequestHandler = async () => { const shortenerInsertSchema = z.object({ link: z.string().url('Link must be in url format'), - projectId: z.number().nullish(), + projectId: z.string().nullish(), }) export const POST: RequestHandler = async (event) => { @@ -32,6 +33,7 @@ export const POST: RequestHandler = async (event) => { const code = nanoid(8) await db.insert(shortener).values({ + id: generateId(8), link: shortenerInsert.data.link, projectId: shortenerInsert.data.projectId, userId: user.id, diff --git a/frontend/src/routes/api/shortener/[id]/+server.ts b/frontend/src/routes/api/shortener/[id]/+server.ts index 8f1bbd4..577a8c9 100644 --- a/frontend/src/routes/api/shortener/[id]/+server.ts +++ b/frontend/src/routes/api/shortener/[id]/+server.ts @@ -31,7 +31,7 @@ export const GET: RequestHandler = async (event) => { const updateShortenerSchema = z.object({ link: z.string().url(), - projectId: z.number().nullish(), + projectId: z.string().nullish(), active: z.boolean(), }) @@ -71,7 +71,7 @@ export const PUT: RequestHandler = async (event) => { export const DELETE: RequestHandler = async (event) => { const shortenerId = event.params.id - const id = z.coerce.number().positive().safeParse(shortenerId) + const id = z.string().safeParse(shortenerId) if (!id.success) { return new Response( diff --git a/redirect/db/models.go b/redirect/db/models.go index 232d06a..b009038 100644 --- a/redirect/db/models.go +++ b/redirect/db/models.go @@ -16,10 +16,10 @@ type EmailVerificationToken struct { } type Project struct { - ID int32 + ID string Uuid pgtype.UUID Name string - UserID int32 + UserID string QrBackground string QrForeground string CustomDomain pgtype.Text @@ -34,17 +34,17 @@ type Project struct { type Session struct { ID string - UserID int32 + UserID string ExpiresAt pgtype.Timestamptz } type Shortener struct { - ID int32 + ID string Link string Code string CreatedAt pgtype.Timestamp - UserID int32 - ProjectID pgtype.Int4 + UserID string + ProjectID pgtype.Text Active bool Ios bool IosLink pgtype.Text @@ -53,7 +53,7 @@ type Shortener struct { } type User struct { - ID int32 + ID string Uuid pgtype.UUID Email string Username pgtype.Text @@ -72,7 +72,7 @@ type User struct { type Visitor struct { ID int32 - ShortenerID int32 + ShortenerID string CreatedAt pgtype.Timestamp CountryCode string Country string diff --git a/redirect/db/query.sql.go b/redirect/db/query.sql.go index 97f2d6a..cdd0142 100644 --- a/redirect/db/query.sql.go +++ b/redirect/db/query.sql.go @@ -27,7 +27,7 @@ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) ` type CreateVisitorParams struct { - ShortenerID int32 + ShortenerID string DeviceType string DeviceVendor string Browser string @@ -96,12 +96,12 @@ type GetShortenerWithDomainParams struct { } type GetShortenerWithDomainRow struct { - ID int32 + ID string Link string Code string CreatedAt pgtype.Timestamp - UserID int32 - ProjectID pgtype.Int4 + UserID string + ProjectID pgtype.Text Active bool Ios bool IosLink pgtype.Text diff --git a/redirect/main.go b/redirect/main.go index 7ac0e35..a942af9 100644 --- a/redirect/main.go +++ b/redirect/main.go @@ -117,7 +117,7 @@ func main() { uastrings := c.GetReqHeaders()["User-Agent"] redirecturl := "" - var shortenerId int32 + var shortenerId string iosEnabled := false iosRedirectUrl := "" androidEnabled := false