diff --git a/frontend/drizzle/0003_glorious_norrin_radd.sql b/frontend/drizzle/0003_glorious_norrin_radd.sql new file mode 100644 index 0000000..f41d1ba --- /dev/null +++ b/frontend/drizzle/0003_glorious_norrin_radd.sql @@ -0,0 +1,8 @@ +CREATE TABLE IF NOT EXISTS "project" ( + "id" serial PRIMARY KEY NOT NULL, + "uuid" uuid DEFAULT gen_random_uuid(), + "name" varchar(255) NOT NULL, + "user_id" integer NOT NULL +); +--> statement-breakpoint +ALTER TABLE "shortener" ADD COLUMN "project_id" integer; \ No newline at end of file diff --git a/frontend/drizzle/meta/0003_snapshot.json b/frontend/drizzle/meta/0003_snapshot.json new file mode 100644 index 0000000..0d497f2 --- /dev/null +++ b/frontend/drizzle/meta/0003_snapshot.json @@ -0,0 +1,228 @@ +{ + "id": "9a3733db-b0ef-4744-b0bf-f69e9b03917c", + "prevId": "8f8ae49c-43bf-4e07-a30d-383e2b767524", + "version": "5", + "dialect": "pg", + "tables": { + "project": { + "name": "project", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "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": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "session": { + "name": "session", + "schema": "", + "columns": { + "token": { + "name": "token", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "expires": { + "name": "expires", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "shortener": { + "name": "shortener", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "link": { + "name": "link", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "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": "integer", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "uuid": { + "name": "uuid", + "type": "uuid", + "primaryKey": false, + "notNull": false, + "default": "gen_random_uuid()" + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "username": { + "name": "username", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + } + }, + "visitor": { + "name": "visitor", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "shortener_id": { + "name": "shortener_id", + "type": "integer", + "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 + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + } + }, + "enums": {}, + "schemas": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + } +} \ No newline at end of file diff --git a/frontend/drizzle/meta/_journal.json b/frontend/drizzle/meta/_journal.json index 638942c..a5c087d 100644 --- a/frontend/drizzle/meta/_journal.json +++ b/frontend/drizzle/meta/_journal.json @@ -22,6 +22,13 @@ "when": 1700882455122, "tag": "0002_robust_the_executioner", "breakpoints": true + }, + { + "idx": 3, + "version": "5", + "when": 1701590526323, + "tag": "0003_glorious_norrin_radd", + "breakpoints": true } ] } \ No newline at end of file diff --git a/frontend/src/lib/db/schema.ts b/frontend/src/lib/db/schema.ts index f3c4832..2a8a2a3 100644 --- a/frontend/src/lib/db/schema.ts +++ b/frontend/src/lib/db/schema.ts @@ -17,6 +17,7 @@ export const shortener = pgTable('shortener', { .defaultNow() .notNull(), userId: integer('user_id').notNull(), + projectId: integer('project_id'), }) export const shortenerRelations = relations( @@ -26,10 +27,32 @@ export const shortenerRelations = relations( fields: [shortener.userId], references: [user.id], }), + project: one(project, { + fields: [shortener.projectId], + references: [project.id], + }), visitor: many(visitor), }), ) +export const project = pgTable('project', { + id: serial('id').primaryKey().notNull(), + uuid: uuid('uuid').defaultRandom(), + name: varchar('name', { length: 255 }).notNull(), + userId: integer('user_id').notNull(), +}) + +export const projectRelations = relations( + project, + ({ one, many }) => ({ + user: one(user, { + fields: [project.userId], + references: [user.id], + }), + shortener: many(shortener), + }), +) + export const user = pgTable('user', { id: serial('id').primaryKey().notNull(), uuid: uuid('uuid').defaultRandom(), diff --git a/frontend/src/routes/(app)/links/+page.server.ts b/frontend/src/routes/(app)/links/+page.server.ts index ab6c881..c793421 100644 --- a/frontend/src/routes/(app)/links/+page.server.ts +++ b/frontend/src/routes/(app)/links/+page.server.ts @@ -8,7 +8,8 @@ export const load = (async (event) => { with: { visitor: true, }, - where: (shortener, { eq }) => eq(shortener.userId, user.id), + where: (shortener, { eq, and, isNull }) => + and(eq(shortener.userId, user.id), isNull(shortener.projectId)), }) return { shorteners } diff --git a/frontend/src/routes/(app)/projects/+page.server.ts b/frontend/src/routes/(app)/projects/+page.server.ts new file mode 100644 index 0000000..edf2f42 --- /dev/null +++ b/frontend/src/routes/(app)/projects/+page.server.ts @@ -0,0 +1,15 @@ +import { db } from '$lib/db' +import type { PageServerLoad } from './$types' + +export const load = (async (event) => { + const user = event.locals.userObject + + const projects = await db.query.project.findMany({ + with: { + shortener: true, + }, + where: (project, { eq }) => eq(project.userId, user.id), + }) + + return { projects } +}) satisfies PageServerLoad diff --git a/frontend/src/routes/(app)/projects/+page.svelte b/frontend/src/routes/(app)/projects/+page.svelte new file mode 100644 index 0000000..ccc4144 --- /dev/null +++ b/frontend/src/routes/(app)/projects/+page.svelte @@ -0,0 +1,109 @@ + + +
+
Projects
+ + + + Add Project + + + + Add Project + + Create A New Project Here. Click Add To Create. + + +
+
+ + +
+
+ + + +
+
+
+ + +{#if data.projects.length > 0} +
+ {#each data.projects as project} + + + + + {project.name} + + + +
+ +
+
+
+
+ {/each} +
+{:else} +
No Data
+{/if} diff --git a/frontend/src/routes/(app)/projects/[uuid]/+page.server.ts b/frontend/src/routes/(app)/projects/[uuid]/+page.server.ts new file mode 100644 index 0000000..99a5cc6 --- /dev/null +++ b/frontend/src/routes/(app)/projects/[uuid]/+page.server.ts @@ -0,0 +1,34 @@ +import { db } from '$lib/db' +import { redirect } from '@sveltejs/kit' +import type { PageServerLoad } from './$types' + +export const load = (async (event) => { + const uuid = event.params.uuid + + const user = event.locals.userObject + + try { + const project = await db.query.project.findFirst({ + where: (project, { eq }) => eq(project.uuid, uuid), + }) + + if (!project) { + throw redirect(303, '/projects') + } + + const shorteners = await db.query.shortener.findMany({ + with: { + visitor: true, + }, + where: (shortener, { eq, and }) => + and( + eq(shortener.userId, user.id), + eq(shortener.projectId, project.id), + ), + }) + + return { project, shorteners } + } catch (error) { + throw redirect(303, '/projects') + } +}) satisfies PageServerLoad diff --git a/frontend/src/routes/(app)/projects/[uuid]/+page.svelte b/frontend/src/routes/(app)/projects/[uuid]/+page.svelte new file mode 100644 index 0000000..3cce6cc --- /dev/null +++ b/frontend/src/routes/(app)/projects/[uuid]/+page.svelte @@ -0,0 +1,200 @@ + + +
+
Links
+ + + + Add Shortner + + + + Add Shortener + + Create A New Shortner Here. Click Add To Save. + + +
+
+ + +
+
+ + + +
+
+
+ + +{#if data.shorteners.length > 0} +
+ {#each data.shorteners as shortener} + + + + + {data.shortener_url + '/' + shortener.code} + + + + {shortener.link} + + +
+
+ +
+ + + + + + + + openEditDialog(shortener.code, shortener.link)}> + Edit + + deleteShortener(shortener.code)} + class="text-destructive data-[highlighted]:bg-destructive"> + Delete + + + + +
+
+
+ {/each} +
+{:else} +
No Data
+{/if} + + + + + Edit Shortener {editShortenerCode} + + Edit Shortner Here. Click Save To Update. + + +
+
+ + +
+
+ + + +
+
diff --git a/frontend/src/routes/api/project/+server.ts b/frontend/src/routes/api/project/+server.ts new file mode 100644 index 0000000..fdedaef --- /dev/null +++ b/frontend/src/routes/api/project/+server.ts @@ -0,0 +1,36 @@ +import { z } from 'zod' +import type { RequestHandler } from './$types' +import { db } from '$lib/db' +import { project } from '$lib/db/schema' + +export const GET: RequestHandler = async () => { + return new Response() +} + +const projectInsertSchema = z.object({ + name: z.string(), +}) + +export const POST: RequestHandler = async (event) => { + const body = await event.request.json() + + const projectInsert = projectInsertSchema.safeParse(body) + + if (!projectInsert.success) { + return new Response( + JSON.stringify({ + success: false, + message: 'Invalid Data', + }), + ) + } + + const user = event.locals.userObject + + await db.insert(project).values({ + name: projectInsert.data.name, + userId: user.id, + }) + + return new Response(JSON.stringify({ success: true })) +}