fix: switch backend to CommonJS, generate Drizzle migrations, add migrate script
All checks were successful
Build and Push Docker Image to Gitea / build-and-push (push) Successful in 4m23s
All checks were successful
Build and Push Docker Image to Gitea / build-and-push (push) Successful in 4m23s
- Remove "type": "module" and switch tsconfig to CommonJS/Node resolution to fix drizzle-kit ESM/CJS incompatibility - Strip .js extensions from all backend TypeScript imports - Fix gamification resolver: combine two .where() calls using and() - Fix index.ts: wrap top-level awaits in async main(), fix Fastify+yoga request handling via handleNodeRequestAndResponse - Generate initial Drizzle SQL migration (0000_pale_hellion.sql) for all 15 tables - Add src/scripts/migrate.ts: programmatic Drizzle migrator for production - Copy migrations folder into Docker image (Dockerfile.backend) - Add schema:migrate npm script Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -45,6 +45,7 @@ RUN mkdir -p packages/backend
|
|||||||
COPY --from=builder --chown=node:node /app/packages/backend/dist ./packages/backend/dist
|
COPY --from=builder --chown=node:node /app/packages/backend/dist ./packages/backend/dist
|
||||||
COPY --from=builder --chown=node:node /app/packages/backend/node_modules ./packages/backend/node_modules
|
COPY --from=builder --chown=node:node /app/packages/backend/node_modules ./packages/backend/node_modules
|
||||||
COPY --from=builder --chown=node:node /app/packages/backend/package.json ./packages/backend/package.json
|
COPY --from=builder --chown=node:node /app/packages/backend/package.json ./packages/backend/package.json
|
||||||
|
COPY --from=builder --chown=node:node /app/packages/backend/src/migrations ./packages/backend/migrations
|
||||||
|
|
||||||
RUN mkdir -p /data/uploads && chown node:node /data/uploads
|
RUN mkdir -p /data/uploads && chown node:node /data/uploads
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { defineConfig } from "drizzle-kit";
|
import { defineConfig } from "drizzle-kit";
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
schema: "./src/db/schema/index.ts",
|
schema: "./src/db/schema/*.ts",
|
||||||
out: "./src/migrations",
|
out: "./src/migrations",
|
||||||
dialect: "postgresql",
|
dialect: "postgresql",
|
||||||
dbCredentials: {
|
dbCredentials: {
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@sexy.pivoine.art/backend",
|
"name": "@sexy.pivoine.art/backend",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"type": "module",
|
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "tsx watch src/index.ts",
|
"dev": "tsx watch src/index.ts",
|
||||||
@@ -10,6 +9,7 @@
|
|||||||
"db:generate": "drizzle-kit generate",
|
"db:generate": "drizzle-kit generate",
|
||||||
"db:migrate": "drizzle-kit migrate",
|
"db:migrate": "drizzle-kit migrate",
|
||||||
"db:studio": "drizzle-kit studio",
|
"db:studio": "drizzle-kit studio",
|
||||||
|
"schema:migrate": "tsx src/scripts/migrate.ts",
|
||||||
"migrate": "tsx src/scripts/data-migration.ts"
|
"migrate": "tsx src/scripts/data-migration.ts"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { drizzle } from "drizzle-orm/node-postgres";
|
import { drizzle } from "drizzle-orm/node-postgres";
|
||||||
import { Pool } from "pg";
|
import { Pool } from "pg";
|
||||||
import * as schema from "./schema/index.js";
|
import * as schema from "./schema/index";
|
||||||
|
|
||||||
const pool = new Pool({
|
const pool = new Pool({
|
||||||
connectionString: process.env.DATABASE_URL || "postgresql://sexy:sexy@localhost:5432/sexy",
|
connectionString: process.env.DATABASE_URL || "postgresql://sexy:sexy@localhost:5432/sexy",
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ import {
|
|||||||
index,
|
index,
|
||||||
uniqueIndex,
|
uniqueIndex,
|
||||||
} from "drizzle-orm/pg-core";
|
} from "drizzle-orm/pg-core";
|
||||||
import { users } from "./users.js";
|
import { users } from "./users";
|
||||||
import { files } from "./files.js";
|
import { files } from "./files";
|
||||||
|
|
||||||
export const articles = pgTable(
|
export const articles = pgTable(
|
||||||
"articles",
|
"articles",
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import {
|
|||||||
index,
|
index,
|
||||||
integer,
|
integer,
|
||||||
} from "drizzle-orm/pg-core";
|
} from "drizzle-orm/pg-core";
|
||||||
import { users } from "./users.js";
|
import { users } from "./users";
|
||||||
|
|
||||||
export const comments = pgTable(
|
export const comments = pgTable(
|
||||||
"comments",
|
"comments",
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import {
|
|||||||
pgEnum,
|
pgEnum,
|
||||||
uniqueIndex,
|
uniqueIndex,
|
||||||
} from "drizzle-orm/pg-core";
|
} from "drizzle-orm/pg-core";
|
||||||
import { users } from "./users.js";
|
import { users } from "./users";
|
||||||
import { recordings } from "./recordings.js";
|
import { recordings } from "./recordings";
|
||||||
|
|
||||||
export const achievementStatusEnum = pgEnum("achievement_status", [
|
export const achievementStatusEnum = pgEnum("achievement_status", [
|
||||||
"draft",
|
"draft",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
export * from "./files.js";
|
export * from "./files";
|
||||||
export * from "./users.js";
|
export * from "./users";
|
||||||
export * from "./videos.js";
|
export * from "./videos";
|
||||||
export * from "./articles.js";
|
export * from "./articles";
|
||||||
export * from "./recordings.js";
|
export * from "./recordings";
|
||||||
export * from "./comments.js";
|
export * from "./comments";
|
||||||
export * from "./gamification.js";
|
export * from "./gamification";
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ import {
|
|||||||
uniqueIndex,
|
uniqueIndex,
|
||||||
jsonb,
|
jsonb,
|
||||||
} from "drizzle-orm/pg-core";
|
} from "drizzle-orm/pg-core";
|
||||||
import { users } from "./users.js";
|
import { users } from "./users";
|
||||||
import { videos } from "./videos.js";
|
import { videos } from "./videos";
|
||||||
|
|
||||||
export const recordingStatusEnum = pgEnum("recording_status", [
|
export const recordingStatusEnum = pgEnum("recording_status", [
|
||||||
"draft",
|
"draft",
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
uniqueIndex,
|
uniqueIndex,
|
||||||
integer,
|
integer,
|
||||||
} from "drizzle-orm/pg-core";
|
} from "drizzle-orm/pg-core";
|
||||||
import { files } from "./files.js";
|
import { files } from "./files";
|
||||||
|
|
||||||
export const roleEnum = pgEnum("user_role", ["model", "viewer", "admin"]);
|
export const roleEnum = pgEnum("user_role", ["model", "viewer", "admin"]);
|
||||||
|
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import {
|
|||||||
uniqueIndex,
|
uniqueIndex,
|
||||||
primaryKey,
|
primaryKey,
|
||||||
} from "drizzle-orm/pg-core";
|
} from "drizzle-orm/pg-core";
|
||||||
import { users } from "./users.js";
|
import { users } from "./users";
|
||||||
import { files } from "./files.js";
|
import { files } from "./files";
|
||||||
|
|
||||||
export const videos = pgTable(
|
export const videos = pgTable(
|
||||||
"videos",
|
"videos",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import SchemaBuilder from "@pothos/core";
|
import SchemaBuilder from "@pothos/core";
|
||||||
import ErrorsPlugin from "@pothos/plugin-errors";
|
import ErrorsPlugin from "@pothos/plugin-errors";
|
||||||
import type { DB } from "../db/connection.js";
|
import type { DB } from "../db/connection";
|
||||||
import type { SessionUser } from "../lib/auth.js";
|
import type { SessionUser } from "../lib/auth";
|
||||||
import type Redis from "ioredis";
|
import type Redis from "ioredis";
|
||||||
import { GraphQLDateTime, GraphQLJSON } from "graphql-scalars";
|
import { GraphQLDateTime, GraphQLJSON } from "graphql-scalars";
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import type { YogaInitialContext } from "graphql-yoga";
|
import type { YogaInitialContext } from "graphql-yoga";
|
||||||
import type { Context } from "./builder.js";
|
import type { Context } from "./builder";
|
||||||
import { getSession } from "../lib/auth.js";
|
import { getSession } from "../lib/auth";
|
||||||
import { db } from "../db/connection.js";
|
import { db } from "../db/connection";
|
||||||
import { redis } from "../lib/auth.js";
|
import { redis } from "../lib/auth";
|
||||||
|
|
||||||
export async function buildContext(ctx: YogaInitialContext & { request: Request; reply: unknown; db: typeof db; redis: typeof redis }): Promise<Context> {
|
export async function buildContext(ctx: YogaInitialContext & { request: Request; reply: unknown; db: typeof db; redis: typeof redis }): Promise<Context> {
|
||||||
const request = ctx.request;
|
const request = ctx.request;
|
||||||
|
|||||||
@@ -9,6 +9,6 @@ import "./resolvers/recordings.js";
|
|||||||
import "./resolvers/comments.js";
|
import "./resolvers/comments.js";
|
||||||
import "./resolvers/gamification.js";
|
import "./resolvers/gamification.js";
|
||||||
import "./resolvers/stats.js";
|
import "./resolvers/stats.js";
|
||||||
import { builder } from "./builder.js";
|
import { builder } from "./builder";
|
||||||
|
|
||||||
export const schema = builder.toSchema();
|
export const schema = builder.toSchema();
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { builder } from "../builder.js";
|
import { builder } from "../builder";
|
||||||
import { ArticleType } from "../types/index.js";
|
import { ArticleType } from "../types/index";
|
||||||
import { articles, users } from "../../db/schema/index.js";
|
import { articles, users } from "../../db/schema/index";
|
||||||
import { eq, and, lte, desc } from "drizzle-orm";
|
import { eq, and, lte, desc } from "drizzle-orm";
|
||||||
|
|
||||||
builder.queryField("articles", (t) =>
|
builder.queryField("articles", (t) =>
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { GraphQLError } from "graphql";
|
import { GraphQLError } from "graphql";
|
||||||
import { builder } from "../builder.js";
|
import { builder } from "../builder";
|
||||||
import { CurrentUserType } from "../types/index.js";
|
import { CurrentUserType } from "../types/index";
|
||||||
import { users } from "../../db/schema/index.js";
|
import { users } from "../../db/schema/index";
|
||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
import { hash, verify as verifyArgon } from "../../lib/argon.js";
|
import { hash, verify as verifyArgon } from "../../lib/argon";
|
||||||
import { setSession, deleteSession } from "../../lib/auth.js";
|
import { setSession, deleteSession } from "../../lib/auth";
|
||||||
import { sendVerification, sendPasswordReset } from "../../lib/email.js";
|
import { sendVerification, sendPasswordReset } from "../../lib/email";
|
||||||
import { slugify } from "../../lib/slugify.js";
|
import { slugify } from "../../lib/slugify";
|
||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
|
|
||||||
builder.mutationField("login", (t) =>
|
builder.mutationField("login", (t) =>
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { GraphQLError } from "graphql";
|
import { GraphQLError } from "graphql";
|
||||||
import { builder } from "../builder.js";
|
import { builder } from "../builder";
|
||||||
import { CommentType } from "../types/index.js";
|
import { CommentType } from "../types/index";
|
||||||
import { comments, users } from "../../db/schema/index.js";
|
import { comments, users } from "../../db/schema/index";
|
||||||
import { eq, and, desc } from "drizzle-orm";
|
import { eq, and, desc } from "drizzle-orm";
|
||||||
import { awardPoints, checkAchievements } from "../../lib/gamification.js";
|
import { awardPoints, checkAchievements } from "../../lib/gamification";
|
||||||
|
|
||||||
builder.queryField("commentsForVideo", (t) =>
|
builder.queryField("commentsForVideo", (t) =>
|
||||||
t.field({
|
t.field({
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { builder } from "../builder.js";
|
import { builder } from "../builder";
|
||||||
import { LeaderboardEntryType, UserGamificationType, AchievementType } from "../types/index.js";
|
import { LeaderboardEntryType, UserGamificationType, AchievementType } from "../types/index";
|
||||||
import { user_stats, users, user_achievements, achievements, user_points } from "../../db/schema/index.js";
|
import { user_stats, users, user_achievements, achievements, user_points } from "../../db/schema/index";
|
||||||
import { eq, desc, gt, count, isNotNull } from "drizzle-orm";
|
import { eq, desc, gt, count, isNotNull, and } from "drizzle-orm";
|
||||||
|
|
||||||
builder.queryField("leaderboard", (t) =>
|
builder.queryField("leaderboard", (t) =>
|
||||||
t.field({
|
t.field({
|
||||||
@@ -73,8 +73,7 @@ builder.queryField("userGamification", (t) =>
|
|||||||
})
|
})
|
||||||
.from(user_achievements)
|
.from(user_achievements)
|
||||||
.leftJoin(achievements, eq(user_achievements.achievement_id, achievements.id))
|
.leftJoin(achievements, eq(user_achievements.achievement_id, achievements.id))
|
||||||
.where(eq(user_achievements.user_id, args.userId))
|
.where(and(eq(user_achievements.user_id, args.userId), isNotNull(user_achievements.date_unlocked)))
|
||||||
.where(isNotNull(user_achievements.date_unlocked))
|
|
||||||
.orderBy(desc(user_achievements.date_unlocked));
|
.orderBy(desc(user_achievements.date_unlocked));
|
||||||
|
|
||||||
const recentPoints = await ctx.db
|
const recentPoints = await ctx.db
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { builder } from "../builder.js";
|
import { builder } from "../builder";
|
||||||
import { ModelType } from "../types/index.js";
|
import { ModelType } from "../types/index";
|
||||||
import { users, user_photos, files } from "../../db/schema/index.js";
|
import { users, user_photos, files } from "../../db/schema/index";
|
||||||
import { eq, and, desc } from "drizzle-orm";
|
import { eq, and, desc } from "drizzle-orm";
|
||||||
|
|
||||||
async function enrichModel(db: any, user: any) {
|
async function enrichModel(db: any, user: any) {
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { GraphQLError } from "graphql";
|
import { GraphQLError } from "graphql";
|
||||||
import { builder } from "../builder.js";
|
import { builder } from "../builder";
|
||||||
import { RecordingType } from "../types/index.js";
|
import { RecordingType } from "../types/index";
|
||||||
import { recordings, recording_plays } from "../../db/schema/index.js";
|
import { recordings, recording_plays } from "../../db/schema/index";
|
||||||
import { eq, and, desc } from "drizzle-orm";
|
import { eq, and, desc } from "drizzle-orm";
|
||||||
import { slugify } from "../../lib/slugify.js";
|
import { slugify } from "../../lib/slugify";
|
||||||
import { awardPoints, checkAchievements } from "../../lib/gamification.js";
|
import { awardPoints, checkAchievements } from "../../lib/gamification";
|
||||||
|
|
||||||
builder.queryField("recordings", (t) =>
|
builder.queryField("recordings", (t) =>
|
||||||
t.field({
|
t.field({
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { builder } from "../builder.js";
|
import { builder } from "../builder";
|
||||||
import { StatsType } from "../types/index.js";
|
import { StatsType } from "../types/index";
|
||||||
import { users, videos } from "../../db/schema/index.js";
|
import { users, videos } from "../../db/schema/index";
|
||||||
import { eq, count } from "drizzle-orm";
|
import { eq, count } from "drizzle-orm";
|
||||||
|
|
||||||
builder.queryField("stats", (t) =>
|
builder.queryField("stats", (t) =>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { GraphQLError } from "graphql";
|
import { GraphQLError } from "graphql";
|
||||||
import { builder } from "../builder.js";
|
import { builder } from "../builder";
|
||||||
import { CurrentUserType, UserType } from "../types/index.js";
|
import { CurrentUserType, UserType } from "../types/index";
|
||||||
import { users } from "../../db/schema/index.js";
|
import { users } from "../../db/schema/index";
|
||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
|
|
||||||
builder.queryField("me", (t) =>
|
builder.queryField("me", (t) =>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { GraphQLError } from "graphql";
|
import { GraphQLError } from "graphql";
|
||||||
import { builder } from "../builder.js";
|
import { builder } from "../builder";
|
||||||
import { VideoType, VideoLikeResponseType, VideoPlayResponseType, VideoLikeStatusType } from "../types/index.js";
|
import { VideoType, VideoLikeResponseType, VideoPlayResponseType, VideoLikeStatusType } from "../types/index";
|
||||||
import { videos, video_models, video_likes, video_plays, users, files } from "../../db/schema/index.js";
|
import { videos, video_models, video_likes, video_plays, users, files } from "../../db/schema/index";
|
||||||
import { eq, and, lte, desc, inArray, count } from "drizzle-orm";
|
import { eq, and, lte, desc, inArray, count } from "drizzle-orm";
|
||||||
|
|
||||||
async function enrichVideo(db: any, video: any) {
|
async function enrichVideo(db: any, video: any) {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { builder } from "../builder.js";
|
import { builder } from "../builder";
|
||||||
|
|
||||||
// File type
|
// File type
|
||||||
export const FileType = builder.objectRef<{
|
export const FileType = builder.objectRef<{
|
||||||
|
|||||||
@@ -5,83 +5,89 @@ import fastifyMultipart from "@fastify/multipart";
|
|||||||
import fastifyStatic from "@fastify/static";
|
import fastifyStatic from "@fastify/static";
|
||||||
import { createYoga } from "graphql-yoga";
|
import { createYoga } from "graphql-yoga";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import { schema } from "./graphql/index.js";
|
import { schema } from "./graphql/index";
|
||||||
import { buildContext } from "./graphql/context.js";
|
import { buildContext } from "./graphql/context";
|
||||||
import { db } from "./db/connection.js";
|
import { db } from "./db/connection";
|
||||||
import { redis } from "./lib/auth.js";
|
import { redis } from "./lib/auth";
|
||||||
|
|
||||||
const PORT = parseInt(process.env.PORT || "4000");
|
const PORT = parseInt(process.env.PORT || "4000");
|
||||||
const UPLOAD_DIR = process.env.UPLOAD_DIR || "/data/uploads";
|
const UPLOAD_DIR = process.env.UPLOAD_DIR || "/data/uploads";
|
||||||
const CORS_ORIGIN = process.env.CORS_ORIGIN || "http://localhost:3000";
|
const CORS_ORIGIN = process.env.CORS_ORIGIN || "http://localhost:3000";
|
||||||
|
|
||||||
const fastify = Fastify({
|
async function main() {
|
||||||
logger: {
|
const fastify = Fastify({
|
||||||
level: process.env.LOG_LEVEL || "info",
|
logger: {
|
||||||
},
|
level: process.env.LOG_LEVEL || "info",
|
||||||
});
|
},
|
||||||
|
});
|
||||||
|
|
||||||
await fastify.register(fastifyCookie, {
|
await fastify.register(fastifyCookie, {
|
||||||
secret: process.env.COOKIE_SECRET || "change-me-in-production",
|
secret: process.env.COOKIE_SECRET || "change-me-in-production",
|
||||||
});
|
});
|
||||||
|
|
||||||
await fastify.register(fastifyCors, {
|
await fastify.register(fastifyCors, {
|
||||||
origin: CORS_ORIGIN,
|
origin: CORS_ORIGIN,
|
||||||
credentials: true,
|
credentials: true,
|
||||||
methods: ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
|
methods: ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
|
||||||
});
|
});
|
||||||
|
|
||||||
await fastify.register(fastifyMultipart, {
|
await fastify.register(fastifyMultipart, {
|
||||||
limits: {
|
limits: {
|
||||||
fileSize: 5 * 1024 * 1024 * 1024, // 5 GB
|
fileSize: 5 * 1024 * 1024 * 1024, // 5 GB
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
await fastify.register(fastifyStatic, {
|
await fastify.register(fastifyStatic, {
|
||||||
root: path.resolve(UPLOAD_DIR),
|
root: path.resolve(UPLOAD_DIR),
|
||||||
prefix: "/assets/",
|
prefix: "/assets/",
|
||||||
decorateReply: false,
|
decorateReply: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const yoga = createYoga({
|
const yoga = createYoga({
|
||||||
schema,
|
schema,
|
||||||
context: buildContext,
|
context: buildContext,
|
||||||
graphqlEndpoint: "/graphql",
|
graphqlEndpoint: "/graphql",
|
||||||
healthCheckEndpoint: "/health",
|
healthCheckEndpoint: "/health",
|
||||||
logging: {
|
logging: {
|
||||||
debug: (...args) => fastify.log.debug(...args),
|
debug: (msg: string) => fastify.log.debug(msg),
|
||||||
info: (...args) => fastify.log.info(...args),
|
info: (msg: string) => fastify.log.info(msg),
|
||||||
warn: (...args) => fastify.log.warn(...args),
|
warn: (msg: string) => fastify.log.warn(msg),
|
||||||
error: (...args) => fastify.log.error(...args),
|
error: (msg: string) => fastify.log.error(msg),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
fastify.route({
|
fastify.route({
|
||||||
url: "/graphql",
|
url: "/graphql",
|
||||||
method: ["GET", "POST", "OPTIONS"],
|
method: ["GET", "POST", "OPTIONS"],
|
||||||
handler: async (request, reply) => {
|
handler: async (request, reply) => {
|
||||||
const response = await yoga.handleNodeRequestAndResponse(request, reply, {
|
const response = await yoga.handleNodeRequestAndResponse(
|
||||||
request,
|
request.raw,
|
||||||
reply,
|
reply.raw,
|
||||||
db,
|
{ reply, db, redis },
|
||||||
redis,
|
);
|
||||||
});
|
reply.status(response.status);
|
||||||
reply.status(response.status);
|
for (const [key, value] of response.headers.entries()) {
|
||||||
for (const [key, value] of response.headers.entries()) {
|
reply.header(key, value);
|
||||||
reply.header(key, value);
|
}
|
||||||
}
|
return reply.send(response.body);
|
||||||
return reply.send(response.body);
|
},
|
||||||
},
|
});
|
||||||
});
|
|
||||||
|
|
||||||
fastify.get("/health", async (_request, reply) => {
|
fastify.get("/health", async (_request, reply) => {
|
||||||
return reply.send({ status: "ok", timestamp: new Date().toISOString() });
|
return reply.send({ status: "ok", timestamp: new Date().toISOString() });
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await fastify.listen({ port: PORT, host: "0.0.0.0" });
|
await fastify.listen({ port: PORT, host: "0.0.0.0" });
|
||||||
fastify.log.info(`Backend running at http://0.0.0.0:${PORT}`);
|
fastify.log.info(`Backend running at http://0.0.0.0:${PORT}`);
|
||||||
fastify.log.info(`GraphQL at http://0.0.0.0:${PORT}/graphql`);
|
fastify.log.info(`GraphQL at http://0.0.0.0:${PORT}/graphql`);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
fastify.log.error(err);
|
fastify.log.error(err);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
main().catch((err) => {
|
||||||
|
console.error("Fatal error:", err);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { eq, sql, and, gt, isNotNull, count, sum } from "drizzle-orm";
|
import { eq, sql, and, gt, isNotNull, count, sum } from "drizzle-orm";
|
||||||
import type { DB } from "../db/connection.js";
|
import type { DB } from "../db/connection";
|
||||||
import {
|
import {
|
||||||
user_points,
|
user_points,
|
||||||
user_stats,
|
user_stats,
|
||||||
@@ -9,7 +9,7 @@ import {
|
|||||||
user_achievements,
|
user_achievements,
|
||||||
achievements,
|
achievements,
|
||||||
users,
|
users,
|
||||||
} from "../db/schema/index.js";
|
} from "../db/schema/index";
|
||||||
|
|
||||||
export const POINT_VALUES = {
|
export const POINT_VALUES = {
|
||||||
RECORDING_CREATE: 50,
|
RECORDING_CREATE: 50,
|
||||||
|
|||||||
233
packages/backend/src/migrations/0000_pale_hellion.sql
Normal file
233
packages/backend/src/migrations/0000_pale_hellion.sql
Normal file
@@ -0,0 +1,233 @@
|
|||||||
|
CREATE TYPE "public"."achievement_status" AS ENUM('draft', 'published');--> statement-breakpoint
|
||||||
|
CREATE TYPE "public"."user_role" AS ENUM('model', 'viewer', 'admin');--> statement-breakpoint
|
||||||
|
CREATE TYPE "public"."recording_status" AS ENUM('draft', 'published', 'archived');--> statement-breakpoint
|
||||||
|
CREATE TABLE "articles" (
|
||||||
|
"id" text PRIMARY KEY NOT NULL,
|
||||||
|
"slug" text NOT NULL,
|
||||||
|
"title" text NOT NULL,
|
||||||
|
"excerpt" text,
|
||||||
|
"content" text,
|
||||||
|
"image" text,
|
||||||
|
"tags" text[] DEFAULT '{}',
|
||||||
|
"publish_date" timestamp DEFAULT now() NOT NULL,
|
||||||
|
"author" text,
|
||||||
|
"category" text,
|
||||||
|
"featured" boolean DEFAULT false,
|
||||||
|
"date_created" timestamp DEFAULT now() NOT NULL,
|
||||||
|
"date_updated" timestamp
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "comments" (
|
||||||
|
"id" integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY (sequence name "comments_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 2147483647 START WITH 1 CACHE 1),
|
||||||
|
"collection" text NOT NULL,
|
||||||
|
"item_id" text NOT NULL,
|
||||||
|
"comment" text NOT NULL,
|
||||||
|
"user_id" text NOT NULL,
|
||||||
|
"date_created" timestamp DEFAULT now() NOT NULL,
|
||||||
|
"date_updated" timestamp
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "files" (
|
||||||
|
"id" text PRIMARY KEY NOT NULL,
|
||||||
|
"title" text,
|
||||||
|
"description" text,
|
||||||
|
"filename" text NOT NULL,
|
||||||
|
"mime_type" text,
|
||||||
|
"filesize" bigint,
|
||||||
|
"duration" integer,
|
||||||
|
"uploaded_by" text,
|
||||||
|
"date_created" timestamp DEFAULT now() NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "achievements" (
|
||||||
|
"id" text PRIMARY KEY NOT NULL,
|
||||||
|
"code" text NOT NULL,
|
||||||
|
"name" text NOT NULL,
|
||||||
|
"description" text,
|
||||||
|
"icon" text,
|
||||||
|
"category" text,
|
||||||
|
"required_count" integer DEFAULT 1 NOT NULL,
|
||||||
|
"points_reward" integer DEFAULT 0 NOT NULL,
|
||||||
|
"status" "achievement_status" DEFAULT 'published' NOT NULL,
|
||||||
|
"sort" integer DEFAULT 0
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "user_achievements" (
|
||||||
|
"id" integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY (sequence name "user_achievements_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 2147483647 START WITH 1 CACHE 1),
|
||||||
|
"user_id" text NOT NULL,
|
||||||
|
"achievement_id" text NOT NULL,
|
||||||
|
"progress" integer DEFAULT 0,
|
||||||
|
"date_unlocked" timestamp
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "user_points" (
|
||||||
|
"id" integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY (sequence name "user_points_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 2147483647 START WITH 1 CACHE 1),
|
||||||
|
"user_id" text NOT NULL,
|
||||||
|
"action" text NOT NULL,
|
||||||
|
"points" integer NOT NULL,
|
||||||
|
"recording_id" text,
|
||||||
|
"date_created" timestamp DEFAULT now() NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "user_stats" (
|
||||||
|
"id" integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY (sequence name "user_stats_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 2147483647 START WITH 1 CACHE 1),
|
||||||
|
"user_id" text NOT NULL,
|
||||||
|
"total_raw_points" integer DEFAULT 0,
|
||||||
|
"total_weighted_points" real DEFAULT 0,
|
||||||
|
"recordings_count" integer DEFAULT 0,
|
||||||
|
"playbacks_count" integer DEFAULT 0,
|
||||||
|
"comments_count" integer DEFAULT 0,
|
||||||
|
"achievements_count" integer DEFAULT 0,
|
||||||
|
"last_updated" timestamp DEFAULT now()
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "user_photos" (
|
||||||
|
"id" integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY (sequence name "user_photos_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 2147483647 START WITH 1 CACHE 1),
|
||||||
|
"user_id" text NOT NULL,
|
||||||
|
"file_id" text NOT NULL,
|
||||||
|
"sort" integer DEFAULT 0
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "users" (
|
||||||
|
"id" text PRIMARY KEY NOT NULL,
|
||||||
|
"email" text NOT NULL,
|
||||||
|
"password_hash" text NOT NULL,
|
||||||
|
"first_name" text,
|
||||||
|
"last_name" text,
|
||||||
|
"artist_name" text,
|
||||||
|
"slug" text,
|
||||||
|
"description" text,
|
||||||
|
"tags" text[] DEFAULT '{}',
|
||||||
|
"role" "user_role" DEFAULT 'viewer' NOT NULL,
|
||||||
|
"avatar" text,
|
||||||
|
"banner" text,
|
||||||
|
"email_verified" boolean DEFAULT false NOT NULL,
|
||||||
|
"email_verify_token" text,
|
||||||
|
"password_reset_token" text,
|
||||||
|
"password_reset_expiry" timestamp,
|
||||||
|
"date_created" timestamp DEFAULT now() NOT NULL,
|
||||||
|
"date_updated" timestamp
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "video_likes" (
|
||||||
|
"id" text PRIMARY KEY NOT NULL,
|
||||||
|
"video_id" text NOT NULL,
|
||||||
|
"user_id" text NOT NULL,
|
||||||
|
"date_created" timestamp DEFAULT now() NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "video_models" (
|
||||||
|
"video_id" text NOT NULL,
|
||||||
|
"user_id" text NOT NULL,
|
||||||
|
CONSTRAINT "video_models_video_id_user_id_pk" PRIMARY KEY("video_id","user_id")
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "video_plays" (
|
||||||
|
"id" text PRIMARY KEY NOT NULL,
|
||||||
|
"video_id" text NOT NULL,
|
||||||
|
"user_id" text,
|
||||||
|
"session_id" text,
|
||||||
|
"duration_watched" integer,
|
||||||
|
"completed" boolean DEFAULT false,
|
||||||
|
"date_created" timestamp DEFAULT now() NOT NULL,
|
||||||
|
"date_updated" timestamp
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "videos" (
|
||||||
|
"id" text PRIMARY KEY NOT NULL,
|
||||||
|
"slug" text NOT NULL,
|
||||||
|
"title" text NOT NULL,
|
||||||
|
"description" text,
|
||||||
|
"image" text,
|
||||||
|
"movie" text,
|
||||||
|
"tags" text[] DEFAULT '{}',
|
||||||
|
"upload_date" timestamp DEFAULT now() NOT NULL,
|
||||||
|
"premium" boolean DEFAULT false,
|
||||||
|
"featured" boolean DEFAULT false,
|
||||||
|
"likes_count" integer DEFAULT 0,
|
||||||
|
"plays_count" integer DEFAULT 0
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "recording_plays" (
|
||||||
|
"id" text PRIMARY KEY NOT NULL,
|
||||||
|
"recording_id" text NOT NULL,
|
||||||
|
"user_id" text,
|
||||||
|
"duration_played" integer DEFAULT 0,
|
||||||
|
"completed" boolean DEFAULT false,
|
||||||
|
"date_created" timestamp DEFAULT now() NOT NULL,
|
||||||
|
"date_updated" timestamp
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "recordings" (
|
||||||
|
"id" text PRIMARY KEY NOT NULL,
|
||||||
|
"title" text NOT NULL,
|
||||||
|
"description" text,
|
||||||
|
"slug" text NOT NULL,
|
||||||
|
"duration" integer NOT NULL,
|
||||||
|
"events" jsonb DEFAULT '[]'::jsonb,
|
||||||
|
"device_info" jsonb DEFAULT '[]'::jsonb,
|
||||||
|
"user_id" text NOT NULL,
|
||||||
|
"status" "recording_status" DEFAULT 'draft' NOT NULL,
|
||||||
|
"tags" text[] DEFAULT '{}',
|
||||||
|
"linked_video" text,
|
||||||
|
"featured" boolean DEFAULT false,
|
||||||
|
"public" boolean DEFAULT false,
|
||||||
|
"original_recording_id" text,
|
||||||
|
"date_created" timestamp DEFAULT now() NOT NULL,
|
||||||
|
"date_updated" timestamp
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
ALTER TABLE "articles" ADD CONSTRAINT "articles_image_files_id_fk" FOREIGN KEY ("image") REFERENCES "public"."files"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "articles" ADD CONSTRAINT "articles_author_users_id_fk" FOREIGN KEY ("author") REFERENCES "public"."users"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "comments" ADD CONSTRAINT "comments_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "user_achievements" ADD CONSTRAINT "user_achievements_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "user_achievements" ADD CONSTRAINT "user_achievements_achievement_id_achievements_id_fk" FOREIGN KEY ("achievement_id") REFERENCES "public"."achievements"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "user_points" ADD CONSTRAINT "user_points_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "user_points" ADD CONSTRAINT "user_points_recording_id_recordings_id_fk" FOREIGN KEY ("recording_id") REFERENCES "public"."recordings"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "user_stats" ADD CONSTRAINT "user_stats_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "user_photos" ADD CONSTRAINT "user_photos_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "user_photos" ADD CONSTRAINT "user_photos_file_id_files_id_fk" FOREIGN KEY ("file_id") REFERENCES "public"."files"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "users" ADD CONSTRAINT "users_avatar_files_id_fk" FOREIGN KEY ("avatar") REFERENCES "public"."files"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "users" ADD CONSTRAINT "users_banner_files_id_fk" FOREIGN KEY ("banner") REFERENCES "public"."files"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "video_likes" ADD CONSTRAINT "video_likes_video_id_videos_id_fk" FOREIGN KEY ("video_id") REFERENCES "public"."videos"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "video_likes" ADD CONSTRAINT "video_likes_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "video_models" ADD CONSTRAINT "video_models_video_id_videos_id_fk" FOREIGN KEY ("video_id") REFERENCES "public"."videos"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "video_models" ADD CONSTRAINT "video_models_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "video_plays" ADD CONSTRAINT "video_plays_video_id_videos_id_fk" FOREIGN KEY ("video_id") REFERENCES "public"."videos"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "video_plays" ADD CONSTRAINT "video_plays_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "videos" ADD CONSTRAINT "videos_image_files_id_fk" FOREIGN KEY ("image") REFERENCES "public"."files"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "videos" ADD CONSTRAINT "videos_movie_files_id_fk" FOREIGN KEY ("movie") REFERENCES "public"."files"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "recording_plays" ADD CONSTRAINT "recording_plays_recording_id_recordings_id_fk" FOREIGN KEY ("recording_id") REFERENCES "public"."recordings"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "recording_plays" ADD CONSTRAINT "recording_plays_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "recordings" ADD CONSTRAINT "recordings_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "recordings" ADD CONSTRAINT "recordings_linked_video_videos_id_fk" FOREIGN KEY ("linked_video") REFERENCES "public"."videos"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
|
||||||
|
CREATE UNIQUE INDEX "articles_slug_idx" ON "articles" USING btree ("slug");--> statement-breakpoint
|
||||||
|
CREATE INDEX "articles_publish_date_idx" ON "articles" USING btree ("publish_date");--> statement-breakpoint
|
||||||
|
CREATE INDEX "articles_featured_idx" ON "articles" USING btree ("featured");--> statement-breakpoint
|
||||||
|
CREATE INDEX "comments_collection_item_idx" ON "comments" USING btree ("collection","item_id");--> statement-breakpoint
|
||||||
|
CREATE INDEX "comments_user_idx" ON "comments" USING btree ("user_id");--> statement-breakpoint
|
||||||
|
CREATE INDEX "files_uploaded_by_idx" ON "files" USING btree ("uploaded_by");--> statement-breakpoint
|
||||||
|
CREATE UNIQUE INDEX "achievements_code_idx" ON "achievements" USING btree ("code");--> statement-breakpoint
|
||||||
|
CREATE INDEX "user_achievements_user_idx" ON "user_achievements" USING btree ("user_id");--> statement-breakpoint
|
||||||
|
CREATE UNIQUE INDEX "user_achievements_unique_idx" ON "user_achievements" USING btree ("user_id","achievement_id");--> statement-breakpoint
|
||||||
|
CREATE INDEX "user_points_user_idx" ON "user_points" USING btree ("user_id");--> statement-breakpoint
|
||||||
|
CREATE INDEX "user_points_date_idx" ON "user_points" USING btree ("date_created");--> statement-breakpoint
|
||||||
|
CREATE UNIQUE INDEX "user_stats_user_idx" ON "user_stats" USING btree ("user_id");--> statement-breakpoint
|
||||||
|
CREATE INDEX "user_photos_user_idx" ON "user_photos" USING btree ("user_id");--> statement-breakpoint
|
||||||
|
CREATE UNIQUE INDEX "users_email_idx" ON "users" USING btree ("email");--> statement-breakpoint
|
||||||
|
CREATE UNIQUE INDEX "users_slug_idx" ON "users" USING btree ("slug");--> statement-breakpoint
|
||||||
|
CREATE INDEX "users_role_idx" ON "users" USING btree ("role");--> statement-breakpoint
|
||||||
|
CREATE INDEX "video_likes_video_idx" ON "video_likes" USING btree ("video_id");--> statement-breakpoint
|
||||||
|
CREATE INDEX "video_likes_user_idx" ON "video_likes" USING btree ("user_id");--> statement-breakpoint
|
||||||
|
CREATE INDEX "video_plays_video_idx" ON "video_plays" USING btree ("video_id");--> statement-breakpoint
|
||||||
|
CREATE INDEX "video_plays_user_idx" ON "video_plays" USING btree ("user_id");--> statement-breakpoint
|
||||||
|
CREATE INDEX "video_plays_date_idx" ON "video_plays" USING btree ("date_created");--> statement-breakpoint
|
||||||
|
CREATE UNIQUE INDEX "videos_slug_idx" ON "videos" USING btree ("slug");--> statement-breakpoint
|
||||||
|
CREATE INDEX "videos_upload_date_idx" ON "videos" USING btree ("upload_date");--> statement-breakpoint
|
||||||
|
CREATE INDEX "videos_featured_idx" ON "videos" USING btree ("featured");--> statement-breakpoint
|
||||||
|
CREATE INDEX "recording_plays_recording_idx" ON "recording_plays" USING btree ("recording_id");--> statement-breakpoint
|
||||||
|
CREATE INDEX "recording_plays_user_idx" ON "recording_plays" USING btree ("user_id");--> statement-breakpoint
|
||||||
|
CREATE UNIQUE INDEX "recordings_slug_idx" ON "recordings" USING btree ("slug");--> statement-breakpoint
|
||||||
|
CREATE INDEX "recordings_user_idx" ON "recordings" USING btree ("user_id");--> statement-breakpoint
|
||||||
|
CREATE INDEX "recordings_status_idx" ON "recordings" USING btree ("status");--> statement-breakpoint
|
||||||
|
CREATE INDEX "recordings_public_idx" ON "recordings" USING btree ("public");
|
||||||
1931
packages/backend/src/migrations/meta/0000_snapshot.json
Normal file
1931
packages/backend/src/migrations/meta/0000_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
13
packages/backend/src/migrations/meta/_journal.json
Normal file
13
packages/backend/src/migrations/meta/_journal.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"version": "7",
|
||||||
|
"dialect": "postgresql",
|
||||||
|
"entries": [
|
||||||
|
{
|
||||||
|
"idx": 0,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1772645674513,
|
||||||
|
"tag": "0000_pale_hellion",
|
||||||
|
"breakpoints": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
23
packages/backend/src/scripts/migrate.ts
Normal file
23
packages/backend/src/scripts/migrate.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { Pool } from "pg";
|
||||||
|
import { drizzle } from "drizzle-orm/node-postgres";
|
||||||
|
import { migrate } from "drizzle-orm/node-postgres/migrator";
|
||||||
|
import path from "path";
|
||||||
|
|
||||||
|
const pool = new Pool({
|
||||||
|
connectionString: process.env.DATABASE_URL || "postgresql://sexy:sexy@localhost:5432/sexy",
|
||||||
|
});
|
||||||
|
|
||||||
|
const db = drizzle(pool);
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
console.log("Running schema migrations...");
|
||||||
|
const migrationsFolder = path.join(__dirname, "../../migrations");
|
||||||
|
await migrate(db, { migrationsFolder });
|
||||||
|
console.log("Schema migrations complete.");
|
||||||
|
await pool.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch((err) => {
|
||||||
|
console.error("Migration failed:", err);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "ES2022",
|
"target": "ES2022",
|
||||||
"module": "NodeNext",
|
"module": "CommonJS",
|
||||||
"moduleResolution": "NodeNext",
|
"moduleResolution": "Node",
|
||||||
"lib": ["ES2022"],
|
"lib": ["ES2022"],
|
||||||
"outDir": "./dist",
|
"outDir": "./dist",
|
||||||
"rootDir": "./src",
|
"rootDir": "./src",
|
||||||
|
|||||||
Reference in New Issue
Block a user