From a10f3ef0f5a23342369c3b1b512834414400eb40 Mon Sep 17 00:00:00 2001 From: Michael Housh Date: Wed, 11 Feb 2026 21:51:52 +0000 Subject: [PATCH] feat-postgres (#1) Store timestamps as strings in the database to fix errors with postgres. Reviewed-on: https://git.housh.dev/michael/swift-duct-calc/pulls/1 Co-authored-by: Michael Housh Co-committed-by: Michael Housh --- .gitea/workflows/release.yaml | 5 +-- .github/workflows/release.yaml | 11 ++++--- .../Internal/ComponentLosses.swift | 4 +-- .../DatabaseClient/Internal/Equipment.swift | 4 +-- .../Internal/EquivalentLengths.swift | 4 +-- .../DatabaseClient/Internal/Migrations.swift | 2 +- .../DatabaseClient/Internal/Projects.swift | 4 +-- Sources/DatabaseClient/Internal/Rooms.swift | 4 +-- .../Internal/UserProfiles.swift | 4 +-- Sources/DatabaseClient/Internal/Users.swift | 8 ++--- Sources/ViewController/Views/Home.swift | 3 +- TODO.md | 1 + docker-compose.yaml | 31 +++++++++++++++++++ docker/docker-compose.yaml | 5 +-- 14 files changed, 63 insertions(+), 27 deletions(-) create mode 100644 docker-compose.yaml diff --git a/.gitea/workflows/release.yaml b/.gitea/workflows/release.yaml index 2ef3fc0..88d2e74 100644 --- a/.gitea/workflows/release.yaml +++ b/.gitea/workflows/release.yaml @@ -9,7 +9,8 @@ on: env: REGISTRY: git.housh.dev - IMAGE_NAME: ductcalc + USERNAME: michael + IMAGE_NAME: ${{ gitea.repository }} jobs: build-and-push-image: @@ -56,5 +57,5 @@ jobs: tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} cache-from: type=registry,ref=${{ env.IMAGE_NAME }}:build - cache-to: mode=max,image-manifest=true,oci-mediatypes=true,type=registry,ref=${{ env.IMAGE_NAME }}:build + cache-to: mode=min,image-manifest=true,oci-mediatypes=true,type=inline,ref=${{ env.IMAGE_NAME }}:build diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index f8d27e1..0714770 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -9,7 +9,8 @@ on: env: REGISTRY: ghcr.io - IMAGE_NAME: ductcalc + IMAGE_NAME: ${{ github.repository }} + USERNAME: m-housh jobs: build-and-push-image: @@ -27,7 +28,7 @@ jobs: uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} - username: ${{ github.repository_owner }} + username: ${{ env.USERNAME }} password: ${{ secrets.CONTAINER_TOKEN }} - name: Set up Docker @@ -48,7 +49,7 @@ jobs: id: meta uses: docker/metadata-action@v5 with: - images: ${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.IMAGE_NAME }} + images: ${{ env.REGISTRY }}/${{ env.USERNAME }}/${{ env.IMAGE_NAME }} tags: | type=ref,event=branch type=semver,pattern={{version}} @@ -67,6 +68,6 @@ jobs: push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} - cache-from: type=registry,ref=${{ github.repository_owner }}/${{ env.IMAGE_NAME }}:buildcache - cache-to: type=registry,ref=${{ github.repository_owner }}/${{ env.IMAGE_NAME }}:buildcache,mode=max + cache-from: type=registry,ref=${{ env.USERNAME }}/${{ env.IMAGE_NAME }}:buildcache + cache-to: mode=max,image-manifest=true,oci-mediatypes=true,type=registry,ref=${{ env.USERNAME }}/${{ env.IMAGE_NAME }}:build diff --git a/Sources/DatabaseClient/Internal/ComponentLosses.swift b/Sources/DatabaseClient/Internal/ComponentLosses.swift index f936802..ee94719 100644 --- a/Sources/DatabaseClient/Internal/ComponentLosses.swift +++ b/Sources/DatabaseClient/Internal/ComponentLosses.swift @@ -66,8 +66,8 @@ extension ComponentPressureLoss { .id() .field("name", .string, .required) .field("value", .double, .required) - .field("createdAt", .datetime) - .field("updatedAt", .datetime) + .field("createdAt", .string) + .field("updatedAt", .string) .field( "projectID", .uuid, .required, .references(ProjectModel.schema, "id", onDelete: .cascade) ) diff --git a/Sources/DatabaseClient/Internal/Equipment.swift b/Sources/DatabaseClient/Internal/Equipment.swift index 3a927c9..c43cd82 100644 --- a/Sources/DatabaseClient/Internal/Equipment.swift +++ b/Sources/DatabaseClient/Internal/Equipment.swift @@ -73,8 +73,8 @@ extension EquipmentInfo { .field("staticPressure", .double, .required) .field("heatingCFM", .int16, .required) .field("coolingCFM", .int16, .required) - .field("createdAt", .datetime) - .field("updatedAt", .datetime) + .field("createdAt", .string) + .field("updatedAt", .string) .field( "projectID", .uuid, .required, .references(ProjectModel.schema, "id", onDelete: .cascade) ) diff --git a/Sources/DatabaseClient/Internal/EquivalentLengths.swift b/Sources/DatabaseClient/Internal/EquivalentLengths.swift index f076436..ddeada1 100644 --- a/Sources/DatabaseClient/Internal/EquivalentLengths.swift +++ b/Sources/DatabaseClient/Internal/EquivalentLengths.swift @@ -90,8 +90,8 @@ extension EquivalentLength { .field("type", .string, .required) .field("straightLengths", .array(of: .int)) .field("groups", .data) - .field("createdAt", .datetime) - .field("updatedAt", .datetime) + .field("createdAt", .string) + .field("updatedAt", .string) .field( "projectID", .uuid, .required, .references(ProjectModel.schema, "id", onDelete: .cascade) ) diff --git a/Sources/DatabaseClient/Internal/Migrations.swift b/Sources/DatabaseClient/Internal/Migrations.swift index b5dd7ac..faf69b8 100644 --- a/Sources/DatabaseClient/Internal/Migrations.swift +++ b/Sources/DatabaseClient/Internal/Migrations.swift @@ -7,10 +7,10 @@ extension DatabaseClient.Migrations: DependencyKey { public static let liveValue = Self( all: { [ - Project.Migrate(), User.Migrate(), User.Token.Migrate(), User.Profile.Migrate(), + Project.Migrate(), ComponentPressureLoss.Migrate(), EquipmentInfo.Migrate(), Room.Migrate(), diff --git a/Sources/DatabaseClient/Internal/Projects.swift b/Sources/DatabaseClient/Internal/Projects.swift index f86cac5..cbb2f2d 100644 --- a/Sources/DatabaseClient/Internal/Projects.swift +++ b/Sources/DatabaseClient/Internal/Projects.swift @@ -120,8 +120,8 @@ extension Project { .field("state", .string, .required) .field("zipCode", .string, .required) .field("sensibleHeatRatio", .double) - .field("createdAt", .datetime) - .field("updatedAt", .datetime) + .field("createdAt", .string) + .field("updatedAt", .string) .field("userID", .uuid, .required, .references(UserModel.schema, "id", onDelete: .cascade)) .unique(on: "userID", "name") .create() diff --git a/Sources/DatabaseClient/Internal/Rooms.swift b/Sources/DatabaseClient/Internal/Rooms.swift index 38115f7..47507b7 100644 --- a/Sources/DatabaseClient/Internal/Rooms.swift +++ b/Sources/DatabaseClient/Internal/Rooms.swift @@ -197,8 +197,8 @@ extension Room { .field("registerCount", .int8, .required) .field("delegatedToID", .uuid, .references(RoomModel.schema, "id")) .field("rectangularSizes", .array) - .field("createdAt", .datetime) - .field("updatedAt", .datetime) + .field("createdAt", .string) + .field("updatedAt", .string) .field( "projectID", .uuid, .required, .references(ProjectModel.schema, "id", onDelete: .cascade) ) diff --git a/Sources/DatabaseClient/Internal/UserProfiles.swift b/Sources/DatabaseClient/Internal/UserProfiles.swift index 1cbbc2c..890fa88 100644 --- a/Sources/DatabaseClient/Internal/UserProfiles.swift +++ b/Sources/DatabaseClient/Internal/UserProfiles.swift @@ -81,8 +81,8 @@ extension User.Profile { .field("zipCode", .string, .required) .field("theme", .string) .field("userID", .uuid, .references(UserModel.schema, "id", onDelete: .cascade)) - .field("createdAt", .datetime) - .field("updatedAt", .datetime) + .field("createdAt", .string) + .field("updatedAt", .string) .unique(on: "userID") .create() } diff --git a/Sources/DatabaseClient/Internal/Users.swift b/Sources/DatabaseClient/Internal/Users.swift index bb539a2..e90197a 100644 --- a/Sources/DatabaseClient/Internal/Users.swift +++ b/Sources/DatabaseClient/Internal/Users.swift @@ -76,8 +76,8 @@ extension User { .id() .field("email", .string, .required) .field("password_hash", .string, .required) - .field("createdAt", .datetime) - .field("updatedAt", .datetime) + .field("createdAt", .string) + .field("updatedAt", .string) .unique(on: "email") .create() } @@ -97,8 +97,8 @@ extension User.Token { .id() .field("value", .string, .required) .field("user_id", .uuid, .required, .references(UserModel.schema, "id")) - .field("createdAt", .datetime) - .field("updatedAt", .datetime) + .field("createdAt", .string) + .field("updatedAt", .string) .unique(on: "value") .create() } diff --git a/Sources/ViewController/Views/Home.swift b/Sources/ViewController/Views/Home.swift index bf4462a..4857996 100644 --- a/Sources/ViewController/Views/Home.swift +++ b/Sources/ViewController/Views/Home.swift @@ -63,7 +63,8 @@ struct HomeView: HTML, Sendable { .class("btn btn-xl btn-primary mt-6"), .hx.get(route: .signup(.index)), .hx.target("body"), - .hx.swap(.outerHTML) + .hx.swap(.outerHTML), + .hx.pushURL(true) ) { "Get Started" } diff --git a/TODO.md b/TODO.md index 1caaa1e..a30798e 100644 --- a/TODO.md +++ b/TODO.md @@ -23,3 +23,4 @@ - [x] Privacy policy - [ ] Update README - [ ] Self hosting documentation +- [x] Check signup flow when using 'get-started' button from home page, it may need a push url. diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..dd980b5 --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,31 @@ +services: + db: + image: docker.io/postgres:18 + restart: unless-stopped + env_file: .env + volumes: + - ./data:/var/lib/postgresql + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ductcalc"] + interval: 5s + timeout: 5s + retries: 5 + + app: + build: + dockerfile: docker/Dockerfile + context: . + restart: unless-stopped + env_file: .env + environment: + - POSTGRES_HOSTNAME=db + depends_on: + db: + condition: healthy + ports: + - 8081:8080 + healthcheck: + test: curl --fail --silent http://0.0.0.0:8080/health || exit 1 + interval: 1m + timeout: 10s + retries: 3 diff --git a/docker/docker-compose.yaml b/docker/docker-compose.yaml index 5f7dd35..3ac1145 100644 --- a/docker/docker-compose.yaml +++ b/docker/docker-compose.yaml @@ -1,4 +1,3 @@ - services: db: image: docker.io/postgres:18 @@ -8,7 +7,9 @@ services: - ./data:/var/lib/postgresql app: - image: ghcr.io/m-housh/ductcalc:latest + build: + dockerfile: docker/Dockerfile + context: . restart: unless-stopped env_file: .env depends_on: