diff --git a/.env.example b/.env.example index 7e795bb..1540ef0 100644 --- a/.env.example +++ b/.env.example @@ -1,3 +1,4 @@ PORT=3000 PROTOCOL_HEADER=x-forwarded-proto -HOST_HEADER=x-forwarded-host \ No newline at end of file +HOST_HEADER=x-forwarded-host +DATABASE_URL=postgres://postgres:password@127.0.0.1:5432/link-shortener \ No newline at end of file diff --git a/bun.lockb b/bun.lockb index 08a94da..ea5762a 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/drizzle.config.ts b/drizzle.config.ts new file mode 100644 index 0000000..bd01fe9 --- /dev/null +++ b/drizzle.config.ts @@ -0,0 +1,15 @@ +// drizzle.config.ts +import type { Config } from 'drizzle-kit'; + +export default { + schema: './src/lib/db/schema.ts', + out: './drizzle', + driver: 'pg', + dbCredentials: { + user: 'postgres', + password: 'password', + host: '0.0.0.0', + port: 5432, + database: 'link-shortener', + }, +} satisfies Config; diff --git a/drizzle/0000_bored_eddie_brock.sql b/drizzle/0000_bored_eddie_brock.sql new file mode 100644 index 0000000..41f9e58 --- /dev/null +++ b/drizzle/0000_bored_eddie_brock.sql @@ -0,0 +1,23 @@ +CREATE TABLE IF NOT EXISTS "shortener" ( + "id" serial PRIMARY KEY NOT NULL, + "link" varchar(255) NOT NULL, + "code" varchar(255) NOT NULL, + "created_at" timestamp DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "user" ( + "id" serial PRIMARY KEY NOT NULL, + "uuid" text NOT NULL, + "email" varchar(255) NOT NULL, + "username" varchar(255) NOT NULL, + "password" varchar(255) NOT NULL, + "created_at" timestamp DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "visitor" ( + "id" serial PRIMARY KEY NOT NULL, + "shortener_id" integer NOT NULL, + "created_at" timestamp DEFAULT now() NOT NULL, + "country_code" varchar(255) NOT NULL, + "country" varchar(255) NOT NULL +); diff --git a/drizzle/0001_sudden_nightmare.sql b/drizzle/0001_sudden_nightmare.sql new file mode 100644 index 0000000..a3ad0df --- /dev/null +++ b/drizzle/0001_sudden_nightmare.sql @@ -0,0 +1,7 @@ +CREATE TABLE IF NOT EXISTS "session" ( + "token" varchar(255) NOT NULL, + "user_id" integer NOT NULL, + "expires" timestamp NOT NULL +); +--> statement-breakpoint +ALTER TABLE "user" ADD CONSTRAINT "user_email_unique" UNIQUE("email"); \ No newline at end of file diff --git a/drizzle/meta/0000_snapshot.json b/drizzle/meta/0000_snapshot.json new file mode 100644 index 0000000..2915846 --- /dev/null +++ b/drizzle/meta/0000_snapshot.json @@ -0,0 +1,138 @@ +{ + "id": "1098bf22-b657-4c36-b85c-51f1a8f6e36d", + "prevId": "00000000-0000-0000-0000-000000000000", + "version": "5", + "dialect": "pg", + "tables": { + "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()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "uuid": { + "name": "uuid", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "username": { + "name": "username", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "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": {} + }, + "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 + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + } + }, + "enums": {}, + "schemas": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/0001_snapshot.json b/drizzle/meta/0001_snapshot.json new file mode 100644 index 0000000..ea5252d --- /dev/null +++ b/drizzle/meta/0001_snapshot.json @@ -0,0 +1,174 @@ +{ + "id": "7c7bf598-99e4-405b-a089-065b8e8226fa", + "prevId": "1098bf22-b657-4c36-b85c-51f1a8f6e36d", + "version": "5", + "dialect": "pg", + "tables": { + "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()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "uuid": { + "name": "uuid", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "username": { + "name": "username", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "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 + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + } + }, + "enums": {}, + "schemas": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json new file mode 100644 index 0000000..996435a --- /dev/null +++ b/drizzle/meta/_journal.json @@ -0,0 +1,20 @@ +{ + "version": "5", + "dialect": "pg", + "entries": [ + { + "idx": 0, + "version": "5", + "when": 1699648628603, + "tag": "0000_bored_eddie_brock", + "breakpoints": true + }, + { + "idx": 1, + "version": "5", + "when": 1699649369484, + "tag": "0001_sudden_nightmare", + "breakpoints": true + } + ] +} \ No newline at end of file diff --git a/package.json b/package.json index 5da5c26..9729ade 100644 --- a/package.json +++ b/package.json @@ -1,38 +1,41 @@ { - "name": "link-shortener-svelte", - "version": "0.0.1", - "private": true, - "scripts": { - "dev": "vite dev", - "build": "vite build", - "preview": "vite preview", - "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", - "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", - "lint": "prettier --plugin-search-dir . --check .", - "format": "prettier --plugin-search-dir . --write ." - }, - "devDependencies": { - "@sveltejs/kit": "^1.20.4", - "autoprefixer": "^10.4.14", - "postcss": "^8.4.24", - "postcss-load-config": "^4.0.1", - "prettier": "^2.8.0", - "prettier-plugin-svelte": "^2.10.1", - "svelte": "^4.0.5", - "svelte-adapter-bun": "^0.5.1", - "svelte-check": "^3.4.3", - "tailwindcss": "^3.3.2", - "tslib": "^2.4.1", - "typescript": "^5.0.0", - "vite": "^4.4.2" - }, - "type": "module", - "dependencies": { - "bits-ui": "^0.9.1", - "clsx": "^2.0.0", - "lucide-svelte": "^0.292.0", - "mode-watcher": "^0.0.7", - "tailwind-merge": "^2.0.0", - "tailwind-variants": "^0.1.18" - } + "name": "link-shortener-svelte", + "version": "0.0.1", + "private": true, + "scripts": { + "dev": "vite dev", + "build": "vite build", + "preview": "vite preview", + "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", + "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", + "lint": "prettier --plugin-search-dir . --check .", + "format": "prettier --plugin-search-dir . --write ." + }, + "devDependencies": { + "@sveltejs/kit": "^1.20.4", + "autoprefixer": "^10.4.14", + "drizzle-kit": "^0.20.1", + "postcss": "^8.4.24", + "postcss-load-config": "^4.0.1", + "prettier": "^2.8.0", + "prettier-plugin-svelte": "^2.10.1", + "svelte": "^4.0.5", + "svelte-adapter-bun": "^0.5.1", + "svelte-check": "^3.4.3", + "tailwindcss": "^3.3.2", + "tslib": "^2.4.1", + "typescript": "^5.0.0", + "vite": "^4.4.2" + }, + "type": "module", + "dependencies": { + "bits-ui": "^0.9.1", + "clsx": "^2.0.0", + "drizzle-orm": "^0.29.0", + "lucide-svelte": "^0.292.0", + "mode-watcher": "^0.0.7", + "postgres": "^3.4.3", + "tailwind-merge": "^2.0.0", + "tailwind-variants": "^0.1.18" + } } diff --git a/src/lib/db/index.ts b/src/lib/db/index.ts new file mode 100644 index 0000000..64d4ed2 --- /dev/null +++ b/src/lib/db/index.ts @@ -0,0 +1,9 @@ +import { drizzle } from 'drizzle-orm/postgres-js'; +import postgres from 'postgres'; +import * as schema from './schema'; + +const client = postgres( + process.env.DATABASE_URL ?? + 'postgres://postgres:password@127.0.0.1:5432/link-shortener', +); +export const db = drizzle(client, { schema }); diff --git a/src/lib/db/schema.ts b/src/lib/db/schema.ts new file mode 100644 index 0000000..80f18f8 --- /dev/null +++ b/src/lib/db/schema.ts @@ -0,0 +1,47 @@ +import { + pgTable, + serial, + varchar, + timestamp, + text, + integer, +} from 'drizzle-orm/pg-core'; + +import { relations } from 'drizzle-orm'; + +export const shortener = pgTable('shortener', { + id: serial('id').primaryKey().notNull(), + link: varchar('link', { length: 255 }).notNull(), + code: varchar('code', { length: 255 }).notNull(), + createdAt: timestamp('created_at', { mode: 'string' }).defaultNow().notNull(), +}); + +export const user = pgTable('user', { + id: serial('id').primaryKey().notNull(), + uuid: text('uuid').notNull(), + email: varchar('email', { length: 255 }).notNull().unique(), + username: varchar('username', { length: 255 }).notNull(), + password: varchar('password', { length: 255 }).notNull(), + createdAt: timestamp('created_at', { mode: 'string' }).defaultNow().notNull(), +}); + +export const visitor = pgTable('visitor', { + id: serial('id').primaryKey().notNull(), + shortenerId: integer('shortener_id').notNull(), + createdAt: timestamp('created_at', { mode: 'string' }).defaultNow().notNull(), + countryCode: varchar('country_code', { length: 255 }).notNull(), + country: varchar('country', { length: 255 }).notNull(), +}); + +export const session = pgTable('session', { + token: varchar('token', { length: 255 }).notNull(), + userId: integer('user_id').notNull(), + expiresAt: timestamp('expires', { mode: 'date' }).notNull(), +}); + +export const sessionRelations = relations(session, ({ one }) => ({ + user: one(user, { + fields: [session.userId], + references: [user.id], + }), +}));