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
19 changes: 7 additions & 12 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -1,14 +1,9 @@
node_modules
**/node_modules
dist
**/dist
.git
.github
.env
.env.*
!.env.example
*.log
*.tsbuildinfo
apps/api/prisma/dev.db
apps/api/prisma/dev.db-*
apps/api/src/generated/prisma
docs/rules.pdf
**/.env
**/node_modules
**/dist
**/*.db
docs
tasks
24 changes: 24 additions & 0 deletions .github/workflows/quality.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: Quality

on:
pull_request:
push:
branches:
- main

permissions:
contents: read

jobs:
verify:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.11
- run: bun install --frozen-lockfile
- run: bun run lint
- run: bun run typecheck
- run: bun run test
- run: bun run build
55 changes: 52 additions & 3 deletions apps/api/Dockerfile.lambda
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,71 @@ COPY apps/web/package.json apps/web/package.json
COPY packages/shared/package.json packages/shared/package.json
RUN bun install --frozen-lockfile

FROM oven/bun:1.3.11 AS production-deps
WORKDIR /app

COPY package.json bun.lock tsconfig.base.json ./
COPY apps/api/package.json apps/api/package.json
COPY apps/web/package.json apps/web/package.json
COPY packages/shared/package.json packages/shared/package.json
RUN bun install --production --frozen-lockfile

FROM deps AS build
WORKDIR /app

COPY package.json bun.lock tsconfig.base.json ./
COPY apps/api ./apps/api
COPY packages/shared ./packages/shared
RUN bun --cwd apps/api prisma generate \
&& bun --cwd apps/api prisma generate --config prisma-pg.config.ts

FROM deps AS migrations
WORKDIR /app

COPY package.json bun.lock tsconfig.base.json ./

RUN apt-get update \
&& apt-get install --yes --no-install-recommends ca-certificates curl \
&& mkdir -p /opt/rds-ca \
&& curl --fail --silent --show-error --location \
https://truststore.pki.rds.amazonaws.com/us-east-1/us-east-1-bundle.pem \
--output /opt/rds-ca/us-east-1-bundle.pem \
&& rm -rf /var/lib/apt/lists/*

COPY apps/api ./apps/api
COPY packages/shared ./packages/shared

ENV NODE_ENV=production
ENV RDS_CA_BUNDLE_PATH=/opt/rds-ca/us-east-1-bundle.pem

WORKDIR /app/apps/api
USER bun
CMD ["bun", "run", "prisma:migrate:pg"]

FROM oven/bun:1.3.11 AS runner
WORKDIR /app

COPY --from=public.ecr.aws/awsguru/aws-lambda-adapter:0.9.1 /lambda-adapter /opt/extensions/lambda-adapter
COPY --from=deps /app/node_modules ./node_modules
COPY --from=deps /app/apps/api/node_modules ./apps/api/node_modules
COPY --from=production-deps /app/node_modules ./node_modules
COPY --from=production-deps /app/apps/api/node_modules ./apps/api/node_modules
COPY package.json bun.lock tsconfig.base.json ./
COPY apps/api ./apps/api
COPY packages/shared ./packages/shared
COPY --from=build /app/apps/api/src/generated ./apps/api/src/generated

ENV NODE_ENV=production
ENV PORT=3000
ENV AWS_LWA_PORT=3000
ENV AWS_LWA_READINESS_CHECK_PATH=/health
ENV RDS_CA_BUNDLE_PATH=/opt/rds-ca/us-east-1-bundle.pem

RUN bun --cwd apps/api prisma generate
RUN apt-get update \
&& apt-get install --yes --no-install-recommends ca-certificates curl \
&& mkdir -p /opt/rds-ca \
&& curl --fail --silent --show-error --location \
https://truststore.pki.rds.amazonaws.com/us-east-1/us-east-1-bundle.pem \
--output /opt/rds-ca/us-east-1-bundle.pem \
&& rm -rf /var/lib/apt/lists/*

WORKDIR /app/apps/api
USER bun
Expand Down
18 changes: 11 additions & 7 deletions apps/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,19 @@
"bun": ">=1.3.0"
},
"scripts": {
"dev": "prisma generate && bun --watch src/index.ts",
"dev": "prisma generate && prisma generate --config prisma-pg.config.ts && bun --watch src/index.ts",
"start": "bun src/index.ts",
"build": "prisma generate && bun build src/index.ts --outdir dist --target bun",
"typecheck": "prisma generate && tsc --noEmit",
"build": "prisma generate && prisma generate --config prisma-pg.config.ts && bun build src/index.ts --outdir dist --target bun",
"typecheck": "prisma generate && prisma generate --config prisma-pg.config.ts && tsc --noEmit",
"prisma": "prisma",
"prisma:generate": "prisma generate",
"prisma:generate:pg": "prisma generate --schema prisma/schema-pg.prisma",
"prisma:generate:pg": "prisma generate --config prisma-pg.config.ts",
"prisma:migrate": "prisma migrate dev",
"prisma:migrate:pg": "prisma migrate deploy --schema prisma/schema-pg.prisma",
"prisma:migrate:pg": "prisma migrate deploy --config prisma-pg.config.ts",
"prisma:migrate:pg:status": "prisma migrate status --config prisma-pg.config.ts",
"prisma:verify:pg": "bun scripts/check-postgres-migrations.mjs",
"prisma:studio": "prisma studio",
"prisma:studio:pg": "prisma studio --schema prisma/schema-pg.prisma",
"prisma:studio:pg": "prisma studio --config prisma-pg.config.ts",
"lint": "eslint ."
},
"dependencies": {
Expand All @@ -29,8 +31,10 @@
"@libsql/client": "^0.17.3",
"@ppwl/shared": "workspace:*",
"@prisma/adapter-libsql": "^7.8.0",
"@prisma/adapter-pg": "^7.8.0",
"@prisma/client": "^7.8.0",
"elysia": "^1.4.28"
"elysia": "^1.4.28",
"pg": "^8.16.0"
},
"devDependencies": {
"@eslint/js": "^10.0.1",
Expand Down
26 changes: 26 additions & 0 deletions apps/api/prisma-pg.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import 'dotenv/config'
import { defineConfig } from 'prisma/config'

function getDatabaseUrl() {
const databaseUrl =
process.env.DATABASE_PG_URL ?? 'postgresql://postgres:postgres@localhost:5432/fakebook'
const rdsCaBundlePath = process.env.RDS_CA_BUNDLE_PATH

if (!rdsCaBundlePath) return databaseUrl

const url = new URL(databaseUrl)
url.searchParams.set('sslmode', 'require')
url.searchParams.set('sslcert', rdsCaBundlePath)
url.searchParams.set('sslaccept', 'strict')
return url.toString()
}

export default defineConfig({
schema: 'prisma/schema-pg.prisma',
migrations: {
path: 'prisma-pg/migrations',
},
datasource: {
url: getDatabaseUrl(),
},
})
152 changes: 152 additions & 0 deletions apps/api/prisma-pg/migrations/0_init/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
-- CreateSchema
CREATE SCHEMA IF NOT EXISTS "public";

-- CreateTable
CREATE TABLE "users" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"username" TEXT,
"email" TEXT NOT NULL,
"password_hash" TEXT,
"avatar_url" TEXT,
"bio" TEXT,
"email_verified_at" TIMESTAMP(3),
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,

CONSTRAINT "users_pkey" PRIMARY KEY ("id")
);

-- CreateTable
CREATE TABLE "accounts" (
"id" TEXT NOT NULL,
"user_id" TEXT NOT NULL,
"provider" TEXT NOT NULL,
"provider_account_id" TEXT NOT NULL,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,

CONSTRAINT "accounts_pkey" PRIMARY KEY ("id")
);

-- CreateTable
CREATE TABLE "sessions" (
"id" TEXT NOT NULL,
"user_id" TEXT NOT NULL,
"token" TEXT NOT NULL,
"expires_at" TIMESTAMP(3) NOT NULL,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,

CONSTRAINT "sessions_pkey" PRIMARY KEY ("id")
);

-- CreateTable
CREATE TABLE "posts" (
"id" TEXT NOT NULL,
"user_id" TEXT NOT NULL,
"content" TEXT NOT NULL,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,

CONSTRAINT "posts_pkey" PRIMARY KEY ("id")
);

-- CreateTable
CREATE TABLE "post_images" (
"id" TEXT NOT NULL,
"post_id" TEXT NOT NULL,
"image_url" TEXT NOT NULL,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,

CONSTRAINT "post_images_pkey" PRIMARY KEY ("id")
);

-- CreateTable
CREATE TABLE "comments" (
"id" TEXT NOT NULL,
"post_id" TEXT NOT NULL,
"user_id" TEXT NOT NULL,
"parent_comment_id" TEXT,
"content" TEXT NOT NULL,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,

CONSTRAINT "comments_pkey" PRIMARY KEY ("id")
);

-- CreateTable
CREATE TABLE "likes" (
"id" TEXT NOT NULL,
"post_id" TEXT NOT NULL,
"user_id" TEXT NOT NULL,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,

CONSTRAINT "likes_pkey" PRIMARY KEY ("id")
);

-- CreateTable
CREATE TABLE "notifications" (
"id" TEXT NOT NULL,
"recipient_id" TEXT NOT NULL,
"actor_id" TEXT,
"post_id" TEXT,
"type" TEXT NOT NULL,
"is_read" BOOLEAN NOT NULL DEFAULT false,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,

CONSTRAINT "notifications_pkey" PRIMARY KEY ("id")
);

-- CreateTable
CREATE TABLE "password_reset_tokens" (
"id" TEXT NOT NULL,
"user_id" TEXT NOT NULL,
"token" TEXT NOT NULL,
"expires_at" TIMESTAMP(3) NOT NULL,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,

CONSTRAINT "password_reset_tokens_pkey" PRIMARY KEY ("id")
);

-- CreateIndex
CREATE UNIQUE INDEX "users_username_key" ON "users"("username");
CREATE UNIQUE INDEX "users_email_key" ON "users"("email");
CREATE INDEX "accounts_user_id_idx" ON "accounts"("user_id");
CREATE UNIQUE INDEX "accounts_provider_provider_account_id_key" ON "accounts"("provider", "provider_account_id");
CREATE UNIQUE INDEX "sessions_token_key" ON "sessions"("token");
CREATE INDEX "sessions_user_id_idx" ON "sessions"("user_id");
CREATE INDEX "sessions_expires_at_idx" ON "sessions"("expires_at");
CREATE INDEX "posts_user_id_idx" ON "posts"("user_id");
CREATE INDEX "posts_created_at_idx" ON "posts"("created_at");
CREATE INDEX "post_images_post_id_idx" ON "post_images"("post_id");
CREATE INDEX "comments_post_id_idx" ON "comments"("post_id");
CREATE INDEX "comments_user_id_idx" ON "comments"("user_id");
CREATE INDEX "comments_parent_comment_id_idx" ON "comments"("parent_comment_id");
CREATE INDEX "likes_user_id_idx" ON "likes"("user_id");
CREATE UNIQUE INDEX "likes_post_id_user_id_key" ON "likes"("post_id", "user_id");
CREATE INDEX "notifications_recipient_id_is_read_idx" ON "notifications"("recipient_id", "is_read");
CREATE INDEX "notifications_actor_id_idx" ON "notifications"("actor_id");
CREATE INDEX "notifications_post_id_idx" ON "notifications"("post_id");
CREATE UNIQUE INDEX "password_reset_tokens_token_key" ON "password_reset_tokens"("token");
CREATE INDEX "password_reset_tokens_user_id_idx" ON "password_reset_tokens"("user_id");
CREATE INDEX "password_reset_tokens_expires_at_idx" ON "password_reset_tokens"("expires_at");

-- AddForeignKey
ALTER TABLE "accounts" ADD CONSTRAINT "accounts_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "sessions" ADD CONSTRAINT "sessions_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "posts" ADD CONSTRAINT "posts_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "post_images" ADD CONSTRAINT "post_images_post_id_fkey" FOREIGN KEY ("post_id") REFERENCES "posts"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "comments" ADD CONSTRAINT "comments_post_id_fkey" FOREIGN KEY ("post_id") REFERENCES "posts"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "comments" ADD CONSTRAINT "comments_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "comments" ADD CONSTRAINT "comments_parent_comment_id_fkey" FOREIGN KEY ("parent_comment_id") REFERENCES "comments"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "likes" ADD CONSTRAINT "likes_post_id_fkey" FOREIGN KEY ("post_id") REFERENCES "posts"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "likes" ADD CONSTRAINT "likes_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "notifications" ADD CONSTRAINT "notifications_recipient_id_fkey" FOREIGN KEY ("recipient_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "notifications" ADD CONSTRAINT "notifications_actor_id_fkey" FOREIGN KEY ("actor_id") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE;
ALTER TABLE "notifications" ADD CONSTRAINT "notifications_post_id_fkey" FOREIGN KEY ("post_id") REFERENCES "posts"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "password_reset_tokens" ADD CONSTRAINT "password_reset_tokens_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
DO $$
BEGIN
IF to_regclass('public."User"') IS NOT NULL THEN
INSERT INTO "users" (
"id",
"name",
"email",
"created_at",
"updated_at"
)
SELECT
'legacy-' || "id"::TEXT,
"name",
"email",
CURRENT_TIMESTAMP,
CURRENT_TIMESTAMP
FROM "User"
ON CONFLICT ("email") DO NOTHING;

DROP TABLE "User";
END IF;
END
$$;
3 changes: 3 additions & 0 deletions apps/api/prisma-pg/migrations/migration_lock.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Please do not edit this file manually
# It should be added in your version-control system (e.g., Git)
provider = "postgresql"
1 change: 0 additions & 1 deletion apps/api/prisma/schema-pg.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ generator client {

datasource db {
provider = "postgresql"
url = env("DATABASE_PG_URL")
}

model User {
Expand Down
Loading
Loading