25 Commits

Author SHA1 Message Date
0f915ccd82 happy monday _ bleh _ (#9)
Some checks failed
🧪 ✨ Unit Tests Workflow / 🧪 📜 JavaScript Tests (push) Successful in 1m23s
🧪 ✨ Unit Tests Workflow / 🧪 🐹 GolangCI-Lint (push) Successful in 1m32s
🧪 ✨ Unit Tests Workflow / 🔍 🐹 Go Tests (push) Successful in 3m35s
🏗️ ✨ Build Workflow / 🖼️ 🔨 Build Frontend (push) Successful in 6m11s
🏗️ ✨ Build Workflow / 🖥️ 🔨 Build Backend (push) Successful in 6m47s
🏗️ ✨ Build Workflow / 🚀 ✨ Deploy Application (push) Failing after 53s
Reviewed-on: Siteworxpro/reloading-manager#9
Co-authored-by: Ron Rise <ron@siteworxpro.com>
Co-committed-by: Ron Rise <ron@siteworxpro.com>
2025-06-11 01:43:53 +00:00
dd383b6fb3 fix tpyo (#8)
All checks were successful
🧪 ✨ Unit Tests Workflow / 🧪 📜 JavaScript Tests (push) Successful in 7m28s
🧪 ✨ Unit Tests Workflow / 🧪 🐹 GolangCI-Lint (push) Successful in 7m40s
🧪 ✨ Unit Tests Workflow / 🔍 🐹 Go Tests (push) Successful in 10m24s
Reviewed-on: Siteworxpro/reloading-manager#8
Co-authored-by: Ron Rise <ron@siteworxpro.com>
Co-committed-by: Ron Rise <ron@siteworxpro.com>
2025-06-10 15:56:03 +00:00
e484aa7e31 tagging release w.t.f.
All checks were successful
🧪 ✨ Unit Tests Workflow / 🧪 📜 JavaScript Tests (push) Successful in 5m58s
🧪 ✨ Unit Tests Workflow / 🔍 🐹 Go Tests (push) Successful in 9m2s
2025-05-16 21:18:03 -04:00
3c60c1b012 [create-pull-request] automated change (#7)
All checks were successful
🧪 ✨ Unit Tests Workflow / 🧪 📜 JavaScript Tests (push) Successful in 6m18s
🧪 ✨ Unit Tests Workflow / 🔍 🐹 Go Tests (push) Successful in 9m20s
Co-authored-by: rrise <+rrise@users.noreply.github.com>
Reviewed-on: Siteworxpro/reloading-manager#7
Co-authored-by: Ron Rise <ron@siteworxpro.com>
Co-committed-by: Ron Rise <ron@siteworxpro.com>
2025-05-16 15:34:23 -04:00
c5f1f32b44 You know the rules and so do I
Some checks failed
🧪 ✨ Unit Tests Workflow / 🧪 📜 JavaScript Tests (push) Successful in 10m30s
🏗️ ✨ Build Workflow / 🖼️ 🔨 Build Frontend (push) Successful in 19m6s
🏗️ ✨ Build Workflow / 🖥️ 🔨 Build Backend (push) Successful in 20m46s
🏗️ ✨ Build Workflow / 🚀 ✨ Deploy Application (push) Failing after 1m18s
🧪 ✨ Unit Tests Workflow / 🔍 🐹 Go Tests (push) Successful in 22m13s
2025-05-16 14:05:49 -04:00
343abbb801 Actual final build before release
Some checks failed
🧪 ✨ Unit Tests Workflow / 🔍 🐹 Go Tests (push) Failing after 2m42s
🧪 ✨ Unit Tests Workflow / 🧪 📜 JavaScript Tests (push) Failing after 2m47s
🏗️ ✨ Build Workflow / 🖼️ 🔨 Build Frontend (push) Successful in 18m37s
🏗️ ✨ Build Workflow / 🖥️ 🔨 Build Backend (push) Successful in 20m14s
🏗️ ✨ Build Workflow / 🚀 ✨ Deploy Application (push) Failing after 54s
2025-05-16 14:02:19 -04:00
13aad5254e Feed. You. Stuff. No time. (#6)
Some checks failed
🧪 ✨ Unit Tests Workflow / 🧪 📜 JavaScript Tests (push) Successful in 1m16s
🧪 ✨ Unit Tests Workflow / 🔍 🐹 Go Tests (push) Successful in 3m53s
🏗️ ✨ Build Workflow / 🖼️ 🔨 Build Frontend (push) Successful in 5m9s
🏗️ ✨ Build Workflow / 🖥️ 🔨 Build Backend (push) Successful in 6m9s
🏗️ ✨ Build Workflow / 🚀 ✨ Deploy Application (push) Failing after 9s
Reviewed-on: Siteworxpro/reloading-manager#6
Co-authored-by: Ron Rise <ron@siteworxpro.com>
Co-committed-by: Ron Rise <ron@siteworxpro.com>
2025-05-16 11:20:41 -04:00
aeba95dc41 [create-pull-request] automated change (#5)
All checks were successful
🧪 ✨ Unit Tests Workflow / 🧪 📜 JavaScript Tests (push) Successful in 1m15s
🧪 ✨ Unit Tests Workflow / 🔍 🐹 Go Tests (push) Successful in 3m14s
Co-authored-by: rrise <+rrise@users.noreply.github.com>
Reviewed-on: Siteworxpro/reloading-manager#5
Co-authored-by: Ron Rise <ron@siteworxpro.com>
Co-committed-by: Ron Rise <ron@siteworxpro.com>
2025-05-16 11:13:40 -04:00
b40edf70d4 and a comma (#4)
Some checks failed
🧪 ✨ Unit Tests Workflow / 🔍 🐹 Go Tests (push) Successful in 2m50s
🏗️ ✨ Build Workflow / 🚀 ✨ Deploy Application (push) Failing after 2m45s
🧪 ✨ Unit Tests Workflow / 🧪 📜 JavaScript Tests (push) Successful in 1m16s
🏗️ ✨ Build Workflow / 🖼️ 🔨 Build Frontend (push) Successful in 5m25s
🏗️ ✨ Build Workflow / 🖥️ 🔨 Build Backend (push) Successful in 6m23s
Reviewed-on: Siteworxpro/reloading-manager#4
Co-authored-by: Ron Rise <ron@siteworxpro.com>
Co-committed-by: Ron Rise <ron@siteworxpro.com>
2025-05-16 10:21:03 -04:00
4f750ae15c [create-pull-request] automated change (#3)
All checks were successful
🧪 ✨ Unit Tests Workflow / 🧪 📜 JavaScript Tests (push) Successful in 8m6s
🧪 ✨ Unit Tests Workflow / 🔍 🐹 Go Tests (push) Successful in 10m49s
Co-authored-by: rrise <+rrise@users.noreply.github.com>
Reviewed-on: Siteworxpro/reloading-manager#3
Co-authored-by: Ron Rise <ron@siteworxpro.com>
Co-committed-by: Ron Rise <ron@siteworxpro.com>
2025-05-16 10:08:55 -04:00
3246450d77 Merge pull request 'This is not the commit message you are looking for' (#2) from deployments into master
Some checks failed
🏗️ ✨ Build Workflow / 🖼️ 🔨 Build Frontend (push) Successful in 5m47s
🧪 ✨ Unit Tests Workflow / 🧪 📜 JavaScript Tests (push) Successful in 1m15s
🏗️ ✨ Build Workflow / 🖥️ 🔨 Build Backend (push) Successful in 6m16s
🏗️ ✨ Build Workflow / 🚀 ✨ Deploy Application (push) Failing after 49s
🧪 ✨ Unit Tests Workflow / 🔍 🐹 Go Tests (push) Successful in 3m11s
Reviewed-on: Siteworxpro/reloading-manager#2
2025-05-16 09:47:00 -04:00
74681e01b3 This is not the commit message you are looking for
All checks were successful
🧪 ✨ Unit Tests Workflow / 🧪 📜 JavaScript Tests (push) Successful in 1m23s
🧪 ✨ Unit Tests Workflow / 🔍 🐹 Go Tests (push) Successful in 3m0s
2025-05-16 09:42:39 -04:00
9dce6b8930 various changes
Some checks failed
🏗️ ✨ Build Workflow / 🖼️ 🔨 Build Frontend (push) Successful in 6m36s
🏗️ ✨ Build Workflow / 🖥️ 🔨 Build Backend (push) Successful in 7m45s
🏗️ ✨ Build Workflow / 🚀 ✨ Deploy Application (push) Failing after 52s
🧪 ✨ Unit Tests Workflow / 🧪 📜 JavaScript Tests (push) Successful in 3m14s
🧪 ✨ Unit Tests Workflow / 🔍 🐹 Go Tests (push) Successful in 5m22s
2025-05-16 09:19:02 -04:00
7c02aa7148 Merge pull request '📝🔄 Update deployment manifest with new image tags' (#1) from release/v0.0.29-deploy into master
All checks were successful
🧪 ✨ Unit Tests Workflow / 🧪 📜 JavaScript Tests (push) Successful in 4m48s
🧪 ✨ Unit Tests Workflow / 🔍 🐹 Go Tests (push) Successful in 8m1s
Reviewed-on: Siteworxpro/reloading-manager#1
2025-05-16 09:09:05 -04:00
GitHub Action 🤖
a8c470a474 📝🔄 Update deployment manifest with new image tags 2025-05-16 13:01:57 +00:00
95b626b439 It's Working!
All checks were successful
🏗️ ✨ Build Workflow / 🖥️ 🔨 Build Backend (push) Successful in 7m3s
🏗️ ✨ Build Workflow / 🚀 ✨ Deploy Application (push) Successful in 57s
🧪 ✨ Unit Tests Workflow / 🔍 🐹 Go Tests (push) Successful in 5m7s
🧪 ✨ Unit Tests Workflow / 🧪 📜 JavaScript Tests (push) Successful in 1m17s
🏗️ ✨ Build Workflow / 🖼️ 🔨 Build Frontend (push) Successful in 5m35s
2025-05-16 08:50:55 -04:00
b9a6598e87 Fucking templates.
All checks were successful
🧪 ✨ Unit Tests Workflow / 🧪 📜 JavaScript Tests (push) Successful in 2m24s
🧪 ✨ Unit Tests Workflow / 🔍 🐹 Go Tests (push) Successful in 5m18s
2025-05-16 08:45:01 -04:00
4251a03139 That last commit was cringe
Some checks failed
🧪 ✨ Unit Tests Workflow / 🔍 🐹 Go Tests (push) Has been cancelled
🧪 ✨ Unit Tests Workflow / 🧪 📜 JavaScript Tests (push) Has been cancelled
2025-05-16 08:44:02 -04:00
691a32ff56 Just committing so I can go home
All checks were successful
🧪✨ Unit Tests Workflow / 🔍🐹 Go Tests (push) Successful in 8m21s
🧪✨ Unit Tests Workflow / 🧪📜 JavaScript Tests (push) Successful in 1m8s
2025-05-16 08:29:57 -04:00
3631a4b5dc ci test 2025-04-21 18:39:16 -04:00
edbd5b4f49 I would rather be playing Destiny 2. 2025-04-21 18:35:37 -04:00
a20bef65ae should work now. 2025-04-21 18:34:38 -04:00
4056e6705d Merge branch 'update-deployment-v0.0.28' into 'master'
Update deployment manifest with new image tags

See merge request rrise/reloading-manager!2
2025-04-21 22:34:01 +00:00
fc4305f161 yo recipes 2025-04-21 18:32:09 -04:00
f0fc8b7707 Update deployment manifest with new image tags 2025-04-21 22:29:48 +00:00
29 changed files with 870 additions and 309 deletions

View File

@@ -1,44 +1,44 @@
on:
push:
tags:
- '**'
- 'v*'
name: 🏗️✨ Build Workflow
name: 🏗️ ✨ Build Workflow
jobs:
BuildFrontend:
name: 🖼️🔨 Build Frontend
name: 🖼️ 🔨 Build Frontend
runs-on: ubuntu-latest
steps:
- name: 🛡️🔒 Add Siteworx CA Certificates
- name: 🛡️ 🔒 Add Siteworx CA Certificates
run: |
apt update && apt install -yq ca-certificates curl
curl -Ls https://siteworxpro.com/hosted/Siteworx+Root+CA.pem -o /usr/local/share/ca-certificates/sw.crt
update-ca-certificates
- name: 📖🔍 Checkout Repository Code
- name: 📖 🔍 Checkout Repository Code
uses: actions/checkout@v2
with:
fetch-depth: 1
- name: 🔑🔐 Login to Docker Hub
- name: 🔑 🔐 Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: 🔑🛠️ Login to Siteworx Registry
- name: 🔑 🛠️ Login to Siteworx Registry
uses: docker/login-action@v3
with:
username: ${{ vars.SITEWORX_USERNAME }}
username: ${{ secrets.SITEWORX_USERNAME }}
password: ${{ secrets.SITEWORX_PASSWORD }}
registry: scr.siteworxpro.com
- name: 🏗️🔧 Set up Docker Buildx
- name: 🏗️ 🔧 Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: 🐳🔨 Build Frontend Container
- name: 🐳 🔨 Build Frontend Container
uses: docker/build-push-action@v6
with:
build-args: |
@@ -48,7 +48,7 @@ jobs:
tags: scr.siteworxpro.com/reloading-manager/frontend:${{ gitea.ref_name }}
push: true
- name: 📦✨ Build Latest Frontend Container
- name: 📦 ✨ Build Latest Frontend Container
uses: docker/build-push-action@v6
with:
build-args: |
@@ -59,7 +59,7 @@ jobs:
push: true
BuildBackend:
name: 🖥️🔨 Build Backend
name: 🖥️ 🔨 Build Backend
runs-on: ubuntu-latest
steps:
@@ -74,23 +74,23 @@ jobs:
with:
fetch-depth: 1
- name: 🔑🔐 Login to Docker Hub
- name: 🔑 🔐 Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: 🔑🛠️ Login to Siteworx Registry
- name: 🔑 🛠️ Login to Siteworx Registry
uses: docker/login-action@v3
with:
username: ${{ vars.SITEWORX_USERNAME }}
username: ${{ secrets.SITEWORX_USERNAME }}
password: ${{ secrets.SITEWORX_PASSWORD }}
registry: scr.siteworxpro.com
- name: 🏗️🔧 Set up Docker Buildx
- name: 🏗️ 🔧 Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: 🐳🔨 Build Backend Container
- name: 🐳 🔨 Build Backend Container
uses: docker/build-push-action@v6
with:
context: ./backend
@@ -98,7 +98,7 @@ jobs:
tags: scr.siteworxpro.com/reloading-manager/backend:${{ gitea.ref_name }}
push: true
- name: 📦✨ Build Latest Backend Container
- name: 📦 ✨ Build Latest Backend Container
uses: docker/build-push-action@v6
with:
context: ./backend
@@ -107,7 +107,7 @@ jobs:
push: true
Deploy:
name: 🚀✨ Deploy Application
name: 🚀 ✨ Deploy Application
runs-on: ubuntu-latest
needs: [BuildFrontend, BuildBackend]
steps:
@@ -118,25 +118,33 @@ jobs:
curl -Ls https://siteworxpro.com/hosted/Siteworx+Root+CA.pem -o /usr/local/share/ca-certificates/sw.crt
update-ca-certificates
- name: 📖🔍 Checkout Repository Code
- name: 📖 🔍 Checkout Repository Code
uses: actions/checkout@v2
with:
fetch-depth: 1
- name: 📝🔧 Update Deployment Manifest
- name: 📝 🔧 Update Deployment Manifest
run: |
echo "## Do not edit this file directly. It is auto-generated by the script." > argocd/deployment/deployment.yml
sed "s|__TAG__|${{ gitea.ref_name }}|g" argocd/template/deployment.yml >> argocd/deployment/deployment.yml
#
# - name: 💾 ✅ Commit Updated Manifest
# uses: EndBug/add-and-commit@v9
# with:
# new_branch: release/${{ gitea.ref_name }}-deploy
# add: argocd/deployment/deployment.yml
# author_name: "GitHub Action 🤖"
# author_email: gitia@siteworxpro.com
# message: "📝 🔄 Update deployment manifest with new image tags"
- name: 💾✅ Commit Updated Manifest
uses: EndBug/add-and-commit@v9
- name: 🚀 ✨ Create Pull Request
uses: peter-evans/create-pull-request@v7
env:
NODE_TLS_REJECT_UNAUTHORIZED: 0
with:
new_branch: release/${{ gitea.ref_name }}-deploy
add: argocd/deployment/deployment.yml
author_name: "GitHub Action 🤖"
author_email: gitia@siteworxpro.com
message: "📝🔄 Update deployment manifest with new image tags"
- name: 🚀✨ Create Pull Request
id: cpr
uses: peter-evans/create-pull-request@v7
base: master
add-paths: argocd/deployment/deployment.yml
title: "🚀 ✨ Release ${GITHUB_REF_NAME} - Deploy"
branch: release/${{ gitea.ref_name }}-deploy
committer: "Gitea Action 🤖 <gitia@siteworxpro.com>"
body: "📝 🔄 Update deployment manifest with new image tags for release ${GITHUB_REF_NAME}"

View File

@@ -1,63 +1,92 @@
on:
workflow_dispatch:
inputs:
test:
description: 'Run tests'
required: true
default: 'true'
push:
branches:
- "*"
name: 🧪✨ Unit Tests Workflow
name: 🧪 ✨ Unit Tests Workflow
jobs:
build-javascript:
name: 🧪📜 JavaScript Tests
name: 🧪 📜 JavaScript Tests
runs-on: ubuntu-latest
steps:
- name: 🛡️🔒 Add Siteworx CA Certificates
- name: 🛡️ 🔒 Add Siteworx CA Certificates
run: |
apt update && apt install -yq ca-certificates curl
curl -Ls https://siteworxpro.com/hosted/Siteworx+Root+CA.pem -o /usr/local/share/ca-certificates/sw.crt
update-ca-certificates
- name: 📖🔍 Checkout Repository Code
- name: 📖 🔍 Checkout Repository Code
uses: actions/checkout@v2
with:
fetch-depth: 1
- name: ⚙️🔧 Set up Node.js Environment
- name: ⚙️ 🔧 Set up Node.js Environment
uses: actions/setup-node@v2
with:
node-version: '22.14.0'
- name: 📦📥 Install Dependencies
- name: 📦 📥 Install Dependencies
run: |
cd frontend
npm install
- name: ✅🧪 Run JavaScript Tests
- name: 🧪 Run JavaScript Tests
run: |
cd frontend
npm run build
golangci-lint:
name: 🧪 🐹 GolangCI-Lint
runs-on: ubuntu-latest
steps:
- name: 🛡️ 🔒 Add Siteworx CA Certificates
run: |
apt update && apt install -yq ca-certificates curl
curl -Ls https://siteworxpro.com/hosted/Siteworx+Root+CA.pem -o /usr/local/share/ca-certificates/sw.crt
update-ca-certificates
- name: 📖 🔍 Checkout Repository Code
uses: actions/checkout@v2
with:
fetch-depth: 1
- name: ⚙️ 🐹 Set up Go Environment
uses: actions/setup-go@v2
with:
go-version: '1.24.3'
cache: true
- name: ✅ 🧪 Run GolangCI-Lint
uses: golangci/golangci-lint-action@v8.0.0
with:
working-directory: backend
test-go:
env:
GOPRIVATE: 'git.siteworxpro.com'
GOPROXY: 'direct'
name: 🔍🐹 Go Tests
name: 🔍 🐹 Go Tests
runs-on: ubuntu-latest
steps:
- name: 🛡️🔒 Add Siteworx CA Certificates
- name: 🛡️ 🔒 Add Siteworx CA Certificates
run: |
apt update && apt install -yq ca-certificates curl
curl -Ls https://siteworxpro.com/hosted/Siteworx+Root+CA.pem -o /usr/local/share/ca-certificates/sw.crt
update-ca-certificates
- name: ⚙️🐹 Set up Go Environment
- name: ⚙️ 🐹 Set up Go Environment
uses: actions/setup-go@v2
with:
go-version: '1.24.0'
go-version: '1.24.3'
cache: true
- name: 📖🔍 Checkout Repository Code
- name: 📖 🔍 Checkout Repository Code
uses: actions/checkout@v2
with:
fetch-depth: 1
- name: 📦📥 Install Dependencies
- name: 📦 📥 Install Dependencies
run: |
cd backend
go mod download
- name: ✅🔍 Run Go Tests
- name: 🔍 Run Go Tests
run: |
cd backend
go test -v ./... -coverprofile=coverage.out

View File

@@ -9,8 +9,11 @@ stages:
NodeJs Tests:
stage: Tests
image: node:22.14.0
except:
- tags
rules:
- if: '$CI_PIPELINE_SOURCE == "push"'
changes:
- frontend/**
- frontend/.gitlab-ci.yml
before_script:
- cd frontend
- npm install
@@ -21,6 +24,11 @@ include:
- project: 'shared/blueprints'
file: 'jobs/golang-tests.yml'
ref: master
rules:
- if: '$CI_PIPELINE_SOURCE == "push"'
changes:
- backend/**
- backend/.gitlab-ci.yml
inputs:
job_name: "Go Tests"
working_directory: "backend"
@@ -62,6 +70,18 @@ include:
context: "frontend"
dockerfile: "frontend/Dockerfile"
- project: 'shared/blueprints'
file: 'jobs/trigger-argocd.yml'
ref: master
rules:
- changes:
- argocd/**/*
inputs:
stage: Trigger
argocdServer: ${ARGOCD_SERVER}
argocdAuthToken: ${ARGOCD_AUTH_TOKEN}
argocdAppName: ${ARGOCD_APP_NAME}
Update Deployment:
image: siteworxpro/alpine:3.21.3
rules:
@@ -108,19 +128,8 @@ Create Merge Request:
curl --request POST --header "PRIVATE-TOKEN: glpat-hv-uxCx3PDNKn7ihyXce" \
--data "source_branch=update-deployment-${CI_COMMIT_TAG}" \
--data "target_branch=master" \
--data "title=Update deployment manifest with new image tags" \
--data "title=Update deployment manifest for version ${CI_COMMIT_TAG}" \
--data "description=This merge request updates the deployment manifest with the new image tags." \
--data "remove_source_branch=true" \
"${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/merge_requests"
Trigger ArgoCD:
stage: Trigger
image: siteworxpro/argocd:v2.14.10
rules:
- changes:
- argocd/deployment/*
variables:
ARGOCD_AUTH_TOKEN: ${ARGOCD_AUTH_TOKEN}
ARGOCD_SERVER: ${ARGOCD_SERVER}
script: |
argocd --grpc-web app sync ${ARGOCD_APP_NAME}
--data "squash_commits=true" \
"${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/merge_requests"

View File

@@ -19,11 +19,11 @@ spec:
- name: siteworxpro
containers:
- name: frontend
image: scr.siteworxpro.com/reloading-manager/frontend:v0.0.27
image: scr.siteworxpro.com/reloading-manager/frontend:v0.0.35
ports:
- containerPort: 80
- name: backend
image: scr.siteworxpro.com/reloading-manager/backend:v0.0.27
image: scr.siteworxpro.com/reloading-manager/backend:v0.0.35
ports:
- containerPort: 8080
envFrom:

17
backend/.golangci.yml Normal file
View File

@@ -0,0 +1,17 @@
version: "2"
linters:
default: standard
enable:
- whitespace
- tagalign
- reassign
- bodyclose
- contextcheck
- containedctx
- godot
- usestdlibvars
formatters:
settings:
gofmt:
simplify: true

View File

@@ -1,4 +1,4 @@
FROM siteworxpro/golang:1.24.0 AS build
FROM siteworxpro/golang:1.24.3 AS build
WORKDIR /app

View File

@@ -4,7 +4,7 @@ docker run -v $(pwd)/postgres:/var/lib/postgresql/data \
-e POSTGRES_PASSWORD=password \
--rm --name postgres \
-p 5432:5432 \
-d scr.siteworxpro.com/library/postgres:17
-d postgres:17
```
```shell

View File

@@ -20,6 +20,10 @@ const (
DbPassword Env.EnvironmentVariable = "DB_PASSWORD"
)
type contextKeyType string
const dbContextKey contextKeyType = "dbcontext"
type Database struct {
Db *pgx.Conn
connected bool
@@ -50,6 +54,24 @@ func (*Database) DSN(hidePassword bool) string {
return fmt.Sprintf("postgres://%s:%s@%s:5432/%s%s", dbUser, dbPassword, dbHost, dbDatabase, extraParams)
}
func NewWithContext(ctx context.Context) context.Context {
db := GetNewDatabase()
return context.WithValue(ctx, dbContextKey, db)
}
func NewFromContext(ctx context.Context) *Database {
if ok := ctx.Value(dbContextKey); ok != nil {
return ctx.Value(dbContextKey).(*Database)
}
return nil
}
func WithContext(ctx context.Context, database *Database) context.Context {
return context.WithValue(ctx, dbContextKey, database)
}
func GetNewDatabase() *Database {
var dbSingleton Database

View File

@@ -11,6 +11,9 @@ import (
func (db *Database) Migrate() {
sqlDB, err := sql.Open("postgres", db.DSN(false))
if err != nil {
log.Fatal(err)
}
driver, err := postgres.WithInstance(sqlDB, &postgres.Config{
MigrationsTable: "schema_migrations",

View File

@@ -1,6 +1,6 @@
module git.siteworxpro.com/reloading-manager/backend
go 1.24.0
go 1.24.3
require (
git.siteworxpro.com/packages/go/utilities v1.3.0
@@ -9,8 +9,8 @@ require (
github.com/go-sql-driver/mysql v1.9.2
github.com/golang-migrate/migrate v3.5.4+incompatible
github.com/google/uuid v1.6.0
github.com/jackc/pgx/v5 v5.7.4
github.com/labstack/echo/v4 v4.13.3
github.com/jackc/pgx/v5 v5.7.5
github.com/labstack/echo/v4 v4.13.4
github.com/labstack/gommon v0.4.2
)
@@ -41,11 +41,11 @@ require (
github.com/stretchr/testify v1.10.0 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect
golang.org/x/crypto v0.37.0 // indirect
golang.org/x/net v0.39.0 // indirect
golang.org/x/sys v0.32.0 // indirect
golang.org/x/text v0.24.0 // indirect
golang.org/x/time v0.11.0 // indirect
golang.org/x/crypto v0.39.0 // indirect
golang.org/x/net v0.41.0 // indirect
golang.org/x/sys v0.33.0 // indirect
golang.org/x/text v0.26.0 // indirect
golang.org/x/time v0.12.0 // indirect
gopkg.in/go-playground/assert.v1 v1.2.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

View File

@@ -42,6 +42,8 @@ github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7Ulw
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.7.4 h1:9wKznZrhWa2QiHL+NjTSPP6yjl3451BX3imWDnokYlg=
github.com/jackc/pgx/v5 v5.7.4/go.mod h1:ncY89UGWxg82EykZUwSpUKEfccBGGYq1xjrOpsbsfGQ=
github.com/jackc/pgx/v5 v5.7.5 h1:JHGfMnQY+IEtGM63d+NGMjoRpysB2JBwDr5fsngwmJs=
github.com/jackc/pgx/v5 v5.7.5/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M=
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
@@ -52,6 +54,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/labstack/echo/v4 v4.13.3 h1:pwhpCPrTl5qry5HRdM5FwdXnhXSLSY+WE+YQSeCaafY=
github.com/labstack/echo/v4 v4.13.3/go.mod h1:o90YNEeQWjDozo584l7AwhJMHN0bOC4tAfg+Xox9q5g=
github.com/labstack/echo/v4 v4.13.4 h1:oTZZW+T3s9gAu5L8vmzihV7/lkXGZuITzTQkTEhcXEA=
github.com/labstack/echo/v4 v4.13.4/go.mod h1:g63b33BZ5vZzcIUF8AtRH40DrTlXnx4UMC8rBdndmjQ=
github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
@@ -88,33 +92,45 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=

View File

@@ -21,12 +21,15 @@ type BulletResponse struct {
func Photo(c echo.Context) error {
db := c.(*database.CustomContext).Db
defer db.Db.Close(context.Background())
defer func() {
_ = db.Db.Close(context.Background())
}()
id := c.Param("id")
uid := handlers.ParseUuidOrBadRequest(c, id)
if uid == nil {
return nil
uid, err := handlers.ParseUuidOrBadRequest(c, id)
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "Invalid UUID format")
}
byId, err := db.Bullets.GetBulletById(context.Background(), *uid)
@@ -48,15 +51,18 @@ func Photo(c echo.Context) error {
func Delete(c echo.Context) error {
db := c.(*database.CustomContext).Db
defer db.Db.Close(context.Background())
defer func() {
_ = db.Db.Close(context.Background())
}()
id := c.Param("id")
uid := handlers.ParseUuidOrBadRequest(c, id)
if uid == nil {
return nil
uid, err := handlers.ParseUuidOrBadRequest(c, id)
if err != nil || uid == nil {
return echo.NewHTTPError(http.StatusBadRequest, "Invalid UUID format")
}
err := db.Bullets.DeleteBullet(context.Background(), *uid)
err = db.Bullets.DeleteBullet(context.Background(), *uid)
if err != nil {
return err
}
@@ -66,12 +72,14 @@ func Delete(c echo.Context) error {
func Put(c echo.Context) error {
db := c.(*database.CustomContext).Db
defer db.Db.Close(context.Background())
defer func() {
_ = db.Db.Close(context.Background())
}()
id := c.Param("id")
uid := handlers.ParseUuidOrBadRequest(c, id)
if uid == nil {
return nil
uid, err := handlers.ParseUuidOrBadRequest(c, id)
if err != nil || uid == nil {
return echo.NewHTTPError(http.StatusBadRequest, "Invalid UUID format")
}
byId, err := db.Bullets.GetBulletById(context.Background(), *uid)
@@ -101,9 +109,9 @@ func Put(c echo.Context) error {
weight, _ := strconv.ParseInt(c.FormValue("weight"), 10, 32)
diameter, _ := strconv.ParseInt(c.FormValue("diameter"), 10, 32)
manufacturerId := c.FormValue("manufacturer_id")
manufacturerUid := handlers.ParseUuidOrBadRequest(c, manufacturerId)
if manufacturerUid == nil {
return nil
manufacturerUid, err := handlers.ParseUuidOrBadRequest(c, manufacturerId)
if err == nil || manufacturerUid == nil {
return echo.NewHTTPError(http.StatusBadRequest, "Invalid UUID format")
}
name := c.FormValue("name")
@@ -147,13 +155,15 @@ func Put(c echo.Context) error {
func Get(c echo.Context) error {
db := c.(*database.CustomContext).Db
defer db.Db.Close(context.Background())
defer func() {
_ = db.Db.Close(context.Background())
}()
if c.Param("id") != "" {
id := c.Param("id")
uid := handlers.ParseUuidOrBadRequest(c, id)
if uid == nil {
return nil
uid, err := handlers.ParseUuidOrBadRequest(c, id)
if err != nil || uid == nil {
return echo.NewHTTPError(http.StatusBadRequest, "Invalid UUID format")
}
byId, err := db.Bullets.GetBulletById(context.Background(), *uid)
@@ -210,7 +220,6 @@ func Get(c echo.Context) error {
}
func Post(c echo.Context) error {
file, err := c.FormFile("photo")
if err != nil {
c.Logger().Error(err)
@@ -245,11 +254,13 @@ func Post(c echo.Context) error {
}
db := c.(*database.CustomContext).Db
defer db.Db.Close(context.Background())
defer func() {
_ = db.Db.Close(context.Background())
}()
manufacturerUid := handlers.ParseUuidOrBadRequest(c, manufacturerId)
if manufacturerUid == nil {
return nil
manufacturerUid, err := handlers.ParseUuidOrBadRequest(c, manufacturerId)
if err != nil || manufacturerUid == nil {
return echo.NewHTTPError(http.StatusBadRequest, "Invalid UUID format")
}
manufacturer, err := db.Manufacturer.GetById(context.Background(), *manufacturerUid)

View File

@@ -22,7 +22,9 @@ type postRequest struct {
func Get(c echo.Context) error {
db := c.(*database.CustomContext).Db
defer db.Db.Close(context.Background())
defer func() {
_ = db.Db.Close(context.Background())
}()
cartridges, err := db.Loads.GetCartridges(context.Background())
if err != nil {
@@ -45,7 +47,9 @@ func Get(c echo.Context) error {
func Post(c echo.Context) error {
db := c.(*database.CustomContext).Db
defer db.Db.Close(context.Background())
defer func() {
_ = db.Db.Close(context.Background())
}()
req := postRequest{}
@@ -79,14 +83,16 @@ func Post(c echo.Context) error {
func Delete(c echo.Context) error {
db := c.(*database.CustomContext).Db
defer db.Db.Close(context.Background())
defer func() {
_ = db.Db.Close(context.Background())
}()
uid := handlers.ParseUuidOrBadRequest(c, c.Param("id"))
if uid == nil {
return nil
uid, err := handlers.ParseUuidOrBadRequest(c, c.Param("id"))
if err != nil || uid == nil {
return echo.NewHTTPError(http.StatusBadRequest, "invalid id")
}
err := db.Loads.DeleteCartridge(context.Background(), *uid)
err = db.Loads.DeleteCartridge(context.Background(), *uid)
if err != nil {
return err

View File

@@ -2,18 +2,12 @@ package handlers
import (
"github.com/labstack/echo/v4"
"net/http"
)
func ReadFile(c echo.Context, formName string) ([]byte, error) {
file, err := c.FormFile(formName)
if err != nil {
c.Logger().Error(err)
_ = c.JSON(http.StatusBadRequest, struct {
Message string `json:"message"`
}{
Message: "No file provided",
})
return nil, err
}

View File

@@ -2,7 +2,6 @@ package loads
import (
"context"
"fmt"
"git.siteworxpro.com/reloading-manager/backend/.gen/loading/public/table"
"git.siteworxpro.com/reloading-manager/backend/database"
"git.siteworxpro.com/reloading-manager/backend/handlers"
@@ -10,8 +9,10 @@ import (
"git.siteworxpro.com/reloading-manager/backend/handlers/primers"
"git.siteworxpro.com/reloading-manager/backend/models/loads"
"github.com/go-jet/jet/v2/postgres"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgtype"
"github.com/labstack/echo/v4"
"mime"
"net/http"
"strings"
)
@@ -43,13 +44,14 @@ type row struct {
}
type loadResponseResults struct {
Id string `json:"id"`
Cartridge string `json:"cartridge"`
Col float32 `json:"col"`
Powder handlers.Powder `json:"powder"`
PowderGr float32 `json:"powder_gr"`
Primer primers.PrimerResponse `json:"primer"`
Bullet bullets.BulletResponse `json:"bullet"`
Id string `json:"id"`
Cartridge string `json:"cartridge"`
CartridgeId string `json:"cartridge_id"`
Col float32 `json:"col"`
Powder handlers.Powder `json:"powder"`
PowderGr float32 `json:"powder_gr"`
Primer primers.PrimerResponse `json:"primer"`
Bullet bullets.BulletResponse `json:"bullet"`
}
type loadResponse struct {
@@ -63,8 +65,28 @@ type ResultChan[T any] struct {
}
func Post(c echo.Context) error {
var exists *loads.GetLoadByIdRow
db := c.(*database.CustomContext).Db
defer db.Db.Close(context.Background())
defer func() {
_ = db.Db.Close(context.Background())
}()
id := c.Param("id")
if id != "" {
uuid, err := handlers.ParseUuid(id)
if err != nil {
return handlers.BadRequest(c, "id is not a valid UUID")
}
gbi, err := db.Loads.GetLoadById(context.Background(), *uuid)
if err != nil || !gbi.ID.Valid {
return handlers.NotFound(c, "load not found")
}
exists = &gbi
}
cartridgeID, err := handlers.ParseUuid(c.FormValue("cartridge_id"))
if err != nil {
@@ -87,8 +109,11 @@ func Post(c echo.Context) error {
}
file, err := handlers.ReadFile(c, "photo")
if err != nil {
if err != nil && exists == nil {
return handlers.BadRequest(c, "photo is not valid")
} else if err != nil {
// If we are updating an existing load, we can ignore the error
file = exists.Photo
}
meta := c.FormValue("meta")
@@ -115,6 +140,26 @@ func Post(c echo.Context) error {
return handlers.BadRequest(c, "col is not valid")
}
if exists != nil {
err = db.Loads.UpdateLoad(context.Background(), loads.UpdateLoadParams{
ID: exists.ID,
CartridgeID: *cartridgeID,
Col: colFl,
PowderID: *powderId,
PowderGr: powderGrFl,
PrimerID: *primerId,
BulletID: *bulletId,
Photo: file,
Meta: []byte(meta),
})
if err != nil {
return err
}
return c.JSON(http.StatusOK, handlers.Response[string]{Payload: exists.ID.String()})
}
uid, err := db.Loads.CreateLoad(context.Background(), loads.CreateLoadParams{
CartridgeID: *cartridgeID,
Col: colFl,
@@ -133,16 +178,66 @@ func Post(c echo.Context) error {
return c.JSON(http.StatusCreated, handlers.Response[string]{Payload: uid.String()})
}
func Photo(c echo.Context) error {
id := c.Param("id")
if id == "" {
return handlers.BadRequest(c, "id is required")
}
uuid, err := handlers.ParseUuid(id)
if err != nil {
return handlers.BadRequest(c, "id is not a valid UUID")
}
db := c.(*database.CustomContext).Db
defer func() {
_ = db.Db.Close(context.Background())
}()
file, err := db.Loads.GetLoadById(context.Background(), *uuid)
if err != nil {
return handlers.NotFound(c, "load photo not found")
}
mt, _, _ := mime.ParseMediaType(string(file.Photo))
return c.Blob(http.StatusOK, mt, file.Photo)
}
func Get(c echo.Context) error {
cTotal := make(chan ResultChan[int64])
id := c.Param("id")
cResults := make(chan ResultChan[[]loadResponseResults])
if id != "" {
go execResultsQuery(cResults, c)
results := <-cResults
if results.Err != nil {
return results.Err
}
if len(results.Result) == 0 {
return handlers.NotFound(c, "load not found")
}
return c.JSON(http.StatusOK, handlers.Response[loadResponseResults]{Status: http.StatusText(http.StatusOK), Payload: results.Result[0]})
}
cTotal := make(chan ResultChan[int64])
go func(ch chan ResultChan[int64]) {
db := database.GetNewDatabase()
defer db.Db.Close(context.Background())
defer func(Db *pgx.Conn, ctx context.Context) {
_ = Db.Close(ctx)
}(db.Db, context.Background())
q := getQuery(c, true)
if q == nil {
ch <- ResultChan[int64]{Result: 0}
return
}
sql, params := q.Sql()
var total int64
@@ -155,103 +250,8 @@ func Get(c echo.Context) error {
} else {
ch <- ResultChan[int64]{Result: total}
}
}(cTotal)
go func(ch chan ResultChan[[]loadResponseResults]) {
db := database.GetNewDatabase()
defer db.Db.Close(context.Background())
q := getQuery(c, false)
fmt.Println(q.DebugSql())
sql, params := q.Sql()
rows, err := db.Db.Query(context.Background(), sql, params...)
if err != nil {
ch <- ResultChan[[]loadResponseResults]{Err: err}
return
}
results := make([]loadResponseResults, 0)
for rows.Next() {
row := row{}
err = rows.Scan(
&row.ID,
&row.Col,
&row.PowderGr,
&row.CartridgeID,
&row.CartridgeName,
&row.CartridgeMeta,
&row.BulletID,
&row.BulletName,
&row.BulletDiameter,
&row.BulletWeight,
&row.BulletMeta,
&row.BulletManufacturerName,
&row.BulletManufacturerUrl,
&row.PrimerID,
&row.PrimerName,
&row.PrimerMeta,
&row.PrimerManufacturerName,
&row.PrimerManufacturerUrl,
&row.PowderID,
&row.PowderName,
&row.PowderMeta,
&row.PowderManufacturerName,
&row.PowderManufacturerUrl,
)
if err != nil {
ch <- ResultChan[[]loadResponseResults]{Err: err}
return
}
results = append(results, loadResponseResults{
Id: row.ID.String(),
Cartridge: row.CartridgeName,
Col: row.Col,
Powder: handlers.Powder{
Id: row.PowderID.String(),
Name: row.PowderName,
Meta: string(row.PowderMeta),
Manufacturer: handlers.Manufacturer{
Name: row.PowderManufacturerName,
Url: row.PowderManufacturerUrl.String,
},
},
PowderGr: row.PowderGr,
Primer: primers.PrimerResponse{
ID: row.PrimerID.String(),
Name: row.PrimerName,
Manufacturer: handlers.Manufacturer{
Name: row.PrimerManufacturerName,
Url: row.PrimerManufacturerUrl.String,
},
},
Bullet: bullets.BulletResponse{
Id: row.BulletID.String(),
Name: row.BulletName,
Diameter: row.BulletDiameter,
Weight: row.BulletWeight,
Manufacturer: handlers.Manufacturer{
Name: row.BulletManufacturerName,
Url: row.BulletManufacturerUrl.String,
},
},
})
}
ch <- ResultChan[[]loadResponseResults]{Result: results}
}(cResults)
go execResultsQuery(cResults, c)
total := <-cTotal
if total.Err != nil {
@@ -266,6 +266,106 @@ func Get(c echo.Context) error {
return c.JSON(http.StatusOK, handlers.Response[loadResponse]{Status: http.StatusText(http.StatusOK), Payload: loadResponse{Total: int(total.Result), Results: results.Result}})
}
func execResultsQuery(ch chan ResultChan[[]loadResponseResults], c echo.Context) {
db := database.GetNewDatabase()
defer func(Db *pgx.Conn, ctx context.Context) {
_ = Db.Close(ctx)
}(db.Db, context.Background())
q := getQuery(c, false)
if q == nil {
ch <- ResultChan[[]loadResponseResults]{Result: []loadResponseResults{}}
return
}
sql, params := q.Sql()
rows, err := db.Db.Query(context.Background(), sql, params...)
if err != nil {
ch <- ResultChan[[]loadResponseResults]{Err: err}
return
}
results := make([]loadResponseResults, 0)
for rows.Next() {
row := row{}
err = rows.Scan(
&row.ID,
&row.Col,
&row.PowderGr,
&row.CartridgeID,
&row.CartridgeName,
&row.CartridgeMeta,
&row.BulletID,
&row.BulletName,
&row.BulletDiameter,
&row.BulletWeight,
&row.BulletMeta,
&row.BulletManufacturerName,
&row.BulletManufacturerUrl,
&row.PrimerID,
&row.PrimerName,
&row.PrimerMeta,
&row.PrimerManufacturerName,
&row.PrimerManufacturerUrl,
&row.PowderID,
&row.PowderName,
&row.PowderMeta,
&row.PowderManufacturerName,
&row.PowderManufacturerUrl,
)
if err != nil {
ch <- ResultChan[[]loadResponseResults]{Err: err}
return
}
results = append(results, loadResponseResults{
Id: row.ID.String(),
Cartridge: row.CartridgeName,
CartridgeId: row.CartridgeID.String(),
Col: row.Col,
Powder: handlers.Powder{
Id: row.PowderID.String(),
Name: row.PowderName,
Meta: string(row.PowderMeta),
Manufacturer: handlers.Manufacturer{
Name: row.PowderManufacturerName,
Url: row.PowderManufacturerUrl.String,
},
},
PowderGr: row.PowderGr,
Primer: primers.PrimerResponse{
ID: row.PrimerID.String(),
Name: row.PrimerName,
Manufacturer: handlers.Manufacturer{
Name: row.PrimerManufacturerName,
Url: row.PrimerManufacturerUrl.String,
},
},
Bullet: bullets.BulletResponse{
Id: row.BulletID.String(),
Name: row.BulletName,
Diameter: row.BulletDiameter,
Weight: row.BulletWeight,
Manufacturer: handlers.Manufacturer{
Name: row.BulletManufacturerName,
Url: row.BulletManufacturerUrl.String,
},
},
})
}
ch <- ResultChan[[]loadResponseResults]{Result: results}
}
func getQuery(c echo.Context, countOnly bool) postgres.SelectStatement {
l := table.Loads.AS("l")
ctg := table.Cartridges.AS("c")
@@ -289,7 +389,6 @@ func getQuery(c echo.Context, countOnly bool) postgres.SelectStatement {
if countOnly {
q = tb.SELECT(postgres.COUNT(l.ID).AS("total"))
} else {
q = tb.SELECT(
// Load
l.ID.AS("id"),
@@ -329,6 +428,17 @@ func getQuery(c echo.Context, countOnly bool) postgres.SelectStatement {
// where expressions
expressions := make([]postgres.BoolExpression, 0)
if c.Param("id") != "" {
uuid, err := handlers.ParseUuid(c.Param("id"))
if err != nil {
return nil
}
q = q.WHERE(l.ID.EQ(postgres.UUID(uuid)))
return q
}
if c.QueryParam("cartridge_id") != "" {
ids := strings.Split(c.QueryParam("cartridge_id"), ",")
if len(ids) > 0 {
@@ -438,17 +548,48 @@ func getQuery(c echo.Context, countOnly bool) postgres.SelectStatement {
if limit == "" {
limit = "50"
}
offset := c.QueryParam("offset")
if offset == "" {
offset = "0"
pageInt := handlers.ParseInt64OrDefault(c.QueryParam("page"), 1)
if pageInt < 1 {
pageInt = 1
}
offset := (pageInt - 1) * handlers.ParseInt64OrDefault(limit, 50)
q = q.LIMIT(handlers.ParseInt64OrDefault(limit, 50)).
OFFSET(handlers.ParseInt64OrDefault(offset, 0))
OFFSET(offset)
return q
}
func Delete(c echo.Context) error {
id := c.Param("id")
if id == "" {
return handlers.BadRequest(c, "id is required")
}
uuid, err := handlers.ParseUuid(id)
if err != nil {
return handlers.BadRequest(c, "id is not a valid UUID")
}
db := c.(*database.CustomContext).Db
defer func() {
_ = db.Db.Close(context.Background())
}()
exists, err := db.Loads.GetLoadById(context.Background(), *uuid)
if err != nil || !exists.ID.Valid {
return handlers.NotFound(c, "load not found")
}
err = db.Loads.DeleteLoad(context.Background(), *uuid)
if err != nil {
return err
}
return c.NoContent(http.StatusNoContent)
}
func getUuidExpr(ids []string) []postgres.Expression {
expr := make([]postgres.Expression, 0)
for _, id := range ids {

View File

@@ -21,13 +21,15 @@ type manufacturerResponse struct {
func Get(c echo.Context) error {
db := c.(*database.CustomContext).Db
defer db.Db.Close(context.Background())
defer func() {
_ = db.Db.Close(context.Background())
}()
if c.Param("id") != "" {
id := c.Param("id")
uid := handlers.ParseUuidOrBadRequest(c, id)
if uid == nil {
return nil
uid, err := handlers.ParseUuidOrBadRequest(c, id)
if err != nil || uid == nil {
return echo.NewHTTPError(http.StatusBadRequest, "Invalid UUID format")
}
byId, err := db.Manufacturer.GetById(context.Background(), *uid)
@@ -73,15 +75,17 @@ func Get(c echo.Context) error {
func Delete(c echo.Context) error {
db := c.(*database.CustomContext).Db
defer db.Db.Close(context.Background())
defer func() {
_ = db.Db.Close(context.Background())
}()
id := c.Param("id")
uid := handlers.ParseUuidOrBadRequest(c, id)
if uid == nil {
return nil
uid, err := handlers.ParseUuidOrBadRequest(c, id)
if err != nil || uid == nil {
return echo.NewHTTPError(http.StatusBadRequest, "Invalid UUID format")
}
_, err := db.Manufacturer.GetById(context.Background(), *uid)
_, err = db.Manufacturer.GetById(context.Background(), *uid)
if err != nil {
return echo.NewHTTPError(http.StatusNotFound, "Not found")
}
@@ -97,16 +101,18 @@ func Delete(c echo.Context) error {
func Post(c echo.Context) error {
db := c.(*database.CustomContext).Db
defer db.Db.Close(context.Background())
defer func() {
_ = db.Db.Close(context.Background())
}()
if c.Param("id") != "" {
id := c.Param("id")
uid := handlers.ParseUuidOrBadRequest(c, id)
if uid == nil {
return nil
uid, err := handlers.ParseUuidOrBadRequest(c, id)
if err != nil || uid == nil {
return echo.NewHTTPError(http.StatusBadRequest, "Invalid UUID format")
}
_, err := db.Manufacturer.GetById(context.Background(), *uid)
_, err = db.Manufacturer.GetById(context.Background(), *uid)
if err != nil {
return echo.NewHTTPError(http.StatusNotFound, "Not found")
}

View File

@@ -13,7 +13,9 @@ import (
func Photo(c echo.Context) error {
db := c.(*database.CustomContext).Db
defer db.Db.Close(context.Background())
defer func() {
_ = db.Db.Close(context.Background())
}()
id := c.Param("id")
uid, err := handlers.ParseUuid(id)
@@ -41,7 +43,9 @@ func Photo(c echo.Context) error {
func Delete(c echo.Context) error {
id := c.Param("id")
db := c.(*database.CustomContext).Db
defer db.Db.Close(context.Background())
defer func() {
_ = db.Db.Close(context.Background())
}()
uid, err := handlers.ParseUuid(id)
if err != nil {
@@ -58,7 +62,9 @@ func Delete(c echo.Context) error {
func Post(c echo.Context) error {
db := c.(*database.CustomContext).Db
defer db.Db.Close(context.Background())
defer func() {
_ = db.Db.Close(context.Background())
}()
if c.Param("id") != "" {
id := c.Param("id")
@@ -163,7 +169,9 @@ func Post(c echo.Context) error {
func Get(c echo.Context) error {
db := c.(*database.CustomContext).Db
defer db.Db.Close(context.Background())
defer func() {
_ = db.Db.Close(context.Background())
}()
if c.Param("id") != "" {
id := c.Param("id")

View File

@@ -3,6 +3,7 @@ package primers
import (
"context"
"encoding/json"
"fmt"
"git.siteworxpro.com/reloading-manager/backend/database"
"git.siteworxpro.com/reloading-manager/backend/handlers"
"git.siteworxpro.com/reloading-manager/backend/models/primers"
@@ -19,7 +20,9 @@ type PrimerResponse struct {
func Delete(c echo.Context) error {
db := c.(*database.CustomContext).Db
defer db.Db.Close(context.Background())
defer func() {
_ = db.Db.Close(context.Background())
}()
id := c.Param("id")
uid, err := handlers.ParseUuid(id)
@@ -37,13 +40,15 @@ func Delete(c echo.Context) error {
func Post(c echo.Context) error {
db := c.(*database.CustomContext).Db
defer db.Db.Close(context.Background())
defer func() {
_ = db.Db.Close(context.Background())
}()
if c.Param("id") != "" {
id := c.Param("id")
uid := handlers.ParseUuidOrBadRequest(c, id)
if uid == nil {
return nil
uid, err := handlers.ParseUuidOrBadRequest(c, id)
if err != nil || uid == nil {
return echo.NewHTTPError(http.StatusBadRequest, "Invalid UUID.")
}
p, err := db.Primer.GetPrimerById(context.Background(), *uid)
@@ -56,9 +61,9 @@ func Post(c echo.Context) error {
}
if c.FormValue("manufacturer_id") != "" {
mid := handlers.ParseUuidOrBadRequest(c, c.FormValue("manufacturer_id"))
if mid == nil {
return nil
mid, err := handlers.ParseUuidOrBadRequest(c, c.FormValue("manufacturer_id"))
if err != nil || mid == nil {
return echo.NewHTTPError(http.StatusBadRequest, "Invalid Manufacturer ID.")
}
p.ManufacturerID = *mid
@@ -112,13 +117,14 @@ func Post(c echo.Context) error {
if metaString == "" {
metaString = "{}"
}
var meta json.RawMessage = []byte(metaString)
meta := []byte(metaString)
newUuid := uuid.New().String()
uid, _ := handlers.ParseUuid(newUuid)
mid := handlers.ParseUuidOrBadRequest(c, manufacturerId)
if mid == nil {
return nil
mid, err := handlers.ParseUuidOrBadRequest(c, manufacturerId)
if err != nil || mid == nil {
return echo.NewHTTPError(http.StatusBadRequest, "Invalid Manufacturer ID.")
}
err = db.Primer.InsertPrimer(context.Background(), primers.InsertPrimerParams{
@@ -145,12 +151,14 @@ func Post(c echo.Context) error {
func Photo(c echo.Context) error {
db := c.(*database.CustomContext).Db
defer db.Db.Close(context.Background())
defer func() {
_ = db.Db.Close(context.Background())
}()
id := c.Param("id")
uid := handlers.ParseUuidOrBadRequest(c, id)
if uid == nil {
return nil
uid, err := handlers.ParseUuidOrBadRequest(c, id)
if err != nil || uid == nil {
return echo.NewHTTPError(http.StatusBadRequest, "Invalid UUID.")
}
byId, err := db.Primer.GetPrimerById(context.Background(), *uid)
@@ -172,18 +180,20 @@ func Photo(c echo.Context) error {
func Get(c echo.Context) error {
db := c.(*database.CustomContext).Db
defer db.Db.Close(context.Background())
defer func() {
_ = db.Db.Close(context.Background())
}()
if c.Param("id") != "" {
id := c.Param("id")
uid := handlers.ParseUuidOrBadRequest(c, id)
if uid == nil {
return nil
uid, err := handlers.ParseUuidOrBadRequest(c, id)
if err != nil || uid == nil {
return echo.NewHTTPError(http.StatusBadRequest, "Invalid UUID.")
}
row, err := db.Primer.GetPrimerById(context.Background(), *uid)
if err != nil {
return err
return fmt.Errorf("primer not found: %v", err)
}
return c.JSON(http.StatusOK, handlers.Response[PrimerResponse]{

View File

@@ -11,7 +11,6 @@ type TestContext struct {
}
func (t TestContext) JSON(code int, i interface{}) error {
if code != 400 {
t.t.Fatal("expected 400")
}

View File

@@ -1,6 +1,7 @@
package handlers
import (
"fmt"
"github.com/google/uuid"
"github.com/jackc/pgx/v5/pgtype"
"github.com/labstack/echo/v4"
@@ -23,7 +24,7 @@ func ParseUuidOrEmpty(s string) *pgtype.UUID {
func ParseUuid(s string) (*pgtype.UUID, error) {
uid, err := uuid.Parse(s)
if err != nil {
return nil, err
return nil, fmt.Errorf("invalid UUID format: %v", err)
}
return &pgtype.UUID{
@@ -32,14 +33,11 @@ func ParseUuid(s string) (*pgtype.UUID, error) {
}, nil
}
func ParseUuidOrBadRequest(c echo.Context, s string) *pgtype.UUID {
func ParseUuidOrBadRequest(c echo.Context, s string) (*pgtype.UUID, error) {
uid, err := ParseUuid(s)
if err != nil {
_ = BadRequest(c, "Invalid UUID.")
return nil
return nil, BadRequest(c, fmt.Sprintf("invalid UUID. %v", err))
}
return uid
return uid, nil
}

View File

@@ -98,7 +98,11 @@ func main() {
// loads
e.GET("/load", loads.Get)
e.GET("/load/:id", loads.Get)
e.GET("/load/:id/photo", loads.Photo)
e.POST("/load", loads.Post)
e.POST("/load/:id", loads.Post)
e.DELETE("/load/:id", loads.Delete)
addr := fmt.Sprintf("0.0.0.0:%s", Port.GetEnvString("8080"))
@@ -113,7 +117,9 @@ func migrate(e *echo.Echo) {
db := database.GetNewDatabase()
defer db.Db.Close(context.Background())
defer func() {
_ = db.Db.Close(context.Background())
}()
db.Migrate()
e.Logger.Info("✅ Complete!")

View File

@@ -18,6 +18,22 @@ delete
from cartridges
where id = $1;
-- name: DeleteLoad :exec
delete from loads
where id = $1;
-- name: UpdateLoad :exec
update loads set
cartridge_id = $1,
col = $2,
powder_id = $3,
powder_gr = $4,
primer_id = $5,
bullet_id = $6,
photo = $7,
meta = $8
where id = $9;
-- name: CreateLoad :one
insert into loads (cartridge_id, col, powder_id, powder_gr, primer_id, bullet_id, photo, meta)
values ($1, $2, $3, $4, $5, $6, $7, $8)
@@ -25,6 +41,7 @@ returning id;
-- name: GetLoadById :one
select l.id as id,
l.photo as photo,
c.id as cartridge_id,
c.name as cartridge_name,
c.meta as cartridge_meta,

View File

@@ -73,6 +73,16 @@ func (q *Queries) DeleteCartridge(ctx context.Context, id pgtype.UUID) error {
return err
}
const deleteLoad = `-- name: DeleteLoad :exec
delete from loads
where id = $1
`
func (q *Queries) DeleteLoad(ctx context.Context, id pgtype.UUID) error {
_, err := q.db.Exec(ctx, deleteLoad, id)
return err
}
const getCartridgeById = `-- name: GetCartridgeById :one
select c.id as id, c.name, c.meta
from cartridges c
@@ -126,6 +136,7 @@ func (q *Queries) GetCartridges(ctx context.Context) ([]GetCartridgesRow, error)
const getLoadById = `-- name: GetLoadById :one
select l.id as id,
l.photo as photo,
c.id as cartridge_id,
c.name as cartridge_name,
c.meta as cartridge_meta,
@@ -147,6 +158,7 @@ where l.id = $1
type GetLoadByIdRow struct {
ID pgtype.UUID `json:"id"`
Photo []byte `json:"photo"`
CartridgeID pgtype.UUID `json:"cartridge_id"`
CartridgeName string `json:"cartridge_name"`
CartridgeMeta []byte `json:"cartridge_meta"`
@@ -165,6 +177,7 @@ func (q *Queries) GetLoadById(ctx context.Context, id pgtype.UUID) (GetLoadByIdR
var i GetLoadByIdRow
err := row.Scan(
&i.ID,
&i.Photo,
&i.CartridgeID,
&i.CartridgeName,
&i.CartridgeMeta,
@@ -376,3 +389,43 @@ func (q *Queries) TotalLoads(ctx context.Context) (int64, error) {
err := row.Scan(&count)
return count, err
}
const updateLoad = `-- name: UpdateLoad :exec
update loads set
cartridge_id = $1,
col = $2,
powder_id = $3,
powder_gr = $4,
primer_id = $5,
bullet_id = $6,
photo = $7,
meta = $8
where id = $9
`
type UpdateLoadParams struct {
CartridgeID pgtype.UUID `json:"cartridge_id"`
Col float32 `json:"col"`
PowderID pgtype.UUID `json:"powder_id"`
PowderGr float32 `json:"powder_gr"`
PrimerID pgtype.UUID `json:"primer_id"`
BulletID pgtype.UUID `json:"bullet_id"`
Photo []byte `json:"photo"`
Meta []byte `json:"meta"`
ID pgtype.UUID `json:"id"`
}
func (q *Queries) UpdateLoad(ctx context.Context, arg UpdateLoadParams) error {
_, err := q.db.Exec(ctx, updateLoad,
arg.CartridgeID,
arg.Col,
arg.PowderID,
arg.PowderGr,
arg.PrimerID,
arg.BulletID,
arg.Photo,
arg.Meta,
arg.ID,
)
return err
}

View File

@@ -0,0 +1,49 @@
<template>
<div class="loader" v-if="loading">
<div class="flex flex-col justify-center items-center">
<ProgressSpinner :style="{ width: '50px', height: '50px' }" strokeWidth="5" />
</div>
<div class="flex flex-row justify-center items-center">
<span class="text-2xl">{{ message }}</span>
</div>
</div>
</template>
<script setup lang="ts">
import { defineAsyncComponent } from 'vue'
const ProgressSpinner = defineAsyncComponent(() => import('primevue/progressspinner'))
defineProps({
loading: {
type: Boolean,
default: false
},
message: {
type: String,
default: 'Loading...'
}
})
</script>
<style scoped lang="scss">
.loader {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(9, 5, 5, 0.56);
z-index: 1000;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
.text-2xl {
margin-top: 10px;
font-size: 24px;
font-weight: bold;
color: #ffffff;
}
}
</style>

View File

@@ -5,4 +5,5 @@ export const icons = {
upload: 'fa-thin fa-sharp fa-upload',
save: 'fa-thin fa-sharp fa-floppy-disk',
delete: 'fa-thin fa-sharp fa-trash',
loading: 'fa-solid fa-sharp fa-cog fa-spin',
}

View File

@@ -1,7 +1,8 @@
<template>
<Card class="md:w-2/3 w-full">
<template #title>
Add New Load
<span v-if="route.params.id">Edit Load</span>
<span v-else>Add New Load</span>
</template>
<template #content>
<div class="grid grid-cols-1">
@@ -66,7 +67,7 @@
COL
</div>
<div class="w-full mt-3">
<InputMask placeholder="0.000" v-model="load.col" mask="9.999" class="w-full" />
<InputMask :unmask="loading" :value="load.col" placeholder="0.000" v-model="load.col" mask="9.999" class="w-full" />
<Message v-if="v$.$dirty && v$.col.$invalid" :value="false" size="small" severity="error"
variant="simple">COL Required
</Message>
@@ -74,15 +75,20 @@
<div class="w-full mt-5">
<label>Picture</label>
<Message v-if="v$.$dirty && !file" :value="false" size="small" severity="error"
<Message v-if="v$.$dirty && (!route.params.id && !file)" :value="false" size="small" severity="error"
variant="simple">Picture Required
</Message>
<div class="w-1/2">
<img v-if="pictureUrl && route.params.id && !loading" :src="pictureUrl" alt="picture" />
</div>
<FileUpload v-model="file" mode="basic" @select="fileSelected" customUpload />
</div>
</div>
</template>
<template #footer>
<Button label="Add" :icon="icons.add" @click="add" />
<Button v-if="!route.params.id" label="Add" :icon="icons.add" @click="add" />
<Button v-else label="Save" :icon="icons.edit" @click="add" />
<Button class="ml-3" severity="danger" v-if="route.params.id" label="Delete" :icon="icons.delete" @click="deleteLoad" />
</template>
</Card>
@@ -103,6 +109,7 @@
</template>
</Dialog>
<FullScreenLoader :loading="loading" />
</template>
<script lang="ts">
interface Select {
@@ -121,13 +128,16 @@ import { defineAsyncComponent, onMounted, ref } from 'vue'
import { Primers } from '../../types/primers'
import { Powder } from '../../types/powder'
import axios from 'axios'
import { Response } from '../../types/Response'
import { Load, Response } from '../../types/Response'
import { icons } from '../../lib/icons.ts'
import { FileUploadSelectEvent } from 'primevue/fileupload'
import useVuelidate from '@vuelidate/core'
import { required } from '@vuelidate/validators'
import { useToast } from 'primevue/usetoast'
import router from '../../router'
import { useRoute, useRouter } from 'vue-router'
const router = useRouter()
const route = useRoute()
const Card = defineAsyncComponent(() => import('primevue/card'))
const Select = defineAsyncComponent(() => import('primevue/select'))
@@ -138,6 +148,7 @@ const InputMask = defineAsyncComponent(() => import('primevue/inputmask'))
const FileUpload = defineAsyncComponent(() => import('primevue/fileupload'))
const Dialog = defineAsyncComponent(() => import('primevue/dialog'))
const Message = defineAsyncComponent(() => import('primevue/message'))
const FullScreenLoader = defineAsyncComponent(() => import('../../components/FullScreenLoader.vue'))
const toast = useToast()
@@ -145,6 +156,7 @@ const bullets = ref<Select[]>([])
const primers = ref<Select[]>([])
const powders = ref<Select[]>([])
const cartridges = ref<Select[]>([])
const loading = ref(false)
const cartridgeName = ref('')
const addCartridgeDialog = ref(false)
@@ -154,6 +166,12 @@ const fileSelected = (e: FileUploadSelectEvent) => {
file.value = e.files[0]
}
const pictureUrl = ref<string | null>(null)
const calcUrl = () => {
const cache = new Date().getMilliseconds()
pictureUrl.value = import.meta.env.VITE_API + `/load/${route.params.id}/photo?cache=${cache}`
}
const load = ref({
bullet: '',
cartridge: '',
@@ -191,21 +209,18 @@ const fetchBullets = async () => {
bullets.value.push({ label: `${bullet.manufacturer.name} ${bullet.weight}gr ${bullet.name}`, value: bullet.id })
})
}
const fetchPrimers = async () => {
const response = await axios.get<any, Response<Primers[]>>(`${import.meta.env.VITE_API}/primer`)
response.data.payload.forEach((primer: Primers) => {
primers.value.push({ label: `${primer.manufacturer.name} ${primer.name}`, value: primer.id })
})
}
const fetchPowders = async () => {
const response = await axios.get<any, Response<Powder[]>>(`${import.meta.env.VITE_API}/powder`)
response.data.payload.forEach((powder: Powder) => {
powders.value.push({ label: `${powder.manufacturer.name} ${powder.name}`, value: powder.id })
})
}
const fetchCartridges = async () => {
cartridges.value = []
@@ -214,7 +229,6 @@ const fetchCartridges = async () => {
cartridges.value.push({ label: `${cartridge.name}`, value: cartridge.id })
})
}
const addCartridgeName = async () => {
if (cartridgeName.value === '') {
return
@@ -237,11 +251,10 @@ const addCartridgeName = async () => {
cartridgeName.value = ''
addCartridgeDialog.value = false
}
const add = async () => {
v$.value.$touch()
if (v$.value.$invalid || !file.value) {
if (v$.value.$invalid || (!route.params.id && !file.value)) {
toast.add({
severity: 'error',
summary: 'Error',
@@ -252,6 +265,12 @@ const add = async () => {
return
}
let id = ""
if (route.params.id) {
id = "/" + route.params.id.toString()
}
const formData = new FormData()
formData.append('bullet_id', load.value.bullet)
formData.append('cartridge_id', load.value.cartridge)
@@ -259,22 +278,43 @@ const add = async () => {
formData.append('powder_gr', load.value.powderGrs.toString())
formData.append('primer_id', load.value.primer)
formData.append('col', load.value.col)
formData.append('photo', file.value)
if (file.value) {
formData.append('photo', file.value)
}
try {
const response = await axios.post<any, Response<string>>(`${import.meta.env.VITE_API}/load`, formData, {
const response = await axios.post<any, Response<string>>(`${import.meta.env.VITE_API}/load${id}`, formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
})
const message = route.params.id ? 'Load updated' : 'Load added'
toast.add({
severity: 'success',
summary: 'Success',
detail: 'Load added',
detail: message,
life: 3000,
})
if (route.params.id) {
load.value = {
bullet: '',
cartridge: '',
powder: '',
powderGrs: 0,
primer: '',
col: '',
}
file.value = null
v$.value.$reset()
await fetchLoad()
return
}
await router.push(`/loads/edit/${response.data.payload}`)
} catch (error) {
toast.add({
@@ -284,14 +324,85 @@ const add = async () => {
life: 3000,
})
}
}
const deleteLoad = async () => {
if (!route.params.id) {
return
}
try {
await axios.delete(`${import.meta.env.VITE_API}/load/${route.params.id}`)
toast.add({
severity: 'success',
summary: 'Success',
detail: 'Load deleted',
life: 3000,
})
await router.push('/loads/search')
} catch (error) {
toast.add({
severity: 'error',
summary: 'Error',
detail: 'Error deleting load',
life: 3000,
})
}
}
const fetchLoad = async () => {
if (!route.params.id) {
return
}
loading.value = true
const response = await axios.get<any, Response<Load>>(`${import.meta.env.VITE_API}/load/${route.params.id}`)
if (response.data.payload) {
load.value.cartridge = response.data.payload.cartridge_id
load.value.bullet = response.data.payload.bullet.id
load.value.powder = response.data.payload.powder.id
load.value.powderGrs = response.data.payload.powder_gr
load.value.primer = response.data.payload.primer.id
load.value.col = response.data.payload.col.toString()
if (!load.value.col.includes('.')) {
load.value.col = `${load.value.col}.000`
} else {
const parts = load.value.col.split('.')
if (parts[1].length < 3) {
load.value.col = `${parts[0]}.${parts[1].padEnd(3, '0')}`
}
}
calcUrl()
} else {
toast.add({
severity: 'error',
summary: 'Error',
detail: 'Load not found',
life: 3000,
})
await router.push('/loads')
}
loading.value = false
}
onMounted(() => {
fetchBullets()
fetchPrimers()
fetchPowders()
fetchCartridges()
if (route.params.id) {
setTimeout(() => {
fetchLoad()
}, 100)
}
})
</script>

View File

@@ -8,13 +8,15 @@
:value="loads"
filterDisplay="row"
paginator
:rowsPerPageOptions="[5, 10, 20, 50]"
size="small"
:sortField="sortField"
:sortOrder="sortOrder"
@update:sortField="(e: string) => {sortField = e; fetchLoads()}"
@update:sortOrder="(e: number | undefined) => {sortOrder = e; fetchLoads()}"
@page="updatePagination"
:rows="rowsPerPage"
lazy
:rows="50"
:totalRecords="total"
:loading="loading"
>
@@ -42,7 +44,9 @@
@change="fetchLoads" />
</template>
<template #body="{ data }">
{{ data.bullet.manufacturer.name }}
<a target="_blank" :href="data.bullet.manufacturer.url">
{{ data.bullet.manufacturer.name }}
</a>
</template>
</Column>
<Column field="bullet_name" header="Bullet" :sortable="true" :showFilterMenu="false">
@@ -63,7 +67,9 @@
@change="fetchLoads" />
</template>
<template #body="{ data }">
{{ data.primer.manufacturer.name }}
<a target="_blank" :href="data.primer.manufacturer.url">
{{ data.primer.manufacturer.name }}
</a>
</template>
</Column>
<Column field="primer_name" header="Primer" :sortable="true" :showFilterMenu="false">
@@ -83,7 +89,9 @@
@change="fetchLoads" />
</template>
<template #body="{ data }">
{{ data.powder.manufacturer.name }}
<a target="_blank" :href="data.powder.manufacturer.url">
{{ data.powder.manufacturer.name }}
</a>
</template>
</Column>
@@ -104,8 +112,13 @@
</Column>
<Column field="edit" header="Edit">
<template #body>
<Button size="small" text :icon="icons.edit" />
<template #body="{ data }">
<Button
size="small"
text
:icon="icons.edit"
@click="router.push('/loads/edit/' + data.id)"
/>
</template>
</Column>
</DataTable>
@@ -116,22 +129,13 @@
import { Bullet } from '../../types/bullet'
import { Powder } from '../../types/powder'
import { Primers } from '../../types/primers'
import { Load } from '../../types/Response'
interface LoadResponse {
total: number
results: Load[]
}
interface Load {
id: string
bullet: Bullet
cartridge: string
powder: Powder
powder_gr: number
primer: Primers
col: number
}
interface Option {
label: string
value: string
@@ -143,6 +147,7 @@ import { Response } from '../../types/Response'
import axios from 'axios'
import Column from 'primevue/column'
import { icons } from '../../lib/icons.ts'
import router from '../../router'
const DataTable = defineAsyncComponent(() => import('primevue/datatable'))
const Card = defineAsyncComponent(() => import('primevue/card'))
@@ -151,6 +156,8 @@ const MultiSelect = defineAsyncComponent(() => import('primevue/multiselect'))
const loads = ref<Load[]>([])
const total = ref(0)
const rowsPerPage = ref(50)
const page = ref(1)
const loading = ref(true)
// use local storage to store the selected filters
@@ -181,6 +188,14 @@ const powderManufacturerSelected = ref<string[]>(localStorageFilters.powderManuf
const powderOptions = ref<Option[]>([])
const powderSelected = ref<string[]>(localStorageFilters.powderSelected ?? [])
const updatePagination = (e: { page: number, rows: number }) => {
console.log(e)
page.value = e.page + 1
rowsPerPage.value = e.rows
fetchLoads()
}
const fetchLoads = async () => {
loading.value = true
@@ -220,6 +235,9 @@ const fetchLoads = async () => {
searchParams.unshift(`powder_id=${powderSelected.value.join(',')}`)
}
searchParams.unshift(`limit=${rowsPerPage.value}`)
searchParams.unshift(`page=${page.value}`)
localStorage.setItem(localStorageKey, JSON.stringify({
cartridgeSelected: cartridgeSelected.value,
bulletManufacturerSelected: bulletManufacturerSelected.value,
@@ -269,6 +287,11 @@ onMounted(() => {
value: primer.manufacturer.id,
}))
// dedupe the primer Manufacturer options
primerManufacturerOptions.value = primerManufacturerOptions.value.filter((option, index, self) =>
index === self.findIndex((o) => o.value === option.value)
)
primerOptions.value = resp.data.payload.map((primer) => ({
label: primer.name,
value: primer.id,
@@ -281,6 +304,12 @@ onMounted(() => {
value: powder.manufacturer.id,
}))
// dedupe the powder Manufacturer options
powderManufacturerOptions.value = powderManufacturerOptions.value.filter((option, index, self) =>
index === self.findIndex((o) => o.value === option.value)
)
powderOptions.value = resp.data.payload.map((powder) => ({
label: powder.name,
value: powder.id,

View File

@@ -61,6 +61,10 @@ const routes = [
path: '/loads/search',
component: () => import('../pages/loads/Search.vue'),
},
{
path: '/loads/edit/:id',
component: () => import('../pages/loads/Add.vue'),
},
] as RouteRecordRaw[]
const router = createRouter({

View File

@@ -1,4 +1,18 @@
import {AxiosResponse} from "axios";
import { Bullet } from './bullet'
import { Powder } from './powder'
import { Primers } from './primers'
interface Load {
id: string
bullet: Bullet
cartridge: string
cartridge_id: string
powder: Powder
powder_gr: number
primer: Primers
col: number
}
export interface Response<T> extends AxiosResponse {
data: {