Initial commit: CleanPlate backend API
Dart Shelf REST API with auth, recipes, AI (Claude), search, and community modules. PostgreSQL, Redis, Meilisearch. Docker Compose for local dev.
This commit is contained in:
145
bin/migrate.dart
Normal file
145
bin/migrate.dart
Normal file
@@ -0,0 +1,145 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:cleanplate_api/src/config/env.dart';
|
||||
import 'package:cleanplate_api/src/config/database.dart';
|
||||
|
||||
final _log = Logger('Migrate');
|
||||
|
||||
/// Database migration runner.
|
||||
///
|
||||
/// Run with: dart run bin/migrate.dart
|
||||
///
|
||||
/// This applies all migrations in order. Each migration is idempotent
|
||||
/// (uses IF NOT EXISTS) so it is safe to re-run.
|
||||
void main(List<String> args) async {
|
||||
Logger.root.level = Level.ALL;
|
||||
Logger.root.onRecord.listen((record) {
|
||||
stderr.writeln(
|
||||
'${record.time} [${record.level.name}] ${record.loggerName}: ${record.message}');
|
||||
});
|
||||
|
||||
_log.info('Starting migrations (env=${Env.environment})...');
|
||||
|
||||
await Database.initialize();
|
||||
|
||||
try {
|
||||
for (var i = 0; i < _migrations.length; i++) {
|
||||
_log.info('Running migration ${i + 1}/${_migrations.length}: ${_migrationNames[i]}');
|
||||
await Database.execute(_migrations[i]);
|
||||
}
|
||||
|
||||
_log.info('All migrations applied successfully');
|
||||
} catch (e, st) {
|
||||
_log.severe('Migration failed', e, st);
|
||||
exit(1);
|
||||
} finally {
|
||||
await Database.close();
|
||||
}
|
||||
}
|
||||
|
||||
const _migrationNames = [
|
||||
'Create users table',
|
||||
'Create refresh_tokens table',
|
||||
'Create recipes table',
|
||||
'Create ingredients table',
|
||||
'Create steps table',
|
||||
'Create saved_recipes table',
|
||||
'Create reviews table',
|
||||
];
|
||||
|
||||
const _migrations = [
|
||||
// 1. Users
|
||||
'''
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id TEXT PRIMARY KEY,
|
||||
email TEXT NOT NULL UNIQUE,
|
||||
username TEXT NOT NULL UNIQUE,
|
||||
password_hash TEXT NOT NULL,
|
||||
bio TEXT,
|
||||
avatar_url TEXT,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
deleted_at TIMESTAMPTZ
|
||||
)
|
||||
''',
|
||||
|
||||
// 2. Refresh tokens
|
||||
'''
|
||||
CREATE TABLE IF NOT EXISTS refresh_tokens (
|
||||
id TEXT PRIMARY KEY,
|
||||
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
token_hash TEXT NOT NULL,
|
||||
expires_at TIMESTAMPTZ NOT NULL,
|
||||
revoked BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
)
|
||||
''',
|
||||
|
||||
// 3. Recipes
|
||||
'''
|
||||
CREATE TABLE IF NOT EXISTS recipes (
|
||||
id TEXT PRIMARY KEY,
|
||||
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
title TEXT NOT NULL,
|
||||
description TEXT,
|
||||
prep_time INTEGER,
|
||||
cook_time INTEGER,
|
||||
servings INTEGER,
|
||||
difficulty TEXT,
|
||||
cuisine TEXT,
|
||||
tags TEXT[] DEFAULT '{}',
|
||||
image_url TEXT,
|
||||
is_ai_generated BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
deleted_at TIMESTAMPTZ
|
||||
)
|
||||
''',
|
||||
|
||||
// 4. Ingredients
|
||||
'''
|
||||
CREATE TABLE IF NOT EXISTS ingredients (
|
||||
id TEXT PRIMARY KEY,
|
||||
recipe_id TEXT NOT NULL REFERENCES recipes(id) ON DELETE CASCADE,
|
||||
name TEXT NOT NULL,
|
||||
quantity TEXT,
|
||||
unit TEXT,
|
||||
sort_order INTEGER NOT NULL DEFAULT 0
|
||||
)
|
||||
''',
|
||||
|
||||
// 5. Steps
|
||||
'''
|
||||
CREATE TABLE IF NOT EXISTS steps (
|
||||
id TEXT PRIMARY KEY,
|
||||
recipe_id TEXT NOT NULL REFERENCES recipes(id) ON DELETE CASCADE,
|
||||
step_number INTEGER NOT NULL,
|
||||
instruction TEXT NOT NULL,
|
||||
duration_minutes INTEGER
|
||||
)
|
||||
''',
|
||||
|
||||
// 6. Saved recipes (bookmarks)
|
||||
'''
|
||||
CREATE TABLE IF NOT EXISTS saved_recipes (
|
||||
id TEXT PRIMARY KEY,
|
||||
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
recipe_id TEXT NOT NULL REFERENCES recipes(id) ON DELETE CASCADE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
UNIQUE(user_id, recipe_id)
|
||||
)
|
||||
''',
|
||||
|
||||
// 7. Reviews
|
||||
'''
|
||||
CREATE TABLE IF NOT EXISTS reviews (
|
||||
id TEXT PRIMARY KEY,
|
||||
recipe_id TEXT NOT NULL REFERENCES recipes(id) ON DELETE CASCADE,
|
||||
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
rating INTEGER NOT NULL CHECK (rating >= 1 AND rating <= 5),
|
||||
comment TEXT,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
)
|
||||
''',
|
||||
];
|
||||
Reference in New Issue
Block a user