feat-postgres (#1)
Some checks failed
CI / Linux Tests (push) Failing after 5m44s

Store timestamps as strings in the database to fix errors with postgres.

Reviewed-on: #1
Co-authored-by: Michael Housh <michael@mhoush.com>
Co-committed-by: Michael Housh <michael@mhoush.com>
This commit was merged in pull request #1.
This commit is contained in:
2026-02-11 21:51:52 +00:00
committed by Gitea
parent e3a731e3fa
commit a10f3ef0f5
14 changed files with 63 additions and 27 deletions

View File

@@ -9,7 +9,8 @@ on:
env: env:
REGISTRY: git.housh.dev REGISTRY: git.housh.dev
IMAGE_NAME: ductcalc USERNAME: michael
IMAGE_NAME: ${{ gitea.repository }}
jobs: jobs:
build-and-push-image: build-and-push-image:
@@ -56,5 +57,5 @@ jobs:
tags: ${{ steps.meta.outputs.tags }} tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }} labels: ${{ steps.meta.outputs.labels }}
cache-from: type=registry,ref=${{ env.IMAGE_NAME }}:build 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

View File

@@ -9,7 +9,8 @@ on:
env: env:
REGISTRY: ghcr.io REGISTRY: ghcr.io
IMAGE_NAME: ductcalc IMAGE_NAME: ${{ github.repository }}
USERNAME: m-housh
jobs: jobs:
build-and-push-image: build-and-push-image:
@@ -27,7 +28,7 @@ jobs:
uses: docker/login-action@v3 uses: docker/login-action@v3
with: with:
registry: ${{ env.REGISTRY }} registry: ${{ env.REGISTRY }}
username: ${{ github.repository_owner }} username: ${{ env.USERNAME }}
password: ${{ secrets.CONTAINER_TOKEN }} password: ${{ secrets.CONTAINER_TOKEN }}
- name: Set up Docker - name: Set up Docker
@@ -48,7 +49,7 @@ jobs:
id: meta id: meta
uses: docker/metadata-action@v5 uses: docker/metadata-action@v5
with: with:
images: ${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.IMAGE_NAME }} images: ${{ env.REGISTRY }}/${{ env.USERNAME }}/${{ env.IMAGE_NAME }}
tags: | tags: |
type=ref,event=branch type=ref,event=branch
type=semver,pattern={{version}} type=semver,pattern={{version}}
@@ -67,6 +68,6 @@ jobs:
push: true push: true
tags: ${{ steps.meta.outputs.tags }} tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }} labels: ${{ steps.meta.outputs.labels }}
cache-from: type=registry,ref=${{ github.repository_owner }}/${{ env.IMAGE_NAME }}:buildcache cache-from: type=registry,ref=${{ env.USERNAME }}/${{ env.IMAGE_NAME }}:buildcache
cache-to: type=registry,ref=${{ github.repository_owner }}/${{ env.IMAGE_NAME }}:buildcache,mode=max cache-to: mode=max,image-manifest=true,oci-mediatypes=true,type=registry,ref=${{ env.USERNAME }}/${{ env.IMAGE_NAME }}:build

View File

@@ -66,8 +66,8 @@ extension ComponentPressureLoss {
.id() .id()
.field("name", .string, .required) .field("name", .string, .required)
.field("value", .double, .required) .field("value", .double, .required)
.field("createdAt", .datetime) .field("createdAt", .string)
.field("updatedAt", .datetime) .field("updatedAt", .string)
.field( .field(
"projectID", .uuid, .required, .references(ProjectModel.schema, "id", onDelete: .cascade) "projectID", .uuid, .required, .references(ProjectModel.schema, "id", onDelete: .cascade)
) )

View File

@@ -73,8 +73,8 @@ extension EquipmentInfo {
.field("staticPressure", .double, .required) .field("staticPressure", .double, .required)
.field("heatingCFM", .int16, .required) .field("heatingCFM", .int16, .required)
.field("coolingCFM", .int16, .required) .field("coolingCFM", .int16, .required)
.field("createdAt", .datetime) .field("createdAt", .string)
.field("updatedAt", .datetime) .field("updatedAt", .string)
.field( .field(
"projectID", .uuid, .required, .references(ProjectModel.schema, "id", onDelete: .cascade) "projectID", .uuid, .required, .references(ProjectModel.schema, "id", onDelete: .cascade)
) )

View File

@@ -90,8 +90,8 @@ extension EquivalentLength {
.field("type", .string, .required) .field("type", .string, .required)
.field("straightLengths", .array(of: .int)) .field("straightLengths", .array(of: .int))
.field("groups", .data) .field("groups", .data)
.field("createdAt", .datetime) .field("createdAt", .string)
.field("updatedAt", .datetime) .field("updatedAt", .string)
.field( .field(
"projectID", .uuid, .required, .references(ProjectModel.schema, "id", onDelete: .cascade) "projectID", .uuid, .required, .references(ProjectModel.schema, "id", onDelete: .cascade)
) )

View File

@@ -7,10 +7,10 @@ extension DatabaseClient.Migrations: DependencyKey {
public static let liveValue = Self( public static let liveValue = Self(
all: { all: {
[ [
Project.Migrate(),
User.Migrate(), User.Migrate(),
User.Token.Migrate(), User.Token.Migrate(),
User.Profile.Migrate(), User.Profile.Migrate(),
Project.Migrate(),
ComponentPressureLoss.Migrate(), ComponentPressureLoss.Migrate(),
EquipmentInfo.Migrate(), EquipmentInfo.Migrate(),
Room.Migrate(), Room.Migrate(),

View File

@@ -120,8 +120,8 @@ extension Project {
.field("state", .string, .required) .field("state", .string, .required)
.field("zipCode", .string, .required) .field("zipCode", .string, .required)
.field("sensibleHeatRatio", .double) .field("sensibleHeatRatio", .double)
.field("createdAt", .datetime) .field("createdAt", .string)
.field("updatedAt", .datetime) .field("updatedAt", .string)
.field("userID", .uuid, .required, .references(UserModel.schema, "id", onDelete: .cascade)) .field("userID", .uuid, .required, .references(UserModel.schema, "id", onDelete: .cascade))
.unique(on: "userID", "name") .unique(on: "userID", "name")
.create() .create()

View File

@@ -197,8 +197,8 @@ extension Room {
.field("registerCount", .int8, .required) .field("registerCount", .int8, .required)
.field("delegatedToID", .uuid, .references(RoomModel.schema, "id")) .field("delegatedToID", .uuid, .references(RoomModel.schema, "id"))
.field("rectangularSizes", .array) .field("rectangularSizes", .array)
.field("createdAt", .datetime) .field("createdAt", .string)
.field("updatedAt", .datetime) .field("updatedAt", .string)
.field( .field(
"projectID", .uuid, .required, .references(ProjectModel.schema, "id", onDelete: .cascade) "projectID", .uuid, .required, .references(ProjectModel.schema, "id", onDelete: .cascade)
) )

View File

@@ -81,8 +81,8 @@ extension User.Profile {
.field("zipCode", .string, .required) .field("zipCode", .string, .required)
.field("theme", .string) .field("theme", .string)
.field("userID", .uuid, .references(UserModel.schema, "id", onDelete: .cascade)) .field("userID", .uuid, .references(UserModel.schema, "id", onDelete: .cascade))
.field("createdAt", .datetime) .field("createdAt", .string)
.field("updatedAt", .datetime) .field("updatedAt", .string)
.unique(on: "userID") .unique(on: "userID")
.create() .create()
} }

View File

@@ -76,8 +76,8 @@ extension User {
.id() .id()
.field("email", .string, .required) .field("email", .string, .required)
.field("password_hash", .string, .required) .field("password_hash", .string, .required)
.field("createdAt", .datetime) .field("createdAt", .string)
.field("updatedAt", .datetime) .field("updatedAt", .string)
.unique(on: "email") .unique(on: "email")
.create() .create()
} }
@@ -97,8 +97,8 @@ extension User.Token {
.id() .id()
.field("value", .string, .required) .field("value", .string, .required)
.field("user_id", .uuid, .required, .references(UserModel.schema, "id")) .field("user_id", .uuid, .required, .references(UserModel.schema, "id"))
.field("createdAt", .datetime) .field("createdAt", .string)
.field("updatedAt", .datetime) .field("updatedAt", .string)
.unique(on: "value") .unique(on: "value")
.create() .create()
} }

View File

@@ -63,7 +63,8 @@ struct HomeView: HTML, Sendable {
.class("btn btn-xl btn-primary mt-6"), .class("btn btn-xl btn-primary mt-6"),
.hx.get(route: .signup(.index)), .hx.get(route: .signup(.index)),
.hx.target("body"), .hx.target("body"),
.hx.swap(.outerHTML) .hx.swap(.outerHTML),
.hx.pushURL(true)
) { ) {
"Get Started" "Get Started"
} }

View File

@@ -23,3 +23,4 @@
- [x] Privacy policy - [x] Privacy policy
- [ ] Update README - [ ] Update README
- [ ] Self hosting documentation - [ ] Self hosting documentation
- [x] Check signup flow when using 'get-started' button from home page, it may need a push url.

31
docker-compose.yaml Normal file
View File

@@ -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

View File

@@ -1,4 +1,3 @@
services: services:
db: db:
image: docker.io/postgres:18 image: docker.io/postgres:18
@@ -8,7 +7,9 @@ services:
- ./data:/var/lib/postgresql - ./data:/var/lib/postgresql
app: app:
image: ghcr.io/m-housh/ductcalc:latest build:
dockerfile: docker/Dockerfile
context: .
restart: unless-stopped restart: unless-stopped
env_file: .env env_file: .env
depends_on: depends_on: