Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
18f7dba
feat(drizzle): add Drizzle ORM schema definitions and infrastructure
dnplkndll Mar 11, 2026
066ae4f
feat(drizzle): add DrizzleRepo base classes, remove dead infrastructure
dnplkndll Mar 11, 2026
f9cbf37
feat(drizzle): migrate doing module repos to Drizzle ORM
dnplkndll Mar 11, 2026
febfe8a
feat(drizzle): migrate messaging module repos to Drizzle ORM
dnplkndll Mar 11, 2026
2312fc2
feat(drizzle): migrate giving module repos to Drizzle ORM
dnplkndll Mar 11, 2026
339b2e6
feat(drizzle): migrate content module repos to Drizzle ORM
dnplkndll Mar 11, 2026
88378d7
feat(drizzle): migrate attendance module repos to Drizzle ORM
dnplkndll Mar 11, 2026
55891e9
feat(drizzle): migrate membership module repos to Drizzle ORM
dnplkndll Mar 11, 2026
7ce25ba
test(drizzle): add integration tests for all Drizzle repo modules
dnplkndll Mar 11, 2026
7549e9b
ci: add test workflow with lint, typecheck, and integration tests
dnplkndll Mar 11, 2026
33d4905
refactor(drizzle): convert raw SQL to query builder across all modules
dnplkndll Mar 11, 2026
eac0570
feat: dual-dialect support (MySQL + PostgreSQL via DB_DIALECT)
dnplkndll Mar 11, 2026
360ff1f
ci: add PostgreSQL integration test job
dnplkndll Mar 14, 2026
5a67aac
fix: PG compatibility, restore removed features, repo correctness
dnplkndll Mar 14, 2026
85817bd
feat: drizzle-kit migrations + fix UserChurchHelper PG compatibility
dnplkndll Mar 15, 2026
6f2f3cd
refactor: replace dbScripts with drizzle-kit migrations for DDL
dnplkndll Mar 15, 2026
d4f7aee
fix: VisitRepo toDateOnly timezone bug + initdb stored proc handling
dnplkndll Mar 15, 2026
4b4a42f
fix: MySQL index collisions, remove debug logging, wire schemaOnly flag
dnplkndll Mar 15, 2026
70aca7b
refactor: consolidate index fixes into initial migrations
dnplkndll Mar 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
161 changes: 161 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
name: Test

on:
pull_request:
branches: [main]
push:
branches: [main]
workflow_dispatch:

concurrency:
group: test-${{ github.ref }}
cancel-in-progress: true

jobs:
lint-and-typecheck:
name: Lint & Typecheck
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "22"
cache: npm
- run: npm ci
- run: npm run lint:check
- run: npm run tsc

integration-tests-mysql:
name: Integration Tests (MySQL)
runs-on: ubuntu-latest
timeout-minutes: 15
needs: lint-and-typecheck

services:
mysql:
image: mysql:8.0
env:
MYSQL_ROOT_PASSWORD: testpass
ports:
- 3306:3306
options: >-
--health-cmd="mysqladmin ping -h localhost -u root -ptestpass"
--health-interval=10s
--health-timeout=5s
--health-retries=10
--health-start-period=30s

env:
TEST_MYSQL_HOST: 127.0.0.1
TEST_MYSQL_PORT: 3306
TEST_MYSQL_USER: root
TEST_MYSQL_PASSWORD: testpass

steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: "22"
cache: npm

- run: npm ci

- name: Create test databases
run: |
mysql -h 127.0.0.1 -u root -ptestpass -e "
CREATE DATABASE IF NOT EXISTS membership;
CREATE DATABASE IF NOT EXISTS attendance;
CREATE DATABASE IF NOT EXISTS content;
CREATE DATABASE IF NOT EXISTS giving;
CREATE DATABASE IF NOT EXISTS messaging;
CREATE DATABASE IF NOT EXISTS doing;
CREATE DATABASE IF NOT EXISTS reporting;
"

- name: Initialize database tables
env:
MEMBERSHIP_CONNECTION_STRING: mysql://root:testpass@127.0.0.1:3306/membership
ATTENDANCE_CONNECTION_STRING: mysql://root:testpass@127.0.0.1:3306/attendance
CONTENT_CONNECTION_STRING: mysql://root:testpass@127.0.0.1:3306/content
GIVING_CONNECTION_STRING: mysql://root:testpass@127.0.0.1:3306/giving
MESSAGING_CONNECTION_STRING: mysql://root:testpass@127.0.0.1:3306/messaging
DOING_CONNECTION_STRING: mysql://root:testpass@127.0.0.1:3306/doing
REPORTING_CONNECTION_STRING: mysql://root:testpass@127.0.0.1:3306/reporting
ENCRYPTION_KEY: test-encryption-key
JWT_SECRET: test-jwt-secret
run: npx tsx tools/initdb.ts

- name: Run integration tests
run: npx jest --config jest.integration.config.cjs --verbose
env:
NODE_OPTIONS: --experimental-vm-modules

integration-tests-postgres:
name: Integration Tests (PostgreSQL)
runs-on: ubuntu-latest
timeout-minutes: 15
needs: lint-and-typecheck

services:
postgres:
image: postgres:16
env:
POSTGRES_USER: testuser
POSTGRES_PASSWORD: testpass
POSTGRES_DB: postgres
ports:
- 5432:5432
options: >-
--health-cmd="pg_isready -U testuser -d postgres"
--health-interval=10s
--health-timeout=5s
--health-retries=10
--health-start-period=30s

steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: "22"
cache: npm

- run: npm ci

- name: Create test databases
env:
PGPASSWORD: testpass
run: |
for db in membership attendance content giving messaging doing reporting; do
psql -h 127.0.0.1 -U testuser -d postgres -c "CREATE DATABASE $db;" || true
done

- name: Initialize database tables
env:
DB_DIALECT: postgres
MEMBERSHIP_CONNECTION_STRING: postgres://testuser:testpass@127.0.0.1:5432/membership
ATTENDANCE_CONNECTION_STRING: postgres://testuser:testpass@127.0.0.1:5432/attendance
CONTENT_CONNECTION_STRING: postgres://testuser:testpass@127.0.0.1:5432/content
GIVING_CONNECTION_STRING: postgres://testuser:testpass@127.0.0.1:5432/giving
MESSAGING_CONNECTION_STRING: postgres://testuser:testpass@127.0.0.1:5432/messaging
DOING_CONNECTION_STRING: postgres://testuser:testpass@127.0.0.1:5432/doing
REPORTING_CONNECTION_STRING: postgres://testuser:testpass@127.0.0.1:5432/reporting
ENCRYPTION_KEY: test-encryption-key
JWT_SECRET: test-jwt-secret
run: npx tsx tools/initdb.ts

- name: Run integration tests
run: npx jest --config jest.integration.config.cjs --verbose
env:
DB_DIALECT: postgres
MEMBERSHIP_CONNECTION_STRING: postgres://testuser:testpass@127.0.0.1:5432/membership
ATTENDANCE_CONNECTION_STRING: postgres://testuser:testpass@127.0.0.1:5432/attendance
CONTENT_CONNECTION_STRING: postgres://testuser:testpass@127.0.0.1:5432/content
GIVING_CONNECTION_STRING: postgres://testuser:testpass@127.0.0.1:5432/giving
MESSAGING_CONNECTION_STRING: postgres://testuser:testpass@127.0.0.1:5432/messaging
DOING_CONNECTION_STRING: postgres://testuser:testpass@127.0.0.1:5432/doing
REPORTING_CONNECTION_STRING: postgres://testuser:testpass@127.0.0.1:5432/reporting
ENCRYPTION_KEY: test-encryption-key
JWT_SECRET: test-jwt-secret
NODE_OPTIONS: --experimental-vm-modules
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ layers/
*.sqlite3
*.db

# Drizzle ORM migrations are committed to version control (drizzle/ directory)

# Certificates
*.pem
*.key
Expand Down
49 changes: 49 additions & 0 deletions drizzle.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { defineConfig } from "drizzle-kit";

/**
* Drizzle Kit configuration for schema migrations.
*
* Each module has its own database, so DB_MODULE is required for generation.
*
* Environment variables:
* DB_DIALECT — "mysql" (default) or "postgres"
* DB_MODULE — module name: membership, attendance, content, giving, messaging, doing
*
* Usage:
* # Generate migrations for a module (MySQL, default):
* DB_MODULE=membership npx drizzle-kit generate
*
* # Generate migrations for a module (PostgreSQL):
* DB_DIALECT=postgres DB_MODULE=membership npx drizzle-kit generate
*
* # Generate for all modules at once:
* npm run migrate:generate:all
*/

const MODULES = ["membership", "attendance", "content", "giving", "messaging", "doing"];

const dialect = (process.env.DB_DIALECT || "mysql").toLowerCase();
const isPostgres = dialect === "postgres" || dialect === "postgresql" || dialect === "pg";
const dbDialect = isPostgres ? "postgresql" : "mysql";
const moduleName = process.env.DB_MODULE;

if (!moduleName) {
console.error("DB_MODULE is required. Set it to one of:", MODULES.join(", "));
console.error("Or use: npm run migrate:generate:all");
process.exit(1);
}

if (!MODULES.includes(moduleName)) {
console.error(`Unknown module: ${moduleName}. Valid modules: ${MODULES.join(", ")}`);
process.exit(1);
}

const schemaPath = isPostgres
? `./src/db/schema/pg/${moduleName}.ts`
: `./src/db/schema/${moduleName}.ts`;

export default defineConfig({
schema: schemaPath,
out: `./drizzle/${dbDialect}/${moduleName}`,
dialect: dbDialect,
});
100 changes: 100 additions & 0 deletions drizzle/mysql/attendance/0000_chemical_sandman.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
CREATE TABLE `settings` (
`id` char(11) NOT NULL,
`churchId` char(11),
`keyName` varchar(255),
`value` varchar(255),
CONSTRAINT `settings_id` PRIMARY KEY(`id`)
);
--> statement-breakpoint
CREATE TABLE `campuses` (
`id` char(11) NOT NULL,
`churchId` char(11),
`name` varchar(255),
`address1` varchar(50),
`address2` varchar(50),
`city` varchar(50),
`state` varchar(10),
`zip` varchar(10),
`removed` boolean,
CONSTRAINT `campuses_id` PRIMARY KEY(`id`)
);
--> statement-breakpoint
CREATE TABLE `groupServiceTimes` (
`id` char(11) NOT NULL,
`churchId` char(11),
`groupId` char(11),
`serviceTimeId` char(11),
CONSTRAINT `groupServiceTimes_id` PRIMARY KEY(`id`)
);
--> statement-breakpoint
CREATE TABLE `serviceTimes` (
`id` char(11) NOT NULL,
`churchId` char(11),
`serviceId` char(11),
`name` varchar(50),
`removed` boolean,
CONSTRAINT `serviceTimes_id` PRIMARY KEY(`id`)
);
--> statement-breakpoint
CREATE TABLE `services` (
`id` char(11) NOT NULL,
`churchId` char(11),
`campusId` char(11),
`name` varchar(50),
`removed` boolean,
CONSTRAINT `services_id` PRIMARY KEY(`id`)
);
--> statement-breakpoint
CREATE TABLE `sessions` (
`id` char(11) NOT NULL,
`churchId` char(11),
`groupId` char(11),
`serviceTimeId` char(11),
`sessionDate` datetime,
CONSTRAINT `sessions_id` PRIMARY KEY(`id`)
);
--> statement-breakpoint
CREATE TABLE `visitSessions` (
`id` char(11) NOT NULL,
`churchId` char(11),
`visitId` char(11),
`sessionId` char(11),
CONSTRAINT `visitSessions_id` PRIMARY KEY(`id`)
);
--> statement-breakpoint
CREATE TABLE `visits` (
`id` char(11) NOT NULL,
`churchId` char(11),
`personId` char(11),
`serviceId` char(11),
`groupId` char(11),
`visitDate` datetime,
`checkinTime` datetime,
`addedBy` char(11),
CONSTRAINT `visits_id` PRIMARY KEY(`id`)
);
--> statement-breakpoint
CREATE INDEX `churchId` ON `settings` (`churchId`);--> statement-breakpoint
CREATE INDEX `churchId` ON `campuses` (`churchId`);--> statement-breakpoint
CREATE INDEX `churchId` ON `groupServiceTimes` (`churchId`);--> statement-breakpoint
CREATE INDEX `groupId` ON `groupServiceTimes` (`groupId`);--> statement-breakpoint
CREATE INDEX `serviceTimeId` ON `groupServiceTimes` (`serviceTimeId`);--> statement-breakpoint
CREATE INDEX `churchId` ON `serviceTimes` (`churchId`);--> statement-breakpoint
CREATE INDEX `serviceId` ON `serviceTimes` (`serviceId`);--> statement-breakpoint
CREATE INDEX `idx_church_service_removed` ON `serviceTimes` (`churchId`,`serviceId`,`removed`);--> statement-breakpoint
CREATE INDEX `churchId` ON `services` (`churchId`);--> statement-breakpoint
CREATE INDEX `campusId` ON `services` (`campusId`);--> statement-breakpoint
CREATE INDEX `churchId` ON `sessions` (`churchId`);--> statement-breakpoint
CREATE INDEX `groupId` ON `sessions` (`groupId`);--> statement-breakpoint
CREATE INDEX `serviceTimeId` ON `sessions` (`serviceTimeId`);--> statement-breakpoint
CREATE INDEX `idx_church_session_date` ON `sessions` (`churchId`,`sessionDate`);--> statement-breakpoint
CREATE INDEX `idx_church_group_service` ON `sessions` (`churchId`,`groupId`,`serviceTimeId`);--> statement-breakpoint
CREATE INDEX `churchId` ON `visitSessions` (`churchId`);--> statement-breakpoint
CREATE INDEX `visitId` ON `visitSessions` (`visitId`);--> statement-breakpoint
CREATE INDEX `sessionId` ON `visitSessions` (`sessionId`);--> statement-breakpoint
CREATE INDEX `churchId` ON `visits` (`churchId`);--> statement-breakpoint
CREATE INDEX `personId` ON `visits` (`personId`);--> statement-breakpoint
CREATE INDEX `serviceId` ON `visits` (`serviceId`);--> statement-breakpoint
CREATE INDEX `groupId` ON `visits` (`groupId`);--> statement-breakpoint
CREATE INDEX `idx_church_visit_date` ON `visits` (`churchId`,`visitDate`);--> statement-breakpoint
CREATE INDEX `idx_church_person` ON `visits` (`churchId`,`personId`);
Loading
Loading