diff --git a/.gitignore b/.gitignore index a7ac383..2436489 100644 --- a/.gitignore +++ b/.gitignore @@ -42,3 +42,4 @@ push_certs/ # Vendor (if not using go modules) # vendor/ +/migrate diff --git a/Dockerfile b/Dockerfile index 7ecef53..6fa15d4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -49,6 +49,12 @@ RUN CGO_ENABLED=0 GOOS=linux GOARCH=${TARGETARCH} go build -ldflags="-w -s" -o / # Build the worker binary RUN CGO_ENABLED=0 GOOS=linux GOARCH=${TARGETARCH} go build -ldflags="-w -s" -o /app/worker ./cmd/worker +# Install goose CLI for production migrations. Pinned to a specific version +# so an upstream behavioural change can't break a deploy unannounced. +# Bumping is a deliberate, reviewable diff. +RUN CGO_ENABLED=0 GOOS=linux GOARCH=${TARGETARCH} \ + go install github.com/pressly/goose/v3/cmd/goose@v3.22.1 + # Base runtime stage for Go services FROM alpine:3.19 AS go-base @@ -64,6 +70,9 @@ WORKDIR /app # Copy all binaries from builder COPY --from=builder /app/api /app/api COPY --from=builder /app/worker /app/worker +# goose is the migration runner — same image is reused as the migrate Job +# entrypoint via `command: ["/usr/local/bin/goose", ...]`. +COPY --from=builder /go/bin/goose /usr/local/bin/goose # Copy templates directory COPY --from=builder /app/templates /app/templates diff --git a/Makefile b/Makefile index 066add4..5ae815b 100644 --- a/Makefile +++ b/Makefile @@ -89,15 +89,36 @@ docker-build-prod: docker build --target worker -t $${REGISTRY:-ghcr.io/treytartt}/honeydue-worker:$${TAG:-latest} . docker build --target admin -t $${REGISTRY:-ghcr.io/treytartt}/honeydue-admin:$${TAG:-latest} . -# Database migrations +# Database migrations (goose) +# +# DATABASE_URL must point at the *direct* (non-pooler) Neon endpoint — +# goose's session-scoped advisory lock won't survive PgBouncer transaction +# mode. Example: +# export DATABASE_URL='host=ep-floral-truth-amttbc5a.c-5.us-east-1.aws.neon.tech \ +# user=neondb_owner password=... dbname=honeyDue sslmode=require' +# +# Bootstrap (one-time, when adopting goose against an existing DB): +# make migrate-status # creates goose_db_version +# psql ... -c "INSERT INTO goose_db_version (version_id, is_applied, tstamp) VALUES (1, true, NOW());" +# +# Day-to-day: +# make migrate-status # show what's pending +# make migrate-up # apply pending migrations +# make migrate-down # roll back the latest migration +# make migrate-new name=add_some_column # scaffold a new SQL migration + migrate-up: - migrate -path migrations -database "$(DATABASE_URL)" up + goose -dir migrations postgres "$(DATABASE_URL)" up migrate-down: - migrate -path migrations -database "$(DATABASE_URL)" down + goose -dir migrations postgres "$(DATABASE_URL)" down -migrate-create: - migrate create -ext sql -dir migrations -seq $(name) +migrate-status: + goose -dir migrations postgres "$(DATABASE_URL)" status + +migrate-new: + @if [ -z "$(name)" ]; then echo "usage: make migrate-new name="; exit 1; fi + goose -dir migrations create $(name) sql # Encrypt existing uploads at rest (run after setting STORAGE_ENCRYPTION_KEY) migrate-encrypt: diff --git a/cmd/api/main.go b/cmd/api/main.go index c0d8ab4..5d12e93 100644 --- a/cmd/api/main.go +++ b/cmd/api/main.go @@ -87,11 +87,14 @@ func main() { log.Error().Err(dbErr).Msg("Failed to connect to database - API will start but database operations will fail") } else { defer database.Close() - // Run database migrations only if connected. - // MigrateWithLock serialises parallel replica starts via a Postgres - // advisory lock so concurrent AutoMigrate calls don't race on DDL. - if err := database.MigrateWithLock(); err != nil { - log.Error().Err(err).Msg("Failed to run database migrations") + // Migrations are managed out-of-band by golang-migrate (see + // cmd/migrate and deploy-k3s/manifests/migrate/job.yaml) so the api + // no longer runs AutoMigrate at startup. Instead we verify the + // schema is at the expected version and refuse to start if not — + // this catches the "operator forgot to run migrate" footgun loudly, + // at boot, instead of with mysterious runtime errors. + if err := database.RequireSchemaApplied(); err != nil { + log.Fatal().Err(err).Msg("Schema precondition failed — run `kubectl -n honeydue create job --from=cronjob/honeydue-migrate` (or `make migrate-up` locally) and retry") } } diff --git a/deploy-k3s/manifests/migrate/job.yaml b/deploy-k3s/manifests/migrate/job.yaml new file mode 100644 index 0000000..8b09f44 --- /dev/null +++ b/deploy-k3s/manifests/migrate/job.yaml @@ -0,0 +1,75 @@ +# One-shot migration Job. Runs goose against Neon's *direct* (non-pooler) +# endpoint, applies any pending migrations from /app/migrations (baked into +# the api image), exits. +# +# 03-deploy.sh deletes any prior Job, applies this one, waits for completion +# with `kubectl wait --for=condition=complete`, and rolls api/worker only +# after the Job succeeds. A Job failure aborts the whole deploy. +# +# We reuse the api image rather than build a separate one — the api Dockerfile +# already installs the goose CLI to /usr/local/bin/goose and copies the +# migrations directory to /app/migrations. +apiVersion: batch/v1 +kind: Job +metadata: + name: honeydue-migrate + namespace: honeydue + labels: + app.kubernetes.io/name: migrate + app.kubernetes.io/part-of: honeydue +spec: + backoffLimit: 0 # fail fast — no silent retries on a bad migration + ttlSecondsAfterFinished: 86400 # keep finished Job for 24h so logs are inspectable + template: + metadata: + labels: + app.kubernetes.io/name: migrate + app.kubernetes.io/part-of: honeydue + spec: + restartPolicy: Never + imagePullSecrets: + - name: ghcr-credentials + securityContext: + runAsNonRoot: true + runAsUser: 1000 + runAsGroup: 1000 + seccompProfile: + type: RuntimeDefault + containers: + - name: goose + image: IMAGE_PLACEHOLDER # Replaced by 03-deploy.sh — same as api + command: ["/bin/sh", "-c"] + # DB_HOST in the ConfigMap points at the -pooler endpoint for runtime. + # goose's session-scoped advisory lock can't survive PgBouncer + # transaction-mode, so we strip the -pooler segment for migrations. + # `set -e` so any sub-command failure exits non-zero. + args: + - | + set -e + DIRECT_HOST=$(echo "$DB_HOST" | sed 's/-pooler\.\(.*\)$/.\1/') + echo "[migrate] running goose up against $DIRECT_HOST" + exec /usr/local/bin/goose \ + -dir /app/migrations \ + postgres "host=$DIRECT_HOST port=$DB_PORT user=$POSTGRES_USER password=$POSTGRES_PASSWORD dbname=$POSTGRES_DB sslmode=$DB_SSLMODE" \ + up + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + capabilities: + drop: ["ALL"] + envFrom: + - configMapRef: + name: honeydue-config + env: + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: honeydue-secrets + key: POSTGRES_PASSWORD + resources: + requests: + cpu: 100m + memory: 64Mi + limits: + cpu: 500m + memory: 256Mi diff --git a/deploy-k3s/scripts/03-deploy.sh b/deploy-k3s/scripts/03-deploy.sh index 47313c5..c42b8b7 100755 --- a/deploy-k3s/scripts/03-deploy.sh +++ b/deploy-k3s/scripts/03-deploy.sh @@ -149,6 +149,23 @@ kubectl apply -f "${MANIFESTS}/namespace.yaml" kubectl apply -f "${MANIFESTS}/redis/" kubectl apply -f "${MANIFESTS}/ingress/" +# --- Run migrations BEFORE rolling api/worker --- +# +# goose-based migration Job. We delete any prior Job (Jobs are immutable — +# applying a duplicate name otherwise fails), apply a fresh one with the new +# api image (which includes /usr/local/bin/goose and /app/migrations), and +# block until it succeeds. A failure aborts the deploy before any new app +# pod sees a stale schema. +log "Running database migrations (goose Job)..." +kubectl delete job honeydue-migrate -n "${NAMESPACE}" --ignore-not-found --wait=true >/dev/null +sed "s|image: IMAGE_PLACEHOLDER|image: ${API_IMAGE}|" "${MANIFESTS}/migrate/job.yaml" | kubectl apply -f - +if ! kubectl wait --namespace="${NAMESPACE}" --for=condition=complete --timeout=10m job/honeydue-migrate; then + warn "migration Job failed — see logs:" + kubectl logs -n "${NAMESPACE}" job/honeydue-migrate --tail=200 || true + die "migrations did not complete cleanly; aborting deploy" +fi +log "Migrations applied; proceeding with api/worker rollout" + # Apply deployments with image substitution sed "s|image: IMAGE_PLACEHOLDER|image: ${API_IMAGE}|" "${MANIFESTS}/api/deployment.yaml" | kubectl apply -f - kubectl apply -f "${MANIFESTS}/api/service.yaml" diff --git a/docs/deployment/08-database.md b/docs/deployment/08-database.md index 7fd29fa..a26cf1a 100644 --- a/docs/deployment/08-database.md +++ b/docs/deployment/08-database.md @@ -150,66 +150,110 @@ the default 25/10. If we hit connection errors in prod, adjust. ## Schema management -### GORM AutoMigrate +### goose -On startup, the Go API's `cmd/api/main.go` calls -`database.MigrateWithLock()` which: +We use [pressly/goose](https://github.com/pressly/goose) (pinned in the +api `Dockerfile` to v3.22.1) for schema migrations. Why goose specifically: -1. Opens a dedicated Postgres connection -2. `SELECT pg_advisory_lock(1751412071)` — acquires a session-level - advisory lock on a hardcoded key -3. Calls `db.AutoMigrate(&models.*{})` for every GORM model -4. `SELECT pg_advisory_unlock(...)` via deferred function -5. Close the connection +- Each migration file runs inside its own transaction by default — + partial-failure recovery is built in (no "dirty" state to manually + unstick like golang-migrate). +- Locking is opt-in. We *don't* opt in. Migrations run as a single + Kubernetes Job — that's the singleton process. No advisory-lock vs + PgBouncer-transaction-mode foot-gun. +- Plain SQL files. No DSL, no library integration in our Go code. -The advisory lock serializes migrations across replicas: when 3 api -pods start simultaneously, one acquires the lock and migrates; the -others block on the lock. Once the first finishes (≤2s for already- -migrated schema, up to 90s on first cold boot), the next acquires and -sees the schema is current (no-op migrate). +See `docs/deployment/19-postmortem-swarm.md` (Schema Versioning section) +for the AutoMigrate-with-advisory-lock approach this replaced and why. -### Why an advisory lock +### Migration files -Without it, concurrent `CREATE TABLE IF NOT EXISTS ...` statements from -multiple replicas would race — Postgres usually handles it, but GORM's -AutoMigrate also alters tables (adds columns, indexes) which can deadlock -under concurrency. +Live under `migrations/`, named `_.sql`. Each file +has both the up and down migration in one file, separated by goose +markers: -The advisory lock pattern (also used by Rails + Django + Alembic) is the -canonical solution. +```sql +-- +goose Up +CREATE TABLE example (id bigint PRIMARY KEY); -### The lock key +-- +goose Down +DROP TABLE example; +``` -`1751412071` is a hardcoded integer in `internal/database/database.go`. -Arbitrary but unique — as long as nothing else in the Postgres instance -uses the same advisory lock key, no conflicts. +Multi-statement constructs (`CREATE FUNCTION`, `DO $$ BEGIN ... END $$`) +need `-- +goose StatementBegin` / `-- +goose StatementEnd` wrappers +because goose splits on semicolons by default. -### First-boot behavior +`migrations/000001_init.sql` is the baseline — captures every +table/index/sequence as it existed when goose was adopted, generated +via `pg_dump --schema-only --no-owner --no-privileges`. The pre-goose +hand-numbered migrations (002-022 in git history at commit +58e6997) had their effects folded into this baseline; they're gone +from the live tree but remain in git for archaeology. -On a **fresh database** (new Neon project), the first api pod runs -through every model's `CREATE TABLE` statement. This is ~50 tables for -honeyDue and takes ~90 seconds. +### Production migration flow -On a **warm database** (tables already exist), AutoMigrate is fast — -typically under 2 seconds. It still runs (GORM checks every model -against the schema) but finds no work to do. +`deploy-k3s/scripts/03-deploy.sh` runs migrations as part of every +deploy, **before** the api/worker rollout starts: -### Where this bit us +``` +1. kubectl delete job honeydue-migrate (idempotent) +2. kubectl apply -f manifests/migrate/job.yaml (with current api image) +3. kubectl wait --for=condition=complete --timeout=10m job/honeydue-migrate +4. (only if Job succeeded) kubectl apply -f manifests/api/... +``` -With 3 api pods starting simultaneously and migrations taking 90s first -time, the lock queue for the last replica is ~180s. We needed a -startupProbe grace of 240s to cover this without false restart loops. -See Chapter 7 §startupProbe and Chapter 19 §MigrateWithLock. +The Job uses the api image — we install the goose CLI binary at +`/usr/local/bin/goose` during the api Dockerfile build, so any pod that +can run api can run goose. No separate image to build/push. -### Downside: no schema versioning +The Job's `command` runs `goose ... up` against the **direct** +(non-pooler) Neon endpoint. Goose's session-scoped advisory lock can't +survive PgBouncer transaction-mode pooling, so the Job script strips +the `-pooler` segment from `DB_HOST` before connecting. The api/worker +runtime continues to use the pooler endpoint for everything else; only +this one Job needs the direct connection. -AutoMigrate can only *add* — new tables, new columns, new indexes. It -won't drop columns, rename them, or change types destructively. For -those we'd need raw SQL migrations (a tool like `golang-migrate` or -`dbmate`). +### Schema-version precondition -Today: we accept that schema changes are additive-only. When we need -destructive changes, we'd hand-write them. +`internal/database/database.go::RequireSchemaApplied()` runs at api and +worker startup. It queries `goose_db_version` for the highest applied +version and refuses to start if the table is missing or the latest row +is `is_applied=false`. This catches "operator forgot to run migrate" as +a clear boot error instead of a mysterious runtime "relation does not +exist" later. + +### Local migration workflow + +```bash +# Set the direct-endpoint DSN once +export DATABASE_URL='host=ep-floral-truth-amttbc5a.c-5.us-east-1.aws.neon.tech \ + user=neondb_owner password=$PG_PASSWORD dbname=honeyDue sslmode=require' + +make migrate-status # what's pending +make migrate-up # apply +make migrate-down # roll back the latest +make migrate-new name=add_widget_col # scaffold a new SQL file +``` + +Each new migration file goes through code review like any other code +change. The deploy-script Job applies it on the next deploy. + +### Bootstrap (one-time, when the prod DB already had a schema) + +Bootstrapping a goose-managed DB whose schema already exists requires +seeding `goose_db_version` so goose treats version 1 as already-applied: + +```bash +# Once. After this, future migrations append normally. +goose -dir migrations postgres "$DATABASE_URL" version # creates the table +psql "$DATABASE_URL" -c \ + "INSERT INTO goose_db_version (version_id, is_applied, tstamp) VALUES (1, true, NOW());" +``` + +This was done for honeyDue's prod Neon project at the time of goose +adoption — no need to repeat unless we set up a fresh DB from a +schema dump. ## What's in the database diff --git a/go.mod b/go.mod index 5fc27aa..c23edc7 100644 --- a/go.mod +++ b/go.mod @@ -68,7 +68,7 @@ require ( cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect cloud.google.com/go/compute/metadata v0.9.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect @@ -96,7 +96,7 @@ require ( github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-sqlite3 v2.0.3+incompatible // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect github.com/robfig/cron/v3 v3.0.1 // indirect github.com/sagikazarmark/locafero v0.9.0 // indirect diff --git a/go.sum b/go.sum index 8c12db5..e226c09 100644 --- a/go.sum +++ b/go.sum @@ -20,8 +20,8 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= @@ -137,8 +137,9 @@ github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8 github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM= github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= diff --git a/internal/database/database.go b/internal/database/database.go index f7e134e..11cdd9f 100644 --- a/internal/database/database.go +++ b/internal/database/database.go @@ -19,11 +19,6 @@ import ( "github.com/uptrace/opentelemetry-go-extra/otelgorm" ) -// migrationAdvisoryLockKey is the pg_advisory_lock key that serializes -// Migrate() across API replicas booting in parallel. Value is arbitrary but -// stable ("hdmg" as bytes = honeydue migration). -const migrationAdvisoryLockKey int64 = 0x68646d67 - // zerologGormWriter adapts zerolog for GORM's logger interface type zerologGormWriter struct{} @@ -189,52 +184,46 @@ func Paginate(page, pageSize int) func(db *gorm.DB) *gorm.DB { } } -// MigrateWithLock runs Migrate() under a Postgres session-level advisory lock -// so that multiple API replicas booting in parallel don't race on AutoMigrate. -// On non-Postgres dialects (sqlite in tests) it falls through to Migrate(). -func MigrateWithLock() error { +// RequireSchemaApplied verifies that goose's version table exists and has +// at least one applied entry. This is the fail-fast that runs at api/worker +// boot: if the operator forgot to run the migrate Job, the pod refuses to +// start with a clear error instead of throwing mysterious "relation does +// not exist" errors deep in a request handler. +// +// On non-Postgres dialects (sqlite in tests) this is a no-op — tests use +// AutoMigrate via testutil.SetupTestDB to create a fresh schema per run. +// goose isn't involved in the test path. +func RequireSchemaApplied() error { if db == nil { return fmt.Errorf("database not initialised") } if db.Dialector.Name() != "postgres" { - return Migrate() + return nil } - sqlDB, err := db.DB() + // goose_db_version stores one row per applied migration, not a single + // "current version" row — so we look for the highest version_id with + // is_applied=true. ORDER BY id DESC LIMIT 1 also catches the case where + // the table exists but is empty (no rows returned, scan leaves Version + // at zero). + type migrationRow struct { + VersionID int64 `gorm:"column:version_id"` + IsApplied bool `gorm:"column:is_applied"` + } + + var row migrationRow + err := db.Raw(`SELECT version_id, is_applied FROM goose_db_version ORDER BY id DESC LIMIT 1`).Scan(&row).Error if err != nil { - return fmt.Errorf("get underlying sql.DB: %w", err) + return fmt.Errorf("goose_db_version check failed (run the migrate Job to bootstrap): %w", err) } - - // Give ourselves up to 5 min to acquire the lock — long enough for a - // slow migration on a peer replica, short enough to fail fast if Postgres - // is hung. - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) - defer cancel() - - conn, err := sqlDB.Conn(ctx) - if err != nil { - return fmt.Errorf("acquire dedicated migration connection: %w", err) + if !row.IsApplied { + return fmt.Errorf("goose_db_version latest row is_applied=false at version=%d — last migration was rolled back or aborted; investigate before starting", row.VersionID) } - defer conn.Close() - - log.Info().Int64("lock_key", migrationAdvisoryLockKey).Msg("Acquiring migration advisory lock...") - if _, err := conn.ExecContext(ctx, "SELECT pg_advisory_lock($1)", migrationAdvisoryLockKey); err != nil { - return fmt.Errorf("pg_advisory_lock: %w", err) + if row.VersionID < 1 { + return fmt.Errorf("goose_db_version is empty — run goose up (or seed a row marking version 1 as applied if the schema already exists)") } - log.Info().Msg("Migration advisory lock acquired") - - defer func() { - // Unlock with a fresh context — the outer ctx may have expired. - unlockCtx, unlockCancel := context.WithTimeout(context.Background(), 10*time.Second) - defer unlockCancel() - if _, err := conn.ExecContext(unlockCtx, "SELECT pg_advisory_unlock($1)", migrationAdvisoryLockKey); err != nil { - log.Warn().Err(err).Msg("Failed to release migration advisory lock (session close will also release)") - } else { - log.Info().Msg("Migration advisory lock released") - } - }() - - return Migrate() + log.Info().Int64("schema_version", row.VersionID).Msg("Schema precondition satisfied") + return nil } // Migrate runs database migrations for all models diff --git a/migrations/000001_init.sql b/migrations/000001_init.sql new file mode 100644 index 0000000..fd139a1 --- /dev/null +++ b/migrations/000001_init.sql @@ -0,0 +1,3456 @@ +-- +goose Up +-- Initial schema baseline. Captures every table/index/sequence as it +-- existed at the moment of the goose adoption commit. Effects of the +-- pre-goose hand-numbered migrations (002–022 in git history at +-- 58e6997) are folded in via pg_dump --schema-only. +-- +-- Production DB already has this schema applied; bootstrap by inserting +-- (1, false, NOW()) into goose_db_version so goose treats v1 as done +-- without re-running this file. See deploy/bootstrap-goose.sh. + +-- +-- PostgreSQL database dump (schema-only) +-- Original header from pg_dump 17.9 (server 17.8) +-- +-- The original dump's SET statements are intentionally omitted here — +-- particularly `SELECT pg_catalog.set_config('search_path', '', false)`, +-- which would prevent goose from inserting into goose_db_version inside +-- the same transaction. The defaults are fine for a fresh DB. + +-- +-- Name: admin_users; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.admin_users ( + id bigint NOT NULL, + email character varying(254) NOT NULL, + password character varying(128) NOT NULL, + first_name character varying(100), + last_name character varying(100), + role character varying(20) DEFAULT 'admin'::character varying, + is_active boolean DEFAULT true, + last_login timestamp with time zone, + created_at timestamp with time zone, + updated_at timestamp with time zone +); + + +-- +-- Name: admin_users_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.admin_users_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: admin_users_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.admin_users_id_seq OWNED BY public.admin_users.id; + + +-- +-- Name: auth_user; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.auth_user ( + id bigint NOT NULL, + password character varying(128) NOT NULL, + last_login timestamp with time zone, + is_superuser boolean DEFAULT false, + username character varying(150) NOT NULL, + first_name character varying(150), + last_name character varying(150), + email character varying(254), + is_staff boolean DEFAULT false, + is_active boolean DEFAULT true, + date_joined timestamp with time zone +); + + +-- +-- Name: auth_user_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.auth_user_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: auth_user_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.auth_user_id_seq OWNED BY public.auth_user.id; + + +-- +-- Name: data_migrations; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.data_migrations ( + id bigint NOT NULL, + name character varying(255) NOT NULL, + applied_at timestamp with time zone NOT NULL +); + + +-- +-- Name: data_migrations_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.data_migrations_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: data_migrations_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.data_migrations_id_seq OWNED BY public.data_migrations.id; + + +-- +-- Name: goadmin_menu; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.goadmin_menu ( + id integer NOT NULL, + parent_id integer DEFAULT 0 NOT NULL, + type integer DEFAULT 0 NOT NULL, + "order" integer DEFAULT 0 NOT NULL, + title character varying(50) DEFAULT ''::character varying NOT NULL, + icon character varying(50) DEFAULT ''::character varying NOT NULL, + uri character varying(3000) DEFAULT ''::character varying NOT NULL, + header character varying(150) DEFAULT ''::character varying, + plugin_name character varying(150) DEFAULT ''::character varying NOT NULL, + uuid character varying(150) DEFAULT ''::character varying, + created_at timestamp without time zone DEFAULT CURRENT_TIMESTAMP, + updated_at timestamp without time zone DEFAULT CURRENT_TIMESTAMP +); + + +-- +-- Name: goadmin_menu_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.goadmin_menu_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: goadmin_menu_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.goadmin_menu_id_seq OWNED BY public.goadmin_menu.id; + + +-- +-- Name: goadmin_operation_log; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.goadmin_operation_log ( + id integer NOT NULL, + user_id integer NOT NULL, + path character varying(255) DEFAULT ''::character varying NOT NULL, + method character varying(10) DEFAULT ''::character varying NOT NULL, + ip character varying(15) DEFAULT ''::character varying NOT NULL, + input text NOT NULL, + created_at timestamp without time zone DEFAULT CURRENT_TIMESTAMP, + updated_at timestamp without time zone DEFAULT CURRENT_TIMESTAMP +); + + +-- +-- Name: goadmin_operation_log_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.goadmin_operation_log_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: goadmin_operation_log_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.goadmin_operation_log_id_seq OWNED BY public.goadmin_operation_log.id; + + +-- +-- Name: goadmin_permissions; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.goadmin_permissions ( + id integer NOT NULL, + name character varying(50) DEFAULT ''::character varying NOT NULL, + slug character varying(50) DEFAULT ''::character varying NOT NULL, + http_method character varying(255) DEFAULT ''::character varying, + http_path text NOT NULL, + created_at timestamp without time zone DEFAULT CURRENT_TIMESTAMP, + updated_at timestamp without time zone DEFAULT CURRENT_TIMESTAMP +); + + +-- +-- Name: goadmin_permissions_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.goadmin_permissions_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: goadmin_permissions_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.goadmin_permissions_id_seq OWNED BY public.goadmin_permissions.id; + + +-- +-- Name: goadmin_role_menu; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.goadmin_role_menu ( + id integer NOT NULL, + role_id integer NOT NULL, + menu_id integer NOT NULL, + created_at timestamp without time zone DEFAULT CURRENT_TIMESTAMP, + updated_at timestamp without time zone DEFAULT CURRENT_TIMESTAMP +); + + +-- +-- Name: goadmin_role_menu_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.goadmin_role_menu_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: goadmin_role_menu_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.goadmin_role_menu_id_seq OWNED BY public.goadmin_role_menu.id; + + +-- +-- Name: goadmin_role_permissions; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.goadmin_role_permissions ( + id integer NOT NULL, + role_id integer NOT NULL, + permission_id integer NOT NULL, + created_at timestamp without time zone DEFAULT CURRENT_TIMESTAMP, + updated_at timestamp without time zone DEFAULT CURRENT_TIMESTAMP +); + + +-- +-- Name: goadmin_role_permissions_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.goadmin_role_permissions_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: goadmin_role_permissions_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.goadmin_role_permissions_id_seq OWNED BY public.goadmin_role_permissions.id; + + +-- +-- Name: goadmin_role_users; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.goadmin_role_users ( + id integer NOT NULL, + role_id integer NOT NULL, + user_id integer NOT NULL, + created_at timestamp without time zone DEFAULT CURRENT_TIMESTAMP, + updated_at timestamp without time zone DEFAULT CURRENT_TIMESTAMP +); + + +-- +-- Name: goadmin_role_users_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.goadmin_role_users_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: goadmin_role_users_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.goadmin_role_users_id_seq OWNED BY public.goadmin_role_users.id; + + +-- +-- Name: goadmin_roles; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.goadmin_roles ( + id integer NOT NULL, + name character varying(50) DEFAULT ''::character varying NOT NULL, + slug character varying(50) DEFAULT ''::character varying NOT NULL, + created_at timestamp without time zone DEFAULT CURRENT_TIMESTAMP, + updated_at timestamp without time zone DEFAULT CURRENT_TIMESTAMP +); + + +-- +-- Name: goadmin_roles_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.goadmin_roles_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: goadmin_roles_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.goadmin_roles_id_seq OWNED BY public.goadmin_roles.id; + + +-- +-- Name: goadmin_session; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.goadmin_session ( + id integer NOT NULL, + sid character varying(50) DEFAULT ''::character varying NOT NULL, + "values" text NOT NULL, + created_at timestamp without time zone DEFAULT CURRENT_TIMESTAMP, + updated_at timestamp without time zone DEFAULT CURRENT_TIMESTAMP +); + + +-- +-- Name: goadmin_session_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.goadmin_session_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: goadmin_session_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.goadmin_session_id_seq OWNED BY public.goadmin_session.id; + + +-- +-- Name: goadmin_site; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.goadmin_site ( + id integer NOT NULL, + key character varying(100) DEFAULT ''::character varying NOT NULL, + value text NOT NULL, + description character varying(3000) DEFAULT ''::character varying, + state integer DEFAULT 0 NOT NULL, + created_at timestamp without time zone DEFAULT CURRENT_TIMESTAMP, + updated_at timestamp without time zone DEFAULT CURRENT_TIMESTAMP +); + + +-- +-- Name: goadmin_site_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.goadmin_site_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: goadmin_site_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.goadmin_site_id_seq OWNED BY public.goadmin_site.id; + + +-- +-- Name: goadmin_user_permissions; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.goadmin_user_permissions ( + id integer NOT NULL, + user_id integer NOT NULL, + permission_id integer NOT NULL, + created_at timestamp without time zone DEFAULT CURRENT_TIMESTAMP, + updated_at timestamp without time zone DEFAULT CURRENT_TIMESTAMP +); + + +-- +-- Name: goadmin_user_permissions_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.goadmin_user_permissions_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: goadmin_user_permissions_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.goadmin_user_permissions_id_seq OWNED BY public.goadmin_user_permissions.id; + + +-- +-- Name: goadmin_users; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.goadmin_users ( + id integer NOT NULL, + username character varying(100) DEFAULT ''::character varying NOT NULL, + password character varying(100) DEFAULT ''::character varying NOT NULL, + name character varying(100) DEFAULT ''::character varying NOT NULL, + avatar character varying(255) DEFAULT ''::character varying, + remember_token character varying(100) DEFAULT ''::character varying, + created_at timestamp without time zone DEFAULT CURRENT_TIMESTAMP, + updated_at timestamp without time zone DEFAULT CURRENT_TIMESTAMP +); + + +-- +-- Name: goadmin_users_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.goadmin_users_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: goadmin_users_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.goadmin_users_id_seq OWNED BY public.goadmin_users.id; + + +-- +-- Name: notifications_notification; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.notifications_notification ( + id bigint NOT NULL, + created_at timestamp with time zone, + updated_at timestamp with time zone, + user_id bigint NOT NULL, + notification_type character varying(50) NOT NULL, + title character varying(200) NOT NULL, + body text NOT NULL, + task_id bigint, + data jsonb DEFAULT '{}'::jsonb, + sent boolean DEFAULT false, + sent_at timestamp with time zone, + read boolean DEFAULT false, + read_at timestamp with time zone, + error_message text +); + + +-- +-- Name: notifications_notification_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.notifications_notification_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: notifications_notification_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.notifications_notification_id_seq OWNED BY public.notifications_notification.id; + + +-- +-- Name: notifications_notificationpreference; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.notifications_notificationpreference ( + id bigint NOT NULL, + created_at timestamp with time zone, + updated_at timestamp with time zone, + user_id bigint NOT NULL, + task_due_soon boolean DEFAULT true, + task_overdue boolean DEFAULT true, + task_completed boolean DEFAULT true, + task_assigned boolean DEFAULT true, + residence_shared boolean DEFAULT true, + warranty_expiring boolean DEFAULT true, + daily_digest boolean DEFAULT true, + email_task_completed boolean DEFAULT true, + task_due_soon_hour bigint, + task_overdue_hour bigint, + warranty_expiring_hour bigint, + daily_digest_hour bigint, + timezone character varying(50) +); + + +-- +-- Name: notifications_notificationpreference_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.notifications_notificationpreference_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: notifications_notificationpreference_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.notifications_notificationpreference_id_seq OWNED BY public.notifications_notificationpreference.id; + + +-- +-- Name: onboarding_emails; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.onboarding_emails ( + id bigint NOT NULL, + user_id bigint NOT NULL, + email_type character varying(50) NOT NULL, + sent_at timestamp with time zone NOT NULL, + opened_at timestamp with time zone, + tracking_id character varying(64), + created_at timestamp with time zone, + updated_at timestamp with time zone +); + + +-- +-- Name: onboarding_emails_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.onboarding_emails_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: onboarding_emails_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.onboarding_emails_id_seq OWNED BY public.onboarding_emails.id; + + +-- +-- Name: push_notifications_apnsdevice; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.push_notifications_apnsdevice ( + id bigint NOT NULL, + name character varying(255), + active boolean DEFAULT true, + user_id bigint, + device_id character varying(255), + registration_id character varying(255), + date_created timestamp with time zone +); + + +-- +-- Name: push_notifications_apnsdevice_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.push_notifications_apnsdevice_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: push_notifications_apnsdevice_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.push_notifications_apnsdevice_id_seq OWNED BY public.push_notifications_apnsdevice.id; + + +-- +-- Name: push_notifications_gcmdevice; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.push_notifications_gcmdevice ( + id bigint NOT NULL, + name character varying(255), + active boolean DEFAULT true, + user_id bigint, + device_id character varying(255), + registration_id character varying(255), + cloud_message_type character varying(3) DEFAULT 'FCM'::character varying, + date_created timestamp with time zone +); + + +-- +-- Name: push_notifications_gcmdevice_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.push_notifications_gcmdevice_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: push_notifications_gcmdevice_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.push_notifications_gcmdevice_id_seq OWNED BY public.push_notifications_gcmdevice.id; + + +-- +-- Name: residence_residence; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.residence_residence ( + id bigint NOT NULL, + created_at timestamp with time zone, + updated_at timestamp with time zone, + owner_id bigint NOT NULL, + name character varying(200) NOT NULL, + property_type_id bigint, + street_address character varying(255), + apartment_unit character varying(50), + city character varying(100), + state_province character varying(100), + postal_code character varying(20), + country character varying(100) DEFAULT 'USA'::character varying, + bedrooms bigint, + bathrooms numeric(3,1), + square_footage bigint, + lot_size numeric(10,2), + year_built bigint, + description text, + purchase_date date, + purchase_price numeric(12,2), + heating_type character varying(50), + cooling_type character varying(50), + water_heater_type character varying(50), + roof_type character varying(50), + has_pool boolean DEFAULT false, + has_sprinkler_system boolean DEFAULT false, + has_septic boolean DEFAULT false, + has_fireplace boolean DEFAULT false, + has_garage boolean DEFAULT false, + has_basement boolean DEFAULT false, + has_attic boolean DEFAULT false, + exterior_type character varying(50), + flooring_primary character varying(50), + landscaping_type character varying(50), + is_primary boolean DEFAULT true, + is_active boolean DEFAULT true +); + + +-- +-- Name: residence_residence_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.residence_residence_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: residence_residence_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.residence_residence_id_seq OWNED BY public.residence_residence.id; + + +-- +-- Name: residence_residence_users; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.residence_residence_users ( + user_id bigint NOT NULL, + residence_id bigint NOT NULL +); + + +-- +-- Name: residence_residencesharecode; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.residence_residencesharecode ( + id bigint NOT NULL, + created_at timestamp with time zone, + updated_at timestamp with time zone, + residence_id bigint NOT NULL, + code character varying(6) NOT NULL, + created_by_id bigint NOT NULL, + is_active boolean DEFAULT true, + expires_at timestamp with time zone +); + + +-- +-- Name: residence_residencesharecode_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.residence_residencesharecode_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: residence_residencesharecode_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.residence_residencesharecode_id_seq OWNED BY public.residence_residencesharecode.id; + + +-- +-- Name: residence_residencetype; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.residence_residencetype ( + id bigint NOT NULL, + created_at timestamp with time zone, + updated_at timestamp with time zone, + name character varying(20) NOT NULL +); + + +-- +-- Name: residence_residencetype_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.residence_residencetype_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: residence_residencetype_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.residence_residencetype_id_seq OWNED BY public.residence_residencetype.id; + + +-- +-- Name: subscription_featurebenefit; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.subscription_featurebenefit ( + id bigint NOT NULL, + created_at timestamp with time zone, + updated_at timestamp with time zone, + feature_name character varying(200) NOT NULL, + free_tier_text character varying(200) NOT NULL, + pro_tier_text character varying(200) NOT NULL, + display_order bigint DEFAULT 0, + is_active boolean DEFAULT true +); + + +-- +-- Name: subscription_featurebenefit_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.subscription_featurebenefit_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: subscription_featurebenefit_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.subscription_featurebenefit_id_seq OWNED BY public.subscription_featurebenefit.id; + + +-- +-- Name: subscription_promotion; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.subscription_promotion ( + id bigint NOT NULL, + created_at timestamp with time zone, + updated_at timestamp with time zone, + promotion_id character varying(50) NOT NULL, + title character varying(200) NOT NULL, + message text NOT NULL, + link character varying(200), + start_date timestamp with time zone NOT NULL, + end_date timestamp with time zone NOT NULL, + target_tier character varying(10) DEFAULT 'free'::character varying, + is_active boolean DEFAULT true +); + + +-- +-- Name: subscription_promotion_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.subscription_promotion_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: subscription_promotion_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.subscription_promotion_id_seq OWNED BY public.subscription_promotion.id; + + +-- +-- Name: subscription_subscriptionsettings; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.subscription_subscriptionsettings ( + id bigint NOT NULL, + enable_limitations boolean DEFAULT false, + enable_monitoring boolean DEFAULT true, + trial_enabled boolean DEFAULT true, + trial_duration_days bigint DEFAULT 14 +); + + +-- +-- Name: subscription_subscriptionsettings_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.subscription_subscriptionsettings_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: subscription_subscriptionsettings_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.subscription_subscriptionsettings_id_seq OWNED BY public.subscription_subscriptionsettings.id; + + +-- +-- Name: subscription_tierlimits; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.subscription_tierlimits ( + id bigint NOT NULL, + created_at timestamp with time zone, + updated_at timestamp with time zone, + tier character varying(10) NOT NULL, + properties_limit bigint, + tasks_limit bigint, + contractors_limit bigint, + documents_limit bigint +); + + +-- +-- Name: subscription_tierlimits_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.subscription_tierlimits_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: subscription_tierlimits_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.subscription_tierlimits_id_seq OWNED BY public.subscription_tierlimits.id; + + +-- +-- Name: subscription_upgradetrigger; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.subscription_upgradetrigger ( + id bigint NOT NULL, + created_at timestamp with time zone, + updated_at timestamp with time zone, + trigger_key character varying(50) NOT NULL, + title character varying(200) NOT NULL, + message text NOT NULL, + promo_html text, + button_text character varying(50) DEFAULT 'Upgrade to Pro'::character varying, + is_active boolean DEFAULT true +); + + +-- +-- Name: subscription_upgradetrigger_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.subscription_upgradetrigger_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: subscription_upgradetrigger_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.subscription_upgradetrigger_id_seq OWNED BY public.subscription_upgradetrigger.id; + + +-- +-- Name: subscription_usersubscription; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.subscription_usersubscription ( + id bigint NOT NULL, + created_at timestamp with time zone, + updated_at timestamp with time zone, + user_id bigint NOT NULL, + tier character varying(10) DEFAULT 'free'::character varying, + apple_receipt_data text, + google_purchase_token text, + stripe_customer_id character varying(255), + stripe_subscription_id character varying(255), + stripe_price_id character varying(255), + subscribed_at timestamp with time zone, + expires_at timestamp with time zone, + auto_renew boolean DEFAULT true, + trial_start timestamp with time zone, + trial_end timestamp with time zone, + trial_used boolean DEFAULT false, + cancelled_at timestamp with time zone, + platform character varying(10), + is_free boolean DEFAULT false +); + + +-- +-- Name: subscription_usersubscription_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.subscription_usersubscription_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: subscription_usersubscription_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.subscription_usersubscription_id_seq OWNED BY public.subscription_usersubscription.id; + + +-- +-- Name: task_climateregion; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.task_climateregion ( + id bigint NOT NULL, + created_at timestamp with time zone, + updated_at timestamp with time zone, + name character varying(100) NOT NULL, + zone_number bigint NOT NULL, + description text, + is_active boolean DEFAULT true +); + + +-- +-- Name: task_climateregion_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.task_climateregion_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: task_climateregion_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.task_climateregion_id_seq OWNED BY public.task_climateregion.id; + + +-- +-- Name: task_contractor; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.task_contractor ( + id bigint NOT NULL, + created_at timestamp with time zone, + updated_at timestamp with time zone, + residence_id bigint, + created_by_id bigint NOT NULL, + name character varying(200) NOT NULL, + company character varying(200), + phone character varying(20), + email character varying(254), + website character varying(200), + notes text, + street_address character varying(255), + city character varying(100), + state_province character varying(100), + postal_code character varying(20), + rating numeric(2,1), + is_favorite boolean DEFAULT false, + is_active boolean DEFAULT true +); + + +-- +-- Name: task_contractor_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.task_contractor_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: task_contractor_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.task_contractor_id_seq OWNED BY public.task_contractor.id; + + +-- +-- Name: task_contractor_specialties; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.task_contractor_specialties ( + contractor_id bigint NOT NULL, + contractor_specialty_id bigint NOT NULL +); + + +-- +-- Name: task_contractorspecialty; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.task_contractorspecialty ( + id bigint NOT NULL, + created_at timestamp with time zone, + updated_at timestamp with time zone, + name character varying(50) NOT NULL, + description text, + icon character varying(50), + display_order bigint DEFAULT 0 +); + + +-- +-- Name: task_contractorspecialty_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.task_contractorspecialty_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: task_contractorspecialty_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.task_contractorspecialty_id_seq OWNED BY public.task_contractorspecialty.id; + + +-- +-- Name: task_document; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.task_document ( + id bigint NOT NULL, + created_at timestamp with time zone, + updated_at timestamp with time zone, + residence_id bigint NOT NULL, + created_by_id bigint NOT NULL, + title character varying(200) NOT NULL, + description text, + document_type character varying(20) DEFAULT 'general'::character varying, + file_url character varying(500), + file_name character varying(255), + file_size bigint, + mime_type character varying(100), + purchase_date date, + expiry_date date, + purchase_price numeric(10,2), + vendor character varying(200), + serial_number character varying(100), + model_number character varying(100), + provider character varying(200), + provider_contact character varying(200), + claim_phone character varying(50), + claim_email character varying(200), + claim_website character varying(500), + notes text, + task_id bigint, + is_active boolean DEFAULT true +); + + +-- +-- Name: task_document_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.task_document_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: task_document_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.task_document_id_seq OWNED BY public.task_document.id; + + +-- +-- Name: task_documentimage; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.task_documentimage ( + id bigint NOT NULL, + created_at timestamp with time zone, + updated_at timestamp with time zone, + document_id bigint NOT NULL, + image_url character varying(500) NOT NULL, + caption character varying(255) +); + + +-- +-- Name: task_documentimage_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.task_documentimage_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: task_documentimage_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.task_documentimage_id_seq OWNED BY public.task_documentimage.id; + + +-- +-- Name: task_reminderlog; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.task_reminderlog ( + id bigint NOT NULL, + task_id bigint NOT NULL, + user_id bigint NOT NULL, + due_date date NOT NULL, + reminder_stage character varying(20) NOT NULL, + sent_at timestamp with time zone DEFAULT CURRENT_TIMESTAMP, + notification_id bigint +); + + +-- +-- Name: task_reminderlog_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.task_reminderlog_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: task_reminderlog_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.task_reminderlog_id_seq OWNED BY public.task_reminderlog.id; + + +-- +-- Name: task_task; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.task_task ( + id bigint NOT NULL, + created_at timestamp with time zone, + updated_at timestamp with time zone, + residence_id bigint NOT NULL, + created_by_id bigint NOT NULL, + assigned_to_id bigint, + title character varying(200) NOT NULL, + description text, + category_id bigint, + priority_id bigint, + frequency_id bigint, + custom_interval_days bigint, + in_progress boolean DEFAULT false, + due_date date, + next_due_date date, + estimated_cost numeric(10,2), + actual_cost numeric(10,2), + contractor_id bigint, + is_cancelled boolean DEFAULT false, + is_archived boolean DEFAULT false, + version bigint DEFAULT 1 NOT NULL, + parent_task_id bigint, + task_template_id bigint +); + + +-- +-- Name: task_task_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.task_task_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: task_task_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.task_task_id_seq OWNED BY public.task_task.id; + + +-- +-- Name: task_taskcategory; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.task_taskcategory ( + id bigint NOT NULL, + created_at timestamp with time zone, + updated_at timestamp with time zone, + name character varying(50) NOT NULL, + description text, + icon character varying(50), + color character varying(7), + display_order bigint DEFAULT 0 +); + + +-- +-- Name: task_taskcategory_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.task_taskcategory_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: task_taskcategory_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.task_taskcategory_id_seq OWNED BY public.task_taskcategory.id; + + +-- +-- Name: task_taskcompletion; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.task_taskcompletion ( + id bigint NOT NULL, + created_at timestamp with time zone, + updated_at timestamp with time zone, + task_id bigint NOT NULL, + completed_by_id bigint NOT NULL, + completed_at timestamp with time zone NOT NULL, + notes text, + actual_cost numeric(10,2), + rating bigint, + completed_from_column character varying(50) DEFAULT 'completed_tasks'::character varying +); + + +-- +-- Name: task_taskcompletion_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.task_taskcompletion_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: task_taskcompletion_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.task_taskcompletion_id_seq OWNED BY public.task_taskcompletion.id; + + +-- +-- Name: task_taskcompletionimage; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.task_taskcompletionimage ( + id bigint NOT NULL, + created_at timestamp with time zone, + updated_at timestamp with time zone, + completion_id bigint NOT NULL, + image_url character varying(500) NOT NULL, + caption character varying(255) +); + + +-- +-- Name: task_taskcompletionimage_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.task_taskcompletionimage_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: task_taskcompletionimage_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.task_taskcompletionimage_id_seq OWNED BY public.task_taskcompletionimage.id; + + +-- +-- Name: task_taskfrequency; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.task_taskfrequency ( + id bigint NOT NULL, + created_at timestamp with time zone, + updated_at timestamp with time zone, + name character varying(20) NOT NULL, + days bigint, + display_order bigint DEFAULT 0 +); + + +-- +-- Name: task_taskfrequency_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.task_taskfrequency_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: task_taskfrequency_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.task_taskfrequency_id_seq OWNED BY public.task_taskfrequency.id; + + +-- +-- Name: task_taskpriority; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.task_taskpriority ( + id bigint NOT NULL, + created_at timestamp with time zone, + updated_at timestamp with time zone, + name character varying(20) NOT NULL, + level bigint NOT NULL, + color character varying(7), + display_order bigint DEFAULT 0 +); + + +-- +-- Name: task_taskpriority_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.task_taskpriority_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: task_taskpriority_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.task_taskpriority_id_seq OWNED BY public.task_taskpriority.id; + + +-- +-- Name: task_tasktemplate; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.task_tasktemplate ( + id bigint NOT NULL, + created_at timestamp with time zone, + updated_at timestamp with time zone, + title character varying(200) NOT NULL, + description text, + category_id bigint, + frequency_id bigint, + icon_ios character varying(100), + icon_android character varying(100), + tags text, + display_order bigint DEFAULT 0, + is_active boolean DEFAULT true, + conditions jsonb DEFAULT '{}'::jsonb +); + + +-- +-- Name: task_tasktemplate_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.task_tasktemplate_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: task_tasktemplate_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.task_tasktemplate_id_seq OWNED BY public.task_tasktemplate.id; + + +-- +-- Name: task_zipclimateregion; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.task_zipclimateregion ( + id bigint NOT NULL, + created_at timestamp with time zone, + updated_at timestamp with time zone, + zip_code character varying(10) NOT NULL, + climate_region_id bigint NOT NULL +); + + +-- +-- Name: task_zipclimateregion_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.task_zipclimateregion_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: task_zipclimateregion_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.task_zipclimateregion_id_seq OWNED BY public.task_zipclimateregion.id; + + +-- +-- Name: user_applesocialauth; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.user_applesocialauth ( + id bigint NOT NULL, + user_id bigint NOT NULL, + apple_id character varying(255) NOT NULL, + email character varying(254), + is_private_email boolean DEFAULT false, + created_at timestamp with time zone, + updated_at timestamp with time zone +); + + +-- +-- Name: user_applesocialauth_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.user_applesocialauth_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: user_applesocialauth_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.user_applesocialauth_id_seq OWNED BY public.user_applesocialauth.id; + + +-- +-- Name: user_authtoken; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.user_authtoken ( + key character varying(40) NOT NULL, + user_id bigint NOT NULL, + created timestamp with time zone +); + + +-- +-- Name: user_confirmationcode; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.user_confirmationcode ( + id bigint NOT NULL, + created_at timestamp with time zone, + updated_at timestamp with time zone, + user_id bigint NOT NULL, + code character varying(6) NOT NULL, + expires_at timestamp with time zone NOT NULL, + is_used boolean DEFAULT false +); + + +-- +-- Name: user_confirmationcode_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.user_confirmationcode_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: user_confirmationcode_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.user_confirmationcode_id_seq OWNED BY public.user_confirmationcode.id; + + +-- +-- Name: user_googlesocialauth; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.user_googlesocialauth ( + id bigint NOT NULL, + user_id bigint NOT NULL, + google_id character varying(255) NOT NULL, + email character varying(254), + name character varying(255), + picture character varying(512), + created_at timestamp with time zone, + updated_at timestamp with time zone +); + + +-- +-- Name: user_googlesocialauth_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.user_googlesocialauth_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: user_googlesocialauth_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.user_googlesocialauth_id_seq OWNED BY public.user_googlesocialauth.id; + + +-- +-- Name: user_passwordresetcode; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.user_passwordresetcode ( + id bigint NOT NULL, + created_at timestamp with time zone, + updated_at timestamp with time zone, + user_id bigint NOT NULL, + code_hash character varying(128) NOT NULL, + reset_token character varying(64) NOT NULL, + expires_at timestamp with time zone NOT NULL, + used boolean DEFAULT false, + attempts bigint DEFAULT 0, + max_attempts bigint DEFAULT 5 +); + + +-- +-- Name: user_passwordresetcode_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.user_passwordresetcode_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: user_passwordresetcode_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.user_passwordresetcode_id_seq OWNED BY public.user_passwordresetcode.id; + + +-- +-- Name: user_userprofile; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.user_userprofile ( + id bigint NOT NULL, + created_at timestamp with time zone, + updated_at timestamp with time zone, + user_id bigint NOT NULL, + verified boolean DEFAULT false, + bio text, + phone_number character varying(15), + date_of_birth date, + profile_picture character varying(100) +); + + +-- +-- Name: user_userprofile_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.user_userprofile_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: user_userprofile_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.user_userprofile_id_seq OWNED BY public.user_userprofile.id; + + +-- +-- Name: admin_users id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.admin_users ALTER COLUMN id SET DEFAULT nextval('public.admin_users_id_seq'::regclass); + + +-- +-- Name: auth_user id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.auth_user ALTER COLUMN id SET DEFAULT nextval('public.auth_user_id_seq'::regclass); + + +-- +-- Name: data_migrations id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.data_migrations ALTER COLUMN id SET DEFAULT nextval('public.data_migrations_id_seq'::regclass); + + +-- +-- Name: goadmin_menu id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.goadmin_menu ALTER COLUMN id SET DEFAULT nextval('public.goadmin_menu_id_seq'::regclass); + + +-- +-- Name: goadmin_operation_log id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.goadmin_operation_log ALTER COLUMN id SET DEFAULT nextval('public.goadmin_operation_log_id_seq'::regclass); + + +-- +-- Name: goadmin_permissions id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.goadmin_permissions ALTER COLUMN id SET DEFAULT nextval('public.goadmin_permissions_id_seq'::regclass); + + +-- +-- Name: goadmin_role_menu id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.goadmin_role_menu ALTER COLUMN id SET DEFAULT nextval('public.goadmin_role_menu_id_seq'::regclass); + + +-- +-- Name: goadmin_role_permissions id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.goadmin_role_permissions ALTER COLUMN id SET DEFAULT nextval('public.goadmin_role_permissions_id_seq'::regclass); + + +-- +-- Name: goadmin_role_users id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.goadmin_role_users ALTER COLUMN id SET DEFAULT nextval('public.goadmin_role_users_id_seq'::regclass); + + +-- +-- Name: goadmin_roles id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.goadmin_roles ALTER COLUMN id SET DEFAULT nextval('public.goadmin_roles_id_seq'::regclass); + + +-- +-- Name: goadmin_session id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.goadmin_session ALTER COLUMN id SET DEFAULT nextval('public.goadmin_session_id_seq'::regclass); + + +-- +-- Name: goadmin_site id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.goadmin_site ALTER COLUMN id SET DEFAULT nextval('public.goadmin_site_id_seq'::regclass); + + +-- +-- Name: goadmin_user_permissions id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.goadmin_user_permissions ALTER COLUMN id SET DEFAULT nextval('public.goadmin_user_permissions_id_seq'::regclass); + + +-- +-- Name: goadmin_users id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.goadmin_users ALTER COLUMN id SET DEFAULT nextval('public.goadmin_users_id_seq'::regclass); + + +-- +-- Name: notifications_notification id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.notifications_notification ALTER COLUMN id SET DEFAULT nextval('public.notifications_notification_id_seq'::regclass); + + +-- +-- Name: notifications_notificationpreference id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.notifications_notificationpreference ALTER COLUMN id SET DEFAULT nextval('public.notifications_notificationpreference_id_seq'::regclass); + + +-- +-- Name: onboarding_emails id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.onboarding_emails ALTER COLUMN id SET DEFAULT nextval('public.onboarding_emails_id_seq'::regclass); + + +-- +-- Name: push_notifications_apnsdevice id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.push_notifications_apnsdevice ALTER COLUMN id SET DEFAULT nextval('public.push_notifications_apnsdevice_id_seq'::regclass); + + +-- +-- Name: push_notifications_gcmdevice id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.push_notifications_gcmdevice ALTER COLUMN id SET DEFAULT nextval('public.push_notifications_gcmdevice_id_seq'::regclass); + + +-- +-- Name: residence_residence id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.residence_residence ALTER COLUMN id SET DEFAULT nextval('public.residence_residence_id_seq'::regclass); + + +-- +-- Name: residence_residencesharecode id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.residence_residencesharecode ALTER COLUMN id SET DEFAULT nextval('public.residence_residencesharecode_id_seq'::regclass); + + +-- +-- Name: residence_residencetype id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.residence_residencetype ALTER COLUMN id SET DEFAULT nextval('public.residence_residencetype_id_seq'::regclass); + + +-- +-- Name: subscription_featurebenefit id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.subscription_featurebenefit ALTER COLUMN id SET DEFAULT nextval('public.subscription_featurebenefit_id_seq'::regclass); + + +-- +-- Name: subscription_promotion id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.subscription_promotion ALTER COLUMN id SET DEFAULT nextval('public.subscription_promotion_id_seq'::regclass); + + +-- +-- Name: subscription_subscriptionsettings id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.subscription_subscriptionsettings ALTER COLUMN id SET DEFAULT nextval('public.subscription_subscriptionsettings_id_seq'::regclass); + + +-- +-- Name: subscription_tierlimits id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.subscription_tierlimits ALTER COLUMN id SET DEFAULT nextval('public.subscription_tierlimits_id_seq'::regclass); + + +-- +-- Name: subscription_upgradetrigger id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.subscription_upgradetrigger ALTER COLUMN id SET DEFAULT nextval('public.subscription_upgradetrigger_id_seq'::regclass); + + +-- +-- Name: subscription_usersubscription id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.subscription_usersubscription ALTER COLUMN id SET DEFAULT nextval('public.subscription_usersubscription_id_seq'::regclass); + + +-- +-- Name: task_climateregion id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.task_climateregion ALTER COLUMN id SET DEFAULT nextval('public.task_climateregion_id_seq'::regclass); + + +-- +-- Name: task_contractor id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.task_contractor ALTER COLUMN id SET DEFAULT nextval('public.task_contractor_id_seq'::regclass); + + +-- +-- Name: task_contractorspecialty id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.task_contractorspecialty ALTER COLUMN id SET DEFAULT nextval('public.task_contractorspecialty_id_seq'::regclass); + + +-- +-- Name: task_document id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.task_document ALTER COLUMN id SET DEFAULT nextval('public.task_document_id_seq'::regclass); + + +-- +-- Name: task_documentimage id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.task_documentimage ALTER COLUMN id SET DEFAULT nextval('public.task_documentimage_id_seq'::regclass); + + +-- +-- Name: task_reminderlog id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.task_reminderlog ALTER COLUMN id SET DEFAULT nextval('public.task_reminderlog_id_seq'::regclass); + + +-- +-- Name: task_task id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.task_task ALTER COLUMN id SET DEFAULT nextval('public.task_task_id_seq'::regclass); + + +-- +-- Name: task_taskcategory id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.task_taskcategory ALTER COLUMN id SET DEFAULT nextval('public.task_taskcategory_id_seq'::regclass); + + +-- +-- Name: task_taskcompletion id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.task_taskcompletion ALTER COLUMN id SET DEFAULT nextval('public.task_taskcompletion_id_seq'::regclass); + + +-- +-- Name: task_taskcompletionimage id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.task_taskcompletionimage ALTER COLUMN id SET DEFAULT nextval('public.task_taskcompletionimage_id_seq'::regclass); + + +-- +-- Name: task_taskfrequency id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.task_taskfrequency ALTER COLUMN id SET DEFAULT nextval('public.task_taskfrequency_id_seq'::regclass); + + +-- +-- Name: task_taskpriority id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.task_taskpriority ALTER COLUMN id SET DEFAULT nextval('public.task_taskpriority_id_seq'::regclass); + + +-- +-- Name: task_tasktemplate id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.task_tasktemplate ALTER COLUMN id SET DEFAULT nextval('public.task_tasktemplate_id_seq'::regclass); + + +-- +-- Name: task_zipclimateregion id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.task_zipclimateregion ALTER COLUMN id SET DEFAULT nextval('public.task_zipclimateregion_id_seq'::regclass); + + +-- +-- Name: user_applesocialauth id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.user_applesocialauth ALTER COLUMN id SET DEFAULT nextval('public.user_applesocialauth_id_seq'::regclass); + + +-- +-- Name: user_confirmationcode id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.user_confirmationcode ALTER COLUMN id SET DEFAULT nextval('public.user_confirmationcode_id_seq'::regclass); + + +-- +-- Name: user_googlesocialauth id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.user_googlesocialauth ALTER COLUMN id SET DEFAULT nextval('public.user_googlesocialauth_id_seq'::regclass); + + +-- +-- Name: user_passwordresetcode id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.user_passwordresetcode ALTER COLUMN id SET DEFAULT nextval('public.user_passwordresetcode_id_seq'::regclass); + + +-- +-- Name: user_userprofile id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.user_userprofile ALTER COLUMN id SET DEFAULT nextval('public.user_userprofile_id_seq'::regclass); + + +-- +-- Name: admin_users admin_users_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.admin_users + ADD CONSTRAINT admin_users_pkey PRIMARY KEY (id); + + +-- +-- Name: auth_user auth_user_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.auth_user + ADD CONSTRAINT auth_user_pkey PRIMARY KEY (id); + + +-- +-- Name: data_migrations data_migrations_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.data_migrations + ADD CONSTRAINT data_migrations_pkey PRIMARY KEY (id); + + +-- +-- Name: goadmin_menu goadmin_menu_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.goadmin_menu + ADD CONSTRAINT goadmin_menu_pkey PRIMARY KEY (id); + + +-- +-- Name: goadmin_operation_log goadmin_operation_log_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.goadmin_operation_log + ADD CONSTRAINT goadmin_operation_log_pkey PRIMARY KEY (id); + + +-- +-- Name: goadmin_permissions goadmin_permissions_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.goadmin_permissions + ADD CONSTRAINT goadmin_permissions_pkey PRIMARY KEY (id); + + +-- +-- Name: goadmin_role_menu goadmin_role_menu_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.goadmin_role_menu + ADD CONSTRAINT goadmin_role_menu_pkey PRIMARY KEY (id); + + +-- +-- Name: goadmin_role_permissions goadmin_role_permissions_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.goadmin_role_permissions + ADD CONSTRAINT goadmin_role_permissions_pkey PRIMARY KEY (id); + + +-- +-- Name: goadmin_role_users goadmin_role_users_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.goadmin_role_users + ADD CONSTRAINT goadmin_role_users_pkey PRIMARY KEY (id); + + +-- +-- Name: goadmin_roles goadmin_roles_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.goadmin_roles + ADD CONSTRAINT goadmin_roles_pkey PRIMARY KEY (id); + + +-- +-- Name: goadmin_session goadmin_session_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.goadmin_session + ADD CONSTRAINT goadmin_session_pkey PRIMARY KEY (id); + + +-- +-- Name: goadmin_site goadmin_site_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.goadmin_site + ADD CONSTRAINT goadmin_site_pkey PRIMARY KEY (id); + + +-- +-- Name: goadmin_user_permissions goadmin_user_permissions_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.goadmin_user_permissions + ADD CONSTRAINT goadmin_user_permissions_pkey PRIMARY KEY (id); + + +-- +-- Name: goadmin_users goadmin_users_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.goadmin_users + ADD CONSTRAINT goadmin_users_pkey PRIMARY KEY (id); + + +-- +-- Name: notifications_notification notifications_notification_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.notifications_notification + ADD CONSTRAINT notifications_notification_pkey PRIMARY KEY (id); + + +-- +-- Name: notifications_notificationpreference notifications_notificationpreference_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.notifications_notificationpreference + ADD CONSTRAINT notifications_notificationpreference_pkey PRIMARY KEY (id); + + +-- +-- Name: onboarding_emails onboarding_emails_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.onboarding_emails + ADD CONSTRAINT onboarding_emails_pkey PRIMARY KEY (id); + + +-- +-- Name: push_notifications_apnsdevice push_notifications_apnsdevice_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.push_notifications_apnsdevice + ADD CONSTRAINT push_notifications_apnsdevice_pkey PRIMARY KEY (id); + + +-- +-- Name: push_notifications_gcmdevice push_notifications_gcmdevice_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.push_notifications_gcmdevice + ADD CONSTRAINT push_notifications_gcmdevice_pkey PRIMARY KEY (id); + + +-- +-- Name: residence_residence residence_residence_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.residence_residence + ADD CONSTRAINT residence_residence_pkey PRIMARY KEY (id); + + +-- +-- Name: residence_residence_users residence_residence_users_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.residence_residence_users + ADD CONSTRAINT residence_residence_users_pkey PRIMARY KEY (user_id, residence_id); + + +-- +-- Name: residence_residencesharecode residence_residencesharecode_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.residence_residencesharecode + ADD CONSTRAINT residence_residencesharecode_pkey PRIMARY KEY (id); + + +-- +-- Name: residence_residencetype residence_residencetype_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.residence_residencetype + ADD CONSTRAINT residence_residencetype_pkey PRIMARY KEY (id); + + +-- +-- Name: subscription_featurebenefit subscription_featurebenefit_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.subscription_featurebenefit + ADD CONSTRAINT subscription_featurebenefit_pkey PRIMARY KEY (id); + + +-- +-- Name: subscription_promotion subscription_promotion_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.subscription_promotion + ADD CONSTRAINT subscription_promotion_pkey PRIMARY KEY (id); + + +-- +-- Name: subscription_subscriptionsettings subscription_subscriptionsettings_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.subscription_subscriptionsettings + ADD CONSTRAINT subscription_subscriptionsettings_pkey PRIMARY KEY (id); + + +-- +-- Name: subscription_tierlimits subscription_tierlimits_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.subscription_tierlimits + ADD CONSTRAINT subscription_tierlimits_pkey PRIMARY KEY (id); + + +-- +-- Name: subscription_upgradetrigger subscription_upgradetrigger_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.subscription_upgradetrigger + ADD CONSTRAINT subscription_upgradetrigger_pkey PRIMARY KEY (id); + + +-- +-- Name: subscription_usersubscription subscription_usersubscription_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.subscription_usersubscription + ADD CONSTRAINT subscription_usersubscription_pkey PRIMARY KEY (id); + + +-- +-- Name: task_climateregion task_climateregion_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.task_climateregion + ADD CONSTRAINT task_climateregion_pkey PRIMARY KEY (id); + + +-- +-- Name: task_contractor task_contractor_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.task_contractor + ADD CONSTRAINT task_contractor_pkey PRIMARY KEY (id); + + +-- +-- Name: task_contractor_specialties task_contractor_specialties_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.task_contractor_specialties + ADD CONSTRAINT task_contractor_specialties_pkey PRIMARY KEY (contractor_id, contractor_specialty_id); + + +-- +-- Name: task_contractorspecialty task_contractorspecialty_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.task_contractorspecialty + ADD CONSTRAINT task_contractorspecialty_pkey PRIMARY KEY (id); + + +-- +-- Name: task_document task_document_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.task_document + ADD CONSTRAINT task_document_pkey PRIMARY KEY (id); + + +-- +-- Name: task_documentimage task_documentimage_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.task_documentimage + ADD CONSTRAINT task_documentimage_pkey PRIMARY KEY (id); + + +-- +-- Name: task_reminderlog task_reminderlog_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.task_reminderlog + ADD CONSTRAINT task_reminderlog_pkey PRIMARY KEY (id); + + +-- +-- Name: task_task task_task_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.task_task + ADD CONSTRAINT task_task_pkey PRIMARY KEY (id); + + +-- +-- Name: task_taskcategory task_taskcategory_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.task_taskcategory + ADD CONSTRAINT task_taskcategory_pkey PRIMARY KEY (id); + + +-- +-- Name: task_taskcompletion task_taskcompletion_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.task_taskcompletion + ADD CONSTRAINT task_taskcompletion_pkey PRIMARY KEY (id); + + +-- +-- Name: task_taskcompletionimage task_taskcompletionimage_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.task_taskcompletionimage + ADD CONSTRAINT task_taskcompletionimage_pkey PRIMARY KEY (id); + + +-- +-- Name: task_taskfrequency task_taskfrequency_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.task_taskfrequency + ADD CONSTRAINT task_taskfrequency_pkey PRIMARY KEY (id); + + +-- +-- Name: task_taskpriority task_taskpriority_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.task_taskpriority + ADD CONSTRAINT task_taskpriority_pkey PRIMARY KEY (id); + + +-- +-- Name: task_tasktemplate task_tasktemplate_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.task_tasktemplate + ADD CONSTRAINT task_tasktemplate_pkey PRIMARY KEY (id); + + +-- +-- Name: task_zipclimateregion task_zipclimateregion_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.task_zipclimateregion + ADD CONSTRAINT task_zipclimateregion_pkey PRIMARY KEY (id); + + +-- +-- Name: user_applesocialauth user_applesocialauth_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.user_applesocialauth + ADD CONSTRAINT user_applesocialauth_pkey PRIMARY KEY (id); + + +-- +-- Name: user_authtoken user_authtoken_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.user_authtoken + ADD CONSTRAINT user_authtoken_pkey PRIMARY KEY (key); + + +-- +-- Name: user_confirmationcode user_confirmationcode_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.user_confirmationcode + ADD CONSTRAINT user_confirmationcode_pkey PRIMARY KEY (id); + + +-- +-- Name: user_googlesocialauth user_googlesocialauth_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.user_googlesocialauth + ADD CONSTRAINT user_googlesocialauth_pkey PRIMARY KEY (id); + + +-- +-- Name: user_passwordresetcode user_passwordresetcode_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.user_passwordresetcode + ADD CONSTRAINT user_passwordresetcode_pkey PRIMARY KEY (id); + + +-- +-- Name: user_userprofile user_userprofile_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.user_userprofile + ADD CONSTRAINT user_userprofile_pkey PRIMARY KEY (id); + + +-- +-- Name: idx_admin_users_email; Type: INDEX; Schema: public; Owner: - +-- + +CREATE UNIQUE INDEX idx_admin_users_email ON public.admin_users USING btree (email); + + +-- +-- Name: idx_auth_user_email; Type: INDEX; Schema: public; Owner: - +-- + +CREATE UNIQUE INDEX idx_auth_user_email ON public.auth_user USING btree (email); + + +-- +-- Name: idx_auth_user_username; Type: INDEX; Schema: public; Owner: - +-- + +CREATE UNIQUE INDEX idx_auth_user_username ON public.auth_user USING btree (username); + + +-- +-- Name: idx_data_migrations_name; Type: INDEX; Schema: public; Owner: - +-- + +CREATE UNIQUE INDEX idx_data_migrations_name ON public.data_migrations USING btree (name); + + +-- +-- Name: idx_goadmin_menu_parent_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX idx_goadmin_menu_parent_id ON public.goadmin_menu USING btree (parent_id); + + +-- +-- Name: idx_goadmin_operation_log_user_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX idx_goadmin_operation_log_user_id ON public.goadmin_operation_log USING btree (user_id); + + +-- +-- Name: idx_goadmin_permissions_slug; Type: INDEX; Schema: public; Owner: - +-- + +CREATE UNIQUE INDEX idx_goadmin_permissions_slug ON public.goadmin_permissions USING btree (slug); + + +-- +-- Name: idx_goadmin_role_menu_menu_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX idx_goadmin_role_menu_menu_id ON public.goadmin_role_menu USING btree (menu_id); + + +-- +-- Name: idx_goadmin_role_menu_role_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX idx_goadmin_role_menu_role_id ON public.goadmin_role_menu USING btree (role_id); + + +-- +-- Name: idx_goadmin_role_permissions_permission_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX idx_goadmin_role_permissions_permission_id ON public.goadmin_role_permissions USING btree (permission_id); + + +-- +-- Name: idx_goadmin_role_permissions_role_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX idx_goadmin_role_permissions_role_id ON public.goadmin_role_permissions USING btree (role_id); + + +-- +-- Name: idx_goadmin_role_users_role_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX idx_goadmin_role_users_role_id ON public.goadmin_role_users USING btree (role_id); + + +-- +-- Name: idx_goadmin_role_users_user_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX idx_goadmin_role_users_user_id ON public.goadmin_role_users USING btree (user_id); + + +-- +-- Name: idx_goadmin_roles_slug; Type: INDEX; Schema: public; Owner: - +-- + +CREATE UNIQUE INDEX idx_goadmin_roles_slug ON public.goadmin_roles USING btree (slug); + + +-- +-- Name: idx_goadmin_session_sid; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX idx_goadmin_session_sid ON public.goadmin_session USING btree (sid); + + +-- +-- Name: idx_goadmin_site_key; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX idx_goadmin_site_key ON public.goadmin_site USING btree (key); + + +-- +-- Name: idx_goadmin_user_permissions_permission_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX idx_goadmin_user_permissions_permission_id ON public.goadmin_user_permissions USING btree (permission_id); + + +-- +-- Name: idx_goadmin_user_permissions_user_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX idx_goadmin_user_permissions_user_id ON public.goadmin_user_permissions USING btree (user_id); + + +-- +-- Name: idx_goadmin_users_username; Type: INDEX; Schema: public; Owner: - +-- + +CREATE UNIQUE INDEX idx_goadmin_users_username ON public.goadmin_users USING btree (username); + + +-- +-- Name: idx_notifications_notification_user_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX idx_notifications_notification_user_id ON public.notifications_notification USING btree (user_id); + + +-- +-- Name: idx_notifications_notificationpreference_user_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE UNIQUE INDEX idx_notifications_notificationpreference_user_id ON public.notifications_notificationpreference USING btree (user_id); + + +-- +-- Name: idx_onboarding_emails_email_type; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX idx_onboarding_emails_email_type ON public.onboarding_emails USING btree (email_type); + + +-- +-- Name: idx_onboarding_emails_tracking_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE UNIQUE INDEX idx_onboarding_emails_tracking_id ON public.onboarding_emails USING btree (tracking_id); + + +-- +-- Name: idx_onboarding_emails_user_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX idx_onboarding_emails_user_id ON public.onboarding_emails USING btree (user_id); + + +-- +-- Name: idx_onboarding_emails_user_type; Type: INDEX; Schema: public; Owner: - +-- + +CREATE UNIQUE INDEX idx_onboarding_emails_user_type ON public.onboarding_emails USING btree (user_id, email_type); + + +-- +-- Name: idx_push_notifications_apnsdevice_registration_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE UNIQUE INDEX idx_push_notifications_apnsdevice_registration_id ON public.push_notifications_apnsdevice USING btree (registration_id); + + +-- +-- Name: idx_push_notifications_apnsdevice_user_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX idx_push_notifications_apnsdevice_user_id ON public.push_notifications_apnsdevice USING btree (user_id); + + +-- +-- Name: idx_push_notifications_gcmdevice_registration_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE UNIQUE INDEX idx_push_notifications_gcmdevice_registration_id ON public.push_notifications_gcmdevice USING btree (registration_id); + + +-- +-- Name: idx_push_notifications_gcmdevice_user_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX idx_push_notifications_gcmdevice_user_id ON public.push_notifications_gcmdevice USING btree (user_id); + + +-- +-- Name: idx_reminderlog_sent_at; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX idx_reminderlog_sent_at ON public.task_reminderlog USING btree (sent_at); + + +-- +-- Name: idx_reminderlog_task_user_date; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX idx_reminderlog_task_user_date ON public.task_reminderlog USING btree (task_id, user_id, due_date); + + +-- +-- Name: idx_residence_residence_is_active; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX idx_residence_residence_is_active ON public.residence_residence USING btree (is_active); + + +-- +-- Name: idx_residence_residence_owner_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX idx_residence_residence_owner_id ON public.residence_residence USING btree (owner_id); + + +-- +-- Name: idx_residence_residencesharecode_code; Type: INDEX; Schema: public; Owner: - +-- + +CREATE UNIQUE INDEX idx_residence_residencesharecode_code ON public.residence_residencesharecode USING btree (code); + + +-- +-- Name: idx_residence_residencesharecode_is_active; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX idx_residence_residencesharecode_is_active ON public.residence_residencesharecode USING btree (is_active); + + +-- +-- Name: idx_residence_residencesharecode_residence_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX idx_residence_residencesharecode_residence_id ON public.residence_residencesharecode USING btree (residence_id); + + +-- +-- Name: idx_subscription_promotion_promotion_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE UNIQUE INDEX idx_subscription_promotion_promotion_id ON public.subscription_promotion USING btree (promotion_id); + + +-- +-- Name: idx_subscription_tierlimits_tier; Type: INDEX; Schema: public; Owner: - +-- + +CREATE UNIQUE INDEX idx_subscription_tierlimits_tier ON public.subscription_tierlimits USING btree (tier); + + +-- +-- Name: idx_subscription_upgradetrigger_trigger_key; Type: INDEX; Schema: public; Owner: - +-- + +CREATE UNIQUE INDEX idx_subscription_upgradetrigger_trigger_key ON public.subscription_upgradetrigger USING btree (trigger_key); + + +-- +-- Name: idx_subscription_usersubscription_user_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE UNIQUE INDEX idx_subscription_usersubscription_user_id ON public.subscription_usersubscription USING btree (user_id); + + +-- +-- Name: idx_task_climateregion_is_active; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX idx_task_climateregion_is_active ON public.task_climateregion USING btree (is_active); + + +-- +-- Name: idx_task_climateregion_name; Type: INDEX; Schema: public; Owner: - +-- + +CREATE UNIQUE INDEX idx_task_climateregion_name ON public.task_climateregion USING btree (name); + + +-- +-- Name: idx_task_climateregion_zone_number; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX idx_task_climateregion_zone_number ON public.task_climateregion USING btree (zone_number); + + +-- +-- Name: idx_task_contractor_created_by_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX idx_task_contractor_created_by_id ON public.task_contractor USING btree (created_by_id); + + +-- +-- Name: idx_task_contractor_is_active; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX idx_task_contractor_is_active ON public.task_contractor USING btree (is_active); + + +-- +-- Name: idx_task_contractor_residence_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX idx_task_contractor_residence_id ON public.task_contractor USING btree (residence_id); + + +-- +-- Name: idx_task_document_created_by_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX idx_task_document_created_by_id ON public.task_document USING btree (created_by_id); + + +-- +-- Name: idx_task_document_expiry_date; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX idx_task_document_expiry_date ON public.task_document USING btree (expiry_date); + + +-- +-- Name: idx_task_document_is_active; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX idx_task_document_is_active ON public.task_document USING btree (is_active); + + +-- +-- Name: idx_task_document_residence_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX idx_task_document_residence_id ON public.task_document USING btree (residence_id); + + +-- +-- Name: idx_task_document_task_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX idx_task_document_task_id ON public.task_document USING btree (task_id); + + +-- +-- Name: idx_task_documentimage_document_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX idx_task_documentimage_document_id ON public.task_documentimage USING btree (document_id); + + +-- +-- Name: idx_task_task_assigned_to_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX idx_task_task_assigned_to_id ON public.task_task USING btree (assigned_to_id); + + +-- +-- Name: idx_task_task_category_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX idx_task_task_category_id ON public.task_task USING btree (category_id); + + +-- +-- Name: idx_task_task_contractor_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX idx_task_task_contractor_id ON public.task_task USING btree (contractor_id); + + +-- +-- Name: idx_task_task_created_by_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX idx_task_task_created_by_id ON public.task_task USING btree (created_by_id); + + +-- +-- Name: idx_task_task_due_date; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX idx_task_task_due_date ON public.task_task USING btree (due_date); + + +-- +-- Name: idx_task_task_frequency_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX idx_task_task_frequency_id ON public.task_task USING btree (frequency_id); + + +-- +-- Name: idx_task_task_in_progress; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX idx_task_task_in_progress ON public.task_task USING btree (in_progress); + + +-- +-- Name: idx_task_task_is_archived; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX idx_task_task_is_archived ON public.task_task USING btree (is_archived); + + +-- +-- Name: idx_task_task_is_cancelled; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX idx_task_task_is_cancelled ON public.task_task USING btree (is_cancelled); + + +-- +-- Name: idx_task_task_next_due_date; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX idx_task_task_next_due_date ON public.task_task USING btree (next_due_date); + + +-- +-- Name: idx_task_task_parent_task_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX idx_task_task_parent_task_id ON public.task_task USING btree (parent_task_id); + + +-- +-- Name: idx_task_task_priority_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX idx_task_task_priority_id ON public.task_task USING btree (priority_id); + + +-- +-- Name: idx_task_task_residence_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX idx_task_task_residence_id ON public.task_task USING btree (residence_id); + + +-- +-- Name: idx_task_task_task_template_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX idx_task_task_task_template_id ON public.task_task USING btree (task_template_id); + + +-- +-- Name: idx_task_taskcompletion_completed_by_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX idx_task_taskcompletion_completed_by_id ON public.task_taskcompletion USING btree (completed_by_id); + + +-- +-- Name: idx_task_taskcompletion_task_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX idx_task_taskcompletion_task_id ON public.task_taskcompletion USING btree (task_id); + + +-- +-- Name: idx_task_taskcompletionimage_completion_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX idx_task_taskcompletionimage_completion_id ON public.task_taskcompletionimage USING btree (completion_id); + + +-- +-- Name: idx_task_tasktemplate_category_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX idx_task_tasktemplate_category_id ON public.task_tasktemplate USING btree (category_id); + + +-- +-- Name: idx_task_tasktemplate_frequency_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX idx_task_tasktemplate_frequency_id ON public.task_tasktemplate USING btree (frequency_id); + + +-- +-- Name: idx_task_tasktemplate_is_active; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX idx_task_tasktemplate_is_active ON public.task_tasktemplate USING btree (is_active); + + +-- +-- Name: idx_task_zipclimateregion_climate_region_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX idx_task_zipclimateregion_climate_region_id ON public.task_zipclimateregion USING btree (climate_region_id); + + +-- +-- Name: idx_task_zipclimateregion_zip_code; Type: INDEX; Schema: public; Owner: - +-- + +CREATE UNIQUE INDEX idx_task_zipclimateregion_zip_code ON public.task_zipclimateregion USING btree (zip_code); + + +-- +-- Name: idx_user_applesocialauth_apple_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE UNIQUE INDEX idx_user_applesocialauth_apple_id ON public.user_applesocialauth USING btree (apple_id); + + +-- +-- Name: idx_user_applesocialauth_user_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE UNIQUE INDEX idx_user_applesocialauth_user_id ON public.user_applesocialauth USING btree (user_id); + + +-- +-- Name: idx_user_authtoken_user_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE UNIQUE INDEX idx_user_authtoken_user_id ON public.user_authtoken USING btree (user_id); + + +-- +-- Name: idx_user_confirmationcode_user_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX idx_user_confirmationcode_user_id ON public.user_confirmationcode USING btree (user_id); + + +-- +-- Name: idx_user_googlesocialauth_google_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE UNIQUE INDEX idx_user_googlesocialauth_google_id ON public.user_googlesocialauth USING btree (google_id); + + +-- +-- Name: idx_user_googlesocialauth_user_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE UNIQUE INDEX idx_user_googlesocialauth_user_id ON public.user_googlesocialauth USING btree (user_id); + + +-- +-- Name: idx_user_passwordresetcode_reset_token; Type: INDEX; Schema: public; Owner: - +-- + +CREATE UNIQUE INDEX idx_user_passwordresetcode_reset_token ON public.user_passwordresetcode USING btree (reset_token); + + +-- +-- Name: idx_user_passwordresetcode_user_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX idx_user_passwordresetcode_user_id ON public.user_passwordresetcode USING btree (user_id); + + +-- +-- Name: idx_user_userprofile_user_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE UNIQUE INDEX idx_user_userprofile_user_id ON public.user_userprofile USING btree (user_id); + + +-- +-- Name: user_authtoken fk_auth_user_auth_token; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.user_authtoken + ADD CONSTRAINT fk_auth_user_auth_token FOREIGN KEY (user_id) REFERENCES public.auth_user(id); + + +-- +-- Name: notifications_notificationpreference fk_auth_user_notification_pref; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.notifications_notificationpreference + ADD CONSTRAINT fk_auth_user_notification_pref FOREIGN KEY (user_id) REFERENCES public.auth_user(id); + + +-- +-- Name: residence_residence fk_auth_user_owned_residences; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.residence_residence + ADD CONSTRAINT fk_auth_user_owned_residences FOREIGN KEY (owner_id) REFERENCES public.auth_user(id); + + +-- +-- Name: user_userprofile fk_auth_user_profile; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.user_userprofile + ADD CONSTRAINT fk_auth_user_profile FOREIGN KEY (user_id) REFERENCES public.auth_user(id); + + +-- +-- Name: subscription_usersubscription fk_auth_user_subscription; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.subscription_usersubscription + ADD CONSTRAINT fk_auth_user_subscription FOREIGN KEY (user_id) REFERENCES public.auth_user(id); + + +-- +-- Name: notifications_notification fk_notifications_notification_user; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.notifications_notification + ADD CONSTRAINT fk_notifications_notification_user FOREIGN KEY (user_id) REFERENCES public.auth_user(id); + + +-- +-- Name: onboarding_emails fk_onboarding_emails_user; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.onboarding_emails + ADD CONSTRAINT fk_onboarding_emails_user FOREIGN KEY (user_id) REFERENCES public.auth_user(id); + + +-- +-- Name: push_notifications_apnsdevice fk_push_notifications_apnsdevice_user; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.push_notifications_apnsdevice + ADD CONSTRAINT fk_push_notifications_apnsdevice_user FOREIGN KEY (user_id) REFERENCES public.auth_user(id); + + +-- +-- Name: push_notifications_gcmdevice fk_push_notifications_gcmdevice_user; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.push_notifications_gcmdevice + ADD CONSTRAINT fk_push_notifications_gcmdevice_user FOREIGN KEY (user_id) REFERENCES public.auth_user(id); + + +-- +-- Name: residence_residence fk_residence_residence_property_type; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.residence_residence + ADD CONSTRAINT fk_residence_residence_property_type FOREIGN KEY (property_type_id) REFERENCES public.residence_residencetype(id); + + +-- +-- Name: residence_residence_users fk_residence_residence_users_residence; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.residence_residence_users + ADD CONSTRAINT fk_residence_residence_users_residence FOREIGN KEY (residence_id) REFERENCES public.residence_residence(id); + + +-- +-- Name: residence_residence_users fk_residence_residence_users_user; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.residence_residence_users + ADD CONSTRAINT fk_residence_residence_users_user FOREIGN KEY (user_id) REFERENCES public.auth_user(id); + + +-- +-- Name: residence_residencesharecode fk_residence_residencesharecode_created_by; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.residence_residencesharecode + ADD CONSTRAINT fk_residence_residencesharecode_created_by FOREIGN KEY (created_by_id) REFERENCES public.auth_user(id); + + +-- +-- Name: residence_residencesharecode fk_residence_residencesharecode_residence; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.residence_residencesharecode + ADD CONSTRAINT fk_residence_residencesharecode_residence FOREIGN KEY (residence_id) REFERENCES public.residence_residence(id); + + +-- +-- Name: task_contractor fk_task_contractor_created_by; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.task_contractor + ADD CONSTRAINT fk_task_contractor_created_by FOREIGN KEY (created_by_id) REFERENCES public.auth_user(id); + + +-- +-- Name: task_contractor fk_task_contractor_residence; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.task_contractor + ADD CONSTRAINT fk_task_contractor_residence FOREIGN KEY (residence_id) REFERENCES public.residence_residence(id); + + +-- +-- Name: task_contractor_specialties fk_task_contractor_specialties_contractor; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.task_contractor_specialties + ADD CONSTRAINT fk_task_contractor_specialties_contractor FOREIGN KEY (contractor_id) REFERENCES public.task_contractor(id); + + +-- +-- Name: task_contractor_specialties fk_task_contractor_specialties_contractor_specialty; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.task_contractor_specialties + ADD CONSTRAINT fk_task_contractor_specialties_contractor_specialty FOREIGN KEY (contractor_specialty_id) REFERENCES public.task_contractorspecialty(id); + + +-- +-- Name: task_task fk_task_contractor_tasks; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.task_task + ADD CONSTRAINT fk_task_contractor_tasks FOREIGN KEY (contractor_id) REFERENCES public.task_contractor(id); + + +-- +-- Name: task_document fk_task_document_created_by; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.task_document + ADD CONSTRAINT fk_task_document_created_by FOREIGN KEY (created_by_id) REFERENCES public.auth_user(id); + + +-- +-- Name: task_documentimage fk_task_document_images; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.task_documentimage + ADD CONSTRAINT fk_task_document_images FOREIGN KEY (document_id) REFERENCES public.task_document(id); + + +-- +-- Name: task_document fk_task_document_residence; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.task_document + ADD CONSTRAINT fk_task_document_residence FOREIGN KEY (residence_id) REFERENCES public.residence_residence(id); + + +-- +-- Name: task_document fk_task_document_task; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.task_document + ADD CONSTRAINT fk_task_document_task FOREIGN KEY (task_id) REFERENCES public.task_task(id); + + +-- +-- Name: task_reminderlog fk_task_reminderlog_notification; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.task_reminderlog + ADD CONSTRAINT fk_task_reminderlog_notification FOREIGN KEY (notification_id) REFERENCES public.notifications_notification(id); + + +-- +-- Name: task_reminderlog fk_task_reminderlog_task; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.task_reminderlog + ADD CONSTRAINT fk_task_reminderlog_task FOREIGN KEY (task_id) REFERENCES public.task_task(id); + + +-- +-- Name: task_reminderlog fk_task_reminderlog_user; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.task_reminderlog + ADD CONSTRAINT fk_task_reminderlog_user FOREIGN KEY (user_id) REFERENCES public.auth_user(id); + + +-- +-- Name: task_task fk_task_task_assigned_to; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.task_task + ADD CONSTRAINT fk_task_task_assigned_to FOREIGN KEY (assigned_to_id) REFERENCES public.auth_user(id); + + +-- +-- Name: task_task fk_task_task_category; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.task_task + ADD CONSTRAINT fk_task_task_category FOREIGN KEY (category_id) REFERENCES public.task_taskcategory(id); + + +-- +-- Name: task_taskcompletion fk_task_task_completions; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.task_taskcompletion + ADD CONSTRAINT fk_task_task_completions FOREIGN KEY (task_id) REFERENCES public.task_task(id); + + +-- +-- Name: task_task fk_task_task_created_by; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.task_task + ADD CONSTRAINT fk_task_task_created_by FOREIGN KEY (created_by_id) REFERENCES public.auth_user(id); + + +-- +-- Name: task_task fk_task_task_frequency; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.task_task + ADD CONSTRAINT fk_task_task_frequency FOREIGN KEY (frequency_id) REFERENCES public.task_taskfrequency(id); + + +-- +-- Name: task_task fk_task_task_parent_task; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.task_task + ADD CONSTRAINT fk_task_task_parent_task FOREIGN KEY (parent_task_id) REFERENCES public.task_task(id); + + +-- +-- Name: task_task fk_task_task_priority; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.task_task + ADD CONSTRAINT fk_task_task_priority FOREIGN KEY (priority_id) REFERENCES public.task_taskpriority(id); + + +-- +-- Name: task_task fk_task_task_residence; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.task_task + ADD CONSTRAINT fk_task_task_residence FOREIGN KEY (residence_id) REFERENCES public.residence_residence(id); + + +-- +-- Name: task_task fk_task_task_task_template; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.task_task + ADD CONSTRAINT fk_task_task_task_template FOREIGN KEY (task_template_id) REFERENCES public.task_tasktemplate(id); + + +-- +-- Name: task_taskcompletion fk_task_taskcompletion_completed_by; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.task_taskcompletion + ADD CONSTRAINT fk_task_taskcompletion_completed_by FOREIGN KEY (completed_by_id) REFERENCES public.auth_user(id); + + +-- +-- Name: task_taskcompletionimage fk_task_taskcompletion_images; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.task_taskcompletionimage + ADD CONSTRAINT fk_task_taskcompletion_images FOREIGN KEY (completion_id) REFERENCES public.task_taskcompletion(id); + + +-- +-- Name: task_tasktemplate fk_task_tasktemplate_category; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.task_tasktemplate + ADD CONSTRAINT fk_task_tasktemplate_category FOREIGN KEY (category_id) REFERENCES public.task_taskcategory(id); + + +-- +-- Name: task_tasktemplate fk_task_tasktemplate_frequency; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.task_tasktemplate + ADD CONSTRAINT fk_task_tasktemplate_frequency FOREIGN KEY (frequency_id) REFERENCES public.task_taskfrequency(id); + + +-- +-- Name: task_zipclimateregion fk_task_zipclimateregion_climate_region; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.task_zipclimateregion + ADD CONSTRAINT fk_task_zipclimateregion_climate_region FOREIGN KEY (climate_region_id) REFERENCES public.task_climateregion(id); + + +-- +-- Name: user_applesocialauth fk_user_applesocialauth_user; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.user_applesocialauth + ADD CONSTRAINT fk_user_applesocialauth_user FOREIGN KEY (user_id) REFERENCES public.auth_user(id); + + +-- +-- Name: user_confirmationcode fk_user_confirmationcode_user; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.user_confirmationcode + ADD CONSTRAINT fk_user_confirmationcode_user FOREIGN KEY (user_id) REFERENCES public.auth_user(id); + + +-- +-- Name: user_googlesocialauth fk_user_googlesocialauth_user; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.user_googlesocialauth + ADD CONSTRAINT fk_user_googlesocialauth_user FOREIGN KEY (user_id) REFERENCES public.auth_user(id); + + +-- +-- Name: user_passwordresetcode fk_user_passwordresetcode_user; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.user_passwordresetcode + ADD CONSTRAINT fk_user_passwordresetcode_user FOREIGN KEY (user_id) REFERENCES public.auth_user(id); + + +-- +-- PostgreSQL database dump complete +-- + + + +-- +goose Down +-- Initial schema rollback is intentionally unimplemented. +-- +-- Reverting migration 000001 means dropping every table in the database. +-- That's not a "down migration" — it's a database wipe. If you genuinely +-- need to redo the schema from scratch, drop the database and re-run +-- `migrate up` against an empty target. +-- +-- Any future migrations (000002, 000003, …) MUST provide working .down.sql +-- files that revert their specific changes (DROP COLUMN, DROP INDEX, etc). diff --git a/migrations/002_goadmin_tables.down.sql b/migrations/002_goadmin_tables.down.sql deleted file mode 100644 index 00f3990..0000000 --- a/migrations/002_goadmin_tables.down.sql +++ /dev/null @@ -1,13 +0,0 @@ --- Rollback GoAdmin tables - -DROP TABLE IF EXISTS goadmin_role_menu; -DROP TABLE IF EXISTS goadmin_role_permissions; -DROP TABLE IF EXISTS goadmin_user_permissions; -DROP TABLE IF EXISTS goadmin_role_users; -DROP TABLE IF EXISTS goadmin_operation_log; -DROP TABLE IF EXISTS goadmin_site; -DROP TABLE IF EXISTS goadmin_menu; -DROP TABLE IF EXISTS goadmin_permissions; -DROP TABLE IF EXISTS goadmin_roles; -DROP TABLE IF EXISTS goadmin_users; -DROP TABLE IF EXISTS goadmin_session; diff --git a/migrations/002_goadmin_tables.up.sql b/migrations/002_goadmin_tables.up.sql deleted file mode 100644 index ebfff80..0000000 --- a/migrations/002_goadmin_tables.up.sql +++ /dev/null @@ -1,185 +0,0 @@ --- GoAdmin required tables for PostgreSQL --- This migration creates all tables needed by GoAdmin - --- Session storage table -CREATE TABLE IF NOT EXISTS goadmin_session ( - id SERIAL PRIMARY KEY, - sid VARCHAR(50) NOT NULL DEFAULT '', - "values" TEXT NOT NULL, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -); -CREATE INDEX IF NOT EXISTS idx_goadmin_session_sid ON goadmin_session(sid); - --- Users table for admin authentication -CREATE TABLE IF NOT EXISTS goadmin_users ( - id SERIAL PRIMARY KEY, - username VARCHAR(100) NOT NULL DEFAULT '', - password VARCHAR(100) NOT NULL DEFAULT '', - name VARCHAR(100) NOT NULL DEFAULT '', - avatar VARCHAR(255) DEFAULT '', - remember_token VARCHAR(100) DEFAULT '', - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -); -CREATE UNIQUE INDEX IF NOT EXISTS idx_goadmin_users_username ON goadmin_users(username); - --- Roles table -CREATE TABLE IF NOT EXISTS goadmin_roles ( - id SERIAL PRIMARY KEY, - name VARCHAR(50) NOT NULL DEFAULT '', - slug VARCHAR(50) NOT NULL DEFAULT '', - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -); -CREATE UNIQUE INDEX IF NOT EXISTS idx_goadmin_roles_slug ON goadmin_roles(slug); - --- Permissions table -CREATE TABLE IF NOT EXISTS goadmin_permissions ( - id SERIAL PRIMARY KEY, - name VARCHAR(50) NOT NULL DEFAULT '', - slug VARCHAR(50) NOT NULL DEFAULT '', - http_method VARCHAR(255) DEFAULT '', - http_path TEXT NOT NULL, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -); -CREATE UNIQUE INDEX IF NOT EXISTS idx_goadmin_permissions_slug ON goadmin_permissions(slug); - --- Role-User relationship table -CREATE TABLE IF NOT EXISTS goadmin_role_users ( - id SERIAL PRIMARY KEY, - role_id INT NOT NULL, - user_id INT NOT NULL, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -); -CREATE INDEX IF NOT EXISTS idx_goadmin_role_users_role_id ON goadmin_role_users(role_id); -CREATE INDEX IF NOT EXISTS idx_goadmin_role_users_user_id ON goadmin_role_users(user_id); - --- User-Permission relationship table -CREATE TABLE IF NOT EXISTS goadmin_user_permissions ( - id SERIAL PRIMARY KEY, - user_id INT NOT NULL, - permission_id INT NOT NULL, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -); -CREATE INDEX IF NOT EXISTS idx_goadmin_user_permissions_user_id ON goadmin_user_permissions(user_id); -CREATE INDEX IF NOT EXISTS idx_goadmin_user_permissions_permission_id ON goadmin_user_permissions(permission_id); - --- Role-Permission relationship table -CREATE TABLE IF NOT EXISTS goadmin_role_permissions ( - id SERIAL PRIMARY KEY, - role_id INT NOT NULL, - permission_id INT NOT NULL, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -); -CREATE INDEX IF NOT EXISTS idx_goadmin_role_permissions_role_id ON goadmin_role_permissions(role_id); -CREATE INDEX IF NOT EXISTS idx_goadmin_role_permissions_permission_id ON goadmin_role_permissions(permission_id); - --- Menu table for admin sidebar -CREATE TABLE IF NOT EXISTS goadmin_menu ( - id SERIAL PRIMARY KEY, - parent_id INT NOT NULL DEFAULT 0, - type INT NOT NULL DEFAULT 0, - "order" INT NOT NULL DEFAULT 0, - title VARCHAR(50) NOT NULL DEFAULT '', - icon VARCHAR(50) NOT NULL DEFAULT '', - uri VARCHAR(3000) NOT NULL DEFAULT '', - header VARCHAR(150) DEFAULT '', - plugin_name VARCHAR(150) NOT NULL DEFAULT '', - uuid VARCHAR(150) DEFAULT '', - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -); -CREATE INDEX IF NOT EXISTS idx_goadmin_menu_parent_id ON goadmin_menu(parent_id); - --- Role-Menu relationship table -CREATE TABLE IF NOT EXISTS goadmin_role_menu ( - id SERIAL PRIMARY KEY, - role_id INT NOT NULL, - menu_id INT NOT NULL, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -); -CREATE INDEX IF NOT EXISTS idx_goadmin_role_menu_role_id ON goadmin_role_menu(role_id); -CREATE INDEX IF NOT EXISTS idx_goadmin_role_menu_menu_id ON goadmin_role_menu(menu_id); - --- Operation log table for audit trail -CREATE TABLE IF NOT EXISTS goadmin_operation_log ( - id SERIAL PRIMARY KEY, - user_id INT NOT NULL, - path VARCHAR(255) NOT NULL DEFAULT '', - method VARCHAR(10) NOT NULL DEFAULT '', - ip VARCHAR(15) NOT NULL DEFAULT '', - input TEXT NOT NULL, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -); -CREATE INDEX IF NOT EXISTS idx_goadmin_operation_log_user_id ON goadmin_operation_log(user_id); - --- Site configuration table -CREATE TABLE IF NOT EXISTS goadmin_site ( - id SERIAL PRIMARY KEY, - key VARCHAR(100) NOT NULL DEFAULT '', - value TEXT NOT NULL, - description VARCHAR(3000) DEFAULT '', - state INT NOT NULL DEFAULT 0, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -); -CREATE INDEX IF NOT EXISTS idx_goadmin_site_key ON goadmin_site(key); - --- Insert default admin user (password: admin) --- Password is bcrypt hash of 'admin' -INSERT INTO goadmin_users (username, password, name, avatar) -VALUES ('admin', '$2a$10$sRv1E1XmGXS5HgU7VK3bNOQRZLGDON0.2xvMlz.bKcIzI3pAF1T3y', 'Administrator', '') -ON CONFLICT DO NOTHING; - --- Insert default roles -INSERT INTO goadmin_roles (name, slug) VALUES ('Administrator', 'administrator') ON CONFLICT DO NOTHING; -INSERT INTO goadmin_roles (name, slug) VALUES ('Operator', 'operator') ON CONFLICT DO NOTHING; - --- Insert default permissions -INSERT INTO goadmin_permissions (name, slug, http_method, http_path) -VALUES ('All permissions', '*', '', '*') ON CONFLICT DO NOTHING; -INSERT INTO goadmin_permissions (name, slug, http_method, http_path) -VALUES ('Dashboard', 'dashboard', 'GET', '/') ON CONFLICT DO NOTHING; - --- Assign admin user to administrator role -INSERT INTO goadmin_role_users (role_id, user_id) -SELECT r.id, u.id FROM goadmin_roles r, goadmin_users u -WHERE r.slug = 'administrator' AND u.username = 'admin' -ON CONFLICT DO NOTHING; - --- Assign all permissions to administrator role -INSERT INTO goadmin_role_permissions (role_id, permission_id) -SELECT r.id, p.id FROM goadmin_roles r, goadmin_permissions p -WHERE r.slug = 'administrator' AND p.slug = '*' -ON CONFLICT DO NOTHING; - --- Insert default menu items -INSERT INTO goadmin_menu (parent_id, type, "order", title, icon, uri, plugin_name) VALUES -(0, 1, 1, 'Dashboard', 'fa-bar-chart', '/', ''), -(0, 1, 2, 'Admin', 'fa-tasks', '', ''), -(2, 1, 1, 'Users', 'fa-users', '/info/goadmin_users', ''), -(2, 1, 2, 'Roles', 'fa-user', '/info/goadmin_roles', ''), -(2, 1, 3, 'Permissions', 'fa-ban', '/info/goadmin_permissions', ''), -(2, 1, 4, 'Menu', 'fa-bars', '/menu', ''), -(2, 1, 5, 'Operation Log', 'fa-history', '/info/goadmin_operation_log', ''), -(0, 1, 3, 'HoneyDue', 'fa-home', '', ''), -(8, 1, 1, 'Users', 'fa-user', '/info/users', ''), -(8, 1, 2, 'Residences', 'fa-building', '/info/residences', ''), -(8, 1, 3, 'Tasks', 'fa-tasks', '/info/tasks', ''), -(8, 1, 4, 'Contractors', 'fa-wrench', '/info/contractors', ''), -(8, 1, 5, 'Documents', 'fa-file', '/info/documents', ''), -(8, 1, 6, 'Notifications', 'fa-bell', '/info/notifications', '') -ON CONFLICT DO NOTHING; - --- Assign all menus to administrator role -INSERT INTO goadmin_role_menu (role_id, menu_id) -SELECT r.id, m.id FROM goadmin_roles r, goadmin_menu m -WHERE r.slug = 'administrator' -ON CONFLICT DO NOTHING; diff --git a/migrations/003_contractor_optional_residence.down.sql b/migrations/003_contractor_optional_residence.down.sql deleted file mode 100644 index bd29d95..0000000 --- a/migrations/003_contractor_optional_residence.down.sql +++ /dev/null @@ -1,4 +0,0 @@ --- Revert: Make residence_id required again --- WARNING: This will fail if there are contractors with NULL residence_id - -ALTER TABLE task_contractor ALTER COLUMN residence_id SET NOT NULL; diff --git a/migrations/003_contractor_optional_residence.up.sql b/migrations/003_contractor_optional_residence.up.sql deleted file mode 100644 index adf8fcf..0000000 --- a/migrations/003_contractor_optional_residence.up.sql +++ /dev/null @@ -1,4 +0,0 @@ --- Make residence_id optional for contractors --- Allows contractors to be personal (no residence) or shared (with residence) - -ALTER TABLE task_contractor ALTER COLUMN residence_id DROP NOT NULL; diff --git a/migrations/004_subscription_is_free.down.sql b/migrations/004_subscription_is_free.down.sql deleted file mode 100644 index df2ba91..0000000 --- a/migrations/004_subscription_is_free.down.sql +++ /dev/null @@ -1,2 +0,0 @@ --- Remove is_free column from subscription_usersubscription table -ALTER TABLE subscription_usersubscription DROP COLUMN IF EXISTS is_free; diff --git a/migrations/004_subscription_is_free.up.sql b/migrations/004_subscription_is_free.up.sql deleted file mode 100644 index 2a4e504..0000000 --- a/migrations/004_subscription_is_free.up.sql +++ /dev/null @@ -1,3 +0,0 @@ --- Add is_free column to subscription_usersubscription table --- When true, user bypasses all limitations regardless of global settings -ALTER TABLE subscription_usersubscription ADD COLUMN IF NOT EXISTS is_free BOOLEAN NOT NULL DEFAULT FALSE; diff --git a/migrations/005_replace_status_with_in_progress.down.sql b/migrations/005_replace_status_with_in_progress.down.sql deleted file mode 100644 index eccb631..0000000 --- a/migrations/005_replace_status_with_in_progress.down.sql +++ /dev/null @@ -1,45 +0,0 @@ --- Rollback: Restore status_id foreign key from in_progress boolean - --- Step 1: Recreate the task_taskstatus table -CREATE TABLE IF NOT EXISTS task_taskstatus ( - id SERIAL PRIMARY KEY, - created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, - deleted_at TIMESTAMPTZ, - name VARCHAR(20) NOT NULL, - description TEXT, - color VARCHAR(7), - display_order INTEGER NOT NULL DEFAULT 0 -); - --- Step 2: Seed the status lookup data -INSERT INTO task_taskstatus (name, description, color, display_order) VALUES - ('Pending', 'Task is waiting to be started', '#808080', 1), - ('In Progress', 'Task is currently being worked on', '#3498db', 2), - ('Completed', 'Task has been finished', '#27ae60', 3), - ('On Hold', 'Task is temporarily paused', '#f39c12', 4), - ('Cancelled', 'Task has been cancelled', '#e74c3c', 5) -ON CONFLICT DO NOTHING; - --- Step 3: Add status_id column back -ALTER TABLE task_task ADD COLUMN IF NOT EXISTS status_id INTEGER; - --- Step 4: Migrate data - set status_id based on in_progress flag --- Set to "In Progress" status if in_progress is true, otherwise "Pending" -UPDATE task_task -SET status_id = ( - CASE - WHEN in_progress = true THEN (SELECT id FROM task_taskstatus WHERE name = 'In Progress' LIMIT 1) - ELSE (SELECT id FROM task_taskstatus WHERE name = 'Pending' LIMIT 1) - END -); - --- Step 5: Add foreign key constraint -ALTER TABLE task_task ADD CONSTRAINT fk_task_task_status - FOREIGN KEY (status_id) REFERENCES task_taskstatus(id); - --- Step 6: Drop the in_progress column -ALTER TABLE task_task DROP COLUMN IF EXISTS in_progress; - --- Step 7: Drop the index -DROP INDEX IF EXISTS idx_task_task_in_progress; diff --git a/migrations/005_replace_status_with_in_progress.up.sql b/migrations/005_replace_status_with_in_progress.up.sql deleted file mode 100644 index b16c333..0000000 --- a/migrations/005_replace_status_with_in_progress.up.sql +++ /dev/null @@ -1,44 +0,0 @@ --- Migration: Replace status_id foreign key with in_progress boolean --- This simplifies the task model since status was only used to determine if a task is "In Progress" - --- Step 1: Add in_progress boolean column with default false -ALTER TABLE task_task ADD COLUMN IF NOT EXISTS in_progress BOOLEAN NOT NULL DEFAULT false; - --- Step 2: Create index on in_progress for query performance -CREATE INDEX IF NOT EXISTS idx_task_task_in_progress ON task_task(in_progress); - --- Step 3: Migrate existing data - set in_progress = true for tasks with "In Progress" status -UPDATE task_task -SET in_progress = true -WHERE status_id IN ( - SELECT id FROM task_taskstatus WHERE LOWER(name) = 'in progress' -); - --- Step 4: Drop the foreign key constraint on status_id (if it exists) --- PostgreSQL syntax - the constraint name might vary -DO $$ -BEGIN - -- Try to drop the constraint if it exists - IF EXISTS ( - SELECT 1 FROM information_schema.table_constraints - WHERE constraint_name = 'fk_task_task_status' - AND table_name = 'task_task' - ) THEN - ALTER TABLE task_task DROP CONSTRAINT fk_task_task_status; - END IF; - - -- Also try the gorm auto-generated constraint name - IF EXISTS ( - SELECT 1 FROM information_schema.table_constraints - WHERE constraint_name = 'task_task_status_id_fkey' - AND table_name = 'task_task' - ) THEN - ALTER TABLE task_task DROP CONSTRAINT task_task_status_id_fkey; - END IF; -END $$; - --- Step 5: Drop the status_id column -ALTER TABLE task_task DROP COLUMN IF EXISTS status_id; - --- Step 6: Drop the task_taskstatus table -DROP TABLE IF EXISTS task_taskstatus; diff --git a/migrations/006_performance_indexes.down.sql b/migrations/006_performance_indexes.down.sql deleted file mode 100644 index 0313106..0000000 --- a/migrations/006_performance_indexes.down.sql +++ /dev/null @@ -1,23 +0,0 @@ --- Rollback performance optimization indexes --- Migration: 006_performance_indexes - -DROP INDEX IF EXISTS idx_user_email_lower; -DROP INDEX IF EXISTS idx_user_username_lower; -DROP INDEX IF EXISTS idx_admin_email_lower; - -DROP INDEX IF EXISTS idx_task_residence_status; -DROP INDEX IF EXISTS idx_task_residence_active; -DROP INDEX IF EXISTS idx_task_next_due_date; - -DROP INDEX IF EXISTS idx_notification_user_read; -DROP INDEX IF EXISTS idx_notification_user_sent; -DROP INDEX IF EXISTS idx_notification_task; - -DROP INDEX IF EXISTS idx_document_residence_active_type; -DROP INDEX IF EXISTS idx_document_expiry_active; - -DROP INDEX IF EXISTS idx_contractor_created_by; -DROP INDEX IF EXISTS idx_completion_task; -DROP INDEX IF EXISTS idx_completion_completed_by; -DROP INDEX IF EXISTS idx_residence_member_user; -DROP INDEX IF EXISTS idx_share_code_active; diff --git a/migrations/006_performance_indexes.up.sql b/migrations/006_performance_indexes.up.sql deleted file mode 100644 index 32073e9..0000000 --- a/migrations/006_performance_indexes.up.sql +++ /dev/null @@ -1,75 +0,0 @@ --- Performance optimization indexes --- Migration: 006_performance_indexes - --- ===================================================== --- CRITICAL: Case-insensitive indexes for auth lookups --- ===================================================== --- These eliminate full table scans on login/registration -CREATE INDEX IF NOT EXISTS idx_user_email_lower ON auth_user (LOWER(email)); -CREATE INDEX IF NOT EXISTS idx_user_username_lower ON auth_user (LOWER(username)); -CREATE INDEX IF NOT EXISTS idx_admin_email_lower ON admin_users (LOWER(email)); - --- ===================================================== --- HIGH PRIORITY: Composite indexes for common queries --- ===================================================== - --- Tasks: Most common query pattern is by residence with status filters -CREATE INDEX IF NOT EXISTS idx_task_residence_status - ON task_task (residence_id, is_cancelled, is_archived); - --- Tasks: For kanban board queries (active tasks by residence) -CREATE INDEX IF NOT EXISTS idx_task_residence_active - ON task_task (residence_id, is_archived, in_progress) - WHERE is_cancelled = false; - --- Tasks: For overdue queries (next_due_date lookups) -CREATE INDEX IF NOT EXISTS idx_task_next_due_date - ON task_task (next_due_date) - WHERE is_cancelled = false AND is_archived = false AND next_due_date IS NOT NULL; - --- Notifications: Queried constantly for unread count -CREATE INDEX IF NOT EXISTS idx_notification_user_read - ON notifications_notification (user_id, read); - --- Notifications: For pending notification worker -CREATE INDEX IF NOT EXISTS idx_notification_user_sent - ON notifications_notification (user_id, sent); - --- Notifications: Task-based lookups -CREATE INDEX IF NOT EXISTS idx_notification_task - ON notifications_notification (task_id) - WHERE task_id IS NOT NULL; - --- Documents: Warranty expiry queries -CREATE INDEX IF NOT EXISTS idx_document_residence_active_type - ON task_document (residence_id, is_active, document_type); - --- Documents: Expiring warranties lookup -CREATE INDEX IF NOT EXISTS idx_document_expiry_active - ON task_document (expiry_date, is_active) - WHERE document_type = 'warranty' AND is_active = true; - --- ===================================================== --- MEDIUM PRIORITY: Foreign key indexes --- ===================================================== - --- Contractor: Query by creator (user's personal contractors) -CREATE INDEX IF NOT EXISTS idx_contractor_created_by - ON task_contractor (created_by_id); - --- Task completions: Query by task -CREATE INDEX IF NOT EXISTS idx_completion_task - ON task_taskcompletion (task_id); - --- Task completions: Query by user who completed -CREATE INDEX IF NOT EXISTS idx_completion_completed_by - ON task_taskcompletion (completed_by_id); - --- Residence members: Query by user -CREATE INDEX IF NOT EXISTS idx_residence_member_user - ON residence_residencemember (user_id); - --- Share codes: Active code lookups -CREATE INDEX IF NOT EXISTS idx_share_code_active - ON residence_residencesharecode (code, is_active) - WHERE is_active = true; diff --git a/migrations/007_custom_interval_days.down.sql b/migrations/007_custom_interval_days.down.sql deleted file mode 100644 index b15309d..0000000 --- a/migrations/007_custom_interval_days.down.sql +++ /dev/null @@ -1,5 +0,0 @@ --- Rollback custom_interval_days column --- Migration: 007_custom_interval_days - -ALTER TABLE task_task -DROP COLUMN IF EXISTS custom_interval_days; diff --git a/migrations/007_custom_interval_days.up.sql b/migrations/007_custom_interval_days.up.sql deleted file mode 100644 index dc76742..0000000 --- a/migrations/007_custom_interval_days.up.sql +++ /dev/null @@ -1,8 +0,0 @@ --- Add custom_interval_days for custom frequency tasks --- Migration: 007_custom_interval_days - -ALTER TABLE task_task -ADD COLUMN IF NOT EXISTS custom_interval_days INTEGER; - --- Add comment for documentation -COMMENT ON COLUMN task_task.custom_interval_days IS 'For Custom frequency tasks, the user-specified number of days between occurrences'; diff --git a/migrations/008_additional_performance_indexes.down.sql b/migrations/008_additional_performance_indexes.down.sql deleted file mode 100644 index cad0228..0000000 --- a/migrations/008_additional_performance_indexes.down.sql +++ /dev/null @@ -1,8 +0,0 @@ --- Rollback additional performance optimization indexes --- Migration: 008_additional_performance_indexes - -DROP INDEX IF EXISTS idx_task_kanban_composite; -DROP INDEX IF EXISTS idx_completion_task_date; -DROP INDEX IF EXISTS idx_sharecode_code_active; -DROP INDEX IF EXISTS idx_residence_users_user_residence; -DROP INDEX IF EXISTS idx_task_in_progress; diff --git a/migrations/008_additional_performance_indexes.up.sql b/migrations/008_additional_performance_indexes.up.sql deleted file mode 100644 index ac0513e..0000000 --- a/migrations/008_additional_performance_indexes.up.sql +++ /dev/null @@ -1,47 +0,0 @@ --- Additional performance optimization indexes --- Migration: 008_additional_performance_indexes - --- ===================================================== --- KANBAN QUERY OPTIMIZATION --- ===================================================== - --- Composite index for kanban board queries --- Covers: WHERE residence_id IN ? AND is_archived = false --- with ordering by due_date, next_due_date -CREATE INDEX IF NOT EXISTS idx_task_kanban_composite - ON task_task (residence_id, is_archived, is_cancelled, next_due_date, due_date) - WHERE is_archived = false; - --- ===================================================== --- COMPLETION QUERY OPTIMIZATION --- ===================================================== - --- Ordering index for completion queries (most recent first) -CREATE INDEX IF NOT EXISTS idx_completion_task_date - ON task_taskcompletion (task_id, completed_at DESC); - --- ===================================================== --- SHARE CODE OPTIMIZATION --- ===================================================== - --- Unique index for active share code lookups -CREATE UNIQUE INDEX IF NOT EXISTS idx_sharecode_code_active - ON residence_residencesharecode (code) - WHERE is_active = true; - --- ===================================================== --- RESIDENCE USER ACCESS OPTIMIZATION --- ===================================================== - --- Index for residence user membership queries (used by FindResidenceIDsByUser) -CREATE INDEX IF NOT EXISTS idx_residence_users_user_residence - ON residence_residence_users (user_id, residence_id); - --- ===================================================== --- TASK IN_PROGRESS QUERIES --- ===================================================== - --- Index for in_progress task queries (kanban "In Progress" column) -CREATE INDEX IF NOT EXISTS idx_task_in_progress - ON task_task (residence_id, in_progress) - WHERE in_progress = true AND is_cancelled = false AND is_archived = false; diff --git a/migrations/009_remove_redundant_indexes.down.sql b/migrations/009_remove_redundant_indexes.down.sql deleted file mode 100644 index fb0acc0..0000000 --- a/migrations/009_remove_redundant_indexes.down.sql +++ /dev/null @@ -1,10 +0,0 @@ --- Rollback: Recreate removed indexes - --- Recreate share code index (from migration 006) -CREATE INDEX IF NOT EXISTS idx_share_code_active - ON residence_residencesharecode (residence_id) - WHERE is_active = true; - --- Recreate notification user+sent index (from migration 006) -CREATE INDEX IF NOT EXISTS idx_notification_user_sent - ON notifications_notification (user_id, sent); diff --git a/migrations/009_remove_redundant_indexes.up.sql b/migrations/009_remove_redundant_indexes.up.sql deleted file mode 100644 index 59def7a..0000000 --- a/migrations/009_remove_redundant_indexes.up.sql +++ /dev/null @@ -1,15 +0,0 @@ --- Migration: 009_remove_redundant_indexes --- Description: Remove indexes that are redundant or unused - --- Remove redundant share code index --- idx_share_code_active is superseded by unique idx_sharecode_code_active (migration 008) --- Both filter WHERE is_active = true, but 008's unique index on (code) is more restrictive -DROP INDEX IF EXISTS idx_share_code_active; - --- Remove unused composite notification index --- idx_notification_user_sent on (user_id, sent) is never used: --- - GetPendingNotifications() only filters on sent, not user_id --- - FindByUser() only filters on user_id, not sent (uses idx_notification_user_created_at) --- - No queries combine user_id AND sent together --- The leading column (user_id) queries already use idx_notification_user_created_at -DROP INDEX IF EXISTS idx_notification_user_sent; diff --git a/migrations/010_add_task_reminder_log.down.sql b/migrations/010_add_task_reminder_log.down.sql deleted file mode 100644 index f31b796..0000000 --- a/migrations/010_add_task_reminder_log.down.sql +++ /dev/null @@ -1,5 +0,0 @@ --- Rollback: Smart Notification Reminder System - -DROP INDEX IF EXISTS idx_reminderlog_sent_at; -DROP INDEX IF EXISTS idx_reminderlog_task_user_date; -DROP TABLE IF EXISTS task_reminderlog; diff --git a/migrations/010_add_task_reminder_log.up.sql b/migrations/010_add_task_reminder_log.up.sql deleted file mode 100644 index 997a2fa..0000000 --- a/migrations/010_add_task_reminder_log.up.sql +++ /dev/null @@ -1,24 +0,0 @@ --- Smart Notification Reminder System --- Tracks which reminders have been sent to prevent duplicates - -CREATE TABLE task_reminderlog ( - id SERIAL PRIMARY KEY, - task_id INTEGER NOT NULL REFERENCES task_task(id) ON DELETE CASCADE, - user_id INTEGER NOT NULL REFERENCES auth_user(id) ON DELETE CASCADE, - due_date DATE NOT NULL, -- Which occurrence this is for - reminder_stage VARCHAR(20) NOT NULL, -- e.g., "reminder_30d", "reminder_7d", "day_of", "overdue_1", "overdue_4" - sent_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), - notification_id INTEGER REFERENCES notifications_notification(id) ON DELETE SET NULL, - - -- Prevent duplicate reminders for same task/user/date/stage - UNIQUE(task_id, user_id, due_date, reminder_stage) -); - --- Index for quick lookup when checking if reminder was already sent -CREATE INDEX idx_reminderlog_task_user_date ON task_reminderlog(task_id, user_id, due_date); - --- Index for cleanup job (delete old logs) -CREATE INDEX idx_reminderlog_sent_at ON task_reminderlog(sent_at); - -COMMENT ON TABLE task_reminderlog IS 'Tracks sent task reminders to prevent duplicate notifications'; -COMMENT ON COLUMN task_reminderlog.reminder_stage IS 'Stage of reminder: reminder_30d, reminder_14d, reminder_7d, reminder_3d, reminder_1d, day_of, overdue_N'; diff --git a/migrations/011_add_timezone_to_notification_preferences.down.sql b/migrations/011_add_timezone_to_notification_preferences.down.sql deleted file mode 100644 index 4c9139d..0000000 --- a/migrations/011_add_timezone_to_notification_preferences.down.sql +++ /dev/null @@ -1,3 +0,0 @@ --- Remove timezone column from notification preferences -ALTER TABLE notifications_notificationpreference -DROP COLUMN IF EXISTS timezone; diff --git a/migrations/011_add_timezone_to_notification_preferences.up.sql b/migrations/011_add_timezone_to_notification_preferences.up.sql deleted file mode 100644 index 2eabcee..0000000 --- a/migrations/011_add_timezone_to_notification_preferences.up.sql +++ /dev/null @@ -1,7 +0,0 @@ --- Add timezone column to notification preferences --- Stores IANA timezone name (e.g., "America/Los_Angeles") for background job calculations -ALTER TABLE notifications_notificationpreference -ADD COLUMN timezone VARCHAR(50); - --- Add comment for documentation -COMMENT ON COLUMN notifications_notificationpreference.timezone IS 'IANA timezone name for daily digest calculations (e.g., America/Los_Angeles)'; diff --git a/migrations/012_webhook_event_log.down.sql b/migrations/012_webhook_event_log.down.sql deleted file mode 100644 index f8c69d8..0000000 --- a/migrations/012_webhook_event_log.down.sql +++ /dev/null @@ -1 +0,0 @@ -DROP TABLE IF EXISTS webhook_event_log; diff --git a/migrations/012_webhook_event_log.up.sql b/migrations/012_webhook_event_log.up.sql deleted file mode 100644 index e0bfc56..0000000 --- a/migrations/012_webhook_event_log.up.sql +++ /dev/null @@ -1,9 +0,0 @@ -CREATE TABLE IF NOT EXISTS webhook_event_log ( - id SERIAL PRIMARY KEY, - event_id VARCHAR(255) NOT NULL, - provider VARCHAR(20) NOT NULL, - event_type VARCHAR(100) NOT NULL, - processed_at TIMESTAMPTZ DEFAULT NOW(), - payload_hash VARCHAR(64), - UNIQUE(provider, event_id) -); diff --git a/migrations/013_business_constraints.down.sql b/migrations/013_business_constraints.down.sql deleted file mode 100644 index 9b116d2..0000000 --- a/migrations/013_business_constraints.down.sql +++ /dev/null @@ -1,5 +0,0 @@ -ALTER TABLE task_task DROP CONSTRAINT IF EXISTS chk_task_not_cancelled_and_archived; -ALTER TABLE subscriptions_usersubscription DROP CONSTRAINT IF EXISTS chk_subscription_tier; -ALTER TABLE notifications_notification DROP CONSTRAINT IF EXISTS chk_notification_sent_consistency; -ALTER TABLE subscriptions_usersubscription DROP CONSTRAINT IF EXISTS uq_subscription_user; -ALTER TABLE notifications_notificationpreference DROP CONSTRAINT IF EXISTS uq_notif_pref_user; diff --git a/migrations/013_business_constraints.up.sql b/migrations/013_business_constraints.up.sql deleted file mode 100644 index 59dffac..0000000 --- a/migrations/013_business_constraints.up.sql +++ /dev/null @@ -1,31 +0,0 @@ --- Prevent task from being both cancelled and archived simultaneously -ALTER TABLE task_task ADD CONSTRAINT chk_task_not_cancelled_and_archived - CHECK (NOT (is_cancelled = true AND is_archived = true)); - --- Subscription tier must be valid -ALTER TABLE subscriptions_usersubscription ADD CONSTRAINT chk_subscription_tier - CHECK (tier IN ('free', 'pro')); - --- Notification: sent_at must be set if sent is true -ALTER TABLE notifications_notification ADD CONSTRAINT chk_notification_sent_consistency - CHECK ((sent = false) OR (sent = true AND sent_at IS NOT NULL)); - --- One subscription per user -DO $$ -BEGIN - IF NOT EXISTS ( - SELECT 1 FROM pg_constraint WHERE conname = 'uq_subscription_user' - ) THEN - ALTER TABLE subscriptions_usersubscription ADD CONSTRAINT uq_subscription_user UNIQUE (user_id); - END IF; -END $$; - --- One notification preference per user -DO $$ -BEGIN - IF NOT EXISTS ( - SELECT 1 FROM pg_constraint WHERE conname = 'uq_notif_pref_user' - ) THEN - ALTER TABLE notifications_notificationpreference ADD CONSTRAINT uq_notif_pref_user UNIQUE (user_id); - END IF; -END $$; diff --git a/migrations/014_task_version_column.down.sql b/migrations/014_task_version_column.down.sql deleted file mode 100644 index b0f2d88..0000000 --- a/migrations/014_task_version_column.down.sql +++ /dev/null @@ -1 +0,0 @@ -ALTER TABLE task_task DROP COLUMN IF EXISTS version; diff --git a/migrations/014_task_version_column.up.sql b/migrations/014_task_version_column.up.sql deleted file mode 100644 index 961db2b..0000000 --- a/migrations/014_task_version_column.up.sql +++ /dev/null @@ -1 +0,0 @@ -ALTER TABLE task_task ADD COLUMN IF NOT EXISTS version INTEGER NOT NULL DEFAULT 1; diff --git a/migrations/015_targeted_indexes.down.sql b/migrations/015_targeted_indexes.down.sql deleted file mode 100644 index 2aff338..0000000 --- a/migrations/015_targeted_indexes.down.sql +++ /dev/null @@ -1,3 +0,0 @@ -DROP INDEX IF EXISTS idx_task_kanban_query; -DROP INDEX IF EXISTS idx_notification_user_unread; -DROP INDEX IF EXISTS idx_document_residence_active; diff --git a/migrations/015_targeted_indexes.up.sql b/migrations/015_targeted_indexes.up.sql deleted file mode 100644 index 22183d4..0000000 --- a/migrations/015_targeted_indexes.up.sql +++ /dev/null @@ -1,14 +0,0 @@ --- Kanban: composite index for active task queries by residence with due date ordering -CREATE INDEX IF NOT EXISTS idx_task_kanban_query - ON task_task (residence_id, is_cancelled, is_archived, next_due_date, due_date) - WHERE is_cancelled = false AND is_archived = false; - --- Notifications: index for unread count (hot query) -CREATE INDEX IF NOT EXISTS idx_notification_user_unread - ON notifications_notification (user_id, read) - WHERE read = false; - --- Documents: residence + active filter -CREATE INDEX IF NOT EXISTS idx_document_residence_active - ON documents_document (residence_id, is_active) - WHERE is_active = true; diff --git a/migrations/016_authtoken_created_at.down.sql b/migrations/016_authtoken_created_at.down.sql deleted file mode 100644 index 48c2acf..0000000 --- a/migrations/016_authtoken_created_at.down.sql +++ /dev/null @@ -1,2 +0,0 @@ --- No-op: the created column is part of Django's original schema and should not --- be removed. diff --git a/migrations/016_authtoken_created_at.up.sql b/migrations/016_authtoken_created_at.up.sql deleted file mode 100644 index 25d2900..0000000 --- a/migrations/016_authtoken_created_at.up.sql +++ /dev/null @@ -1,6 +0,0 @@ --- Ensure created column exists on user_authtoken (Django already creates it, --- but this migration guarantees it for fresh Go-only deployments). -ALTER TABLE user_authtoken ADD COLUMN IF NOT EXISTS created TIMESTAMP WITH TIME ZONE DEFAULT NOW(); - --- Backfill any rows that may have a NULL created timestamp. -UPDATE user_authtoken SET created = NOW() WHERE created IS NULL; diff --git a/migrations/017_fk_indexes.down.sql b/migrations/017_fk_indexes.down.sql deleted file mode 100644 index 2fa6068..0000000 --- a/migrations/017_fk_indexes.down.sql +++ /dev/null @@ -1,40 +0,0 @@ --- Rollback: 017_fk_indexes --- Drop all FK indexes added in the up migration. - --- auth / user tables -DROP INDEX IF EXISTS idx_authtoken_user_id; -DROP INDEX IF EXISTS idx_userprofile_user_id; -DROP INDEX IF EXISTS idx_confirmationcode_user_id; -DROP INDEX IF EXISTS idx_passwordresetcode_user_id; -DROP INDEX IF EXISTS idx_applesocialauth_user_id; -DROP INDEX IF EXISTS idx_googlesocialauth_user_id; - --- push notification device tables -DROP INDEX IF EXISTS idx_apnsdevice_user_id; -DROP INDEX IF EXISTS idx_gcmdevice_user_id; - --- notification tables -DROP INDEX IF EXISTS idx_notificationpreference_user_id; - --- subscription tables -DROP INDEX IF EXISTS idx_subscription_user_id; - --- residence tables -DROP INDEX IF EXISTS idx_residence_owner_id; -DROP INDEX IF EXISTS idx_sharecode_residence_id; -DROP INDEX IF EXISTS idx_sharecode_created_by_id; - --- task tables -DROP INDEX IF EXISTS idx_task_created_by_id; -DROP INDEX IF EXISTS idx_task_assigned_to_id; -DROP INDEX IF EXISTS idx_task_category_id; -DROP INDEX IF EXISTS idx_task_priority_id; -DROP INDEX IF EXISTS idx_task_frequency_id; -DROP INDEX IF EXISTS idx_task_contractor_id; -DROP INDEX IF EXISTS idx_task_parent_task_id; -DROP INDEX IF EXISTS idx_completionimage_completion_id; -DROP INDEX IF EXISTS idx_document_created_by_id; -DROP INDEX IF EXISTS idx_document_task_id; -DROP INDEX IF EXISTS idx_documentimage_document_id; -DROP INDEX IF EXISTS idx_contractor_residence_id; -DROP INDEX IF EXISTS idx_reminderlog_notification_id; diff --git a/migrations/017_fk_indexes.up.sql b/migrations/017_fk_indexes.up.sql deleted file mode 100644 index 0e3c62f..0000000 --- a/migrations/017_fk_indexes.up.sql +++ /dev/null @@ -1,131 +0,0 @@ --- Migration: 017_fk_indexes --- Add indexes on all foreign key columns that are not already covered by existing indexes. --- Uses CREATE INDEX IF NOT EXISTS to be idempotent (safe to re-run). - --- ===================================================== --- auth / user tables --- ===================================================== - --- user_authtoken: user_id (unique FK, but ensure index exists) -CREATE UNIQUE INDEX IF NOT EXISTS idx_authtoken_user_id - ON user_authtoken (user_id); - --- user_userprofile: user_id (unique FK) -CREATE UNIQUE INDEX IF NOT EXISTS idx_userprofile_user_id - ON user_userprofile (user_id); - --- user_confirmationcode: user_id -CREATE INDEX IF NOT EXISTS idx_confirmationcode_user_id - ON user_confirmationcode (user_id); - --- user_passwordresetcode: user_id -CREATE INDEX IF NOT EXISTS idx_passwordresetcode_user_id - ON user_passwordresetcode (user_id); - --- user_applesocialauth: user_id (unique FK) -CREATE UNIQUE INDEX IF NOT EXISTS idx_applesocialauth_user_id - ON user_applesocialauth (user_id); - --- user_googlesocialauth: user_id (unique FK) -CREATE UNIQUE INDEX IF NOT EXISTS idx_googlesocialauth_user_id - ON user_googlesocialauth (user_id); - --- ===================================================== --- push notification device tables --- ===================================================== - --- push_notifications_apnsdevice: user_id -CREATE INDEX IF NOT EXISTS idx_apnsdevice_user_id - ON push_notifications_apnsdevice (user_id); - --- push_notifications_gcmdevice: user_id -CREATE INDEX IF NOT EXISTS idx_gcmdevice_user_id - ON push_notifications_gcmdevice (user_id); - --- ===================================================== --- notification tables --- ===================================================== - --- notifications_notificationpreference: user_id (unique FK) -CREATE UNIQUE INDEX IF NOT EXISTS idx_notificationpreference_user_id - ON notifications_notificationpreference (user_id); - --- ===================================================== --- subscription tables --- ===================================================== - --- subscription_usersubscription: user_id (unique FK) -CREATE UNIQUE INDEX IF NOT EXISTS idx_subscription_user_id - ON subscription_usersubscription (user_id); - --- ===================================================== --- residence tables --- ===================================================== - --- residence_residence: owner_id -CREATE INDEX IF NOT EXISTS idx_residence_owner_id - ON residence_residence (owner_id); - --- residence_residencesharecode: residence_id (may already exist from model index tag via GORM) -CREATE INDEX IF NOT EXISTS idx_sharecode_residence_id - ON residence_residencesharecode (residence_id); - --- residence_residencesharecode: created_by_id -CREATE INDEX IF NOT EXISTS idx_sharecode_created_by_id - ON residence_residencesharecode (created_by_id); - --- ===================================================== --- task tables --- ===================================================== - --- task_task: created_by_id -CREATE INDEX IF NOT EXISTS idx_task_created_by_id - ON task_task (created_by_id); - --- task_task: assigned_to_id -CREATE INDEX IF NOT EXISTS idx_task_assigned_to_id - ON task_task (assigned_to_id); - --- task_task: category_id -CREATE INDEX IF NOT EXISTS idx_task_category_id - ON task_task (category_id); - --- task_task: priority_id -CREATE INDEX IF NOT EXISTS idx_task_priority_id - ON task_task (priority_id); - --- task_task: frequency_id -CREATE INDEX IF NOT EXISTS idx_task_frequency_id - ON task_task (frequency_id); - --- task_task: contractor_id -CREATE INDEX IF NOT EXISTS idx_task_contractor_id - ON task_task (contractor_id); - --- task_task: parent_task_id -CREATE INDEX IF NOT EXISTS idx_task_parent_task_id - ON task_task (parent_task_id); - --- task_taskcompletionimage: completion_id -CREATE INDEX IF NOT EXISTS idx_completionimage_completion_id - ON task_taskcompletionimage (completion_id); - --- task_document: created_by_id -CREATE INDEX IF NOT EXISTS idx_document_created_by_id - ON task_document (created_by_id); - --- task_document: task_id -CREATE INDEX IF NOT EXISTS idx_document_task_id - ON task_document (task_id); - --- task_documentimage: document_id -CREATE INDEX IF NOT EXISTS idx_documentimage_document_id - ON task_documentimage (document_id); - --- task_contractor: residence_id -CREATE INDEX IF NOT EXISTS idx_contractor_residence_id - ON task_contractor (residence_id); - --- task_reminderlog: notification_id -CREATE INDEX IF NOT EXISTS idx_reminderlog_notification_id - ON task_reminderlog (notification_id); diff --git a/migrations/018_audit_log.down.sql b/migrations/018_audit_log.down.sql deleted file mode 100644 index 55f8feb..0000000 --- a/migrations/018_audit_log.down.sql +++ /dev/null @@ -1,2 +0,0 @@ --- Rollback: 018_audit_log -DROP TABLE IF EXISTS audit_log; diff --git a/migrations/018_audit_log.up.sql b/migrations/018_audit_log.up.sql deleted file mode 100644 index 4a9ff9e..0000000 --- a/migrations/018_audit_log.up.sql +++ /dev/null @@ -1,16 +0,0 @@ --- Migration: 018_audit_log --- Create audit_log table for tracking security-relevant events (login, register, etc.) - -CREATE TABLE IF NOT EXISTS audit_log ( - id SERIAL PRIMARY KEY, - user_id INTEGER, - event_type VARCHAR(50) NOT NULL, - ip_address VARCHAR(45), - user_agent TEXT, - details JSONB, - created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() -); - -CREATE INDEX idx_audit_log_user_id ON audit_log(user_id); -CREATE INDEX idx_audit_log_event_type ON audit_log(event_type); -CREATE INDEX idx_audit_log_created_at ON audit_log(created_at); diff --git a/migrations/019_residence_home_profile.down.sql b/migrations/019_residence_home_profile.down.sql deleted file mode 100644 index 5259cb4..0000000 --- a/migrations/019_residence_home_profile.down.sql +++ /dev/null @@ -1,18 +0,0 @@ --- Migration: 019_residence_home_profile (rollback) --- Remove home profile fields from residence - -ALTER TABLE residence_residence - DROP COLUMN IF EXISTS heating_type, - DROP COLUMN IF EXISTS cooling_type, - DROP COLUMN IF EXISTS water_heater_type, - DROP COLUMN IF EXISTS roof_type, - DROP COLUMN IF EXISTS has_pool, - DROP COLUMN IF EXISTS has_sprinkler_system, - DROP COLUMN IF EXISTS has_septic, - DROP COLUMN IF EXISTS has_fireplace, - DROP COLUMN IF EXISTS has_garage, - DROP COLUMN IF EXISTS has_basement, - DROP COLUMN IF EXISTS has_attic, - DROP COLUMN IF EXISTS exterior_type, - DROP COLUMN IF EXISTS flooring_primary, - DROP COLUMN IF EXISTS landscaping_type; diff --git a/migrations/019_residence_home_profile.up.sql b/migrations/019_residence_home_profile.up.sql deleted file mode 100644 index accea87..0000000 --- a/migrations/019_residence_home_profile.up.sql +++ /dev/null @@ -1,18 +0,0 @@ --- Migration: 019_residence_home_profile --- Add home profile fields to residence for smart onboarding task suggestions - -ALTER TABLE residence_residence - ADD COLUMN IF NOT EXISTS heating_type VARCHAR(50), - ADD COLUMN IF NOT EXISTS cooling_type VARCHAR(50), - ADD COLUMN IF NOT EXISTS water_heater_type VARCHAR(50), - ADD COLUMN IF NOT EXISTS roof_type VARCHAR(50), - ADD COLUMN IF NOT EXISTS has_pool BOOLEAN DEFAULT FALSE, - ADD COLUMN IF NOT EXISTS has_sprinkler_system BOOLEAN DEFAULT FALSE, - ADD COLUMN IF NOT EXISTS has_septic BOOLEAN DEFAULT FALSE, - ADD COLUMN IF NOT EXISTS has_fireplace BOOLEAN DEFAULT FALSE, - ADD COLUMN IF NOT EXISTS has_garage BOOLEAN DEFAULT FALSE, - ADD COLUMN IF NOT EXISTS has_basement BOOLEAN DEFAULT FALSE, - ADD COLUMN IF NOT EXISTS has_attic BOOLEAN DEFAULT FALSE, - ADD COLUMN IF NOT EXISTS exterior_type VARCHAR(50), - ADD COLUMN IF NOT EXISTS flooring_primary VARCHAR(50), - ADD COLUMN IF NOT EXISTS landscaping_type VARCHAR(50); diff --git a/migrations/020_template_conditions.down.sql b/migrations/020_template_conditions.down.sql deleted file mode 100644 index 8765af6..0000000 --- a/migrations/020_template_conditions.down.sql +++ /dev/null @@ -1,4 +0,0 @@ --- Migration: 020_template_conditions (rollback) --- Remove conditions column from task templates - -ALTER TABLE task_tasktemplate DROP COLUMN IF EXISTS conditions; diff --git a/migrations/020_template_conditions.up.sql b/migrations/020_template_conditions.up.sql deleted file mode 100644 index 560403e..0000000 --- a/migrations/020_template_conditions.up.sql +++ /dev/null @@ -1,4 +0,0 @@ --- Migration: 020_template_conditions --- Add conditions column to task templates for residence-aware suggestions - -ALTER TABLE task_tasktemplate ADD COLUMN IF NOT EXISTS conditions JSONB DEFAULT '{}'; diff --git a/migrations/021_task_template_id.down.sql b/migrations/021_task_template_id.down.sql deleted file mode 100644 index 60f764a..0000000 --- a/migrations/021_task_template_id.down.sql +++ /dev/null @@ -1,2 +0,0 @@ -DROP INDEX IF EXISTS idx_task_task_task_template_id; -ALTER TABLE task_task DROP COLUMN IF EXISTS task_template_id; diff --git a/migrations/021_task_template_id.up.sql b/migrations/021_task_template_id.up.sql deleted file mode 100644 index c2b7abc..0000000 --- a/migrations/021_task_template_id.up.sql +++ /dev/null @@ -1,13 +0,0 @@ --- Add a backlink from task_task to task_tasktemplate so that tasks created from --- a template (e.g. onboarding suggestions or the template catalog) can be --- reported on and filtered. Nullable — user-created custom tasks remain unset. - -ALTER TABLE task_task - ADD COLUMN IF NOT EXISTS task_template_id BIGINT NULL; - -CREATE INDEX IF NOT EXISTS idx_task_task_task_template_id - ON task_task (task_template_id); - --- Deferred FK — not enforced at the DB level because task_tasktemplate rows --- may be renamed/retired; application code is the source of truth for the --- relationship and already tolerates nil. diff --git a/migrations/022_drop_task_template_regions_join.down.sql b/migrations/022_drop_task_template_regions_join.down.sql deleted file mode 100644 index 175846f..0000000 --- a/migrations/022_drop_task_template_regions_join.down.sql +++ /dev/null @@ -1,12 +0,0 @@ --- Recreates the legacy task_tasktemplate_regions join table. Data is not --- restored — if a rollback needs the prior associations they have to be --- reseeded from the task template conditions JSON. - -CREATE TABLE IF NOT EXISTS task_tasktemplate_regions ( - task_template_id BIGINT NOT NULL, - climate_region_id BIGINT NOT NULL, - PRIMARY KEY (task_template_id, climate_region_id) -); - -CREATE INDEX IF NOT EXISTS idx_task_tasktemplate_regions_region - ON task_tasktemplate_regions (climate_region_id); diff --git a/migrations/022_drop_task_template_regions_join.up.sql b/migrations/022_drop_task_template_regions_join.up.sql deleted file mode 100644 index 5d2ef5d..0000000 --- a/migrations/022_drop_task_template_regions_join.up.sql +++ /dev/null @@ -1,5 +0,0 @@ --- Drop the legacy many-to-many join table task_tasktemplate_regions. --- Climate-region affinity now lives in task_tasktemplate.conditions->'climate_region_id' --- and is scored by SuggestionService alongside the other home-profile conditions. - -DROP TABLE IF EXISTS task_tasktemplate_regions;