Implementr form

This commit is contained in:
2025-06-26 13:46:32 +02:00
parent 711fb34021
commit da0524a4fc
12 changed files with 903 additions and 4 deletions

37
src/lib/actions/url.ts Normal file
View File

@@ -0,0 +1,37 @@
"use server"
import { getSession } from "../auth/session"
import { insertUrl } from "../db/urls"
import { urlFormSchema } from "../schema/url"
type Response = {
error: boolean
message: string
}
export async function addUrl(unsafeData: unknown): Promise<Response> {
const { session } = await getSession()
if (!session) {
return {
error: true,
message: "You must be logged in to create a short link."
}
}
const { error, data } = urlFormSchema.safeParse(unsafeData)
if (error) {
return {
error: true,
message: "Error parsing form data."
}
}
await insertUrl(data)
return {
error: false,
message: "Short link created successfully!"
}
}

15
src/lib/db/urls.ts Normal file
View File

@@ -0,0 +1,15 @@
import { eq } from "drizzle-orm";
import { db } from "../drizzle/db";
import { urls } from "../drizzle/schema";
export function insertUrl(data: typeof urls.$inferInsert) {
return db.insert(urls).values(data)
}
export function updateUrl(id: string, data: Omit<Partial<typeof urls.$inferInsert>, "id">) {
return db.update(urls).set(data).where(eq(urls.id, id))
}
export function deleteUrl(id: string) {
return db.delete(urls).where(eq(urls.id, id))
}

View File

@@ -0,0 +1,2 @@
ALTER TABLE "urls" ALTER COLUMN "id" SET DEFAULT gen_random_uuid();--> statement-breakpoint
ALTER TABLE "urls" ALTER COLUMN "title" DROP NOT NULL;

View File

@@ -0,0 +1,532 @@
{
"id": "3be7b31f-8b1b-457a-839a-2d490f48e833",
"prevId": "79b6d1a7-2d90-461c-bd57-51a42fc17b23",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.account": {
"name": "account",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"account_id": {
"name": "account_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"provider_id": {
"name": "provider_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"user_id": {
"name": "user_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"access_token": {
"name": "access_token",
"type": "text",
"primaryKey": false,
"notNull": false
},
"refresh_token": {
"name": "refresh_token",
"type": "text",
"primaryKey": false,
"notNull": false
},
"id_token": {
"name": "id_token",
"type": "text",
"primaryKey": false,
"notNull": false
},
"access_token_expires_at": {
"name": "access_token_expires_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false
},
"refresh_token_expires_at": {
"name": "refresh_token_expires_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false
},
"scope": {
"name": "scope",
"type": "text",
"primaryKey": false,
"notNull": false
},
"password": {
"name": "password",
"type": "text",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {
"account_user_id_user_id_fk": {
"name": "account_user_id_user_id_fk",
"tableFrom": "account",
"tableTo": "user",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.session": {
"name": "session",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"expires_at": {
"name": "expires_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true
},
"token": {
"name": "token",
"type": "text",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true
},
"ip_address": {
"name": "ip_address",
"type": "text",
"primaryKey": false,
"notNull": false
},
"user_agent": {
"name": "user_agent",
"type": "text",
"primaryKey": false,
"notNull": false
},
"user_id": {
"name": "user_id",
"type": "text",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {
"session_user_id_user_id_fk": {
"name": "session_user_id_user_id_fk",
"tableFrom": "session",
"tableTo": "user",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"session_token_unique": {
"name": "session_token_unique",
"nullsNotDistinct": false,
"columns": [
"token"
]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.urls": {
"name": "urls",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"url": {
"name": "url",
"type": "varchar",
"primaryKey": false,
"notNull": true
},
"slug": {
"name": "slug",
"type": "varchar",
"primaryKey": false,
"notNull": true
},
"title": {
"name": "title",
"type": "varchar",
"primaryKey": false,
"notNull": false
},
"max_visits": {
"name": "max_visits",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"exp_date": {
"name": "exp_date",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"forward_query_params": {
"name": "forward_query_params",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": true
},
"crawable": {
"name": "crawable",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": false
},
"created_at": {
"name": "created_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false,
"default": "now()"
}
},
"indexes": {
"urls_slug_idx": {
"name": "urls_slug_idx",
"columns": [
{
"expression": "slug",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
},
"urls_url_idx": {
"name": "urls_url_idx",
"columns": [
{
"expression": "url",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
},
"exp_date_idx": {
"name": "exp_date_idx",
"columns": [
{
"expression": "exp_date",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"urls_slug_unique": {
"name": "urls_slug_unique",
"nullsNotDistinct": false,
"columns": [
"slug"
]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.user": {
"name": "user",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"email": {
"name": "email",
"type": "text",
"primaryKey": false,
"notNull": true
},
"email_verified": {
"name": "email_verified",
"type": "boolean",
"primaryKey": false,
"notNull": true
},
"image": {
"name": "image",
"type": "text",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"user_email_unique": {
"name": "user_email_unique",
"nullsNotDistinct": false,
"columns": [
"email"
]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.verification": {
"name": "verification",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"identifier": {
"name": "identifier",
"type": "text",
"primaryKey": false,
"notNull": true
},
"value": {
"name": "value",
"type": "text",
"primaryKey": false,
"notNull": true
},
"expires_at": {
"name": "expires_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.visits": {
"name": "visits",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true
},
"url_id": {
"name": "url_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"ip_address": {
"name": "ip_address",
"type": "varchar",
"primaryKey": false,
"notNull": true
},
"user_agent": {
"name": "user_agent",
"type": "varchar",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false,
"default": "now()"
}
},
"indexes": {
"visits_url_id_idx": {
"name": "visits_url_id_idx",
"columns": [
{
"expression": "url_id",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {
"visits_url_id_urls_id_fk": {
"name": "visits_url_id_urls_id_fk",
"tableFrom": "visits",
"tableTo": "urls",
"columnsFrom": [
"url_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
}
},
"enums": {},
"schemas": {},
"sequences": {},
"roles": {},
"policies": {},
"views": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}

View File

@@ -8,6 +8,13 @@
"when": 1750934261568,
"tag": "0000_cultured_colonel_america",
"breakpoints": true
},
{
"idx": 1,
"version": "7",
"when": 1750938276218,
"tag": "0001_flawless_goblin_queen",
"breakpoints": true
}
]
}

View File

@@ -7,10 +7,10 @@ const createdAt = timestamp("created_at", { withTimezone: true }).defaultNow()
const updatedAt = timestamp("updated_at", { withTimezone: true }).defaultNow().$onUpdate(() => new Date())
export const urls = pgTable("urls", {
id: uuid("id").primaryKey().notNull(),
id: uuid("id").primaryKey().notNull().defaultRandom(),
url: varchar("url").notNull(),
slug: varchar("slug").unique().notNull(),
title: varchar("title").notNull(),
title: varchar("title"),
maxVisits: integer("max_visits"),
expDate: timestamp("exp_date", { withTimezone: true }),
forwardQueryParams: boolean("forward_query_params").default(true),

13
src/lib/schema/url.ts Normal file
View File

@@ -0,0 +1,13 @@
import { z } from "zod";
export const urlFormSchema = z.object({
url: z.string().url("Please enter a valid URL"),
slug: z
.string()
.min(1, "Slug is required")
.max(50, "Slug must be 50 characters or less")
.regex(
/^[a-zA-Z0-9-_]+$/,
"Slug can only contain letters, numbers, hyphens, and underscores"
)
})