Initial import: KeepNote service (dcntrlized-auth consumer)

This commit is contained in:
2026-08-14 23:35:38 +01:00
commit 77dfcaf64a
49 changed files with 2346 additions and 0 deletions
+76
View File
@@ -0,0 +1,76 @@
\c dcntrlized;
CREATE SCHEMA IF NOT EXISTS keepnote;
-- ============================================================================
-- Auth (D3/D5, planning/platform/shared-auth/README.md): identity moved to
-- the shared dcntrlized-auth service. The per-service `users` table is
-- DISABLED below; data tables keep a plain-UUID user_id (no FK) that points
-- at auth.users(id). Existing dev rows are re-pointed by 002_shared_auth.sql.
-- ============================================================================
-- CREATE TABLE IF NOT EXISTS keepnote.users (
-- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
-- username VARCHAR(255) NOT NULL UNIQUE,
-- password_hash VARCHAR(255) NOT NULL,
-- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
-- );
CREATE TABLE IF NOT EXISTS keepnote.notes (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL,
title VARCHAR(255) NOT NULL,
content TEXT,
created TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
modified TIMESTAMP DEFAULT CURRENT_TIMESTAMP
-- user_id is a plain UUID (platform account id, auth.users) -- no FK (D5).
-- FOREIGN KEY (user_id) REFERENCES keepnote.users(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS keepnote.tags (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(255) NOT NULL UNIQUE
);
CREATE TABLE IF NOT EXISTS keepnote.note_tags (
note_id UUID NOT NULL,
tag_id UUID NOT NULL,
PRIMARY KEY (note_id, tag_id),
FOREIGN KEY (note_id) REFERENCES keepnote.notes(id) ON DELETE CASCADE,
FOREIGN KEY (tag_id) REFERENCES keepnote.tags(id) ON DELETE CASCADE
);
-- Added IF NOT EXISTS to indexes
CREATE INDEX IF NOT EXISTS idx_user_id ON keepnote.notes(user_id);
CREATE INDEX IF NOT EXISTS idx_note_id ON keepnote.note_tags(note_id);
CREATE INDEX IF NOT EXISTS idx_tag_id ON keepnote.note_tags(tag_id);
-- Placed the function inside the keepnote schema
CREATE OR REPLACE FUNCTION keepnote.update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
NEW.modified = NOW();
RETURN NEW;
END;
$$ language 'plpgsql';
-- Drop the trigger if it exists, then recreate it
DROP TRIGGER IF EXISTS update_notes_modtime ON keepnote.notes;
CREATE TRIGGER update_notes_modtime
BEFORE UPDATE ON keepnote.notes
FOR EACH ROW
EXECUTE FUNCTION keepnote.update_updated_at_column();
-- Idempotent User Creation using a DO block
DO $$
BEGIN
IF NOT EXISTS (SELECT FROM pg_catalog.pg_roles WHERE rolname = 'keepnote_user') THEN
CREATE USER keepnote_user WITH PASSWORD 'keepnote';
END IF;
END
$$;
-- Grants are naturally idempotent (they won't error if re-run)
GRANT CONNECT ON DATABASE dcntrlized TO keepnote_user;
GRANT USAGE ON SCHEMA keepnote TO keepnote_user;
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA keepnote TO keepnote_user;
ALTER DEFAULT PRIVILEGES IN SCHEMA keepnote GRANT ALL ON TABLES TO keepnote_user;
+19
View File
@@ -0,0 +1,19 @@
\c dcntrlized;
-- Plain-UUID user_id: drop the legacy FK (D5).
ALTER TABLE IF EXISTS keepnote.notes DROP CONSTRAINT IF EXISTS notes_user_id_fkey;
-- Re-point existing rows to auth.users by username, then drop the old table.
-- No-op on fresh DBs (the old users table never existed).
DO $$
BEGIN
IF EXISTS (SELECT FROM pg_catalog.pg_tables WHERE schemaname='keepnote' AND tablename='users')
AND EXISTS (SELECT FROM pg_catalog.pg_tables WHERE schemaname='auth' AND tablename='users') THEN
UPDATE keepnote.notes n
SET user_id = a.id
FROM keepnote.users old
JOIN auth.users a ON a.username = old.username
WHERE n.user_id = old.id;
DROP TABLE keepnote.users;
END IF;
END $$;
+2
View File
@@ -0,0 +1,2 @@
psycopg2
bcrypt
+11
View File
@@ -0,0 +1,11 @@
"""
User seeding moved to the shared dcntrlized-auth service (D3/D8,
planning/platform/shared-auth/README.md). Accounts are seeded by
dcntrlized-auth/database/seeds/001_seed_users.py:
admin / [email protected] / admin_password_123
johndoe / [email protected] / john_password_456
janedoe / [email protected] / jane_password_789
Kept as a stub for historical reference; intentionally does nothing.
"""
if __name__ == "__main__":
print("User seeding moved to dcntrlized-auth. Nothing to do here.")
+85
View File
@@ -0,0 +1,85 @@
import os
import sys
import argparse
import subprocess
from pathlib import Path
# Hardcoded directories
MIGRATIONS_DIR = "migrations"
SEEDS_DIR = "seeds"
def main():
# Connection settings are env-overridable so the script can target either a
# local Postgres or the shared dcntrlized-db container (PGHOST=dcntrlized-db).
user = os.environ.get("PGUSER", "postgres")
password = os.environ.get("PGPASSWORD", "")
dbname = os.environ.get("PGDATABASE", "postgres")
host = os.environ.get("PGHOST", "localhost")
port = os.environ.get("PGPORT", "5432")
print("==========================================")
print("Starting Database Setup & Seeding")
print(f"Host: {host}:{port} | User: {user} | DB: {dbname}")
print("==========================================\n")
os.environ["PGUSER"] = user
os.environ["PGPASSWORD"] = password
os.environ["PGDATABASE"] = dbname
os.environ["PGHOST"] = host
os.environ["PGPORT"] = port
# 1. Run SQL Migrations
print("--- Running SQL Migrations ---")
migrations_path = Path(MIGRATIONS_DIR)
if not migrations_path.is_dir():
print(f"Warning: Migrations directory '{MIGRATIONS_DIR}' not found.")
else:
# Grab all .sql files and sort them (e.g., 001_..., 002_...)
sql_files = sorted(migrations_path.glob("*.sql"))
if not sql_files:
print(f"No SQL migration files found in '{MIGRATIONS_DIR}/'.")
for sql_file in sql_files:
print(f"Applying: {sql_file.name}")
# Run psql with ON_ERROR_STOP=1 to fail fast if SQL throws an error
result = subprocess.run(
["psql", "-v", "ON_ERROR_STOP=1", "-f", str(sql_file)],
check=False
)
if result.returncode != 0:
print(f"\n❌ Migration failed on {sql_file.name}")
sys.exit(result.returncode)
# 2. Run Python Seed Scripts
print("\n--- Running Python Seed Scripts ---")
os.environ["PGDATABASE"] = "dcntrlized"
seeds_path = Path(SEEDS_DIR)
if not seeds_path.is_dir():
print(f"Warning: Seeds directory '{SEEDS_DIR}' not found.")
else:
# Grab all .py files and sort them
py_files = sorted(seeds_path.glob("*.py"))
if not py_files:
print(f"No Python seed files found in '{SEEDS_DIR}/'.")
for py_file in py_files:
print(f"Executing: {py_file.name}")
# Run the seed script using the same Python interpreter running this script
result = subprocess.run(
[sys.executable, str(py_file)],
check=False
)
if result.returncode != 0:
print(f"\n❌ Seeding failed on {py_file.name}")
sys.exit(result.returncode)
print("\n==========================================")
print("✅ All migrations and seeds completed successfully!")
print("==========================================")
if __name__ == "__main__":
main()