diff --git a/compose.yml b/compose.yml index 29c64af..4951774 100644 --- a/compose.yml +++ b/compose.yml @@ -4,6 +4,8 @@ services: image: postgres:16-alpine container_name: sexy_postgres restart: unless-stopped + ports: + - "5432:5432" volumes: - postgres_data:/var/lib/postgresql/data environment: @@ -19,6 +21,8 @@ services: image: redis:7-alpine container_name: sexy_redis restart: unless-stopped + ports: + - "6379:6379" volumes: - redis_data:/data command: redis-server --appendonly yes diff --git a/packages/backend/src/graphql/context.ts b/packages/backend/src/graphql/context.ts index c9f2f8d..0d5f1b0 100644 --- a/packages/backend/src/graphql/context.ts +++ b/packages/backend/src/graphql/context.ts @@ -1,10 +1,18 @@ import type { YogaInitialContext } from "graphql-yoga"; +import type { FastifyRequest, FastifyReply } from "fastify"; import type { Context } from "./builder"; import { getSession } from "../lib/auth"; import { db } from "../db/connection"; import { redis } from "../lib/auth"; -export async function buildContext(ctx: YogaInitialContext & { request: Request; reply: unknown; db: typeof db; redis: typeof redis }): Promise { +type ServerContext = { + req: FastifyRequest; + reply: FastifyReply; + db: typeof db; + redis: typeof redis; +}; + +export async function buildContext(ctx: YogaInitialContext & ServerContext): Promise { const request = ctx.request; const cookieHeader = request.headers.get("cookie") || ""; diff --git a/packages/backend/src/graphql/resolvers/auth.ts b/packages/backend/src/graphql/resolvers/auth.ts index 6f0344f..0cc2d12 100644 --- a/packages/backend/src/graphql/resolvers/auth.ts +++ b/packages/backend/src/graphql/resolvers/auth.ts @@ -129,7 +129,11 @@ builder.mutationField("register", (t) => email_verified: false, }); - await sendVerification(args.email, verifyToken); + try { + await sendVerification(args.email, verifyToken); + } catch (e) { + console.warn("Failed to send verification email:", (e as Error).message); + } return true; }, }), @@ -184,7 +188,11 @@ builder.mutationField("requestPasswordReset", (t) => .set({ password_reset_token: token, password_reset_expiry: expiry }) .where(eq(users.id, user[0].id)); - await sendPasswordReset(args.email, token); + try { + await sendPasswordReset(args.email, token); + } catch (e) { + console.warn("Failed to send password reset email:", (e as Error).message); + } return true; }, }), diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts index 1390743..7e01144 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -1,10 +1,9 @@ -import Fastify from "fastify"; +import Fastify, { FastifyRequest, FastifyReply } from "fastify"; import fastifyCookie from "@fastify/cookie"; import fastifyCors from "@fastify/cors"; import fastifyMultipart from "@fastify/multipart"; import fastifyStatic from "@fastify/static"; import { createYoga } from "graphql-yoga"; -import { Readable } from "stream"; import path from "path"; import { schema } from "./graphql/index"; import { buildContext } from "./graphql/context"; @@ -44,34 +43,24 @@ async function main() { decorateReply: false, }); - const yoga = createYoga({ + const yoga = createYoga<{ req: FastifyRequest; reply: FastifyReply; db: typeof db; redis: typeof redis }>({ schema, context: buildContext, graphqlEndpoint: "/graphql", healthCheckEndpoint: "/health", logging: { - debug: (msg: string) => fastify.log.debug(msg), - info: (msg: string) => fastify.log.info(msg), - warn: (msg: string) => fastify.log.warn(msg), - error: (msg: string) => fastify.log.error(msg), + debug: (...args) => args.forEach((arg) => fastify.log.debug(arg)), + info: (...args) => args.forEach((arg) => fastify.log.info(arg)), + warn: (...args) => args.forEach((arg) => fastify.log.warn(arg)), + error: (...args) => args.forEach((arg) => fastify.log.error(arg)), }, }); fastify.route({ url: "/graphql", method: ["GET", "POST", "OPTIONS"], - handler: async (request, reply) => { - const response = await yoga.handleNodeRequestAndResponse( - request.raw, - reply.raw, - { reply, db, redis }, - ); - reply.status(response.status); - for (const [key, value] of response.headers.entries()) { - reply.header(key, value); - } - return reply.send(Readable.from(response.body)); - }, + handler: (req, reply) => + yoga.handleNodeRequestAndResponse(req, reply, { req, reply, db, redis }), }); fastify.get("/health", async (_request, reply) => { diff --git a/packages/backend/src/scripts/migrate.ts b/packages/backend/src/scripts/migrate.ts index 6d666d5..8083c30 100644 --- a/packages/backend/src/scripts/migrate.ts +++ b/packages/backend/src/scripts/migrate.ts @@ -11,7 +11,11 @@ const db = drizzle(pool); async function main() { console.log("Running schema migrations..."); - const migrationsFolder = path.join(__dirname, "../../migrations"); + // In dev (tsx): __dirname = src/scripts → migrations are at src/migrations + // In prod (node dist): __dirname = dist/scripts → migrations are at ../../migrations (package root) + const migrationsFolder = __dirname.includes("/src/") + ? path.join(__dirname, "../migrations") + : path.join(__dirname, "../../migrations"); await migrate(db, { migrationsFolder }); console.log("Schema migrations complete."); await pool.end();