feat: add videos table to schema and implement video upload route with metadata persistence
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import { pgTable, timestamp, uuid, varchar } from 'drizzle-orm/pg-core';
|
import { pgTable, timestamp, uuid, varchar, text } from 'drizzle-orm/pg-core';
|
||||||
|
|
||||||
export const users = pgTable('users', {
|
export const users = pgTable('users', {
|
||||||
id: uuid('id').defaultRandom().primaryKey(),
|
id: uuid('id').defaultRandom().primaryKey(),
|
||||||
@@ -15,3 +15,16 @@ export const events = pgTable('events', {
|
|||||||
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
|
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
|
||||||
videoUrl: varchar('video_url', { length: 255 }).notNull().unique(),
|
videoUrl: varchar('video_url', { length: 255 }).notNull().unique(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const videos = pgTable('videos', {
|
||||||
|
id: uuid('id').defaultRandom().primaryKey(),
|
||||||
|
userId: uuid('user_id').notNull().references(() => users.id),
|
||||||
|
objectKey: varchar('object_key', { length: 1024 }).notNull().unique(),
|
||||||
|
bucket: varchar('bucket', { length: 255 }).notNull(),
|
||||||
|
uploadUrl: text('upload_url').notNull(),
|
||||||
|
downloadUrl: text('download_url'),
|
||||||
|
status: varchar('status', { length: 32 }).notNull().default('pending'),
|
||||||
|
expiresAt: timestamp('expires_at', { withTimezone: true }),
|
||||||
|
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
|
||||||
|
updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull(),
|
||||||
|
});
|
||||||
|
|||||||
17
Backend/drizzle/0003_little_shard.sql
Normal file
17
Backend/drizzle/0003_little_shard.sql
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
CREATE TABLE "videos" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||||
|
"user_id" uuid NOT NULL,
|
||||||
|
"object_key" varchar(1024) NOT NULL,
|
||||||
|
"bucket" varchar(255) NOT NULL,
|
||||||
|
"upload_url" text NOT NULL,
|
||||||
|
"download_url" text,
|
||||||
|
"status" varchar(32) DEFAULT 'pending' NOT NULL,
|
||||||
|
"expires_at" timestamp with time zone,
|
||||||
|
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||||
|
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||||
|
CONSTRAINT "videos_object_key_unique" UNIQUE("object_key")
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
ALTER TABLE "events" ADD COLUMN "creator_id" uuid;--> statement-breakpoint
|
||||||
|
ALTER TABLE "videos" ADD CONSTRAINT "videos_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "events" ADD CONSTRAINT "events_creator_id_users_id_fk" FOREIGN KEY ("creator_id") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE no action;
|
||||||
238
Backend/drizzle/meta/0003_snapshot.json
Normal file
238
Backend/drizzle/meta/0003_snapshot.json
Normal file
@@ -0,0 +1,238 @@
|
|||||||
|
{
|
||||||
|
"id": "9ccc4d0d-ac49-4a3a-83e3-6b1d7eb9ba03",
|
||||||
|
"prevId": "1edd6921-94b0-4232-805a-d0f96da49b67",
|
||||||
|
"version": "7",
|
||||||
|
"dialect": "postgresql",
|
||||||
|
"tables": {
|
||||||
|
"public.events": {
|
||||||
|
"name": "events",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "gen_random_uuid()"
|
||||||
|
},
|
||||||
|
"creator_id": {
|
||||||
|
"name": "creator_id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"name": "title",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "timestamp with time zone",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
},
|
||||||
|
"video_url": {
|
||||||
|
"name": "video_url",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"events_creator_id_users_id_fk": {
|
||||||
|
"name": "events_creator_id_users_id_fk",
|
||||||
|
"tableFrom": "events",
|
||||||
|
"tableTo": "users",
|
||||||
|
"columnsFrom": [
|
||||||
|
"creator_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {
|
||||||
|
"events_video_url_unique": {
|
||||||
|
"name": "events_video_url_unique",
|
||||||
|
"nullsNotDistinct": false,
|
||||||
|
"columns": [
|
||||||
|
"video_url"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.users": {
|
||||||
|
"name": "users",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "gen_random_uuid()"
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"name": "email",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"password_hash": {
|
||||||
|
"name": "password_hash",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "timestamp with time zone",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {
|
||||||
|
"users_email_unique": {
|
||||||
|
"name": "users_email_unique",
|
||||||
|
"nullsNotDistinct": false,
|
||||||
|
"columns": [
|
||||||
|
"email"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.videos": {
|
||||||
|
"name": "videos",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "gen_random_uuid()"
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"name": "user_id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"object_key": {
|
||||||
|
"name": "object_key",
|
||||||
|
"type": "varchar(1024)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"bucket": {
|
||||||
|
"name": "bucket",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"upload_url": {
|
||||||
|
"name": "upload_url",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"download_url": {
|
||||||
|
"name": "download_url",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"name": "status",
|
||||||
|
"type": "varchar(32)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "'pending'"
|
||||||
|
},
|
||||||
|
"expires_at": {
|
||||||
|
"name": "expires_at",
|
||||||
|
"type": "timestamp with time zone",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "timestamp with time zone",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "timestamp with time zone",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"videos_user_id_users_id_fk": {
|
||||||
|
"name": "videos_user_id_users_id_fk",
|
||||||
|
"tableFrom": "videos",
|
||||||
|
"tableTo": "users",
|
||||||
|
"columnsFrom": [
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {
|
||||||
|
"videos_object_key_unique": {
|
||||||
|
"name": "videos_object_key_unique",
|
||||||
|
"nullsNotDistinct": false,
|
||||||
|
"columns": [
|
||||||
|
"object_key"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"enums": {},
|
||||||
|
"schemas": {},
|
||||||
|
"sequences": {},
|
||||||
|
"roles": {},
|
||||||
|
"policies": {},
|
||||||
|
"views": {},
|
||||||
|
"_meta": {
|
||||||
|
"columns": {},
|
||||||
|
"schemas": {},
|
||||||
|
"tables": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,6 +22,13 @@
|
|||||||
"when": 1770406019154,
|
"when": 1770406019154,
|
||||||
"tag": "0002_hesitant_molecule_man",
|
"tag": "0002_hesitant_molecule_man",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 3,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1770407807059,
|
||||||
|
"tag": "0003_little_shard",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
import { Router } from 'express';
|
import { Router } from 'express';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
import { db } from '../db/client';
|
||||||
|
import { videos } from '../db/schema';
|
||||||
import { requireAuth } from '../middleware/auth';
|
import { requireAuth } from '../middleware/auth';
|
||||||
import {
|
import {
|
||||||
ensureMinioBucket,
|
ensureMinioBucket,
|
||||||
@@ -55,6 +57,33 @@ router.post('/upload-url', async (req, res) => {
|
|||||||
|
|
||||||
const objectKey = buildObjectKey(user.userId, parsed.data.fileName, parsed.data.prefix);
|
const objectKey = buildObjectKey(user.userId, parsed.data.fileName, parsed.data.prefix);
|
||||||
const uploadUrl = await minioClient.presignedPutObject(minioBucket, objectKey, minioPresignedExpirySeconds);
|
const uploadUrl = await minioClient.presignedPutObject(minioBucket, objectKey, minioPresignedExpirySeconds);
|
||||||
|
const now = new Date();
|
||||||
|
const expiresAt = new Date(now.getTime() + minioPresignedExpirySeconds * 1000);
|
||||||
|
|
||||||
|
const [videoRecord] = await db
|
||||||
|
.insert(videos)
|
||||||
|
.values({
|
||||||
|
userId: user.userId,
|
||||||
|
objectKey,
|
||||||
|
bucket: minioBucket,
|
||||||
|
uploadUrl,
|
||||||
|
status: 'upload_link_sent',
|
||||||
|
expiresAt,
|
||||||
|
updatedAt: now,
|
||||||
|
})
|
||||||
|
.returning({
|
||||||
|
id: videos.id,
|
||||||
|
objectKey: videos.objectKey,
|
||||||
|
bucket: videos.bucket,
|
||||||
|
status: videos.status,
|
||||||
|
createdAt: videos.createdAt,
|
||||||
|
expiresAt: videos.expiresAt,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!videoRecord) {
|
||||||
|
res.status(500).json({ message: 'Unable to persist video metadata' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
res.status(201).json({
|
res.status(201).json({
|
||||||
message: 'Dummy upload URL generated',
|
message: 'Dummy upload URL generated',
|
||||||
@@ -62,6 +91,7 @@ router.post('/upload-url', async (req, res) => {
|
|||||||
objectKey,
|
objectKey,
|
||||||
uploadUrl,
|
uploadUrl,
|
||||||
expiresInSeconds: minioPresignedExpirySeconds,
|
expiresInSeconds: minioPresignedExpirySeconds,
|
||||||
|
video: videoRecord,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user