Phase 1 scaffold: auth for all apps
- Backend: Fastify with two listeners (public + internal), routes, services, DB migration + seed - client_app: Flutter with BLoC, all auth screens (welcome, display name, register, OTP, force-register) - mitra_app: Flutter with BLoC, OTP-only login - control_center: React + Vite, email/password login, mitra/user management, anonymity settings - Docs: phase1 plan, API contract, client app mockup - CLAUDE.md and shared memory for all subprojects Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
10
backend/src/db/client.js
Normal file
10
backend/src/db/client.js
Normal file
@@ -0,0 +1,10 @@
|
||||
import postgres from 'postgres'
|
||||
|
||||
let sql
|
||||
|
||||
export const getDb = () => {
|
||||
if (!sql) {
|
||||
sql = postgres(process.env.DATABASE_URL)
|
||||
}
|
||||
return sql
|
||||
}
|
||||
74
backend/src/db/migrate.js
Normal file
74
backend/src/db/migrate.js
Normal file
@@ -0,0 +1,74 @@
|
||||
import 'dotenv/config'
|
||||
import { getDb } from './client.js'
|
||||
|
||||
const sql = getDb()
|
||||
|
||||
const migrate = async () => {
|
||||
await sql`
|
||||
CREATE EXTENSION IF NOT EXISTS "pgcrypto"
|
||||
`
|
||||
|
||||
await sql`
|
||||
CREATE TABLE IF NOT EXISTS roles (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
name VARCHAR(100) NOT NULL UNIQUE,
|
||||
permissions JSONB NOT NULL DEFAULT '{}',
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
)
|
||||
`
|
||||
|
||||
await sql`
|
||||
CREATE TABLE IF NOT EXISTS customers (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
firebase_uid VARCHAR(255) UNIQUE,
|
||||
phone VARCHAR(20) UNIQUE,
|
||||
display_name VARCHAR(100) NOT NULL,
|
||||
is_anonymous BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
)
|
||||
`
|
||||
|
||||
await sql`
|
||||
CREATE TABLE IF NOT EXISTS mitras (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
firebase_uid VARCHAR(255) UNIQUE,
|
||||
phone VARCHAR(20) NOT NULL UNIQUE,
|
||||
display_name VARCHAR(100) NOT NULL,
|
||||
is_active BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
)
|
||||
`
|
||||
|
||||
await sql`
|
||||
CREATE TABLE IF NOT EXISTS control_center_users (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
firebase_uid VARCHAR(255) NOT NULL UNIQUE,
|
||||
email VARCHAR(255) NOT NULL UNIQUE,
|
||||
display_name VARCHAR(100) NOT NULL,
|
||||
role_id UUID NOT NULL REFERENCES roles(id),
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
)
|
||||
`
|
||||
|
||||
await sql`
|
||||
CREATE TABLE IF NOT EXISTS app_config (
|
||||
key VARCHAR(100) PRIMARY KEY,
|
||||
value JSONB NOT NULL,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
)
|
||||
`
|
||||
|
||||
await sql`
|
||||
INSERT INTO app_config (key, value)
|
||||
VALUES ('anonymity', '{"enabled": true}')
|
||||
ON CONFLICT (key) DO NOTHING
|
||||
`
|
||||
|
||||
console.log('Migration complete.')
|
||||
await sql.end()
|
||||
}
|
||||
|
||||
migrate().catch((err) => {
|
||||
console.error('Migration failed:', err)
|
||||
process.exit(1)
|
||||
})
|
||||
51
backend/src/db/seed.js
Normal file
51
backend/src/db/seed.js
Normal file
@@ -0,0 +1,51 @@
|
||||
import 'dotenv/config'
|
||||
import admin from 'firebase-admin'
|
||||
import { getDb } from './client.js'
|
||||
import { initFirebase } from '../plugins/firebase.js'
|
||||
|
||||
const sql = getDb()
|
||||
|
||||
const seed = async () => {
|
||||
initFirebase()
|
||||
|
||||
// Create super_admin role
|
||||
const [role] = await sql`
|
||||
INSERT INTO roles (name, permissions)
|
||||
VALUES (
|
||||
'super_admin',
|
||||
${sql.json({
|
||||
mitra: ['create', 'read', 'update', 'delete'],
|
||||
control_center_users: ['create', 'read', 'update', 'delete'],
|
||||
config: ['read', 'update'],
|
||||
roles: ['create', 'read', 'update', 'delete'],
|
||||
})}
|
||||
)
|
||||
ON CONFLICT (name) DO UPDATE SET permissions = EXCLUDED.permissions
|
||||
RETURNING id
|
||||
`
|
||||
|
||||
// Create first super admin user in Firebase
|
||||
const email = process.env.SEED_ADMIN_EMAIL || 'admin@halobestie.com'
|
||||
const password = process.env.SEED_ADMIN_PASSWORD || 'ChangeMe123!'
|
||||
|
||||
let firebaseUser
|
||||
try {
|
||||
firebaseUser = await admin.auth().getUserByEmail(email)
|
||||
} catch {
|
||||
firebaseUser = await admin.auth().createUser({ email, password, displayName: 'Super Admin' })
|
||||
}
|
||||
|
||||
await sql`
|
||||
INSERT INTO control_center_users (firebase_uid, email, display_name, role_id)
|
||||
VALUES (${firebaseUser.uid}, ${email}, 'Super Admin', ${role.id})
|
||||
ON CONFLICT (email) DO NOTHING
|
||||
`
|
||||
|
||||
console.log(`Seed complete. Admin: ${email}`)
|
||||
await sql.end()
|
||||
}
|
||||
|
||||
seed().catch((err) => {
|
||||
console.error('Seed failed:', err)
|
||||
process.exit(1)
|
||||
})
|
||||
Reference in New Issue
Block a user