Skip to content

Commit 39f04e5

Browse files
committed
chore: initial commit — @backendworks/post-db package with CI/CD
0 parents  commit 39f04e5

23 files changed

Lines changed: 954 additions & 0 deletions

.env.example

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Database
2+
DATABASE_URL=postgresql://USER:PASSWORD@HOST:5432/DB?schema=post

.github/copilot-instructions.md

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
# Copilot Instructions – @backendworks/post-db
2+
3+
## Overview
4+
5+
Shared npm package published to **GitHub Packages** (`@backendworks/post-db`). Owns the **Post Prisma schema**, all database migrations, and exposes an **ORM-agnostic repository interface** so `post-service` and `post-worker` never import `PrismaClient` directly.
6+
7+
Both `post-service` and `post-worker` must always pin to the **same version range** of this package.
8+
9+
## Versioning Rules
10+
11+
| Change type | Version bump |
12+
| ------------------------------------ | ------------ |
13+
| Schema change (new field, new model) | **minor** |
14+
| Breaking interface change | **major** |
15+
| Internal Prisma query fix | **patch** |
16+
17+
## Developer Workflows
18+
19+
Run all commands from the `packages/post-db/` directory:
20+
21+
```bash
22+
npm run prisma:migrate # dotenv -e .env.docker -- prisma migrate dev
23+
npm run prisma:generate # regenerates Prisma client from schema
24+
npm run prisma:studio # opens Prisma Studio against the post DB
25+
npm run build # tsc compile → dist/ (run before publishing)
26+
npm publish # publishes to GitHub Packages (requires GITHUB_TOKEN)
27+
```
28+
29+
## ORM-Agnostic Design
30+
31+
The package exposes interfaces, not Prisma types. Apps interact only with the public API:
32+
33+
```typescript
34+
import {
35+
createPostDbManager,
36+
IPostDbManager,
37+
IPostRepository,
38+
} from "@backendworks/post-db";
39+
40+
// Wire up once at module init (NestJS provider factory)
41+
const dbManager: IPostDbManager = createPostDbManager(process.env.DATABASE_URL);
42+
43+
// Inject IPostRepository in services — never PrismaClient
44+
const post = await dbManager.postRepository.findById(postId);
45+
```
46+
47+
### Swapping the ORM
48+
49+
To replace Prisma with TypeORM or Drizzle:
50+
51+
1. Replace `src/client/prisma.client.ts` with the new client factory
52+
2. Replace `src/repositories/post.repository.ts` with a new implementation of `IPostRepository`
53+
3. **Do not touch** `src/interfaces/` or `src/index.ts` — the contract stays the same
54+
4. Run `npm run build` and bump the package version
55+
56+
## Schema Ownership
57+
58+
`prisma/schema.prisma` is the **single source of truth** for the post database. No app or worker has its own `schema.prisma`.
59+
60+
Current schema covers:
61+
62+
- `Post` — id (UUID), title, content, images (String[]), createdBy (UUID — references User in auth DB), deletedAt?, isDeleted (Boolean), createdAt, updatedAt
63+
64+
Note: `createdBy` is a plain UUID — there is **no foreign key** to the auth DB. Author enrichment happens at the service layer via `GrpcAuthService.getUserById()`.
65+
66+
### Running a migration
67+
68+
```bash
69+
# From packages/post-db/
70+
npm run prisma:migrate # prompts for migration name, applies to dev DB
71+
```
72+
73+
Always bump the package **minor version** after a schema change and update both `post-service` and `post-worker` to the new version.
74+
75+
## Public API (`src/index.ts`)
76+
77+
```typescript
78+
export { IPostDbManager } from "./interfaces/db-manager.interface";
79+
export { IPostRepository } from "./interfaces/post.repository.interface";
80+
export { createPostDbManager } from "./client/prisma.client";
81+
```
82+
83+
Never export `PrismaClient`, raw Prisma types, or implementation details.
84+
85+
## Folder Structure
86+
87+
```
88+
packages/post-db/
89+
prisma/
90+
schema.prisma # Post — source of truth for post DB
91+
migrations/ # All post DB migrations — never edit manually
92+
src/
93+
client/
94+
prisma.client.ts # PrismaClient singleton + createPostDbManager() factory
95+
interfaces/
96+
post.repository.interface.ts # IPostRepository — ORM-agnostic CRUD contract
97+
db-manager.interface.ts # IPostDbManager { postRepository: IPostRepository }
98+
repositories/
99+
post.repository.ts # Concrete Prisma implementation of IPostRepository
100+
operations/
101+
base.operations.ts # Generic helpers: findById, findOne, findMany,
102+
# create, update, softDelete (sets deletedAt + isDeleted)
103+
index.ts # Public API — ONLY thing consumers should import from
104+
package.json # name: "@backendworks/post-db", "main": "dist/index.js"
105+
tsconfig.json # Compiles src/ → dist/
106+
```
107+
108+
## IPostRepository Interface Contract
109+
110+
```typescript
111+
interface IPostRepository {
112+
findById(id: string): Promise<Post | null>;
113+
findOne(filter: Partial<Post>): Promise<Post | null>;
114+
findMany(options: QueryOptions): Promise<PaginatedResult<Post>>;
115+
create(data: CreatePostInput): Promise<Post>;
116+
update(id: string, data: Partial<Post>): Promise<Post>;
117+
softDelete(id: string): Promise<Post>; // sets deletedAt + isDeleted: true
118+
softDeleteMany(ids: string[]): Promise<number>; // bulk soft-delete, returns count
119+
}
120+
```
121+
122+
All methods treat `deletedAt: null` as the baseline — soft-deleted posts are never returned unless explicitly queried.
123+
124+
## Testing Conventions
125+
126+
- Unit tests in `test/unit/` with 100% coverage on `*.repository.ts` and `*.operations.ts`
127+
- Mock `PrismaClient` at the method level: `prisma.post.findUnique = jest.fn()`
128+
- Integration tests (if any) run against a real Docker Postgres — never the production DB

.github/workflows/ci.yml

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main, develop]
6+
pull_request:
7+
branches: [main, develop]
8+
9+
jobs:
10+
lint-and-build:
11+
name: Lint, Format & Build
12+
runs-on: ubuntu-latest
13+
14+
steps:
15+
- name: Checkout
16+
uses: actions/checkout@v4
17+
18+
- name: Setup Node
19+
uses: actions/setup-node@v4
20+
with:
21+
node-version: 20
22+
registry-url: https://npm.pkg.github.com
23+
scope: "@backendworks"
24+
cache: npm
25+
26+
- name: Install dependencies
27+
run: npm ci
28+
env:
29+
NODE_AUTH_TOKEN: ${{ secrets.GH_TOKEN }}
30+
31+
- name: Generate Prisma client
32+
run: npx prisma generate
33+
34+
- name: Lint
35+
run: npm run lint
36+
37+
- name: Format check
38+
run: npx prettier --check "src/**/*.ts"
39+
40+
- name: Build
41+
run: npm run build

.github/workflows/deploy.yml

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
name: Deploy (GitHub Packages)
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
version_bump:
7+
description: "Version bump type"
8+
required: true
9+
default: patch
10+
type: choice
11+
options:
12+
- patch
13+
- minor
14+
- major
15+
push:
16+
branches:
17+
- main
18+
paths:
19+
- "src/**"
20+
- "prisma/**"
21+
- "package.json"
22+
23+
permissions:
24+
contents: write
25+
packages: write
26+
27+
jobs:
28+
publish:
29+
name: Publish to GitHub Packages
30+
runs-on: ubuntu-latest
31+
32+
steps:
33+
- name: Checkout
34+
uses: actions/checkout@v4
35+
with:
36+
token: ${{ secrets.GH_TOKEN }}
37+
fetch-depth: 0
38+
39+
- name: Setup Node
40+
uses: actions/setup-node@v4
41+
with:
42+
node-version: 20
43+
registry-url: https://npm.pkg.github.com
44+
scope: "@backendworks"
45+
cache: npm
46+
47+
- name: Install dependencies
48+
run: npm ci
49+
env:
50+
NODE_AUTH_TOKEN: ${{ secrets.GH_TOKEN }}
51+
52+
- name: Generate Prisma client
53+
run: npx prisma generate
54+
55+
- name: Build
56+
run: npm run build
57+
58+
- name: Configure git
59+
run: |
60+
git config user.name "github-actions[bot]"
61+
git config user.email "github-actions[bot]@users.noreply.github.com"
62+
63+
- name: Determine version bump
64+
id: bump
65+
run: |
66+
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
67+
echo "type=${{ inputs.version_bump }}" >> $GITHUB_OUTPUT
68+
else
69+
echo "type=patch" >> $GITHUB_OUTPUT
70+
fi
71+
72+
- name: Bump version
73+
id: version
74+
run: |
75+
npm version ${{ steps.bump.outputs.type }} --no-git-tag-version
76+
VERSION=$(node -p "require('./package.json').version")
77+
echo "new_version=$VERSION" >> $GITHUB_OUTPUT
78+
79+
- name: Commit and tag version
80+
run: |
81+
git add package.json
82+
git commit -m "chore(release): @backendworks/post-db@${{ steps.version.outputs.new_version }}"
83+
git tag "v${{ steps.version.outputs.new_version }}"
84+
git push origin HEAD:main --follow-tags
85+
86+
- name: Publish to GitHub Packages
87+
run: npm publish
88+
env:
89+
NODE_AUTH_TOKEN: ${{ secrets.GH_TOKEN }}
90+
91+
- name: Create GitHub Release
92+
uses: softprops/action-gh-release@v2
93+
with:
94+
tag_name: v${{ steps.version.outputs.new_version }}
95+
name: "@backendworks/post-db v${{ steps.version.outputs.new_version }}"
96+
body: |
97+
## @backendworks/post-db v${{ steps.version.outputs.new_version }}
98+
99+
Published to [GitHub Packages](https://github.com/orgs/backendworks/packages).
100+
101+
### Install
102+
```bash
103+
npm install @backendworks/post-db@${{ steps.version.outputs.new_version }}
104+
```
105+
generate_release_notes: true
106+
token: ${{ secrets.GH_TOKEN }}

.github/workflows/publish.yml

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
# Reusable workflow: build, bump version, publish to GitHub Packages, create GitHub Release
2+
# Called by release.yml in each package repo
3+
name: Publish Package (reusable)
4+
5+
on:
6+
workflow_call:
7+
inputs:
8+
package-path:
9+
description: "Relative path to the package directory (e.g. packages/post-db)"
10+
required: true
11+
type: string
12+
package-name:
13+
description: "npm package name (e.g. @backendworks/post-db)"
14+
required: true
15+
type: string
16+
bump:
17+
description: "Version bump type: patch | minor | major"
18+
required: false
19+
type: string
20+
default: "patch"
21+
secrets:
22+
GH_TOKEN:
23+
required: true
24+
25+
jobs:
26+
publish:
27+
name: Build & Publish ${{ inputs.package-name }}
28+
runs-on: ubuntu-latest
29+
permissions:
30+
contents: write # push version tag + commit
31+
packages: write # publish to GitHub Packages
32+
id-token: write # provenance attestation
33+
34+
defaults:
35+
run:
36+
working-directory: ${{ inputs.package-path }}
37+
38+
steps:
39+
- name: Checkout
40+
uses: actions/checkout@v4
41+
with:
42+
token: ${{ secrets.GH_TOKEN }}
43+
fetch-depth: 0
44+
45+
- name: Setup Node
46+
uses: actions/setup-node@v4
47+
with:
48+
node-version: 20
49+
registry-url: "https://npm.pkg.github.com"
50+
scope: "@backendworks"
51+
52+
- name: Install dependencies
53+
run: npm ci
54+
55+
- name: Generate Prisma client
56+
run: npx prisma generate
57+
58+
- name: Build
59+
run: npm run build
60+
61+
- name: Configure git
62+
run: |
63+
git config user.name "github-actions[bot]"
64+
git config user.email "github-actions[bot]@users.noreply.github.com"
65+
66+
- name: Bump version
67+
id: bump
68+
run: |
69+
npm version ${{ inputs.bump }} --no-git-tag-version
70+
VERSION=$(node -p "require('./package.json').version")
71+
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
72+
73+
- name: Commit & tag
74+
run: |
75+
git add package.json
76+
git commit -m "chore(release): ${{ inputs.package-name }}@${{ steps.bump.outputs.version }}"
77+
git tag "${{ inputs.package-name }}@${{ steps.bump.outputs.version }}"
78+
git push origin HEAD --follow-tags
79+
env:
80+
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
81+
82+
- name: Publish to GitHub Packages
83+
run: npm publish
84+
env:
85+
NODE_AUTH_TOKEN: ${{ secrets.GH_TOKEN }}
86+
87+
- name: Create GitHub Release
88+
uses: softprops/action-gh-release@v2
89+
with:
90+
tag_name: "${{ inputs.package-name }}@${{ steps.bump.outputs.version }}"
91+
name: "${{ inputs.package-name }} v${{ steps.bump.outputs.version }}"
92+
generate_release_notes: true
93+
token: ${{ secrets.GH_TOKEN }}

0 commit comments

Comments
 (0)