feat(01-02): create Flyway migrator service and initial migration
- Add Migrator trait with migrate/status ZIO effects - Create V1 migration for summer_users table - Configure Flyway with summer_migrations table and classpath location - Migrations run manually via CLI, not on startup
This commit is contained in:
26
summercms/resources/db/migration/V1__create_summer_users.sql
Normal file
26
summercms/resources/db/migration/V1__create_summer_users.sql
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
-- Summer CMS initial schema
|
||||||
|
-- Creates the base users table for future auth
|
||||||
|
|
||||||
|
CREATE TABLE summer_users (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
email VARCHAR(255) NOT NULL UNIQUE,
|
||||||
|
password_hash VARCHAR(255) NOT NULL,
|
||||||
|
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX idx_summer_users_email ON summer_users(email);
|
||||||
|
|
||||||
|
-- Trigger for updated_at
|
||||||
|
CREATE OR REPLACE FUNCTION update_updated_at_column()
|
||||||
|
RETURNS TRIGGER AS $$
|
||||||
|
BEGIN
|
||||||
|
NEW.updated_at = NOW();
|
||||||
|
RETURN NEW;
|
||||||
|
END;
|
||||||
|
$$ language 'plpgsql';
|
||||||
|
|
||||||
|
CREATE TRIGGER update_summer_users_updated_at
|
||||||
|
BEFORE UPDATE ON summer_users
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE FUNCTION update_updated_at_column();
|
||||||
80
summercms/src/db/Migrator.scala
Normal file
80
summercms/src/db/Migrator.scala
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
package db
|
||||||
|
|
||||||
|
import org.flywaydb.core.Flyway
|
||||||
|
import org.flywaydb.core.api.MigrationInfo
|
||||||
|
import zio.*
|
||||||
|
import javax.sql.DataSource
|
||||||
|
|
||||||
|
/** Migration status summary
|
||||||
|
*
|
||||||
|
* @param current
|
||||||
|
* The currently applied migration version (None if no migrations applied)
|
||||||
|
* @param pending
|
||||||
|
* Number of pending migrations waiting to be applied
|
||||||
|
*/
|
||||||
|
case class MigrationStatus(current: Option[String], pending: Int)
|
||||||
|
|
||||||
|
/** Flyway database migration service
|
||||||
|
*
|
||||||
|
* Wraps Flyway migrations in ZIO effects for controlled execution. Migrations are NOT auto-run on
|
||||||
|
* application startup - they must be explicitly invoked via CLI (Phase 5: CLI Scaffolding).
|
||||||
|
*
|
||||||
|
* Migration files are stored in: resources/db/migration/ Following Flyway naming convention:
|
||||||
|
* V{version}__{description}.sql
|
||||||
|
*
|
||||||
|
* Example: V1__create_summer_users.sql
|
||||||
|
*/
|
||||||
|
trait Migrator {
|
||||||
|
|
||||||
|
/** Run all pending migrations
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* Number of migrations applied
|
||||||
|
*/
|
||||||
|
def migrate: Task[Int]
|
||||||
|
|
||||||
|
/** Get current migration status
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* Status with current version and pending count
|
||||||
|
*/
|
||||||
|
def status: Task[MigrationStatus]
|
||||||
|
}
|
||||||
|
|
||||||
|
object Migrator {
|
||||||
|
|
||||||
|
/** Live implementation of Migrator backed by Flyway */
|
||||||
|
val live: ZLayer[DataSource, Nothing, Migrator] =
|
||||||
|
ZLayer.fromFunction { (ds: DataSource) =>
|
||||||
|
new Migrator {
|
||||||
|
private def flyway: Flyway =
|
||||||
|
Flyway.configure()
|
||||||
|
.dataSource(ds)
|
||||||
|
.locations("classpath:db/migration")
|
||||||
|
.table("summer_migrations")
|
||||||
|
.baselineOnMigrate(true)
|
||||||
|
.load()
|
||||||
|
|
||||||
|
override def migrate: Task[Int] =
|
||||||
|
ZIO.attempt {
|
||||||
|
flyway.migrate().migrationsExecuted
|
||||||
|
}
|
||||||
|
|
||||||
|
override def status: Task[MigrationStatus] =
|
||||||
|
ZIO.attempt {
|
||||||
|
val info = flyway.info()
|
||||||
|
val current = Option(info.current()).map(_.getVersion.getVersion)
|
||||||
|
val pending = info.pending().length
|
||||||
|
MigrationStatus(current, pending)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Accessor for migrate effect */
|
||||||
|
def migrate: ZIO[Migrator, Throwable, Int] =
|
||||||
|
ZIO.serviceWithZIO[Migrator](_.migrate)
|
||||||
|
|
||||||
|
/** Accessor for status effect */
|
||||||
|
def status: ZIO[Migrator, Throwable, MigrationStatus] =
|
||||||
|
ZIO.serviceWithZIO[Migrator](_.status)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user