Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 0 additions & 88 deletions .github/workflows/backend-ci.yml

This file was deleted.

42 changes: 42 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
name: Backend CI/CD
on:
pull_request:
paths:
- "apps/backend/**"
- "packages/**"
- "pnpm-lock.yaml"
- "turbo.json"
- "package.json"

jobs:
test:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 10.0.0

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "23"
cache: "pnpm"

- name: Cache pnpm dependencies
uses: actions/cache@v4
with:
path: ~/.pnpm-store
key: ${{ runner.os }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Run tests
working-directory: apps/backend
run: pnpm run test
4 changes: 0 additions & 4 deletions apps/backend/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,10 @@ IMPORT_SERIES_QUEUE=import-series-queue
MAL_CLIENT_ID=banana

# Feature Flags
ENABLE_CERTS=false
ENABLE_IMPORT_MOVIES=true
ENABLE_IMPORT_SERIES=true
ENABLE_SQS=true
ENABLE_CRON_JOBS=false

# Certs
ENABLE_CERTS=false

# OpenAI
OPENAI_API_KEY=
50 changes: 0 additions & 50 deletions apps/backend/.env.test

This file was deleted.

4 changes: 0 additions & 4 deletions apps/backend/deploy/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -195,10 +195,6 @@ const app = new awsx.classic.ecs.FargateService('aws-host-app', {
name: 'IMPORT_SERIES_QUEUE',
valueFrom: '/plotwist/prod/IMPORT_SERIES_QUEUE',
},
{
name: 'ENABLE_CERTS',
valueFrom: '/plotwist/prod/ENABLE_CERTS',
},
{
name: 'ENABLE_SQS',
valueFrom: '/plotwist/prod/ENABLE_SQS',
Expand Down
3 changes: 1 addition & 2 deletions apps/backend/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,10 @@ services:
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: POSTGRES_DB
POSTGRES_DB: plotwist_db
POSTGRESQL_REPLICATION_USE_PASSFILE: "no"
volumes:
- postgres_data:/var/lib/postgresql/data
- ./priv/init-db.sh:/docker-entrypoint-initdb.d/init-db.sh
ports:
- 5432:5432
networks:
Expand Down
2 changes: 1 addition & 1 deletion apps/backend/drizzle.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { config } from '@/config'

export default {
schema: 'src/db/schema/index.ts',
out: 'priv/migrations',
out: 'src/db/migrations',
dialect: 'postgresql',
dbCredentials: { url: config.db.DATABASE_URL },
schemaFilter: ['auth', 'public'],
Expand Down
12 changes: 6 additions & 6 deletions apps/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,9 @@
"db:migrate": "dotenv -e .env -- drizzle-kit migrate",
"db:migrate:test": "dotenv -e .env.test -- drizzle-kit migrate",
"db:studio": "dotenv -e .env -- drizzle-kit studio",
"pretest": "pnpm run db:migrate:test",
"test": "dotenv -e .env.test -- vitest",
"test:ui": "dotenv -e .env.test -- vitest --ui",
"test:coverage": "dotenv -e .env.test -- vitest --coverage",
"test": "dotenv -e .env.test -- vitest --config vitest.config.ts",
"test:ui": "dotenv -e .env.test -- vitest --config vitest.config.ts --ui",
"test:coverage": "dotenv -e .env.test -- vitest --config vitest.config.ts --coverage",
"format": "biome check src --write",
"email:dev": "email dev --dir src/emails",
"typecheck": "tsc --noEmit",
Expand All @@ -41,8 +40,6 @@
"@types/node-cron": "^3.0.11",
"@types/react": "^19.2.7",
"@types/react-dom": "^19.2.3",
"react": "^19.2.3",
"react-dom": "^19.2.3",
"@vitest/coverage-v8": "^4.0.16",
"adm-zip": "^0.5.16",
"axios": "^1.13.2",
Expand All @@ -64,6 +61,8 @@
"pino-pretty": "^13.1.3",
"postgres": "^3.4.7",
"puppeteer": "^24.34.0",
"react": "^19.2.3",
"react-dom": "^19.2.3",
"resend": "^6.6.0",
"stripe": "^20.1.0",
"typeid-js": "^1.2.0"
Expand All @@ -81,6 +80,7 @@
"ioredis-mock": "^8.13.1",
"localstack": "^1.0.0",
"react-email": "^5.1.1",
"testcontainers": "^11.11.0",
"tsup": "^8.5.1",
"tsx": "^4.21.0",
"typescript": "^5.9.3",
Expand Down
10 changes: 0 additions & 10 deletions apps/backend/priv/init-db.sh

This file was deleted.

1 change: 0 additions & 1 deletion apps/backend/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,6 @@ function loadSQSQueues() {

function loadFeatureFlags() {
const schema = z.object({
ENABLE_CERTS: z.string(),
ENABLE_IMPORT_MOVIES: z.string(),
ENABLE_IMPORT_SERIES: z.string(),
ENABLE_SQS: z.string(),
Expand Down
58 changes: 58 additions & 0 deletions apps/backend/src/db/utils/postgres-errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -383,3 +383,61 @@ export enum PgInternalError {
DataCorrupted = 'XX001',
IndexCorrupted = 'XX002',
}

// Utility functions to handle Drizzle-wrapped PostgresErrors

import postgres from 'postgres'

/**
* Extracts PostgresError from either the error itself or its cause.
* Drizzle ORM wraps PostgresError in a DrizzleQueryError with the original in `cause`.
*/
export function getPostgresError(
error: unknown
): postgres.PostgresError | null {
if (error instanceof postgres.PostgresError) {
return error
}

const cause = (error as { cause?: unknown })?.cause
if (cause instanceof postgres.PostgresError) {
return cause
}

return null
}

/**
* Checks if the error is a unique constraint violation (duplicate key).
*/
export function isUniqueViolation(error: unknown): boolean {
const pgError = getPostgresError(error)
return pgError?.code === PgIntegrityConstraintViolation.UniqueViolation
}

/**
* Checks if the error is a foreign key constraint violation.
*/
export function isForeignKeyViolation(error: unknown): boolean {
const pgError = getPostgresError(error)
return pgError?.code === PgIntegrityConstraintViolation.ForeignKeyViolation
}

/**
* Checks if the error is a not null constraint violation.
*/
export function isNotNullViolation(error: unknown): boolean {
const pgError = getPostgresError(error)
return pgError?.code === PgIntegrityConstraintViolation.NotNullViolation
}

/**
* Checks if the error matches a specific constraint name.
*/
export function isConstraintViolation(
error: unknown,
constraintName: string
): boolean {
const pgError = getPostgresError(error)
return pgError?.constraint_name === constraintName
}
9 changes: 3 additions & 6 deletions apps/backend/src/domain/services/follows/create-follow.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import postgres from 'postgres'
import { insertFollow } from '@/db/repositories/followers-repository'
import { PgIntegrityConstraintViolation } from '@/db/utils/postgres-errors'
import { isUniqueViolation } from '@/db/utils/postgres-errors'
import { FollowAlreadyRegisteredError } from '@/domain/errors/follow-already-registered'

export type CreateFollowServiceInput = {
Expand All @@ -17,10 +16,8 @@ export async function createFollowService({

return { follow }
} catch (error) {
if (error instanceof postgres.PostgresError) {
if (error.code === PgIntegrityConstraintViolation.UniqueViolation) {
return new FollowAlreadyRegisteredError()
}
if (isUniqueViolation(error)) {
return new FollowAlreadyRegisteredError()
}

throw error
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import postgres from 'postgres'
import { insertUserImport } from '@/db/repositories/user-import-repository'
import { PgIntegrityConstraintViolation } from '@/db/utils/postgres-errors'
import { isForeignKeyViolation } from '@/db/utils/postgres-errors'
import type { InsertUserImportWithItems } from '@/domain/entities/import'
import { FailedToInsertUserImport } from '@/domain/errors/failed-to-import-user-items'
import { UserNotFoundError } from '@/domain/errors/user-not-found'
Expand All @@ -9,10 +8,8 @@ export async function createUserImport(params: InsertUserImportWithItems) {
try {
return await insertUserImport(params)
} catch (error) {
if (error instanceof postgres.PostgresError) {
if (error.code === PgIntegrityConstraintViolation.ForeignKeyViolation) {
return new UserNotFoundError()
}
if (isForeignKeyViolation(error)) {
return new UserNotFoundError()
}
throw new FailedToInsertUserImport()
}
Expand Down
Loading