Skip to content
Draft
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
92 changes: 92 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
name: CI

on:
push:
branches:
- main
pull_request:
branches:
- '**'

jobs:
backend:
name: Backend Tests & Linting
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Create backend .env file
run: |
cat > backend/.env << EOF
NODE_ENV=test
PORT=3001
DATABASE_URL=postgresql://devuser:devpassword@postgres:5432/devdb
DIRECT_URL=postgresql://devuser:devpassword@postgres:5432/devdb
SYNC_FROM_PRODUCTION=false
TMDB_API_TOKEN=dummy-token-for-ci-tests
SUPABASE_JWT_SECRET=dummy-jwt-secret-for-ci-tests
SUPABASE_URL=https://dummy.supabase.co
EOF

- name: Start services
run: docker compose -f docker-compose.ci.yml up -d

- name: Wait for services to be healthy
run: |
echo "Waiting for PostgreSQL to be ready..."
timeout 60 bash -c 'until docker compose -f docker-compose.ci.yml exec -T postgres pg_isready -U devuser -d devdb; do sleep 2; done'
echo "PostgreSQL is ready!"

echo "Waiting for backend to be ready..."
sleep 10

- name: Check service logs
if: failure()
run: docker compose -f docker-compose.ci.yml logs

# Commented out to avoid merge conflicts for now
# - name: Run backend linting
# run: docker compose -f docker-compose.ci.yml exec -T backend npm run lint

- name: Run backend tests
run: docker compose -f docker-compose.ci.yml exec -T backend npm run test

- name: Cleanup
if: always()
run: docker compose -f docker-compose.ci.yml down -v

frontend:
name: Frontend Tests & Linting
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22'

- name: Install dependencies
run: |
cd frontend
npm install

- name: Run TypeScript type checking
run: |
cd frontend
npx tsc --noEmit

# Commented out to avoid merge conflicts for now
# - name: Run frontend linting
# run: |
# cd frontend
# npm run lint

- name: Run frontend tests
run: |
cd frontend
npm run test -- --run
40 changes: 40 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,51 @@ npx prisma generate # Regenerate client
### Frontend (Expo)
```bash
npm run frontend:start # Start Expo dev server
npm run frontend:test # Run tests
npm run frontend:lint # Lint code
npm run frontend:type-check # TypeScript type checking
npm run frontend:android # Run on Android
npm run frontend:ios # Run on iOS
npm run frontend:clean # Clean rebuild
```

### Linting & Type Checking (Run Locally Before Pushing)

To check your code before pushing (same checks that run in CI):

```bash
# Check everything
npm run lint # Run linting for both backend and frontend
npm run type-check # Run TypeScript type checking for frontend

# Or check individually
npm run backend:lint # Lint backend only
npm run frontend:lint # Lint frontend only
npm run frontend:type-check # TypeScript check frontend only

# Auto-fix linting issues
npm run lint:fix # Fix linting for both backend and frontend
npm run backend:lint:fix # Fix backend linting only
npm run frontend:lint:fix # Fix frontend linting only
```

**Order for local pre-push checks:**
```bash
# 1. Install dependencies
npm run install:all

# 2. Start backend
npm run backend:start

# 3. Run linting and type checks
npm run lint # Lint both backend and frontend
npm run type-check # TypeScript type checking

# 4. Run tests
npm run backend:test # Backend tests
npm run frontend:test # Frontend tests
```

## Database Usage

**In development, you ONLY use local PostgreSQL - production is never touched (😅):**
Expand Down
6 changes: 3 additions & 3 deletions backend/.eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ module.exports = {
sourceType: 'module',
project: './tsconfig.json',
},
plugins: ['@typescript-eslint', 'prettier'],
plugins: ['@typescript-eslint' /* , 'prettier' */],
extends: [
'eslint:recommended',
'prettier',
// 'prettier',
],
root: true,
env: {
Expand All @@ -23,7 +23,7 @@ module.exports = {
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-explicit-any': 'warn',
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
'prettier/prettier': 'error',
// 'prettier/prettier': 'error',
'prefer-const': 'error',
'no-var': 'error',
},
Expand Down
8 changes: 8 additions & 0 deletions backend/.prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"singleQuote": true,
"trailingComma": "es5",
"tabWidth": 2,
"semi": true,
"printWidth": 80
}

5 changes: 4 additions & 1 deletion backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,16 @@
"dependencies": {
"@prisma/client": "^6.15.0",
"@scalar/express-api-reference": "^0.8.16",
"axios": "^1.7.0",
"cors": "^2.8.5",
"dotenv": "^17.2.2",
"express": "^4.21.2",
"jsonwebtoken": "^9.0.2",
"jwks-rsa": "^3.2.0",
"pg": "^8.16.3",
"prisma": "^6.15.0",
"swagger-autogen": "^2.23.7"
"swagger-autogen": "^2.23.7",
"uuid": "^10.0.0"
},
"devDependencies": {
"@types/cors": "^2.8.19",
Expand All @@ -41,6 +43,7 @@
"@types/node": "^24.3.0",
"@types/pg": "^8.11.10",
"@types/supertest": "^6.0.2",
"@types/uuid": "^10.0.0",
"@typescript-eslint/eslint-plugin": "^7.18.0",
"@typescript-eslint/parser": "^7.18.0",
"eslint": "^8.57.0",
Expand Down
110 changes: 59 additions & 51 deletions backend/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -111,20 +111,21 @@ model mfa_challenges {
/// This model or at least one of its fields has comments in the database, and requires an additional setup for migrations: Read more: https://pris.ly/d/database-comments
/// This model contains row level security and requires additional setup for migrations. Visit https://pris.ly/d/row-level-security for more info.
model mfa_factors {
id String @id @db.Uuid
user_id String @db.Uuid
friendly_name String?
factor_type factor_type
status factor_status
created_at DateTime @db.Timestamptz(6)
updated_at DateTime @db.Timestamptz(6)
secret String?
phone String?
last_challenged_at DateTime? @unique @db.Timestamptz(6)
web_authn_credential Json?
web_authn_aaguid String? @db.Uuid
mfa_challenges mfa_challenges[]
users auth_users @relation(fields: [user_id], references: [id], onDelete: Cascade, onUpdate: NoAction)
id String @id @db.Uuid
user_id String @db.Uuid
friendly_name String?
factor_type factor_type
status factor_status
created_at DateTime @db.Timestamptz(6)
updated_at DateTime @db.Timestamptz(6)
secret String?
phone String?
last_challenged_at DateTime? @unique @db.Timestamptz(6)
web_authn_credential Json?
web_authn_aaguid String? @db.Uuid
last_webauthn_challenge_data Json?
mfa_challenges mfa_challenges[]
users auth_users @relation(fields: [user_id], references: [id], onDelete: Cascade, onUpdate: NoAction)

@@unique([user_id, phone], map: "unique_phone_factor_per_user")
@@index([user_id, created_at], map: "factor_id_created_at_idx")
Expand Down Expand Up @@ -285,22 +286,24 @@ model schema_migrations {
/// This model or at least one of its fields has comments in the database, and requires an additional setup for migrations: Read more: https://pris.ly/d/database-comments
/// This model contains row level security and requires additional setup for migrations. Visit https://pris.ly/d/row-level-security for more info.
model sessions {
id String @id @db.Uuid
user_id String @db.Uuid
created_at DateTime? @db.Timestamptz(6)
updated_at DateTime? @db.Timestamptz(6)
factor_id String? @db.Uuid
aal aal_level?
not_after DateTime? @db.Timestamptz(6)
refreshed_at DateTime? @db.Timestamp(6)
user_agent String?
ip String? @db.Inet
tag String?
oauth_client_id String? @db.Uuid
mfa_amr_claims mfa_amr_claims[]
refresh_tokens refresh_tokens[]
oauth_clients oauth_clients? @relation(fields: [oauth_client_id], references: [id], onDelete: Cascade, onUpdate: NoAction)
users auth_users @relation(fields: [user_id], references: [id], onDelete: Cascade, onUpdate: NoAction)
id String @id @db.Uuid
user_id String @db.Uuid
created_at DateTime? @db.Timestamptz(6)
updated_at DateTime? @db.Timestamptz(6)
factor_id String? @db.Uuid
aal aal_level?
not_after DateTime? @db.Timestamptz(6)
refreshed_at DateTime? @db.Timestamp(6)
user_agent String?
ip String? @db.Inet
tag String?
oauth_client_id String? @db.Uuid
refresh_token_hmac_key String?
refresh_token_counter BigInt?
mfa_amr_claims mfa_amr_claims[]
refresh_tokens refresh_tokens[]
oauth_clients oauth_clients? @relation(fields: [oauth_client_id], references: [id], onDelete: Cascade, onUpdate: NoAction)
users auth_users @relation(fields: [user_id], references: [id], onDelete: Cascade, onUpdate: NoAction)

@@index([not_after(sort: Desc)])
@@index([oauth_client_id])
Expand Down Expand Up @@ -397,49 +400,54 @@ model auth_users {
}

model Comment {
id String @id
userId String @db.Uuid
ratingId String?
postId String?
content String
createdAt DateTime @default(now())
Post Post? @relation(fields: [postId], references: [id])
Rating Rating? @relation(fields: [ratingId], references: [id])
UserProfile UserProfile @relation(fields: [userId], references: [userId])
id String @id @default(uuid())
userId String @db.Uuid
ratingId String?
postId String?
content String
parentId String?
createdAt DateTime @default(now())

parent_comment Comment? @relation("CommentThread", fields: [parentId], references: [id], onDelete: SetNull, onUpdate: NoAction)
child_comment Comment[] @relation("CommentThread")

Post Post? @relation("PostComments", fields: [postId], references: [id], onDelete: Cascade, onUpdate: NoAction)
Rating Rating? @relation("RatingComments", fields: [ratingId], references: [id], onDelete: Cascade, onUpdate: NoAction)
UserProfile UserProfile @relation("UserComments", fields: [userId], references: [userId], onDelete: Cascade, onUpdate: NoAction)

@@schema("public")
}

model Post {
id String @id
id String @id @default(uuid())
userId String @db.Uuid
content String
type PostType
votes Int @default(0)
createdAt DateTime @default(now())
Comment Comment[]
UserProfile UserProfile @relation(fields: [userId], references: [userId])
Comment Comment[] @relation("PostComments")
UserProfile UserProfile @relation("UserPosts", fields: [userId], references: [userId], onDelete: Cascade, onUpdate: NoAction)

@@schema("public")
}

model Rating {
id String @id
id String @id @default(uuid())
userId String @db.Uuid
movieId String
stars Int
comment String?
tags String[] @default([])
date DateTime
votes Int @default(0)
Comment Comment[]
UserProfile UserProfile @relation(fields: [userId], references: [userId])
Comment Comment[] @relation("RatingComments")
UserProfile UserProfile @relation("UserRatings", fields: [userId], references: [userId], onDelete: Cascade, onUpdate: NoAction)

@@schema("public")
}

model UserFollow {
id String @id
id String @id @default(uuid())
followerId String @db.Uuid
followingId String @db.Uuid
UserProfile_UserFollow_followerIdToUserProfile UserProfile @relation("UserFollow_followerIdToUserProfile", fields: [followerId], references: [userId])
Expand All @@ -456,10 +464,10 @@ model UserProfile {
preferredCategories String[] @default([])
favoriteMovies String[] @default([])
createdAt DateTime @default(now())
updatedAt DateTime
Comment Comment[]
Post Post[]
Rating Rating[]
updatedAt DateTime @default(now()) @updatedAt
Comment Comment[] @relation("UserComments")
Post Post[] @relation("UserPosts")
Rating Rating[] @relation("UserRatings")
UserFollow_UserFollow_followerIdToUserProfile UserFollow[] @relation("UserFollow_followerIdToUserProfile")
UserFollow_UserFollow_followingIdToUserProfile UserFollow[] @relation("UserFollow_followingIdToUserProfile")

Expand All @@ -476,7 +484,7 @@ model bootcamp {
}

model local_event {
id String @id(map: "local_events_pkey") @db.Uuid
id String @id(map: "local_events_pkey") @default(uuid()) @db.Uuid
title String
time DateTime? @default(dbgenerated("(now() AT TIME ZONE 'utc'::text)")) @db.Timestamptz(6)
description String
Expand Down
Loading