Initial commit
This commit is contained in:
18
apps/backend/src/lib/logger.ts
Normal file
18
apps/backend/src/lib/logger.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import pino from 'pino'
|
||||
|
||||
const isProd = Bun.env.NODE_ENV === 'production'
|
||||
|
||||
export const logger = pino({
|
||||
level: Bun.env.LOG_LEVEL ?? (isProd ? 'info' : 'debug'),
|
||||
transport: isProd
|
||||
? undefined
|
||||
: {
|
||||
target: 'pino-pretty',
|
||||
options: {
|
||||
colorize: true,
|
||||
singleLine: true,
|
||||
translateTime: 'SYS:standard',
|
||||
ignore: 'pid,hostname',
|
||||
},
|
||||
},
|
||||
})
|
||||
51
apps/backend/src/lib/mailer.ts
Normal file
51
apps/backend/src/lib/mailer.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import nodemailer from 'nodemailer'
|
||||
|
||||
let cachedTransporter: ReturnType<typeof nodemailer.createTransport> | null = null
|
||||
|
||||
function getTransporter() {
|
||||
if (cachedTransporter) return cachedTransporter
|
||||
|
||||
const smtpHost = Bun.env.SMTP_HOST
|
||||
const smtpPort = Number(Bun.env.SMTP_PORT ?? 587)
|
||||
const smtpUser = Bun.env.SMTP_USER
|
||||
const smtpPass = Bun.env.SMTP_PASS
|
||||
|
||||
if (!smtpHost || !smtpUser || !smtpPass) {
|
||||
throw new Error(
|
||||
'Missing SMTP env vars. Set SMTP_HOST, SMTP_PORT, SMTP_USER and SMTP_PASS.',
|
||||
)
|
||||
}
|
||||
|
||||
cachedTransporter = nodemailer.createTransport({
|
||||
host: smtpHost,
|
||||
port: smtpPort,
|
||||
secure: smtpPort === 465,
|
||||
auth: {
|
||||
user: smtpUser,
|
||||
pass: smtpPass,
|
||||
},
|
||||
})
|
||||
|
||||
return cachedTransporter
|
||||
}
|
||||
|
||||
export async function sendMail(input: {
|
||||
to: string
|
||||
subject: string
|
||||
html: string
|
||||
text: string
|
||||
}) {
|
||||
const smtpFrom = Bun.env.SMTP_FROM
|
||||
|
||||
if (!smtpFrom) {
|
||||
throw new Error('Missing SMTP_FROM env var.')
|
||||
}
|
||||
|
||||
await getTransporter().sendMail({
|
||||
from: smtpFrom,
|
||||
to: input.to,
|
||||
subject: input.subject,
|
||||
html: input.html,
|
||||
text: input.text,
|
||||
})
|
||||
}
|
||||
50
apps/backend/src/lib/prisma.ts
Normal file
50
apps/backend/src/lib/prisma.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { PrismaPg } from '@prisma/adapter-pg'
|
||||
import { PrismaClient } from '@/generated/prisma/client'
|
||||
|
||||
const globalForPrisma = globalThis as unknown as { prisma?: PrismaClient }
|
||||
|
||||
function hasExpectedDelegates(client: PrismaClient) {
|
||||
const prismaClient = client as unknown as {
|
||||
court?: { findMany?: unknown }
|
||||
sport?: { findMany?: unknown }
|
||||
}
|
||||
|
||||
return (
|
||||
typeof prismaClient.court?.findMany === 'function' &&
|
||||
typeof prismaClient.sport?.findMany === 'function'
|
||||
)
|
||||
}
|
||||
|
||||
function buildPrismaClient() {
|
||||
const databaseUrl = Bun.env.DATABASE_URL
|
||||
|
||||
if (!databaseUrl) {
|
||||
throw new Error('Missing DATABASE_URL in backend environment.')
|
||||
}
|
||||
|
||||
const adapter = new PrismaPg({
|
||||
connectionString: databaseUrl,
|
||||
})
|
||||
|
||||
return new PrismaClient({ adapter })
|
||||
}
|
||||
|
||||
export function getPrismaClient() {
|
||||
if (globalForPrisma.prisma && hasExpectedDelegates(globalForPrisma.prisma)) {
|
||||
return globalForPrisma.prisma
|
||||
}
|
||||
|
||||
if (globalForPrisma.prisma && !hasExpectedDelegates(globalForPrisma.prisma)) {
|
||||
void globalForPrisma.prisma.$disconnect()
|
||||
}
|
||||
|
||||
const prisma = buildPrismaClient()
|
||||
|
||||
if (Bun.env.NODE_ENV !== 'production') {
|
||||
globalForPrisma.prisma = prisma
|
||||
}
|
||||
|
||||
return prisma
|
||||
}
|
||||
|
||||
export const db = getPrismaClient()
|
||||
26
apps/backend/src/lib/supabase-admin.ts
Normal file
26
apps/backend/src/lib/supabase-admin.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { createClient } from '@supabase/supabase-js'
|
||||
|
||||
let cachedClient: ReturnType<typeof createClient> | null = null
|
||||
|
||||
export function getSupabaseAdminClient() {
|
||||
if (cachedClient) return cachedClient
|
||||
|
||||
const supabaseUrl = Bun.env.SUPABASE_URL ?? Bun.env.VITE_SUPABASE_URL
|
||||
const supabaseServiceRoleKey = Bun.env.SUPABASE_SERVICE_ROLE_KEY
|
||||
|
||||
if (!supabaseUrl || !supabaseServiceRoleKey) {
|
||||
throw new Error(
|
||||
'Missing Supabase admin env vars. Set SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY in backend environment.',
|
||||
)
|
||||
}
|
||||
|
||||
cachedClient = createClient(supabaseUrl, supabaseServiceRoleKey, {
|
||||
auth: {
|
||||
autoRefreshToken: false,
|
||||
persistSession: false,
|
||||
detectSessionInUrl: false,
|
||||
},
|
||||
})
|
||||
|
||||
return cachedClient
|
||||
}
|
||||
19
apps/backend/src/lib/supabase.ts
Normal file
19
apps/backend/src/lib/supabase.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { createClient } from '@supabase/supabase-js'
|
||||
|
||||
const supabaseUrl = Bun.env.SUPABASE_URL ?? Bun.env.VITE_SUPABASE_URL
|
||||
const supabaseAnonKey =
|
||||
Bun.env.SUPABASE_ANON_KEY ?? Bun.env.VITE_SUPABASE_ANON_KEY
|
||||
|
||||
if (!supabaseUrl || !supabaseAnonKey) {
|
||||
throw new Error(
|
||||
'Missing Supabase env vars. Set SUPABASE_URL/SUPABASE_ANON_KEY (or VITE_SUPABASE_URL/VITE_SUPABASE_ANON_KEY) in backend environment.',
|
||||
)
|
||||
}
|
||||
|
||||
export const supabase = createClient(supabaseUrl, supabaseAnonKey, {
|
||||
auth: {
|
||||
autoRefreshToken: false,
|
||||
persistSession: false,
|
||||
detectSessionInUrl: false,
|
||||
},
|
||||
})
|
||||
Reference in New Issue
Block a user