You've already forked reloading-manager
No changes made
This commit is contained in:
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
.idea/
|
||||||
|
postgres/
|
||||||
|
migrate
|
||||||
35
backend/.gitlab-ci.yml
Normal file
35
backend/.gitlab-ci.yml
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
stages:
|
||||||
|
- test
|
||||||
|
- build
|
||||||
|
|
||||||
|
variables:
|
||||||
|
IMAGE_URL: siteworxpro/
|
||||||
|
IMAGE_NAME: reloading-backend
|
||||||
|
|
||||||
|
|
||||||
|
Unit Tests:
|
||||||
|
stage: test
|
||||||
|
only:
|
||||||
|
- branches
|
||||||
|
image: siteworxpro/golang:1.24.0
|
||||||
|
before_script:
|
||||||
|
- go mod tidy
|
||||||
|
script:
|
||||||
|
- go test ./...
|
||||||
|
|
||||||
|
Build:
|
||||||
|
stage: build
|
||||||
|
dependencies:
|
||||||
|
- Unit Tests
|
||||||
|
only:
|
||||||
|
- master
|
||||||
|
image: docker:dind
|
||||||
|
before_script:
|
||||||
|
- docker login -u siteworxpro -p ${DOCKER_TOKEN}
|
||||||
|
script:
|
||||||
|
- TAG=${CI_COMMIT_REF_NAME}-${CI_COMMIT_SHORT_SHA}
|
||||||
|
- docker build -t ${IMAGE_NAME} .
|
||||||
|
- docker tag ${IMAGE_NAME}:latest ${IMAGE_URL}${IMAGE_NAME}:latest
|
||||||
|
- docker tag ${IMAGE_URL}${IMAGE_NAME}:latest ${IMAGE_URL}${IMAGE_NAME}:${TAG}
|
||||||
|
- docker push ${IMAGE_URL}${IMAGE_NAME}:${TAG}
|
||||||
|
- docker push ${IMAGE_URL}${IMAGE_NAME}:latest
|
||||||
19
backend/Dockerfile
Normal file
19
backend/Dockerfile
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
FROM siteworxpro/golang:1.24.0 AS build
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
ARG CGO_ENABLED=0
|
||||||
|
ARG GOOS=linux
|
||||||
|
ARG GOPROXY=direct
|
||||||
|
ARG GOPRIVATE=git.s.int
|
||||||
|
|
||||||
|
ADD . .
|
||||||
|
|
||||||
|
RUN go mod tidy
|
||||||
|
RUN go build -o app
|
||||||
|
|
||||||
|
FROM scratch
|
||||||
|
|
||||||
|
COPY --from=build /app/app .
|
||||||
|
|
||||||
|
ENTRYPOINT ["/app"]
|
||||||
8
backend/README.md
Normal file
8
backend/README.md
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
```shell
|
||||||
|
docker run -v $(pwd)/postgres:/var/lib/postgresql/data \
|
||||||
|
-e POSTGRES_DB=loading \
|
||||||
|
-e POSTGRES_PASSWORD=password \
|
||||||
|
--rm --name postgres \
|
||||||
|
-p 5432:5432 \
|
||||||
|
-d scr.siteworxpro.com/library/postgres:17
|
||||||
|
```
|
||||||
63
backend/database/conn.go
Normal file
63
backend/database/conn.go
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
package database
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"git.s.int/packages/go/utilities/Env"
|
||||||
|
"git.s.int/reloading-manager/backend/models/bullets"
|
||||||
|
"git.s.int/reloading-manager/backend/models/loads"
|
||||||
|
"git.s.int/reloading-manager/backend/models/manufacturer"
|
||||||
|
"git.s.int/reloading-manager/backend/models/powder"
|
||||||
|
"git.s.int/reloading-manager/backend/models/primers"
|
||||||
|
"github.com/jackc/pgx/v5"
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
DbHost Env.EnvironmentVariable = "DB_HOST"
|
||||||
|
DbDatabase Env.EnvironmentVariable = "DB_DATABASE"
|
||||||
|
DbUser Env.EnvironmentVariable = "DB_USER"
|
||||||
|
DbPassword Env.EnvironmentVariable = "DB_PASSWORD"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Database struct {
|
||||||
|
Db *pgx.Conn
|
||||||
|
connected bool
|
||||||
|
databaseName string
|
||||||
|
Bullets *bullets.Queries
|
||||||
|
Manufacturer *manufacturer.Queries
|
||||||
|
Powder *powder.Queries
|
||||||
|
Primer *primers.Queries
|
||||||
|
Loads *loads.Queries
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Database) DSN() string {
|
||||||
|
dbHost := DbHost.GetEnvString("localhost")
|
||||||
|
dbDatabase := DbDatabase.GetEnvString("loading")
|
||||||
|
dbUser := DbUser.GetEnvString("postgres")
|
||||||
|
dbPassword := DbPassword.GetEnvString("password")
|
||||||
|
|
||||||
|
extraParams := "?sslmode=disable"
|
||||||
|
|
||||||
|
return fmt.Sprintf("postgres://%s:%s@%s:5432/%s%s", dbUser, dbPassword, dbHost, dbDatabase, extraParams)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetNewDatabase() *Database {
|
||||||
|
var dbSingleton Database
|
||||||
|
|
||||||
|
conn, dbErr := pgx.Connect(context.Background(), dbSingleton.DSN())
|
||||||
|
if dbErr != nil {
|
||||||
|
log.Fatal(dbErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
dbSingleton.Db = conn
|
||||||
|
dbSingleton.connected = true
|
||||||
|
dbSingleton.databaseName = DbDatabase.GetEnvString("loading")
|
||||||
|
dbSingleton.Bullets = bullets.New(dbSingleton.Db)
|
||||||
|
dbSingleton.Manufacturer = manufacturer.New(dbSingleton.Db)
|
||||||
|
dbSingleton.Powder = powder.New(dbSingleton.Db)
|
||||||
|
dbSingleton.Primer = primers.New(dbSingleton.Db)
|
||||||
|
dbSingleton.Loads = loads.New(dbSingleton.Db)
|
||||||
|
|
||||||
|
return &dbSingleton
|
||||||
|
}
|
||||||
25
backend/database/middleware.go
Normal file
25
backend/database/middleware.go
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
package database
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CustomContext struct {
|
||||||
|
echo.Context
|
||||||
|
Db *Database
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateDatabaseMiddleware() echo.MiddlewareFunc {
|
||||||
|
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||||
|
return func(c echo.Context) error {
|
||||||
|
cc := &CustomContext{
|
||||||
|
Context: c,
|
||||||
|
Db: GetNewDatabase(),
|
||||||
|
}
|
||||||
|
|
||||||
|
res := next(cc)
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
34
backend/database/migrate.go
Normal file
34
backend/database/migrate.go
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
package database
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
_ "github.com/go-sql-driver/mysql"
|
||||||
|
"github.com/golang-migrate/migrate"
|
||||||
|
"github.com/golang-migrate/migrate/database/postgres"
|
||||||
|
_ "github.com/golang-migrate/migrate/source/file"
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (db *Database) Migrate() {
|
||||||
|
sqlDB, err := sql.Open("postgres", db.DSN())
|
||||||
|
|
||||||
|
driver, err := postgres.WithInstance(sqlDB, &postgres.Config{})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
m, err := migrate.NewWithDatabaseInstance(
|
||||||
|
"file://./migrations",
|
||||||
|
db.databaseName,
|
||||||
|
driver,
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = m.Up()
|
||||||
|
if err != nil && err.Error() != "no change" {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
45
backend/go.mod
Normal file
45
backend/go.mod
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
module git.s.int/reloading-manager/backend
|
||||||
|
|
||||||
|
go 1.24.0
|
||||||
|
|
||||||
|
require (
|
||||||
|
git.s.int/packages/go/utilities v1.2.2
|
||||||
|
github.com/go-playground/validator v9.31.0+incompatible
|
||||||
|
github.com/go-sql-driver/mysql v1.9.1
|
||||||
|
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/labstack/gommon v0.4.2
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
filippo.io/edwards25519 v1.1.0 // indirect
|
||||||
|
github.com/Microsoft/go-winio v0.6.1 // indirect
|
||||||
|
github.com/distribution/reference v0.6.0 // indirect
|
||||||
|
github.com/docker/distribution v2.8.3+incompatible // indirect
|
||||||
|
github.com/docker/docker v24.0.5+incompatible // indirect
|
||||||
|
github.com/docker/go-connections v0.4.0 // indirect
|
||||||
|
github.com/docker/go-units v0.5.0 // indirect
|
||||||
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
|
github.com/gogo/protobuf v1.3.2 // indirect
|
||||||
|
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
|
||||||
|
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||||
|
github.com/leodido/go-urn v1.4.0 // indirect
|
||||||
|
github.com/lib/pq v1.10.9 // indirect
|
||||||
|
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
|
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||||
|
github.com/opencontainers/image-spec v1.0.2 // indirect
|
||||||
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
|
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||||
|
github.com/valyala/fasttemplate v1.2.2 // indirect
|
||||||
|
golang.org/x/crypto v0.36.0 // indirect
|
||||||
|
golang.org/x/net v0.38.0 // indirect
|
||||||
|
golang.org/x/sys v0.31.0 // indirect
|
||||||
|
golang.org/x/text v0.23.0 // indirect
|
||||||
|
golang.org/x/time v0.11.0 // indirect
|
||||||
|
gopkg.in/go-playground/assert.v1 v1.2.1 // indirect
|
||||||
|
)
|
||||||
152
backend/go.sum
Normal file
152
backend/go.sum
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||||
|
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||||
|
git.s.int/packages/go/utilities v1.2.2 h1:IXKdrTgRc7tnDUB4sOWD/kjwgw9luUzvsaPzX+Dhm7Y=
|
||||||
|
git.s.int/packages/go/utilities v1.2.2/go.mod h1:1nIS3PzUaLiNBBkyme408XbI725PiureeTV7iBXfUI0=
|
||||||
|
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
|
||||||
|
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
|
||||||
|
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/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
||||||
|
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||||
|
github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
|
||||||
|
github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||||
|
github.com/docker/docker v24.0.5+incompatible h1:WmgcE4fxyI6EEXxBRxsHnZXrO1pQ3smi0k/jho4HLeY=
|
||||||
|
github.com/docker/docker v24.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||||
|
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
|
||||||
|
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
|
||||||
|
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||||
|
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||||
|
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||||
|
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||||
|
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||||
|
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||||
|
github.com/go-playground/validator v9.31.0+incompatible h1:UA72EPEogEnq76ehGdEDp4Mit+3FDh548oRqwVgNsHA=
|
||||||
|
github.com/go-playground/validator v9.31.0+incompatible/go.mod h1:yrEkQXlcI+PugkyDjY2bRrL/UBU4f3rvrgkN3V8JEig=
|
||||||
|
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
|
||||||
|
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
||||||
|
github.com/go-sql-driver/mysql v1.9.1 h1:FrjNGn/BsJQjVRuSa8CBrM5BWA9BWoXXat3KrtSb/iI=
|
||||||
|
github.com/go-sql-driver/mysql v1.9.1/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
|
||||||
|
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||||
|
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||||
|
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
|
||||||
|
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
|
||||||
|
github.com/golang-migrate/migrate v3.5.4+incompatible h1:R7OzwvCJTCgwapPCiX6DyBiu2czIUMDCB118gFTKTUA=
|
||||||
|
github.com/golang-migrate/migrate v3.5.4+incompatible/go.mod h1:IsVUlFN5puWOmXrqjgGUfIRIbU7mr8oNBE2tyERd9Wk=
|
||||||
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||||
|
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||||
|
github.com/jackc/pgx/v5 v5.7.3 h1:PO1wNKj/bTAwxSJnO1Z4Ai8j4magtqg2SLNjEDzcXQo=
|
||||||
|
github.com/jackc/pgx/v5 v5.7.3/go.mod h1:ncY89UGWxg82EykZUwSpUKEfccBGGYq1xjrOpsbsfGQ=
|
||||||
|
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/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=
|
||||||
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
|
github.com/labstack/echo/v4 v4.12.0 h1:IKpw49IMryVB2p1a4dzwlhP1O2Tf2E0Ir/450lH+kI0=
|
||||||
|
github.com/labstack/echo/v4 v4.12.0/go.mod h1:UP9Cr2DJXbOK3Kr9ONYzNowSh7HP0aG0ShAyycHSJvM=
|
||||||
|
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/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.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
|
||||||
|
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
|
||||||
|
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||||
|
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||||
|
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||||
|
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||||
|
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||||
|
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||||
|
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||||
|
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||||
|
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||||
|
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||||
|
github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM=
|
||||||
|
github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
|
||||||
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
|
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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
|
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
|
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||||
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
|
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||||
|
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||||
|
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
|
||||||
|
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
||||||
|
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
||||||
|
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||||
|
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
|
||||||
|
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
|
||||||
|
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.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
|
||||||
|
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||||
|
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.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
|
||||||
|
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
|
||||||
|
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
|
||||||
|
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
||||||
|
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.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
||||||
|
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
|
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
|
||||||
|
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.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
||||||
|
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
|
||||||
|
golang.org/x/sys v0.31.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.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||||
|
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||||
|
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
|
||||||
|
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
|
||||||
|
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||||
|
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||||
|
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
|
||||||
|
golang.org/x/time v0.11.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=
|
||||||
|
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
|
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
|
||||||
|
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||||
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM=
|
||||||
|
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
292
backend/handlers/bullets/handler.go
Normal file
292
backend/handlers/bullets/handler.go
Normal file
@@ -0,0 +1,292 @@
|
|||||||
|
package bullets
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"git.s.int/reloading-manager/backend/database"
|
||||||
|
"git.s.int/reloading-manager/backend/handlers"
|
||||||
|
"git.s.int/reloading-manager/backend/models/bullets"
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BulletResponse struct {
|
||||||
|
Id string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Diameter int32 `json:"diameter"`
|
||||||
|
Weight int32 `json:"weight"`
|
||||||
|
Manufacturer handlers.Manufacturer `json:"manufacturer"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func Photo(c echo.Context) error {
|
||||||
|
db := c.(*database.CustomContext).Db
|
||||||
|
defer db.Db.Close(context.Background())
|
||||||
|
|
||||||
|
id := c.Param("id")
|
||||||
|
uid := handlers.ParseUuidOrBadRequest(c, id)
|
||||||
|
if uid == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
byId, err := db.Bullets.GetBulletById(context.Background(), *uid)
|
||||||
|
if err != nil {
|
||||||
|
return echo.NewHTTPError(http.StatusNotFound, "Not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
mime := http.DetectContentType(byId.Photo)
|
||||||
|
|
||||||
|
c.Response().Header().Set("Content-Type", mime)
|
||||||
|
c.Response().Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
|
||||||
|
c.Response().Header().Set("Pragma", "no-cache")
|
||||||
|
c.Response().Header().Set("Expires", "0")
|
||||||
|
|
||||||
|
_, err = c.Response().Write(byId.Photo)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func Delete(c echo.Context) error {
|
||||||
|
db := c.(*database.CustomContext).Db
|
||||||
|
defer db.Db.Close(context.Background())
|
||||||
|
|
||||||
|
id := c.Param("id")
|
||||||
|
uid := handlers.ParseUuidOrBadRequest(c, id)
|
||||||
|
if uid == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err := db.Bullets.DeleteBullet(context.Background(), *uid)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(http.StatusOK, handlers.Response[string]{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Put(c echo.Context) error {
|
||||||
|
db := c.(*database.CustomContext).Db
|
||||||
|
defer db.Db.Close(context.Background())
|
||||||
|
|
||||||
|
id := c.Param("id")
|
||||||
|
uid := handlers.ParseUuidOrBadRequest(c, id)
|
||||||
|
if uid == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
byId, err := db.Bullets.GetBulletById(context.Background(), *uid)
|
||||||
|
if err != nil {
|
||||||
|
return echo.NewHTTPError(http.StatusNotFound, "Not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := c.FormFile("photo")
|
||||||
|
var fileData []byte
|
||||||
|
if err == nil {
|
||||||
|
handler, err := file.Open()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := make([]byte, file.Size)
|
||||||
|
_, _ = handler.Read(buf)
|
||||||
|
fileData = buf
|
||||||
|
} else {
|
||||||
|
fileData = byId.Photo
|
||||||
|
}
|
||||||
|
|
||||||
|
var meta json.RawMessage = []byte(c.FormValue("meta"))
|
||||||
|
if len(meta) == 0 {
|
||||||
|
meta = []byte("{}")
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
name := c.FormValue("name")
|
||||||
|
if name == "" {
|
||||||
|
_ = c.JSON(http.StatusBadRequest, struct {
|
||||||
|
Message string `json:"message"`
|
||||||
|
}{
|
||||||
|
Message: "Name is required.",
|
||||||
|
})
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
manufacturer, err := db.Manufacturer.GetById(context.Background(), *manufacturerUid)
|
||||||
|
if err != nil {
|
||||||
|
_ = c.JSON(http.StatusBadRequest, struct {
|
||||||
|
Message string `json:"message"`
|
||||||
|
}{
|
||||||
|
Message: "Invalid Manufacturer ID",
|
||||||
|
})
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err = db.Bullets.UpdateBullet(context.Background(), bullets.UpdateBulletParams{
|
||||||
|
Name: name,
|
||||||
|
Weight: int32(weight),
|
||||||
|
Diameter: int32(diameter),
|
||||||
|
Photo: fileData,
|
||||||
|
Meta: meta,
|
||||||
|
ManufacturerID: manufacturer.ID,
|
||||||
|
ID: byId.ID,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Get(c echo.Context) error {
|
||||||
|
db := c.(*database.CustomContext).Db
|
||||||
|
defer db.Db.Close(context.Background())
|
||||||
|
|
||||||
|
if c.Param("id") != "" {
|
||||||
|
id := c.Param("id")
|
||||||
|
uid := handlers.ParseUuidOrBadRequest(c, id)
|
||||||
|
if uid == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
byId, err := db.Bullets.GetBulletById(context.Background(), *uid)
|
||||||
|
if err != nil {
|
||||||
|
return echo.NewHTTPError(http.StatusNotFound, "Not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
response := handlers.Response[BulletResponse]{
|
||||||
|
Status: http.StatusText(http.StatusOK),
|
||||||
|
Payload: BulletResponse{
|
||||||
|
Id: uid.String(),
|
||||||
|
Name: byId.Name,
|
||||||
|
Diameter: byId.Diameter,
|
||||||
|
Weight: byId.Weight,
|
||||||
|
Manufacturer: handlers.Manufacturer{Id: byId.ManufactuererID.String(), Name: byId.ManufacutuerName, Url: byId.ManufacturerUrl.String},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(http.StatusOK, response)
|
||||||
|
}
|
||||||
|
|
||||||
|
bulletList, err := db.Bullets.ForPage(context.Background(), bullets.ForPageParams{
|
||||||
|
Limit: 100,
|
||||||
|
Offset: 0,
|
||||||
|
})
|
||||||
|
|
||||||
|
if bulletList == nil {
|
||||||
|
bulletList = make([]bullets.ForPageRow, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
var payload = make([]BulletResponse, 0)
|
||||||
|
for _, bullet := range bulletList {
|
||||||
|
payload = append(payload, BulletResponse{
|
||||||
|
Id: bullet.ID.String(),
|
||||||
|
Name: bullet.Name,
|
||||||
|
Weight: bullet.Weight,
|
||||||
|
Diameter: bullet.Diameter,
|
||||||
|
Manufacturer: handlers.Manufacturer{Id: bullet.ManufacturerID.String(), Name: bullet.ManufacturerName, Url: bullet.ManufacturerUrl.String},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = c.JSON(http.StatusOK, handlers.Response[[]BulletResponse]{
|
||||||
|
Payload: payload,
|
||||||
|
Status: "OK",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Post(c echo.Context) error {
|
||||||
|
|
||||||
|
file, err := c.FormFile("photo")
|
||||||
|
if err != nil {
|
||||||
|
c.Logger().Error(err)
|
||||||
|
_ = c.JSON(http.StatusBadRequest, struct {
|
||||||
|
Message string `json:"message"`
|
||||||
|
}{
|
||||||
|
Message: "No file provided",
|
||||||
|
})
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
handler, err := file.Open()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := make([]byte, file.Size)
|
||||||
|
_, _ = handler.Read(buf)
|
||||||
|
|
||||||
|
weight, _ := strconv.ParseInt(c.FormValue("weight"), 10, 32)
|
||||||
|
diameter, _ := strconv.ParseInt(c.FormValue("diameter"), 10, 32)
|
||||||
|
manufacturerId := c.FormValue("manufacturer_id")
|
||||||
|
name := c.FormValue("name")
|
||||||
|
if name == "" {
|
||||||
|
_ = c.JSON(http.StatusBadRequest, struct {
|
||||||
|
Message string `json:"message"`
|
||||||
|
}{
|
||||||
|
Message: "Name is required.",
|
||||||
|
})
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
db := c.(*database.CustomContext).Db
|
||||||
|
defer db.Db.Close(context.Background())
|
||||||
|
|
||||||
|
manufacturerUid := handlers.ParseUuidOrBadRequest(c, manufacturerId)
|
||||||
|
if manufacturerUid == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
manufacturer, err := db.Manufacturer.GetById(context.Background(), *manufacturerUid)
|
||||||
|
if err != nil {
|
||||||
|
_ = c.JSON(http.StatusBadRequest, struct {
|
||||||
|
Message string `json:"message"`
|
||||||
|
}{
|
||||||
|
Message: "Invalid Manufacturer ID",
|
||||||
|
})
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var meta json.RawMessage = []byte(c.FormValue("meta"))
|
||||||
|
if len(meta) == 0 {
|
||||||
|
meta = []byte("{}")
|
||||||
|
}
|
||||||
|
|
||||||
|
id, err := db.Bullets.AddBullet(context.Background(), bullets.AddBulletParams{
|
||||||
|
Name: name,
|
||||||
|
Weight: int32(weight),
|
||||||
|
Diameter: int32(diameter),
|
||||||
|
Photo: buf,
|
||||||
|
Meta: meta,
|
||||||
|
ManufacturerID: manufacturer.ID,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = c.JSON(http.StatusAccepted, handlers.Response[string]{
|
||||||
|
Status: http.StatusText(http.StatusAccepted),
|
||||||
|
Payload: id.String(),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
96
backend/handlers/cartridge/handler.go
Normal file
96
backend/handlers/cartridge/handler.go
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
package cartridge
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"git.s.int/reloading-manager/backend/database"
|
||||||
|
"git.s.int/reloading-manager/backend/handlers"
|
||||||
|
"git.s.int/reloading-manager/backend/models/loads"
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
type response struct {
|
||||||
|
Id string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type postRequest struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Meta string `json:"meta"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func Get(c echo.Context) error {
|
||||||
|
db := c.(*database.CustomContext).Db
|
||||||
|
defer db.Db.Close(context.Background())
|
||||||
|
|
||||||
|
cartridges, err := db.Loads.GetCartridges(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
var res = make([]response, 0)
|
||||||
|
for _, cartridge := range cartridges {
|
||||||
|
res = append(res, response{
|
||||||
|
Id: cartridge.ID.String(),
|
||||||
|
Name: cartridge.Name,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(http.StatusOK, handlers.Response[[]response]{
|
||||||
|
Payload: res,
|
||||||
|
Status: http.StatusText(http.StatusOK),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Post(c echo.Context) error {
|
||||||
|
db := c.(*database.CustomContext).Db
|
||||||
|
defer db.Db.Close(context.Background())
|
||||||
|
|
||||||
|
req := postRequest{}
|
||||||
|
|
||||||
|
err := c.Bind(&req)
|
||||||
|
if err != nil {
|
||||||
|
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.Name == "" {
|
||||||
|
return echo.NewHTTPError(http.StatusBadRequest, "name is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
var meta json.RawMessage
|
||||||
|
if req.Meta != "" {
|
||||||
|
meta = []byte(req.Meta)
|
||||||
|
} else {
|
||||||
|
meta = []byte("{}")
|
||||||
|
}
|
||||||
|
|
||||||
|
retId, err := db.Loads.CreateCartridge(context.Background(), loads.CreateCartridgeParams{
|
||||||
|
Name: req.Name,
|
||||||
|
Meta: meta,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return handlers.BadRequest(c, "already exists")
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(http.StatusOK, map[string]string{"id": retId.String()})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Delete(c echo.Context) error {
|
||||||
|
db := c.(*database.CustomContext).Db
|
||||||
|
defer db.Db.Close(context.Background())
|
||||||
|
|
||||||
|
uid := handlers.ParseUuidOrBadRequest(c, c.Param("id"))
|
||||||
|
if uid == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err := db.Loads.DeleteCartridge(context.Background(), *uid)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.NoContent(http.StatusOK)
|
||||||
|
}
|
||||||
29
backend/handlers/file.go
Normal file
29
backend/handlers/file.go
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
handler, err := file.Open()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := make([]byte, file.Size)
|
||||||
|
_, _ = handler.Read(buf)
|
||||||
|
|
||||||
|
return buf, nil
|
||||||
|
}
|
||||||
215
backend/handlers/loads/handler.go
Normal file
215
backend/handlers/loads/handler.go
Normal file
@@ -0,0 +1,215 @@
|
|||||||
|
package loads
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"git.s.int/reloading-manager/backend/database"
|
||||||
|
"git.s.int/reloading-manager/backend/handlers"
|
||||||
|
"git.s.int/reloading-manager/backend/handlers/bullets"
|
||||||
|
"git.s.int/reloading-manager/backend/handlers/primers"
|
||||||
|
"git.s.int/reloading-manager/backend/models/loads"
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type loadResponse struct {
|
||||||
|
Total int `json:"total"`
|
||||||
|
Results []loadResponseResults `json:"results"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ResultChan[T any] struct {
|
||||||
|
Result T
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func Post(c echo.Context) error {
|
||||||
|
db := c.(*database.CustomContext).Db
|
||||||
|
defer db.Db.Close(context.Background())
|
||||||
|
|
||||||
|
cartridgeID, err := handlers.ParseUuid(c.FormValue("cartridge_id"))
|
||||||
|
if err != nil {
|
||||||
|
return handlers.BadRequest(c, "cartridge_id is not a valid UUID")
|
||||||
|
}
|
||||||
|
|
||||||
|
powderId, err := handlers.ParseUuid(c.FormValue("powder_id"))
|
||||||
|
if err != nil {
|
||||||
|
return handlers.BadRequest(c, "powder_id is not a valid UUID")
|
||||||
|
}
|
||||||
|
|
||||||
|
primerId, err := handlers.ParseUuid(c.FormValue("primer_id"))
|
||||||
|
if err != nil {
|
||||||
|
return handlers.BadRequest(c, "primer_id is not a valid UUID")
|
||||||
|
}
|
||||||
|
|
||||||
|
bulletId, err := handlers.ParseUuid(c.FormValue("bullet_id"))
|
||||||
|
if err != nil {
|
||||||
|
return handlers.BadRequest(c, "bullet_id is not a valid UUID")
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := handlers.ReadFile(c, "photo")
|
||||||
|
if err != nil {
|
||||||
|
return handlers.BadRequest(c, "photo is not valid")
|
||||||
|
}
|
||||||
|
|
||||||
|
meta := c.FormValue("meta")
|
||||||
|
if meta == "" {
|
||||||
|
meta = "{}"
|
||||||
|
}
|
||||||
|
|
||||||
|
powderGr := c.FormValue("powder_gr")
|
||||||
|
if powderGr == "" {
|
||||||
|
return handlers.BadRequest(c, "powder_gr is not valid")
|
||||||
|
}
|
||||||
|
|
||||||
|
powderGrFl, err := handlers.ParseFloat32(powderGr)
|
||||||
|
if err != nil {
|
||||||
|
return handlers.BadRequest(c, "powder_gr is not valid")
|
||||||
|
}
|
||||||
|
|
||||||
|
col := c.FormValue("col")
|
||||||
|
if col == "" {
|
||||||
|
return handlers.BadRequest(c, "col is not valid")
|
||||||
|
}
|
||||||
|
colFl, err := handlers.ParseFloat32(col)
|
||||||
|
if err != nil {
|
||||||
|
return handlers.BadRequest(c, "col is not valid")
|
||||||
|
}
|
||||||
|
|
||||||
|
uid, err := db.Loads.CreateLoad(context.Background(), loads.CreateLoadParams{
|
||||||
|
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.StatusCreated, handlers.Response[string]{Payload: uid.String()})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Get(c echo.Context) error {
|
||||||
|
db := c.(*database.CustomContext).Db
|
||||||
|
defer db.Db.Close(context.Background())
|
||||||
|
|
||||||
|
cTotal := make(chan ResultChan[int64])
|
||||||
|
cResults := make(chan ResultChan[[]loadResponseResults])
|
||||||
|
|
||||||
|
go func(c chan ResultChan[int64]) {
|
||||||
|
db := database.GetNewDatabase()
|
||||||
|
defer db.Db.Close(context.Background())
|
||||||
|
|
||||||
|
total, err := db.Loads.TotalLoads(context.Background())
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
c <- ResultChan[int64]{Err: err}
|
||||||
|
} else {
|
||||||
|
c <- ResultChan[int64]{Result: total}
|
||||||
|
}
|
||||||
|
|
||||||
|
}(cTotal)
|
||||||
|
|
||||||
|
go func(ch chan ResultChan[[]loadResponseResults]) {
|
||||||
|
|
||||||
|
rows, err := db.Loads.GetLoads(context.Background(), loads.GetLoadsParams{
|
||||||
|
Limit: handlers.ParseInt32WithDefault(c.QueryParam("limit"), 50),
|
||||||
|
Offset: handlers.ParseInt32WithDefault(c.QueryParam("offset"), 0),
|
||||||
|
|
||||||
|
OrderBy: "cartridge_name asc",
|
||||||
|
|
||||||
|
SearchCartridge: c.QueryParam("search_cartridge") != "",
|
||||||
|
Cartridge: c.QueryParam("search_cartridge"),
|
||||||
|
SearchBullet: c.QueryParam("search_bullet") != "",
|
||||||
|
Bullet: c.QueryParam("search_bullet") + "%",
|
||||||
|
SearchBulletManufacturer: c.QueryParam("search_bullet_manufacturer") != "",
|
||||||
|
BulletManufacturer: c.QueryParam("search_bullet_manufacturer") + "%",
|
||||||
|
SearchPrimer: c.QueryParam("search_primer") != "",
|
||||||
|
Primer: c.QueryParam("search_primer") + "%",
|
||||||
|
SearchPrimerManufacturer: c.QueryParam("search_primer_manufacturer") != "",
|
||||||
|
PrimeManufacturer: c.QueryParam("search_primer_manufacturer") + "%",
|
||||||
|
SearchPowder: c.QueryParam("search_powder") != "",
|
||||||
|
Powder: c.QueryParam("search_powder") + "%",
|
||||||
|
SearchPowderManufacturer: c.QueryParam("search_powder_manufacturer") != "",
|
||||||
|
PowderManufacturer: c.QueryParam("search_powder_manufacturer") + "%",
|
||||||
|
|
||||||
|
SearchCartridgeID: c.QueryParam("search_cartridge_id") != "",
|
||||||
|
CartridgeID: *handlers.ParseUuidOrEmpty(c.QueryParam("search_cartridge_id")),
|
||||||
|
SearchBulletID: c.QueryParam("search_bullet_id") != "",
|
||||||
|
BulletID: *handlers.ParseUuidOrEmpty(c.QueryParam("search_bullet_id")),
|
||||||
|
SearchPrimerID: c.QueryParam("search_primer_id") != "",
|
||||||
|
PrimerID: *handlers.ParseUuidOrEmpty(c.QueryParam("search_primer_id")),
|
||||||
|
SearchPowderID: c.QueryParam("search_powder_id") != "",
|
||||||
|
PowderID: *handlers.ParseUuidOrEmpty(c.QueryParam("search_powder_id")),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
ch <- ResultChan[[]loadResponseResults]{Err: err}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
results := make([]loadResponseResults, 0)
|
||||||
|
for _, row := range rows {
|
||||||
|
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)
|
||||||
|
|
||||||
|
total := <-cTotal
|
||||||
|
if total.Err != nil {
|
||||||
|
return total.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
results := <-cResults
|
||||||
|
if results.Err != nil {
|
||||||
|
return results.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(http.StatusOK, handlers.Response[loadResponse]{Status: http.StatusText(http.StatusOK), Payload: loadResponse{Total: int(total.Result), Results: results.Result}})
|
||||||
|
}
|
||||||
167
backend/handlers/manufacturer/handler.go
Normal file
167
backend/handlers/manufacturer/handler.go
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
package manufacturer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"git.s.int/reloading-manager/backend/database"
|
||||||
|
"git.s.int/reloading-manager/backend/handlers"
|
||||||
|
"git.s.int/reloading-manager/backend/models/manufacturer"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
type manufacturerResponse struct {
|
||||||
|
Id string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Url string `json:"url"`
|
||||||
|
CreatedAt string `json:"created_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func Get(c echo.Context) error {
|
||||||
|
db := c.(*database.CustomContext).Db
|
||||||
|
defer db.Db.Close(context.Background())
|
||||||
|
|
||||||
|
if c.Param("id") != "" {
|
||||||
|
id := c.Param("id")
|
||||||
|
uid := handlers.ParseUuidOrBadRequest(c, id)
|
||||||
|
if uid == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
byId, err := db.Manufacturer.GetById(context.Background(), *uid)
|
||||||
|
if err != nil {
|
||||||
|
return echo.NewHTTPError(http.StatusNotFound, "Not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
response := handlers.Response[manufacturerResponse]{
|
||||||
|
Status: http.StatusText(http.StatusOK),
|
||||||
|
Payload: manufacturerResponse{
|
||||||
|
Id: byId.ID.String(),
|
||||||
|
Name: byId.Name,
|
||||||
|
Url: byId.Url.String,
|
||||||
|
CreatedAt: byId.CreatedAt.Time.Format(handlers.DateFormat),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(http.StatusOK, response)
|
||||||
|
}
|
||||||
|
|
||||||
|
all, err := db.Manufacturer.GetAll(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var payload []manufacturerResponse
|
||||||
|
for _, m := range all {
|
||||||
|
payload = append(payload, manufacturerResponse{
|
||||||
|
Id: m.ID.String(),
|
||||||
|
Name: m.Name,
|
||||||
|
Url: m.Url.String,
|
||||||
|
CreatedAt: m.CreatedAt.Time.Format(handlers.DateFormat),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
response := handlers.Response[[]manufacturerResponse]{
|
||||||
|
Status: http.StatusText(http.StatusOK),
|
||||||
|
Payload: payload,
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(http.StatusOK, response)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Delete(c echo.Context) error {
|
||||||
|
db := c.(*database.CustomContext).Db
|
||||||
|
defer db.Db.Close(context.Background())
|
||||||
|
|
||||||
|
id := c.Param("id")
|
||||||
|
uid := handlers.ParseUuidOrBadRequest(c, id)
|
||||||
|
if uid == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := db.Manufacturer.GetById(context.Background(), *uid)
|
||||||
|
if err != nil {
|
||||||
|
return echo.NewHTTPError(http.StatusNotFound, "Not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = db.Manufacturer.DeleteById(context.Background(), *uid)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return echo.NewHTTPError(http.StatusBadRequest, "Cannot delete manufacturer")
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.NoContent(http.StatusNoContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Post(c echo.Context) error {
|
||||||
|
db := c.(*database.CustomContext).Db
|
||||||
|
defer db.Db.Close(context.Background())
|
||||||
|
|
||||||
|
if c.Param("id") != "" {
|
||||||
|
id := c.Param("id")
|
||||||
|
uid := handlers.ParseUuidOrBadRequest(c, id)
|
||||||
|
if uid == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := db.Manufacturer.GetById(context.Background(), *uid)
|
||||||
|
if err != nil {
|
||||||
|
return echo.NewHTTPError(http.StatusNotFound, "Not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonMap := make(map[string]interface{})
|
||||||
|
_ = json.NewDecoder(c.Request().Body).Decode(&jsonMap)
|
||||||
|
|
||||||
|
if jsonMap["name"] == "" {
|
||||||
|
return handlers.BadRequest(c, "Name is required.")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = db.Manufacturer.UpdateManufacturer(context.Background(), manufacturer.UpdateManufacturerParams{
|
||||||
|
Name: jsonMap["name"].(string),
|
||||||
|
Url: pgtype.Text{
|
||||||
|
String: jsonMap["url"].(string),
|
||||||
|
Valid: true,
|
||||||
|
},
|
||||||
|
ID: *uid,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
newUuid := uuid.New().String()
|
||||||
|
uid, _ := handlers.ParseUuid(newUuid)
|
||||||
|
|
||||||
|
jsonMap := make(map[string]interface{})
|
||||||
|
_ = json.NewDecoder(c.Request().Body).Decode(&jsonMap)
|
||||||
|
|
||||||
|
if jsonMap["name"] == nil || jsonMap["name"] == "" {
|
||||||
|
return handlers.BadRequest(c, "Name is required.")
|
||||||
|
}
|
||||||
|
|
||||||
|
var url string
|
||||||
|
if jsonMap["url"] != nil {
|
||||||
|
url = jsonMap["url"].(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := db.Manufacturer.CreateManufacturer(context.Background(), manufacturer.CreateManufacturerParams{
|
||||||
|
ID: *uid,
|
||||||
|
Name: jsonMap["name"].(string),
|
||||||
|
Url: pgtype.Text{
|
||||||
|
String: url,
|
||||||
|
Valid: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(http.StatusAccepted, handlers.Response[string]{
|
||||||
|
Status: http.StatusText(http.StatusAccepted),
|
||||||
|
Payload: newUuid,
|
||||||
|
})
|
||||||
|
}
|
||||||
21
backend/handlers/numbers.go
Normal file
21
backend/handlers/numbers.go
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
import "strconv"
|
||||||
|
|
||||||
|
func ParseFloat32(v string) (float32, error) {
|
||||||
|
fl, err := strconv.ParseFloat(v, 32)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return float32(fl), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseInt32WithDefault(s string, def int32) int32 {
|
||||||
|
sInt, err := strconv.ParseInt(s, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
return def
|
||||||
|
}
|
||||||
|
|
||||||
|
return int32(sInt)
|
||||||
|
}
|
||||||
217
backend/handlers/powder/handler.go
Normal file
217
backend/handlers/powder/handler.go
Normal file
@@ -0,0 +1,217 @@
|
|||||||
|
package powder
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"git.s.int/reloading-manager/backend/database"
|
||||||
|
"git.s.int/reloading-manager/backend/handlers"
|
||||||
|
"git.s.int/reloading-manager/backend/models/powder"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Photo(c echo.Context) error {
|
||||||
|
db := c.(*database.CustomContext).Db
|
||||||
|
defer db.Db.Close(context.Background())
|
||||||
|
|
||||||
|
id := c.Param("id")
|
||||||
|
uid, err := handlers.ParseUuid(id)
|
||||||
|
if err != nil {
|
||||||
|
return handlers.BadRequest(c, "Invalid UUID")
|
||||||
|
}
|
||||||
|
|
||||||
|
byId, err := db.Powder.GetPowderById(context.Background(), *uid)
|
||||||
|
if err != nil {
|
||||||
|
return echo.NewHTTPError(http.StatusNotFound, "Not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
mime := http.DetectContentType(byId.Photo)
|
||||||
|
|
||||||
|
c.Response().Header().Set("Content-Type", mime)
|
||||||
|
c.Response().Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
|
||||||
|
c.Response().Header().Set("Pragma", "no-cache")
|
||||||
|
c.Response().Header().Set("Expires", "0")
|
||||||
|
|
||||||
|
_, err = c.Response().Write(byId.Photo)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func Delete(c echo.Context) error {
|
||||||
|
id := c.Param("id")
|
||||||
|
db := c.(*database.CustomContext).Db
|
||||||
|
defer db.Db.Close(context.Background())
|
||||||
|
|
||||||
|
uid, err := handlers.ParseUuid(id)
|
||||||
|
if err != nil {
|
||||||
|
return handlers.BadRequest(c, "Invalid UUID")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = db.Powder.DeletePowder(context.Background(), *uid)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(http.StatusOK, handlers.Response[string]{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Post(c echo.Context) error {
|
||||||
|
db := c.(*database.CustomContext).Db
|
||||||
|
defer db.Db.Close(context.Background())
|
||||||
|
|
||||||
|
if c.Param("id") != "" {
|
||||||
|
id := c.Param("id")
|
||||||
|
|
||||||
|
uid, err := handlers.ParseUuid(id)
|
||||||
|
if err != nil {
|
||||||
|
return handlers.BadRequest(c, "Invalid UUID")
|
||||||
|
}
|
||||||
|
|
||||||
|
p, err := db.Powder.GetPowderById(context.Background(), *uid)
|
||||||
|
if err != nil {
|
||||||
|
return handlers.NotFound(c, "Not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
file, _ := c.FormFile("photo")
|
||||||
|
if file != nil {
|
||||||
|
handler, _ := file.Open()
|
||||||
|
buf := make([]byte, file.Size)
|
||||||
|
_, _ = handler.Read(buf)
|
||||||
|
p.Photo = buf
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.FormValue("name") != "" {
|
||||||
|
p.Name = c.FormValue("name")
|
||||||
|
}
|
||||||
|
|
||||||
|
muid, err := handlers.ParseUuid(c.FormValue("manufacturer_id"))
|
||||||
|
if err != nil {
|
||||||
|
return handlers.BadRequest(c, "Invalid UUID")
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.FormValue("manufacturer_id") != "" {
|
||||||
|
p.ManufacturerID = *muid
|
||||||
|
}
|
||||||
|
|
||||||
|
metaString := c.FormValue("meta")
|
||||||
|
if metaString == "" {
|
||||||
|
metaString = string(p.Meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = db.Powder.UpdatePowder(context.Background(), powder.UpdatePowderParams{
|
||||||
|
ID: p.ID,
|
||||||
|
Name: p.Name,
|
||||||
|
ManufacturerID: p.ManufacturerID,
|
||||||
|
Photo: p.Photo,
|
||||||
|
Meta: []byte(metaString),
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(http.StatusOK, handlers.Response[string]{})
|
||||||
|
}
|
||||||
|
|
||||||
|
buf, err := handlers.ReadFile(c, "photo")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
newUuid := uuid.New().String()
|
||||||
|
pgUuid, _ := handlers.ParseUuid(newUuid)
|
||||||
|
mfUid, _ := handlers.ParseUuid(c.FormValue("manufacturer_id"))
|
||||||
|
|
||||||
|
jsonMap := make(map[string]interface{})
|
||||||
|
jsonMap["name"] = c.FormValue("name")
|
||||||
|
if jsonMap["name"] == nil || jsonMap["name"] == "" {
|
||||||
|
return handlers.BadRequest(c, "Name is required.")
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonMap["manufacturer_id"] = c.FormValue("manufacturer_id")
|
||||||
|
if jsonMap["manufacturer_id"] == nil || jsonMap["manufacturer_id"] == "" {
|
||||||
|
return handlers.BadRequest(c, "manufacturer_id is required.")
|
||||||
|
}
|
||||||
|
|
||||||
|
metaString := c.FormValue("meta")
|
||||||
|
if metaString == "" {
|
||||||
|
metaString = "{}"
|
||||||
|
}
|
||||||
|
var meta json.RawMessage = []byte(metaString)
|
||||||
|
err = db.Powder.InsertPowder(context.Background(), powder.InsertPowderParams{
|
||||||
|
ID: *pgUuid,
|
||||||
|
Name: jsonMap["name"].(string),
|
||||||
|
ManufacturerID: *mfUid,
|
||||||
|
Meta: meta,
|
||||||
|
Photo: buf,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(http.StatusCreated, handlers.Response[struct {
|
||||||
|
Id string `json:"id"`
|
||||||
|
}]{
|
||||||
|
Status: http.StatusText(http.StatusCreated),
|
||||||
|
Payload: struct {
|
||||||
|
Id string `json:"id"`
|
||||||
|
}{Id: newUuid},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Get(c echo.Context) error {
|
||||||
|
db := c.(*database.CustomContext).Db
|
||||||
|
defer db.Db.Close(context.Background())
|
||||||
|
|
||||||
|
if c.Param("id") != "" {
|
||||||
|
id := c.Param("id")
|
||||||
|
uid, err := handlers.ParseUuid(id)
|
||||||
|
if err != nil {
|
||||||
|
return handlers.BadRequest(c, "Invalid UUID")
|
||||||
|
}
|
||||||
|
|
||||||
|
p, err := db.Powder.GetPowderById(context.Background(), *uid)
|
||||||
|
if err != nil {
|
||||||
|
return handlers.BadRequest(c, "Not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
response := handlers.Response[handlers.Powder]{
|
||||||
|
Payload: handlers.Powder{
|
||||||
|
Id: uid.String(),
|
||||||
|
Name: p.Name,
|
||||||
|
Manufacturer: handlers.Manufacturer{Id: p.ManufacturerID.String(), Name: p.ManufacturerName, Url: p.ManufacturerUrl.String},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
response.Status = http.StatusText(http.StatusOK)
|
||||||
|
|
||||||
|
return c.JSON(http.StatusOK, response)
|
||||||
|
}
|
||||||
|
|
||||||
|
powders, err := db.Powder.GetPowders(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
response := handlers.Response[[]handlers.Powder]{
|
||||||
|
Payload: make([]handlers.Powder, 0),
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, p := range powders {
|
||||||
|
response.Payload = append(response.Payload, handlers.Powder{
|
||||||
|
Id: p.ID.String(),
|
||||||
|
Name: p.Name,
|
||||||
|
Manufacturer: handlers.Manufacturer{
|
||||||
|
Id: p.ManufacturerID.String(),
|
||||||
|
Name: p.ManufacturerName,
|
||||||
|
Url: p.ManufacturerUrl.String,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
response.Status = http.StatusText(http.StatusOK)
|
||||||
|
|
||||||
|
return c.JSON(200, response)
|
||||||
|
}
|
||||||
226
backend/handlers/primers/handler.go
Normal file
226
backend/handlers/primers/handler.go
Normal file
@@ -0,0 +1,226 @@
|
|||||||
|
package primers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"git.s.int/reloading-manager/backend/database"
|
||||||
|
"git.s.int/reloading-manager/backend/handlers"
|
||||||
|
"git.s.int/reloading-manager/backend/models/primers"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PrimerResponse struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Manufacturer handlers.Manufacturer `json:"manufacturer"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func Delete(c echo.Context) error {
|
||||||
|
db := c.(*database.CustomContext).Db
|
||||||
|
defer db.Db.Close(context.Background())
|
||||||
|
|
||||||
|
id := c.Param("id")
|
||||||
|
uid, err := handlers.ParseUuid(id)
|
||||||
|
if err != nil {
|
||||||
|
return handlers.BadRequest(c, "Invalid UUID.")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = db.Primer.DeletePrimer(context.Background(), *uid)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(http.StatusNoContent, handlers.Response[string]{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Post(c echo.Context) error {
|
||||||
|
db := c.(*database.CustomContext).Db
|
||||||
|
defer db.Db.Close(context.Background())
|
||||||
|
|
||||||
|
if c.Param("id") != "" {
|
||||||
|
id := c.Param("id")
|
||||||
|
uid := handlers.ParseUuidOrBadRequest(c, id)
|
||||||
|
if uid == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
p, err := db.Primer.GetPrimerById(context.Background(), *uid)
|
||||||
|
if err != nil {
|
||||||
|
return handlers.NotFound(c, "Primer not found.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.FormValue("name") != "" {
|
||||||
|
p.Name = c.FormValue("name")
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.FormValue("manufacturer_id") != "" {
|
||||||
|
mid := handlers.ParseUuidOrBadRequest(c, c.FormValue("manufacturer_id"))
|
||||||
|
if mid == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
p.ManufacturerID = *mid
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.FormValue("meta") != "" {
|
||||||
|
p.Meta = json.RawMessage(c.Param("meta"))
|
||||||
|
}
|
||||||
|
|
||||||
|
file, _ := c.FormFile("photo")
|
||||||
|
if file != nil {
|
||||||
|
photo, err := handlers.ReadFile(c, "photo")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.Photo = photo
|
||||||
|
}
|
||||||
|
|
||||||
|
err = db.Primer.UpdatePrimer(context.Background(), primers.UpdatePrimerParams{
|
||||||
|
ID: p.ID,
|
||||||
|
Name: p.Name,
|
||||||
|
ManufacturerID: p.ManufacturerID,
|
||||||
|
Meta: p.Meta,
|
||||||
|
Photo: p.Photo,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(http.StatusOK, handlers.Response[string]{Payload: p.ID.String(), Status: http.StatusText(http.StatusOK)})
|
||||||
|
}
|
||||||
|
|
||||||
|
buf, err := handlers.ReadFile(c, "photo")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
name := c.FormValue("name")
|
||||||
|
manufacturerId := c.FormValue("manufacturer_id")
|
||||||
|
metaString := c.FormValue("meta")
|
||||||
|
|
||||||
|
if name == "" {
|
||||||
|
return handlers.BadRequest(c, "Name is required.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if manufacturerId == "" {
|
||||||
|
return handlers.BadRequest(c, "Manufacturer ID is required.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if metaString == "" {
|
||||||
|
metaString = "{}"
|
||||||
|
}
|
||||||
|
var meta json.RawMessage = []byte(metaString)
|
||||||
|
|
||||||
|
newUuid := uuid.New().String()
|
||||||
|
uid, _ := handlers.ParseUuid(newUuid)
|
||||||
|
mid := handlers.ParseUuidOrBadRequest(c, manufacturerId)
|
||||||
|
if mid == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err = db.Primer.InsertPrimer(context.Background(), primers.InsertPrimerParams{
|
||||||
|
ID: *uid,
|
||||||
|
Name: name,
|
||||||
|
ManufacturerID: *mid,
|
||||||
|
Meta: meta,
|
||||||
|
Photo: buf,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(http.StatusCreated, handlers.Response[struct {
|
||||||
|
Id string `json:"id"`
|
||||||
|
}]{
|
||||||
|
Status: http.StatusText(http.StatusCreated),
|
||||||
|
Payload: struct {
|
||||||
|
Id string `json:"id"`
|
||||||
|
}{Id: newUuid},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Photo(c echo.Context) error {
|
||||||
|
db := c.(*database.CustomContext).Db
|
||||||
|
defer db.Db.Close(context.Background())
|
||||||
|
|
||||||
|
id := c.Param("id")
|
||||||
|
uid := handlers.ParseUuidOrBadRequest(c, id)
|
||||||
|
if uid == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
byId, err := db.Primer.GetPrimerById(context.Background(), *uid)
|
||||||
|
if err != nil {
|
||||||
|
return echo.NewHTTPError(http.StatusNotFound, "Not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
mime := http.DetectContentType(byId.Photo)
|
||||||
|
|
||||||
|
c.Response().Header().Set("Content-Type", mime)
|
||||||
|
c.Response().Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
|
||||||
|
c.Response().Header().Set("Pragma", "no-cache")
|
||||||
|
c.Response().Header().Set("Expires", "0")
|
||||||
|
|
||||||
|
_, err = c.Response().Write(byId.Photo)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func Get(c echo.Context) error {
|
||||||
|
db := c.(*database.CustomContext).Db
|
||||||
|
defer db.Db.Close(context.Background())
|
||||||
|
|
||||||
|
if c.Param("id") != "" {
|
||||||
|
id := c.Param("id")
|
||||||
|
uid := handlers.ParseUuidOrBadRequest(c, id)
|
||||||
|
if uid == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
row, err := db.Primer.GetPrimerById(context.Background(), *uid)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(http.StatusOK, handlers.Response[PrimerResponse]{
|
||||||
|
Status: http.StatusText(http.StatusOK),
|
||||||
|
Payload: PrimerResponse{
|
||||||
|
ID: row.ID.String(),
|
||||||
|
Name: row.Name,
|
||||||
|
Manufacturer: handlers.Manufacturer{
|
||||||
|
Id: row.ManufacturerID.String(),
|
||||||
|
Name: row.ManufacturerName,
|
||||||
|
Url: row.ManufacturerUrl.String,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
rows, err := db.Primer.GetPrimers(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
response := handlers.Response[[]PrimerResponse]{
|
||||||
|
Status: "OK",
|
||||||
|
Payload: make([]PrimerResponse, 0),
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, row := range rows {
|
||||||
|
response.Payload = append(response.Payload, PrimerResponse{
|
||||||
|
ID: row.ID.String(),
|
||||||
|
Name: row.Name,
|
||||||
|
Manufacturer: handlers.Manufacturer{
|
||||||
|
Id: row.ManufacturerID.String(),
|
||||||
|
Name: row.ManufacturerName,
|
||||||
|
Url: row.ManufacturerUrl.String,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(http.StatusOK, response)
|
||||||
|
}
|
||||||
42
backend/handlers/response.go
Normal file
42
backend/handlers/response.go
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
const DateFormat = "2006-01-02 15:04:05"
|
||||||
|
|
||||||
|
type Response[T any] struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
Message string `json:"message,omitempty"`
|
||||||
|
Payload T `json:"payload"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func BadRequest(c echo.Context, message string) error {
|
||||||
|
return c.JSON(http.StatusBadRequest, Response[string]{
|
||||||
|
Status: http.StatusText(http.StatusBadRequest),
|
||||||
|
Message: message,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func NotFound(c echo.Context, message string) error {
|
||||||
|
return c.JSON(http.StatusNotFound, Response[string]{
|
||||||
|
Status: http.StatusText(http.StatusNotFound),
|
||||||
|
Message: message,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type Manufacturer struct {
|
||||||
|
Id string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Url string `json:"url"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Powder struct {
|
||||||
|
Id string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Meta string `json:"meta"`
|
||||||
|
Photo string `json:"photo,omitempty"`
|
||||||
|
Manufacturer Manufacturer `json:"manufacturer"`
|
||||||
|
}
|
||||||
38
backend/handlers/responses_test.go
Normal file
38
backend/handlers/responses_test.go
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TestContext struct {
|
||||||
|
echo.Context
|
||||||
|
t *testing.T
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t TestContext) JSON(code int, i interface{}) error {
|
||||||
|
|
||||||
|
if code != 400 {
|
||||||
|
t.t.Fatal("expected 400")
|
||||||
|
}
|
||||||
|
|
||||||
|
switch i.(type) {
|
||||||
|
case Response[string]:
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
t.t.Fatal("expected response")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBadRequest(t *testing.T) {
|
||||||
|
context := TestContext{
|
||||||
|
t: t,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := BadRequest(context, "Something is wrong")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Should not have returned an error")
|
||||||
|
}
|
||||||
|
}
|
||||||
45
backend/handlers/uuid.go
Normal file
45
backend/handlers/uuid.go
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ParseUuidOrEmpty(s string) *pgtype.UUID {
|
||||||
|
ParsedUuid, err := uuid.Parse(s)
|
||||||
|
if err != nil {
|
||||||
|
return &pgtype.UUID{
|
||||||
|
Valid: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &pgtype.UUID{
|
||||||
|
Bytes: ParsedUuid,
|
||||||
|
Valid: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseUuid(s string) (*pgtype.UUID, error) {
|
||||||
|
uid, err := uuid.Parse(s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &pgtype.UUID{
|
||||||
|
Bytes: uid,
|
||||||
|
Valid: true,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseUuidOrBadRequest(c echo.Context, s string) *pgtype.UUID {
|
||||||
|
uid, err := ParseUuid(s)
|
||||||
|
if err != nil {
|
||||||
|
_ = BadRequest(c, "Invalid UUID.")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return uid
|
||||||
|
|
||||||
|
}
|
||||||
114
backend/main.go
Normal file
114
backend/main.go
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"git.s.int/packages/go/utilities/Env"
|
||||||
|
"git.s.int/reloading-manager/backend/database"
|
||||||
|
"git.s.int/reloading-manager/backend/handlers/bullets"
|
||||||
|
"git.s.int/reloading-manager/backend/handlers/cartridge"
|
||||||
|
"git.s.int/reloading-manager/backend/handlers/loads"
|
||||||
|
"git.s.int/reloading-manager/backend/handlers/manufacturer"
|
||||||
|
"git.s.int/reloading-manager/backend/handlers/powder"
|
||||||
|
"git.s.int/reloading-manager/backend/handlers/primers"
|
||||||
|
"github.com/go-playground/validator"
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
"github.com/labstack/echo/v4/middleware"
|
||||||
|
"github.com/labstack/gommon/log"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
Port Env.EnvironmentVariable = "PORT"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
CustomValidator struct {
|
||||||
|
validator *validator.Validate
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func (cv *CustomValidator) Validate(i interface{}) error {
|
||||||
|
if err := cv.validator.Struct(i); err != nil {
|
||||||
|
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
e := echo.New()
|
||||||
|
|
||||||
|
e.Validator = &CustomValidator{validator: validator.New()}
|
||||||
|
|
||||||
|
e.HideBanner = true
|
||||||
|
e.HidePort = true
|
||||||
|
|
||||||
|
e.Logger.SetLevel(log.DEBUG)
|
||||||
|
|
||||||
|
e.Use(database.CreateDatabaseMiddleware())
|
||||||
|
e.Use(middleware.Recover())
|
||||||
|
e.Use(middleware.Logger())
|
||||||
|
e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
|
||||||
|
AllowOrigins: []string{"http://localhost:5173", "http://127.0.0.1:5173"},
|
||||||
|
AllowMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE"},
|
||||||
|
}))
|
||||||
|
|
||||||
|
migrate(e)
|
||||||
|
|
||||||
|
// Bullets
|
||||||
|
e.GET("/bullet", bullets.Get)
|
||||||
|
e.GET("/bullet/:id", bullets.Get)
|
||||||
|
e.GET("/bullet/:id/photo", bullets.Photo)
|
||||||
|
e.POST("/bullet", bullets.Post)
|
||||||
|
e.POST("/bullet/:id", bullets.Put)
|
||||||
|
e.DELETE("/bullet/:id", bullets.Delete)
|
||||||
|
|
||||||
|
// Manufacturers
|
||||||
|
e.GET("/manufacturer", manufacturer.Get)
|
||||||
|
e.GET("/manufacturer/:id", manufacturer.Get)
|
||||||
|
e.POST("/manufacturer", manufacturer.Post)
|
||||||
|
e.POST("/manufacturer/:id", manufacturer.Post)
|
||||||
|
e.DELETE("/manufacturer/:id", manufacturer.Delete)
|
||||||
|
|
||||||
|
// Powders
|
||||||
|
e.GET("/powder", powder.Get)
|
||||||
|
e.GET("/powder/:id", powder.Get)
|
||||||
|
e.GET("/powder/:id/photo", powder.Photo)
|
||||||
|
e.POST("/powder", powder.Post)
|
||||||
|
e.POST("/powder/:id", powder.Post)
|
||||||
|
e.DELETE("/powder/:id", powder.Delete)
|
||||||
|
|
||||||
|
// Primers
|
||||||
|
e.GET("/primer", primers.Get)
|
||||||
|
e.GET("/primer/:id", primers.Get)
|
||||||
|
e.GET("/primer/:id/photo", primers.Photo)
|
||||||
|
e.POST("/primer", primers.Post)
|
||||||
|
e.POST("/primer/:id", primers.Post)
|
||||||
|
e.DELETE("/primer/:id", primers.Delete)
|
||||||
|
|
||||||
|
// cartridge
|
||||||
|
e.GET("/cartridge", cartridge.Get)
|
||||||
|
e.POST("/cartridge", cartridge.Post)
|
||||||
|
e.DELETE("/cartridge/:id", cartridge.Delete)
|
||||||
|
|
||||||
|
// loads
|
||||||
|
e.GET("/load", loads.Get)
|
||||||
|
e.POST("/load", loads.Post)
|
||||||
|
|
||||||
|
addr := fmt.Sprintf("0.0.0.0:%s", Port.GetEnvString("8080"))
|
||||||
|
|
||||||
|
e.Logger.Info("Starting Server...")
|
||||||
|
e.Logger.Infof("Listening on %v", addr)
|
||||||
|
e.Logger.Fatal(e.Start(addr))
|
||||||
|
}
|
||||||
|
|
||||||
|
func migrate(e *echo.Echo) {
|
||||||
|
e.Logger.Info("Starting migrations...")
|
||||||
|
|
||||||
|
db := database.GetNewDatabase()
|
||||||
|
|
||||||
|
defer db.Db.Close(context.Background())
|
||||||
|
|
||||||
|
db.Migrate()
|
||||||
|
e.Logger.Info("Done!")
|
||||||
|
}
|
||||||
2
backend/migrations/000001_create_initial_tables.down.sql
Normal file
2
backend/migrations/000001_create_initial_tables.down.sql
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
drop table if exists bullets;
|
||||||
|
drop table if exists manufacturers;
|
||||||
42
backend/migrations/000001_create_initial_tables.up.sql
Normal file
42
backend/migrations/000001_create_initial_tables.up.sql
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
create table manufacturers
|
||||||
|
(
|
||||||
|
id uuid primary key default gen_random_uuid(),
|
||||||
|
name varchar(255) not null,
|
||||||
|
url varchar(255),
|
||||||
|
created_at timestamp default NOW() not null
|
||||||
|
);
|
||||||
|
|
||||||
|
insert into manufacturers (name, url)
|
||||||
|
values ('Hornady', 'https://www.hornady.com/'),
|
||||||
|
('CCI', 'https://www.cci-ammunition.com/'),
|
||||||
|
('IMR Powders', 'https://shop.hodgdon.com/imr/'),
|
||||||
|
('Accurate Powders', 'https://hodgdonpowderco.com/accurate/'),
|
||||||
|
('Federal', 'https://www.federalpremium.com/'),
|
||||||
|
('Remington', 'https://www.remington.com/'),
|
||||||
|
('Winchester', 'https://www.winchester.com/'),
|
||||||
|
('Speer', 'https://www.speer.com/'),
|
||||||
|
('Nosler', 'https://www.nosler.com/'),
|
||||||
|
('Sierra', 'https://www.sierrabullets.com/'),
|
||||||
|
('Barnes', 'https://www.barnesbullets.com/'),
|
||||||
|
('Berger', 'https://www.bergerbullets.com/'),
|
||||||
|
('Lapua', 'https://www.lapua.com/'),
|
||||||
|
('Norma', 'https://www.norma-ammunition.com/'),
|
||||||
|
('Berrys Bullets', 'https://www.berrysmfg.com/'),
|
||||||
|
('Starline Brass', 'https://www.starlinebrass.com/'),
|
||||||
|
('Swift', 'https://www.swiftbullets.com/');
|
||||||
|
|
||||||
|
create table bullets
|
||||||
|
(
|
||||||
|
id uuid primary key default gen_random_uuid(),
|
||||||
|
name varchar(255) not null,
|
||||||
|
weight int not null,
|
||||||
|
diameter int not null,
|
||||||
|
meta json,
|
||||||
|
photo bytea,
|
||||||
|
manufacturer_id uuid not null,
|
||||||
|
created_at timestamp default NOW() not null
|
||||||
|
);
|
||||||
|
|
||||||
|
alter table bullets
|
||||||
|
add constraint bullets_manufacturers_id_fk
|
||||||
|
foreign key (manufacturer_id) references manufacturers (id);
|
||||||
1
backend/migrations/000002_create_powder_tables.down.sql
Normal file
1
backend/migrations/000002_create_powder_tables.down.sql
Normal file
@@ -0,0 +1 @@
|
|||||||
|
drop table if exists powders;
|
||||||
11
backend/migrations/000002_create_powder_tables.up.sql
Normal file
11
backend/migrations/000002_create_powder_tables.up.sql
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
create table powders
|
||||||
|
(
|
||||||
|
id uuid primary key default gen_random_uuid(),
|
||||||
|
name varchar(255) not null,
|
||||||
|
meta json,
|
||||||
|
photo bytea,
|
||||||
|
manufacturer_id uuid not null,
|
||||||
|
created_at timestamp default NOW() not null,
|
||||||
|
constraint powders_manufacturers_id_fk
|
||||||
|
foreign key (manufacturer_id) references manufacturers (id)
|
||||||
|
);
|
||||||
11
backend/migrations/000003_create_primers_table.up.sql
Normal file
11
backend/migrations/000003_create_primers_table.up.sql
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
create table primers
|
||||||
|
(
|
||||||
|
id uuid primary key default gen_random_uuid(),
|
||||||
|
name varchar(255) not null,
|
||||||
|
meta json,
|
||||||
|
photo bytea,
|
||||||
|
manufacturer_id uuid not null,
|
||||||
|
created_at timestamp default NOW() not null,
|
||||||
|
constraint primers_manufacturers_id_fk
|
||||||
|
foreign key (manufacturer_id) references manufacturers (id)
|
||||||
|
);
|
||||||
2
backend/migrations/000004_create_loads_tables.down.sql
Normal file
2
backend/migrations/000004_create_loads_tables.down.sql
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
drop table if exists loads;
|
||||||
|
drop table if exists cartridges;
|
||||||
32
backend/migrations/000004_create_loads_tables.up.sql
Normal file
32
backend/migrations/000004_create_loads_tables.up.sql
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
create table cartridges
|
||||||
|
(
|
||||||
|
id uuid primary key default gen_random_uuid(),
|
||||||
|
name varchar(255) not null,
|
||||||
|
created_at timestamp default current_timestamp not null,
|
||||||
|
meta json not null,
|
||||||
|
constraint cartridges_name_uindex
|
||||||
|
unique (name)
|
||||||
|
);
|
||||||
|
|
||||||
|
create table loads
|
||||||
|
(
|
||||||
|
id uuid primary key default gen_random_uuid(),
|
||||||
|
cartridge_id uuid not null,
|
||||||
|
col float4 not null,
|
||||||
|
powder_id uuid not null,
|
||||||
|
powder_gr float4 not null,
|
||||||
|
primer_id uuid not null,
|
||||||
|
bullet_id uuid not null,
|
||||||
|
photo bytea not null,
|
||||||
|
created_at timestamp default current_timestamp not null,
|
||||||
|
meta json not null,
|
||||||
|
constraint loads_bullets_id_fk
|
||||||
|
foreign key (bullet_id) references bullets (id),
|
||||||
|
constraint loads_powders_id_fk
|
||||||
|
foreign key (powder_id) references powders (id),
|
||||||
|
constraint loads_primers_id_fk
|
||||||
|
foreign key (primer_id) references primers (id),
|
||||||
|
constraint loads_cartridges_id_fk
|
||||||
|
foreign key (cartridge_id) references cartridges (id)
|
||||||
|
);
|
||||||
|
|
||||||
20
backend/models/bullets-schema.sql
Normal file
20
backend/models/bullets-schema.sql
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
create table bullets
|
||||||
|
(
|
||||||
|
id uuid primary key,
|
||||||
|
name varchar(255) not null,
|
||||||
|
weight int not null,
|
||||||
|
diameter int not null,
|
||||||
|
meta json,
|
||||||
|
photo bytea,
|
||||||
|
manufacturer_id uuid not null,
|
||||||
|
created_at timestamp default NOW() not null
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
create table manufacturers
|
||||||
|
(
|
||||||
|
id uuid primary key ,
|
||||||
|
name varchar(255) not null,
|
||||||
|
url varchar(255),
|
||||||
|
created_at timestamp default NOW() not null
|
||||||
|
);
|
||||||
63
backend/models/bullets.sql
Normal file
63
backend/models/bullets.sql
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
-- name: GetBullets :many
|
||||||
|
select bullets.id as id,
|
||||||
|
bullets.name as name,
|
||||||
|
weight,
|
||||||
|
diameter,
|
||||||
|
bullets.created_at,
|
||||||
|
meta,
|
||||||
|
m.id as manufacturer_id,
|
||||||
|
m.name as manufacturer_name,
|
||||||
|
m.url as manufacturer_url
|
||||||
|
from bullets
|
||||||
|
join manufacturers m on m.id = bullets.manufacturer_id
|
||||||
|
order by manufacturer_name, bullets.name desc;
|
||||||
|
|
||||||
|
-- name: GetBulletById :one
|
||||||
|
select bullets.id as id,
|
||||||
|
bullets.name,
|
||||||
|
bullets.diameter,
|
||||||
|
bullets.weight,
|
||||||
|
bullets.meta,
|
||||||
|
bullets.photo,
|
||||||
|
bullets.created_at,
|
||||||
|
m.id as manufactuerer_id,
|
||||||
|
m.name as manufacutuer_name,
|
||||||
|
m.url as manufacturer_url
|
||||||
|
from bullets
|
||||||
|
join manufacturers m on m.id = bullets.manufacturer_id
|
||||||
|
where bullets.id = $1;
|
||||||
|
|
||||||
|
-- name: ForPage :many
|
||||||
|
select bullets.id as id,
|
||||||
|
bullets.name as name,
|
||||||
|
weight,
|
||||||
|
diameter,
|
||||||
|
bullets.created_at,
|
||||||
|
meta,
|
||||||
|
m.id as manufacturer_id,
|
||||||
|
m.name as manufacturer_name,
|
||||||
|
m.url as manufacturer_url
|
||||||
|
from bullets
|
||||||
|
join manufacturers m on m.id = bullets.manufacturer_id
|
||||||
|
order by manufacturer_name, bullets.name desc
|
||||||
|
limit $1 offset $2;
|
||||||
|
|
||||||
|
-- name: DeleteBullet :exec
|
||||||
|
DELETE
|
||||||
|
from bullets
|
||||||
|
where id = $1;
|
||||||
|
|
||||||
|
-- name: AddBullet :one
|
||||||
|
insert into bullets (name, weight, diameter, photo, meta, manufacturer_id)
|
||||||
|
values ($1, $2, $3, $4, $5, $6)
|
||||||
|
returning id;
|
||||||
|
|
||||||
|
-- name: UpdateBullet :exec
|
||||||
|
UPDATE bullets
|
||||||
|
set name=$1,
|
||||||
|
weight=$2,
|
||||||
|
diameter=$3,
|
||||||
|
photo=$4,
|
||||||
|
meta=$5,
|
||||||
|
manufacturer_id=$6
|
||||||
|
where id = $7;
|
||||||
253
backend/models/bullets/bullets.sql.go
Normal file
253
backend/models/bullets/bullets.sql.go
Normal file
@@ -0,0 +1,253 @@
|
|||||||
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// sqlc v1.27.0
|
||||||
|
// source: bullets.sql
|
||||||
|
|
||||||
|
package bullets
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
|
)
|
||||||
|
|
||||||
|
const addBullet = `-- name: AddBullet :one
|
||||||
|
insert into bullets (name, weight, diameter, photo, meta, manufacturer_id)
|
||||||
|
values ($1, $2, $3, $4, $5, $6)
|
||||||
|
returning id
|
||||||
|
`
|
||||||
|
|
||||||
|
type AddBulletParams struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Weight int32 `json:"weight"`
|
||||||
|
Diameter int32 `json:"diameter"`
|
||||||
|
Photo []byte `json:"photo"`
|
||||||
|
Meta []byte `json:"meta"`
|
||||||
|
ManufacturerID pgtype.UUID `json:"manufacturer_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) AddBullet(ctx context.Context, arg AddBulletParams) (pgtype.UUID, error) {
|
||||||
|
row := q.db.QueryRow(ctx, addBullet,
|
||||||
|
arg.Name,
|
||||||
|
arg.Weight,
|
||||||
|
arg.Diameter,
|
||||||
|
arg.Photo,
|
||||||
|
arg.Meta,
|
||||||
|
arg.ManufacturerID,
|
||||||
|
)
|
||||||
|
var id pgtype.UUID
|
||||||
|
err := row.Scan(&id)
|
||||||
|
return id, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteBullet = `-- name: DeleteBullet :exec
|
||||||
|
DELETE
|
||||||
|
from bullets
|
||||||
|
where id = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) DeleteBullet(ctx context.Context, id pgtype.UUID) error {
|
||||||
|
_, err := q.db.Exec(ctx, deleteBullet, id)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const forPage = `-- name: ForPage :many
|
||||||
|
select bullets.id as id,
|
||||||
|
bullets.name as name,
|
||||||
|
weight,
|
||||||
|
diameter,
|
||||||
|
bullets.created_at,
|
||||||
|
meta,
|
||||||
|
m.id as manufacturer_id,
|
||||||
|
m.name as manufacturer_name,
|
||||||
|
m.url as manufacturer_url
|
||||||
|
from bullets
|
||||||
|
join manufacturers m on m.id = bullets.manufacturer_id
|
||||||
|
order by manufacturer_name, bullets.name desc
|
||||||
|
limit $1 offset $2
|
||||||
|
`
|
||||||
|
|
||||||
|
type ForPageParams struct {
|
||||||
|
Limit int32 `json:"limit"`
|
||||||
|
Offset int32 `json:"offset"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ForPageRow struct {
|
||||||
|
ID pgtype.UUID `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Weight int32 `json:"weight"`
|
||||||
|
Diameter int32 `json:"diameter"`
|
||||||
|
CreatedAt pgtype.Timestamp `json:"created_at"`
|
||||||
|
Meta []byte `json:"meta"`
|
||||||
|
ManufacturerID pgtype.UUID `json:"manufacturer_id"`
|
||||||
|
ManufacturerName string `json:"manufacturer_name"`
|
||||||
|
ManufacturerUrl pgtype.Text `json:"manufacturer_url"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) ForPage(ctx context.Context, arg ForPageParams) ([]ForPageRow, error) {
|
||||||
|
rows, err := q.db.Query(ctx, forPage, arg.Limit, arg.Offset)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var items []ForPageRow
|
||||||
|
for rows.Next() {
|
||||||
|
var i ForPageRow
|
||||||
|
if err := rows.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.Name,
|
||||||
|
&i.Weight,
|
||||||
|
&i.Diameter,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.Meta,
|
||||||
|
&i.ManufacturerID,
|
||||||
|
&i.ManufacturerName,
|
||||||
|
&i.ManufacturerUrl,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const getBulletById = `-- name: GetBulletById :one
|
||||||
|
select bullets.id as id,
|
||||||
|
bullets.name,
|
||||||
|
bullets.diameter,
|
||||||
|
bullets.weight,
|
||||||
|
bullets.meta,
|
||||||
|
bullets.photo,
|
||||||
|
bullets.created_at,
|
||||||
|
m.id as manufactuerer_id,
|
||||||
|
m.name as manufacutuer_name,
|
||||||
|
m.url as manufacturer_url
|
||||||
|
from bullets
|
||||||
|
join manufacturers m on m.id = bullets.manufacturer_id
|
||||||
|
where bullets.id = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
type GetBulletByIdRow struct {
|
||||||
|
ID pgtype.UUID `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Diameter int32 `json:"diameter"`
|
||||||
|
Weight int32 `json:"weight"`
|
||||||
|
Meta []byte `json:"meta"`
|
||||||
|
Photo []byte `json:"photo"`
|
||||||
|
CreatedAt pgtype.Timestamp `json:"created_at"`
|
||||||
|
ManufactuererID pgtype.UUID `json:"manufactuerer_id"`
|
||||||
|
ManufacutuerName string `json:"manufacutuer_name"`
|
||||||
|
ManufacturerUrl pgtype.Text `json:"manufacturer_url"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) GetBulletById(ctx context.Context, id pgtype.UUID) (GetBulletByIdRow, error) {
|
||||||
|
row := q.db.QueryRow(ctx, getBulletById, id)
|
||||||
|
var i GetBulletByIdRow
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.Name,
|
||||||
|
&i.Diameter,
|
||||||
|
&i.Weight,
|
||||||
|
&i.Meta,
|
||||||
|
&i.Photo,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.ManufactuererID,
|
||||||
|
&i.ManufacutuerName,
|
||||||
|
&i.ManufacturerUrl,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const getBullets = `-- name: GetBullets :many
|
||||||
|
select bullets.id as id,
|
||||||
|
bullets.name as name,
|
||||||
|
weight,
|
||||||
|
diameter,
|
||||||
|
bullets.created_at,
|
||||||
|
meta,
|
||||||
|
m.id as manufacturer_id,
|
||||||
|
m.name as manufacturer_name,
|
||||||
|
m.url as manufacturer_url
|
||||||
|
from bullets
|
||||||
|
join manufacturers m on m.id = bullets.manufacturer_id
|
||||||
|
order by manufacturer_name, bullets.name desc
|
||||||
|
`
|
||||||
|
|
||||||
|
type GetBulletsRow struct {
|
||||||
|
ID pgtype.UUID `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Weight int32 `json:"weight"`
|
||||||
|
Diameter int32 `json:"diameter"`
|
||||||
|
CreatedAt pgtype.Timestamp `json:"created_at"`
|
||||||
|
Meta []byte `json:"meta"`
|
||||||
|
ManufacturerID pgtype.UUID `json:"manufacturer_id"`
|
||||||
|
ManufacturerName string `json:"manufacturer_name"`
|
||||||
|
ManufacturerUrl pgtype.Text `json:"manufacturer_url"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) GetBullets(ctx context.Context) ([]GetBulletsRow, error) {
|
||||||
|
rows, err := q.db.Query(ctx, getBullets)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var items []GetBulletsRow
|
||||||
|
for rows.Next() {
|
||||||
|
var i GetBulletsRow
|
||||||
|
if err := rows.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.Name,
|
||||||
|
&i.Weight,
|
||||||
|
&i.Diameter,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.Meta,
|
||||||
|
&i.ManufacturerID,
|
||||||
|
&i.ManufacturerName,
|
||||||
|
&i.ManufacturerUrl,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateBullet = `-- name: UpdateBullet :exec
|
||||||
|
UPDATE bullets
|
||||||
|
set name=$1,
|
||||||
|
weight=$2,
|
||||||
|
diameter=$3,
|
||||||
|
photo=$4,
|
||||||
|
meta=$5,
|
||||||
|
manufacturer_id=$6
|
||||||
|
where id = $7
|
||||||
|
`
|
||||||
|
|
||||||
|
type UpdateBulletParams struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Weight int32 `json:"weight"`
|
||||||
|
Diameter int32 `json:"diameter"`
|
||||||
|
Photo []byte `json:"photo"`
|
||||||
|
Meta []byte `json:"meta"`
|
||||||
|
ManufacturerID pgtype.UUID `json:"manufacturer_id"`
|
||||||
|
ID pgtype.UUID `json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) UpdateBullet(ctx context.Context, arg UpdateBulletParams) error {
|
||||||
|
_, err := q.db.Exec(ctx, updateBullet,
|
||||||
|
arg.Name,
|
||||||
|
arg.Weight,
|
||||||
|
arg.Diameter,
|
||||||
|
arg.Photo,
|
||||||
|
arg.Meta,
|
||||||
|
arg.ManufacturerID,
|
||||||
|
arg.ID,
|
||||||
|
)
|
||||||
|
return err
|
||||||
|
}
|
||||||
32
backend/models/bullets/db.go
Normal file
32
backend/models/bullets/db.go
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// sqlc v1.27.0
|
||||||
|
|
||||||
|
package bullets
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/jackc/pgx/v5"
|
||||||
|
"github.com/jackc/pgx/v5/pgconn"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DBTX interface {
|
||||||
|
Exec(context.Context, string, ...interface{}) (pgconn.CommandTag, error)
|
||||||
|
Query(context.Context, string, ...interface{}) (pgx.Rows, error)
|
||||||
|
QueryRow(context.Context, string, ...interface{}) pgx.Row
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(db DBTX) *Queries {
|
||||||
|
return &Queries{db: db}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Queries struct {
|
||||||
|
db DBTX
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) WithTx(tx pgx.Tx) *Queries {
|
||||||
|
return &Queries{
|
||||||
|
db: tx,
|
||||||
|
}
|
||||||
|
}
|
||||||
27
backend/models/bullets/models.go
Normal file
27
backend/models/bullets/models.go
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// sqlc v1.27.0
|
||||||
|
|
||||||
|
package bullets
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Bullet struct {
|
||||||
|
ID pgtype.UUID `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Weight int32 `json:"weight"`
|
||||||
|
Diameter int32 `json:"diameter"`
|
||||||
|
Meta []byte `json:"meta"`
|
||||||
|
Photo []byte `json:"photo"`
|
||||||
|
ManufacturerID pgtype.UUID `json:"manufacturer_id"`
|
||||||
|
CreatedAt pgtype.Timestamp `json:"created_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Manufacturer struct {
|
||||||
|
ID pgtype.UUID `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Url pgtype.Text `json:"url"`
|
||||||
|
CreatedAt pgtype.Timestamp `json:"created_at"`
|
||||||
|
}
|
||||||
73
backend/models/loads-schema.sql
Normal file
73
backend/models/loads-schema.sql
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
create table cartridges
|
||||||
|
(
|
||||||
|
id uuid primary key,
|
||||||
|
name varchar(255) not null,
|
||||||
|
created_at timestamp default current_timestamp not null,
|
||||||
|
meta json not null
|
||||||
|
);
|
||||||
|
|
||||||
|
create table loads
|
||||||
|
(
|
||||||
|
id uuid primary key,
|
||||||
|
cartridge_id uuid not null,
|
||||||
|
col float4 not null,
|
||||||
|
powder_id uuid not null,
|
||||||
|
powder_gr float4 not null,
|
||||||
|
primer_id uuid not null,
|
||||||
|
bullet_id uuid not null,
|
||||||
|
photo bytea not null,
|
||||||
|
created_at timestamp default current_timestamp not null,
|
||||||
|
meta json not null,
|
||||||
|
constraint loads_bullets_id_fk
|
||||||
|
foreign key (bullet_id) references bullets (id),
|
||||||
|
constraint loads_powders_id_fk
|
||||||
|
foreign key (powder_id) references powders (id),
|
||||||
|
constraint loads_primers_id_fk
|
||||||
|
foreign key (primer_id) references primers (id),
|
||||||
|
constraint loads_cartridges_id_fk
|
||||||
|
foreign key (cartridge_id) references cartridges (id)
|
||||||
|
);
|
||||||
|
|
||||||
|
create table bullets
|
||||||
|
(
|
||||||
|
id uuid primary key,
|
||||||
|
name varchar(255) not null,
|
||||||
|
weight int not null,
|
||||||
|
diameter int not null,
|
||||||
|
meta json,
|
||||||
|
photo bytea,
|
||||||
|
manufacturer_id uuid not null,
|
||||||
|
created_at timestamp default NOW() not null
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
create table manufacturers
|
||||||
|
(
|
||||||
|
id uuid primary key,
|
||||||
|
name varchar(255) not null,
|
||||||
|
url varchar(255),
|
||||||
|
created_at timestamp default NOW() not null
|
||||||
|
);
|
||||||
|
|
||||||
|
create table powders
|
||||||
|
(
|
||||||
|
id uuid primary key,
|
||||||
|
name varchar(255) not null,
|
||||||
|
meta json,
|
||||||
|
photo bytea,
|
||||||
|
manufacturer_id uuid not null,
|
||||||
|
created_at timestamp default NOW() not null,
|
||||||
|
constraint powders_manufacturers_id_fk
|
||||||
|
foreign key (manufacturer_id) references manufacturers (id)
|
||||||
|
);
|
||||||
|
create table primers
|
||||||
|
(
|
||||||
|
id uuid primary key,
|
||||||
|
name varchar(255) not null,
|
||||||
|
meta json,
|
||||||
|
photo bytea,
|
||||||
|
manufacturer_id uuid not null,
|
||||||
|
created_at timestamp default NOW() not null,
|
||||||
|
constraint primers_manufacturers_id_fk
|
||||||
|
foreign key (manufacturer_id) references manufacturers (id)
|
||||||
|
);
|
||||||
100
backend/models/loads.sql
Normal file
100
backend/models/loads.sql
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
-- name: GetCartridgeById :one
|
||||||
|
select c.id as id, c.name, c.meta
|
||||||
|
from cartridges c
|
||||||
|
where id = $1;
|
||||||
|
|
||||||
|
-- name: GetCartridges :many
|
||||||
|
select c.id as id, c.name, c.meta
|
||||||
|
from cartridges c
|
||||||
|
order by c.name;
|
||||||
|
|
||||||
|
-- name: CreateCartridge :one
|
||||||
|
insert into cartridges (name, meta)
|
||||||
|
values ($1, $2)
|
||||||
|
returning id;
|
||||||
|
|
||||||
|
-- name: DeleteCartridge :exec
|
||||||
|
delete
|
||||||
|
from cartridges
|
||||||
|
where id = $1;
|
||||||
|
|
||||||
|
-- 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)
|
||||||
|
returning id;
|
||||||
|
|
||||||
|
-- name: GetLoadById :one
|
||||||
|
select l.id as id,
|
||||||
|
c.id as cartridge_id,
|
||||||
|
c.name as cartridge_name,
|
||||||
|
c.meta as cartridge_meta,
|
||||||
|
l.col,
|
||||||
|
p.id as powder_id,
|
||||||
|
p.name as powder_name,
|
||||||
|
p.meta as powder_meta,
|
||||||
|
l.powder_gr,
|
||||||
|
pr.id as primer_id,
|
||||||
|
pr.name as primer_name,
|
||||||
|
pr.meta as primer_meta
|
||||||
|
from public.loads l
|
||||||
|
join cartridges c on l.cartridge_id = c.id
|
||||||
|
join bullets b on l.bullet_id = b.id
|
||||||
|
join powders p on l.powder_id = p.id
|
||||||
|
join primers pr on l.primer_id = pr.id
|
||||||
|
where l.id = $1;
|
||||||
|
|
||||||
|
-- name: TotalLoads :one
|
||||||
|
select count(*)
|
||||||
|
from loads;
|
||||||
|
|
||||||
|
-- name: GetLoads :many
|
||||||
|
select l.id as id,
|
||||||
|
l.col,
|
||||||
|
l.powder_gr,
|
||||||
|
c.id as cartridge_id,
|
||||||
|
c.name as cartridge_name,
|
||||||
|
c.meta as cartridge_meta,
|
||||||
|
p.id as powder_id,
|
||||||
|
p.name as powder_name,
|
||||||
|
p.meta as powder_meta,
|
||||||
|
p_m.name as powder_manufacturer_name,
|
||||||
|
p_m.url as powder_manufacturer_url,
|
||||||
|
pr.id as primer_id,
|
||||||
|
pr.name as primer_name,
|
||||||
|
pr.meta as primer_meta,
|
||||||
|
pr_m.name as primer_manufacturer_name,
|
||||||
|
pr_m.url as primer_manufacturer_url,
|
||||||
|
b.id as bullet_id,
|
||||||
|
b.name as bullet_name,
|
||||||
|
b.meta as bullet_meta,
|
||||||
|
b.weight as bullet_weight,
|
||||||
|
b.diameter as bullet_diameter,
|
||||||
|
b_m.name as bullet_manufacturer_name,
|
||||||
|
b_m.url as bullet_manufacturer_url
|
||||||
|
from public.loads l
|
||||||
|
join cartridges c on l.cartridge_id = c.id
|
||||||
|
join bullets b on l.bullet_id = b.id
|
||||||
|
join manufacturers b_m on b.manufacturer_id = b_m.id
|
||||||
|
join powders p on l.powder_id = p.id
|
||||||
|
join manufacturers p_m on p.manufacturer_id = p_m.id
|
||||||
|
join primers pr on l.primer_id = pr.id
|
||||||
|
join manufacturers pr_m on pr.manufacturer_id = pr_m.id
|
||||||
|
where 1 = 1
|
||||||
|
|
||||||
|
-- Loose Search By Name
|
||||||
|
AND (CASE WHEN @search_cartridge::bool THEN lower(c.name) like lower(@cartridge) ELSE TRUE END)
|
||||||
|
AND (CASE WHEN @search_bullet::bool THEN lower(b.name) like lower(@bullet) ELSE TRUE END)
|
||||||
|
AND (CASE WHEN @search_bullet_manufacturer::bool THEN lower(b_m.name) like lower(@bullet_manufacturer) ELSE TRUE END)
|
||||||
|
AND (CASE WHEN @search_primer::bool THEN lower(pr.name) like lower(@primer) ELSE TRUE END)
|
||||||
|
AND (CASE WHEN @search_primer_manufacturer::bool THEN lower(pr_m.name) like lower(@prime_manufacturer) ELSE TRUE END)
|
||||||
|
AND (CASE WHEN @search_powder::bool THEN lower(p.name) like lower(@powder) ELSE TRUE END)
|
||||||
|
AND (CASE WHEN @search_powder_manufacturer::bool THEN lower(p_m.name) like lower(@powder_manufacturer) ELSE TRUE END)
|
||||||
|
|
||||||
|
-- Exact Search By ID
|
||||||
|
AND (CASE WHEN @search_cartridge_id::bool THEN c.id = @cartridge_id::uuid ELSE TRUE END)
|
||||||
|
AND (CASE WHEN @search_bullet_id::bool THEN b.id = @bullet_id::uuid ELSE TRUE END)
|
||||||
|
AND (CASE WHEN @search_primer_id::bool THEN pr.id = @primer_id::uuid ELSE TRUE END)
|
||||||
|
AND (CASE WHEN @search_powder_id::bool THEN p.id = @powder_id::uuid ELSE TRUE END)
|
||||||
|
|
||||||
|
order by @order_by
|
||||||
|
limit $1 offset $2;
|
||||||
32
backend/models/loads/db.go
Normal file
32
backend/models/loads/db.go
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// sqlc v1.27.0
|
||||||
|
|
||||||
|
package loads
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/jackc/pgx/v5"
|
||||||
|
"github.com/jackc/pgx/v5/pgconn"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DBTX interface {
|
||||||
|
Exec(context.Context, string, ...interface{}) (pgconn.CommandTag, error)
|
||||||
|
Query(context.Context, string, ...interface{}) (pgx.Rows, error)
|
||||||
|
QueryRow(context.Context, string, ...interface{}) pgx.Row
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(db DBTX) *Queries {
|
||||||
|
return &Queries{db: db}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Queries struct {
|
||||||
|
db DBTX
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) WithTx(tx pgx.Tx) *Queries {
|
||||||
|
return &Queries{
|
||||||
|
db: tx,
|
||||||
|
}
|
||||||
|
}
|
||||||
370
backend/models/loads/loads.sql.go
Normal file
370
backend/models/loads/loads.sql.go
Normal file
@@ -0,0 +1,370 @@
|
|||||||
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// sqlc v1.27.0
|
||||||
|
// source: loads.sql
|
||||||
|
|
||||||
|
package loads
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
|
)
|
||||||
|
|
||||||
|
const createCartridge = `-- name: CreateCartridge :one
|
||||||
|
insert into cartridges (name, meta)
|
||||||
|
values ($1, $2)
|
||||||
|
returning id
|
||||||
|
`
|
||||||
|
|
||||||
|
type CreateCartridgeParams struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Meta []byte `json:"meta"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) CreateCartridge(ctx context.Context, arg CreateCartridgeParams) (pgtype.UUID, error) {
|
||||||
|
row := q.db.QueryRow(ctx, createCartridge, arg.Name, arg.Meta)
|
||||||
|
var id pgtype.UUID
|
||||||
|
err := row.Scan(&id)
|
||||||
|
return id, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const createLoad = `-- 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)
|
||||||
|
returning id
|
||||||
|
`
|
||||||
|
|
||||||
|
type CreateLoadParams 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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) CreateLoad(ctx context.Context, arg CreateLoadParams) (pgtype.UUID, error) {
|
||||||
|
row := q.db.QueryRow(ctx, createLoad,
|
||||||
|
arg.CartridgeID,
|
||||||
|
arg.Col,
|
||||||
|
arg.PowderID,
|
||||||
|
arg.PowderGr,
|
||||||
|
arg.PrimerID,
|
||||||
|
arg.BulletID,
|
||||||
|
arg.Photo,
|
||||||
|
arg.Meta,
|
||||||
|
)
|
||||||
|
var id pgtype.UUID
|
||||||
|
err := row.Scan(&id)
|
||||||
|
return id, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteCartridge = `-- name: DeleteCartridge :exec
|
||||||
|
delete
|
||||||
|
from cartridges
|
||||||
|
where id = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) DeleteCartridge(ctx context.Context, id pgtype.UUID) error {
|
||||||
|
_, err := q.db.Exec(ctx, deleteCartridge, id)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const getCartridgeById = `-- name: GetCartridgeById :one
|
||||||
|
select c.id as id, c.name, c.meta
|
||||||
|
from cartridges c
|
||||||
|
where id = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
type GetCartridgeByIdRow struct {
|
||||||
|
ID pgtype.UUID `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Meta []byte `json:"meta"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) GetCartridgeById(ctx context.Context, id pgtype.UUID) (GetCartridgeByIdRow, error) {
|
||||||
|
row := q.db.QueryRow(ctx, getCartridgeById, id)
|
||||||
|
var i GetCartridgeByIdRow
|
||||||
|
err := row.Scan(&i.ID, &i.Name, &i.Meta)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const getCartridges = `-- name: GetCartridges :many
|
||||||
|
select c.id as id, c.name, c.meta
|
||||||
|
from cartridges c
|
||||||
|
order by c.name
|
||||||
|
`
|
||||||
|
|
||||||
|
type GetCartridgesRow struct {
|
||||||
|
ID pgtype.UUID `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Meta []byte `json:"meta"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) GetCartridges(ctx context.Context) ([]GetCartridgesRow, error) {
|
||||||
|
rows, err := q.db.Query(ctx, getCartridges)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var items []GetCartridgesRow
|
||||||
|
for rows.Next() {
|
||||||
|
var i GetCartridgesRow
|
||||||
|
if err := rows.Scan(&i.ID, &i.Name, &i.Meta); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const getLoadById = `-- name: GetLoadById :one
|
||||||
|
select l.id as id,
|
||||||
|
c.id as cartridge_id,
|
||||||
|
c.name as cartridge_name,
|
||||||
|
c.meta as cartridge_meta,
|
||||||
|
l.col,
|
||||||
|
p.id as powder_id,
|
||||||
|
p.name as powder_name,
|
||||||
|
p.meta as powder_meta,
|
||||||
|
l.powder_gr,
|
||||||
|
pr.id as primer_id,
|
||||||
|
pr.name as primer_name,
|
||||||
|
pr.meta as primer_meta
|
||||||
|
from public.loads l
|
||||||
|
join cartridges c on l.cartridge_id = c.id
|
||||||
|
join bullets b on l.bullet_id = b.id
|
||||||
|
join powders p on l.powder_id = p.id
|
||||||
|
join primers pr on l.primer_id = pr.id
|
||||||
|
where l.id = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
type GetLoadByIdRow struct {
|
||||||
|
ID pgtype.UUID `json:"id"`
|
||||||
|
CartridgeID pgtype.UUID `json:"cartridge_id"`
|
||||||
|
CartridgeName string `json:"cartridge_name"`
|
||||||
|
CartridgeMeta []byte `json:"cartridge_meta"`
|
||||||
|
Col float32 `json:"col"`
|
||||||
|
PowderID pgtype.UUID `json:"powder_id"`
|
||||||
|
PowderName string `json:"powder_name"`
|
||||||
|
PowderMeta []byte `json:"powder_meta"`
|
||||||
|
PowderGr float32 `json:"powder_gr"`
|
||||||
|
PrimerID pgtype.UUID `json:"primer_id"`
|
||||||
|
PrimerName string `json:"primer_name"`
|
||||||
|
PrimerMeta []byte `json:"primer_meta"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) GetLoadById(ctx context.Context, id pgtype.UUID) (GetLoadByIdRow, error) {
|
||||||
|
row := q.db.QueryRow(ctx, getLoadById, id)
|
||||||
|
var i GetLoadByIdRow
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.CartridgeID,
|
||||||
|
&i.CartridgeName,
|
||||||
|
&i.CartridgeMeta,
|
||||||
|
&i.Col,
|
||||||
|
&i.PowderID,
|
||||||
|
&i.PowderName,
|
||||||
|
&i.PowderMeta,
|
||||||
|
&i.PowderGr,
|
||||||
|
&i.PrimerID,
|
||||||
|
&i.PrimerName,
|
||||||
|
&i.PrimerMeta,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const getLoads = `-- name: GetLoads :many
|
||||||
|
select l.id as id,
|
||||||
|
l.col,
|
||||||
|
l.powder_gr,
|
||||||
|
c.id as cartridge_id,
|
||||||
|
c.name as cartridge_name,
|
||||||
|
c.meta as cartridge_meta,
|
||||||
|
p.id as powder_id,
|
||||||
|
p.name as powder_name,
|
||||||
|
p.meta as powder_meta,
|
||||||
|
p_m.name as powder_manufacturer_name,
|
||||||
|
p_m.url as powder_manufacturer_url,
|
||||||
|
pr.id as primer_id,
|
||||||
|
pr.name as primer_name,
|
||||||
|
pr.meta as primer_meta,
|
||||||
|
pr_m.name as primer_manufacturer_name,
|
||||||
|
pr_m.url as primer_manufacturer_url,
|
||||||
|
b.id as bullet_id,
|
||||||
|
b.name as bullet_name,
|
||||||
|
b.meta as bullet_meta,
|
||||||
|
b.weight as bullet_weight,
|
||||||
|
b.diameter as bullet_diameter,
|
||||||
|
b_m.name as bullet_manufacturer_name,
|
||||||
|
b_m.url as bullet_manufacturer_url
|
||||||
|
from public.loads l
|
||||||
|
join cartridges c on l.cartridge_id = c.id
|
||||||
|
join bullets b on l.bullet_id = b.id
|
||||||
|
join manufacturers b_m on b.manufacturer_id = b_m.id
|
||||||
|
join powders p on l.powder_id = p.id
|
||||||
|
join manufacturers p_m on p.manufacturer_id = p_m.id
|
||||||
|
join primers pr on l.primer_id = pr.id
|
||||||
|
join manufacturers pr_m on pr.manufacturer_id = pr_m.id
|
||||||
|
where 1 = 1
|
||||||
|
|
||||||
|
-- Loose Search By Name
|
||||||
|
AND (CASE WHEN $3::bool THEN lower(c.name) like lower($4) ELSE TRUE END)
|
||||||
|
AND (CASE WHEN $5::bool THEN lower(b.name) like lower($6) ELSE TRUE END)
|
||||||
|
AND (CASE WHEN $7::bool THEN lower(b_m.name) like lower($8) ELSE TRUE END)
|
||||||
|
AND (CASE WHEN $9::bool THEN lower(pr.name) like lower($10) ELSE TRUE END)
|
||||||
|
AND (CASE WHEN $11::bool THEN lower(pr_m.name) like lower($12) ELSE TRUE END)
|
||||||
|
AND (CASE WHEN $13::bool THEN lower(p.name) like lower($14) ELSE TRUE END)
|
||||||
|
AND (CASE WHEN $15::bool THEN lower(p_m.name) like lower($16) ELSE TRUE END)
|
||||||
|
|
||||||
|
-- Exact Search By ID
|
||||||
|
AND (CASE WHEN $17::bool THEN c.id = $18::uuid ELSE TRUE END)
|
||||||
|
AND (CASE WHEN $19::bool THEN b.id = $20::uuid ELSE TRUE END)
|
||||||
|
AND (CASE WHEN $21::bool THEN pr.id = $22::uuid ELSE TRUE END)
|
||||||
|
AND (CASE WHEN $23::bool THEN p.id = $24::uuid ELSE TRUE END)
|
||||||
|
|
||||||
|
order by $25
|
||||||
|
limit $1 offset $2
|
||||||
|
`
|
||||||
|
|
||||||
|
type GetLoadsParams struct {
|
||||||
|
Limit int32 `json:"limit"`
|
||||||
|
Offset int32 `json:"offset"`
|
||||||
|
SearchCartridge bool `json:"search_cartridge"`
|
||||||
|
Cartridge string `json:"cartridge"`
|
||||||
|
SearchBullet bool `json:"search_bullet"`
|
||||||
|
Bullet string `json:"bullet"`
|
||||||
|
SearchBulletManufacturer bool `json:"search_bullet_manufacturer"`
|
||||||
|
BulletManufacturer string `json:"bullet_manufacturer"`
|
||||||
|
SearchPrimer bool `json:"search_primer"`
|
||||||
|
Primer string `json:"primer"`
|
||||||
|
SearchPrimerManufacturer bool `json:"search_primer_manufacturer"`
|
||||||
|
PrimeManufacturer string `json:"prime_manufacturer"`
|
||||||
|
SearchPowder bool `json:"search_powder"`
|
||||||
|
Powder string `json:"powder"`
|
||||||
|
SearchPowderManufacturer bool `json:"search_powder_manufacturer"`
|
||||||
|
PowderManufacturer string `json:"powder_manufacturer"`
|
||||||
|
SearchCartridgeID bool `json:"search_cartridge_id"`
|
||||||
|
CartridgeID pgtype.UUID `json:"cartridge_id"`
|
||||||
|
SearchBulletID bool `json:"search_bullet_id"`
|
||||||
|
BulletID pgtype.UUID `json:"bullet_id"`
|
||||||
|
SearchPrimerID bool `json:"search_primer_id"`
|
||||||
|
PrimerID pgtype.UUID `json:"primer_id"`
|
||||||
|
SearchPowderID bool `json:"search_powder_id"`
|
||||||
|
PowderID pgtype.UUID `json:"powder_id"`
|
||||||
|
OrderBy interface{} `json:"order_by"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetLoadsRow struct {
|
||||||
|
ID pgtype.UUID `json:"id"`
|
||||||
|
Col float32 `json:"col"`
|
||||||
|
PowderGr float32 `json:"powder_gr"`
|
||||||
|
CartridgeID pgtype.UUID `json:"cartridge_id"`
|
||||||
|
CartridgeName string `json:"cartridge_name"`
|
||||||
|
CartridgeMeta []byte `json:"cartridge_meta"`
|
||||||
|
PowderID pgtype.UUID `json:"powder_id"`
|
||||||
|
PowderName string `json:"powder_name"`
|
||||||
|
PowderMeta []byte `json:"powder_meta"`
|
||||||
|
PowderManufacturerName string `json:"powder_manufacturer_name"`
|
||||||
|
PowderManufacturerUrl pgtype.Text `json:"powder_manufacturer_url"`
|
||||||
|
PrimerID pgtype.UUID `json:"primer_id"`
|
||||||
|
PrimerName string `json:"primer_name"`
|
||||||
|
PrimerMeta []byte `json:"primer_meta"`
|
||||||
|
PrimerManufacturerName string `json:"primer_manufacturer_name"`
|
||||||
|
PrimerManufacturerUrl pgtype.Text `json:"primer_manufacturer_url"`
|
||||||
|
BulletID pgtype.UUID `json:"bullet_id"`
|
||||||
|
BulletName string `json:"bullet_name"`
|
||||||
|
BulletMeta []byte `json:"bullet_meta"`
|
||||||
|
BulletWeight int32 `json:"bullet_weight"`
|
||||||
|
BulletDiameter int32 `json:"bullet_diameter"`
|
||||||
|
BulletManufacturerName string `json:"bullet_manufacturer_name"`
|
||||||
|
BulletManufacturerUrl pgtype.Text `json:"bullet_manufacturer_url"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) GetLoads(ctx context.Context, arg GetLoadsParams) ([]GetLoadsRow, error) {
|
||||||
|
rows, err := q.db.Query(ctx, getLoads,
|
||||||
|
arg.Limit,
|
||||||
|
arg.Offset,
|
||||||
|
arg.SearchCartridge,
|
||||||
|
arg.Cartridge,
|
||||||
|
arg.SearchBullet,
|
||||||
|
arg.Bullet,
|
||||||
|
arg.SearchBulletManufacturer,
|
||||||
|
arg.BulletManufacturer,
|
||||||
|
arg.SearchPrimer,
|
||||||
|
arg.Primer,
|
||||||
|
arg.SearchPrimerManufacturer,
|
||||||
|
arg.PrimeManufacturer,
|
||||||
|
arg.SearchPowder,
|
||||||
|
arg.Powder,
|
||||||
|
arg.SearchPowderManufacturer,
|
||||||
|
arg.PowderManufacturer,
|
||||||
|
arg.SearchCartridgeID,
|
||||||
|
arg.CartridgeID,
|
||||||
|
arg.SearchBulletID,
|
||||||
|
arg.BulletID,
|
||||||
|
arg.SearchPrimerID,
|
||||||
|
arg.PrimerID,
|
||||||
|
arg.SearchPowderID,
|
||||||
|
arg.PowderID,
|
||||||
|
arg.OrderBy,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var items []GetLoadsRow
|
||||||
|
for rows.Next() {
|
||||||
|
var i GetLoadsRow
|
||||||
|
if err := rows.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.Col,
|
||||||
|
&i.PowderGr,
|
||||||
|
&i.CartridgeID,
|
||||||
|
&i.CartridgeName,
|
||||||
|
&i.CartridgeMeta,
|
||||||
|
&i.PowderID,
|
||||||
|
&i.PowderName,
|
||||||
|
&i.PowderMeta,
|
||||||
|
&i.PowderManufacturerName,
|
||||||
|
&i.PowderManufacturerUrl,
|
||||||
|
&i.PrimerID,
|
||||||
|
&i.PrimerName,
|
||||||
|
&i.PrimerMeta,
|
||||||
|
&i.PrimerManufacturerName,
|
||||||
|
&i.PrimerManufacturerUrl,
|
||||||
|
&i.BulletID,
|
||||||
|
&i.BulletName,
|
||||||
|
&i.BulletMeta,
|
||||||
|
&i.BulletWeight,
|
||||||
|
&i.BulletDiameter,
|
||||||
|
&i.BulletManufacturerName,
|
||||||
|
&i.BulletManufacturerUrl,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const totalLoads = `-- name: TotalLoads :one
|
||||||
|
select count(*)
|
||||||
|
from loads
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) TotalLoads(ctx context.Context) (int64, error) {
|
||||||
|
row := q.db.QueryRow(ctx, totalLoads)
|
||||||
|
var count int64
|
||||||
|
err := row.Scan(&count)
|
||||||
|
return count, err
|
||||||
|
}
|
||||||
65
backend/models/loads/models.go
Normal file
65
backend/models/loads/models.go
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// sqlc v1.27.0
|
||||||
|
|
||||||
|
package loads
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Bullet struct {
|
||||||
|
ID pgtype.UUID `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Weight int32 `json:"weight"`
|
||||||
|
Diameter int32 `json:"diameter"`
|
||||||
|
Meta []byte `json:"meta"`
|
||||||
|
Photo []byte `json:"photo"`
|
||||||
|
ManufacturerID pgtype.UUID `json:"manufacturer_id"`
|
||||||
|
CreatedAt pgtype.Timestamp `json:"created_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Cartridge struct {
|
||||||
|
ID pgtype.UUID `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
CreatedAt pgtype.Timestamp `json:"created_at"`
|
||||||
|
Meta []byte `json:"meta"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Load struct {
|
||||||
|
ID pgtype.UUID `json:"id"`
|
||||||
|
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"`
|
||||||
|
CreatedAt pgtype.Timestamp `json:"created_at"`
|
||||||
|
Meta []byte `json:"meta"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Manufacturer struct {
|
||||||
|
ID pgtype.UUID `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Url pgtype.Text `json:"url"`
|
||||||
|
CreatedAt pgtype.Timestamp `json:"created_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Powder struct {
|
||||||
|
ID pgtype.UUID `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Meta []byte `json:"meta"`
|
||||||
|
Photo []byte `json:"photo"`
|
||||||
|
ManufacturerID pgtype.UUID `json:"manufacturer_id"`
|
||||||
|
CreatedAt pgtype.Timestamp `json:"created_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Primer struct {
|
||||||
|
ID pgtype.UUID `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Meta []byte `json:"meta"`
|
||||||
|
Photo []byte `json:"photo"`
|
||||||
|
ManufacturerID pgtype.UUID `json:"manufacturer_id"`
|
||||||
|
CreatedAt pgtype.Timestamp `json:"created_at"`
|
||||||
|
}
|
||||||
7
backend/models/manufacturer-schema.sql
Normal file
7
backend/models/manufacturer-schema.sql
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
create table manufacturers
|
||||||
|
(
|
||||||
|
id uuid primary key ,
|
||||||
|
name varchar(255) not null,
|
||||||
|
url varchar(255),
|
||||||
|
created_at timestamp default NOW() not null
|
||||||
|
);
|
||||||
23
backend/models/manufacturer.sql
Normal file
23
backend/models/manufacturer.sql
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
-- name: GetAll :many
|
||||||
|
select id, name, url, created_at
|
||||||
|
from manufacturers
|
||||||
|
order by name;
|
||||||
|
|
||||||
|
-- name: CreateManufacturer :exec
|
||||||
|
insert into manufacturers (id, name, url) values ($1, $2, $3);
|
||||||
|
|
||||||
|
-- name: UpdateManufacturer :exec
|
||||||
|
update manufacturers
|
||||||
|
set name=$1,
|
||||||
|
url=$2
|
||||||
|
where id = $3;
|
||||||
|
|
||||||
|
-- name: GetById :one
|
||||||
|
select id, name, url, created_at
|
||||||
|
from manufacturers
|
||||||
|
where id = $1;
|
||||||
|
|
||||||
|
-- name: DeleteById :exec
|
||||||
|
delete
|
||||||
|
from manufacturers
|
||||||
|
where id = $1;
|
||||||
32
backend/models/manufacturer/db.go
Normal file
32
backend/models/manufacturer/db.go
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// sqlc v1.27.0
|
||||||
|
|
||||||
|
package manufacturer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/jackc/pgx/v5"
|
||||||
|
"github.com/jackc/pgx/v5/pgconn"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DBTX interface {
|
||||||
|
Exec(context.Context, string, ...interface{}) (pgconn.CommandTag, error)
|
||||||
|
Query(context.Context, string, ...interface{}) (pgx.Rows, error)
|
||||||
|
QueryRow(context.Context, string, ...interface{}) pgx.Row
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(db DBTX) *Queries {
|
||||||
|
return &Queries{db: db}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Queries struct {
|
||||||
|
db DBTX
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) WithTx(tx pgx.Tx) *Queries {
|
||||||
|
return &Queries{
|
||||||
|
db: tx,
|
||||||
|
}
|
||||||
|
}
|
||||||
105
backend/models/manufacturer/manufacturer.sql.go
Normal file
105
backend/models/manufacturer/manufacturer.sql.go
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// sqlc v1.27.0
|
||||||
|
// source: manufacturer.sql
|
||||||
|
|
||||||
|
package manufacturer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
|
)
|
||||||
|
|
||||||
|
const createManufacturer = `-- name: CreateManufacturer :exec
|
||||||
|
insert into manufacturers (id, name, url) values ($1, $2, $3)
|
||||||
|
`
|
||||||
|
|
||||||
|
type CreateManufacturerParams struct {
|
||||||
|
ID pgtype.UUID `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Url pgtype.Text `json:"url"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) CreateManufacturer(ctx context.Context, arg CreateManufacturerParams) error {
|
||||||
|
_, err := q.db.Exec(ctx, createManufacturer, arg.ID, arg.Name, arg.Url)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteById = `-- name: DeleteById :exec
|
||||||
|
delete
|
||||||
|
from manufacturers
|
||||||
|
where id = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) DeleteById(ctx context.Context, id pgtype.UUID) error {
|
||||||
|
_, err := q.db.Exec(ctx, deleteById, id)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const getAll = `-- name: GetAll :many
|
||||||
|
select id, name, url, created_at
|
||||||
|
from manufacturers
|
||||||
|
order by name
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) GetAll(ctx context.Context) ([]Manufacturer, error) {
|
||||||
|
rows, err := q.db.Query(ctx, getAll)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var items []Manufacturer
|
||||||
|
for rows.Next() {
|
||||||
|
var i Manufacturer
|
||||||
|
if err := rows.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.Name,
|
||||||
|
&i.Url,
|
||||||
|
&i.CreatedAt,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const getById = `-- name: GetById :one
|
||||||
|
select id, name, url, created_at
|
||||||
|
from manufacturers
|
||||||
|
where id = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) GetById(ctx context.Context, id pgtype.UUID) (Manufacturer, error) {
|
||||||
|
row := q.db.QueryRow(ctx, getById, id)
|
||||||
|
var i Manufacturer
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.Name,
|
||||||
|
&i.Url,
|
||||||
|
&i.CreatedAt,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateManufacturer = `-- name: UpdateManufacturer :exec
|
||||||
|
update manufacturers
|
||||||
|
set name=$1,
|
||||||
|
url=$2
|
||||||
|
where id = $3
|
||||||
|
`
|
||||||
|
|
||||||
|
type UpdateManufacturerParams struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Url pgtype.Text `json:"url"`
|
||||||
|
ID pgtype.UUID `json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) UpdateManufacturer(ctx context.Context, arg UpdateManufacturerParams) error {
|
||||||
|
_, err := q.db.Exec(ctx, updateManufacturer, arg.Name, arg.Url, arg.ID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
16
backend/models/manufacturer/models.go
Normal file
16
backend/models/manufacturer/models.go
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// sqlc v1.27.0
|
||||||
|
|
||||||
|
package manufacturer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Manufacturer struct {
|
||||||
|
ID pgtype.UUID `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Url pgtype.Text `json:"url"`
|
||||||
|
CreatedAt pgtype.Timestamp `json:"created_at"`
|
||||||
|
}
|
||||||
19
backend/models/powder-schema.sql
Normal file
19
backend/models/powder-schema.sql
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
create table powders
|
||||||
|
(
|
||||||
|
id uuid primary key,
|
||||||
|
name varchar(255) not null,
|
||||||
|
meta json,
|
||||||
|
photo bytea,
|
||||||
|
manufacturer_id uuid not null,
|
||||||
|
created_at timestamp default NOW() not null,
|
||||||
|
constraint powders_manufacturers_id_fk
|
||||||
|
foreign key (manufacturer_id) references manufacturers (id)
|
||||||
|
);
|
||||||
|
|
||||||
|
create table manufacturers
|
||||||
|
(
|
||||||
|
id uuid primary key ,
|
||||||
|
name varchar(255) not null,
|
||||||
|
url varchar(255),
|
||||||
|
created_at timestamp default NOW() not null
|
||||||
|
);
|
||||||
41
backend/models/powder.sql
Normal file
41
backend/models/powder.sql
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
-- name: GetPowderById :one
|
||||||
|
select powders.id as id,
|
||||||
|
powders.name as name,
|
||||||
|
powders.created_at,
|
||||||
|
powders.photo,
|
||||||
|
meta,
|
||||||
|
m.id as manufacturer_id,
|
||||||
|
m.name as manufacturer_name,
|
||||||
|
m.url as manufacturer_url
|
||||||
|
from powders
|
||||||
|
join manufacturers m on m.id = powders.manufacturer_id
|
||||||
|
where powders.id = $1;
|
||||||
|
|
||||||
|
-- name: GetPowders :many
|
||||||
|
select powders.id as id,
|
||||||
|
powders.name as name,
|
||||||
|
powders.created_at,
|
||||||
|
meta,
|
||||||
|
m.id as manufacturer_id,
|
||||||
|
m.name as manufacturer_name,
|
||||||
|
m.url as manufacturer_url
|
||||||
|
from powders
|
||||||
|
join manufacturers m on m.id = powders.manufacturer_id
|
||||||
|
order by powders.name desc;
|
||||||
|
|
||||||
|
-- name: DeletePowder :exec
|
||||||
|
DELETE
|
||||||
|
FROM powders
|
||||||
|
WHERE id = $1;
|
||||||
|
|
||||||
|
-- name: InsertPowder :exec
|
||||||
|
insert into powders (id, name, manufacturer_id, meta, photo)
|
||||||
|
values ($1, $2, $3, $4, $5);
|
||||||
|
|
||||||
|
-- name: UpdatePowder :exec
|
||||||
|
update powders
|
||||||
|
set name = $1,
|
||||||
|
manufacturer_id = $2,
|
||||||
|
meta = $3,
|
||||||
|
photo = $4
|
||||||
|
where id = $5;
|
||||||
32
backend/models/powder/db.go
Normal file
32
backend/models/powder/db.go
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// sqlc v1.27.0
|
||||||
|
|
||||||
|
package powder
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/jackc/pgx/v5"
|
||||||
|
"github.com/jackc/pgx/v5/pgconn"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DBTX interface {
|
||||||
|
Exec(context.Context, string, ...interface{}) (pgconn.CommandTag, error)
|
||||||
|
Query(context.Context, string, ...interface{}) (pgx.Rows, error)
|
||||||
|
QueryRow(context.Context, string, ...interface{}) pgx.Row
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(db DBTX) *Queries {
|
||||||
|
return &Queries{db: db}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Queries struct {
|
||||||
|
db DBTX
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) WithTx(tx pgx.Tx) *Queries {
|
||||||
|
return &Queries{
|
||||||
|
db: tx,
|
||||||
|
}
|
||||||
|
}
|
||||||
25
backend/models/powder/models.go
Normal file
25
backend/models/powder/models.go
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// sqlc v1.27.0
|
||||||
|
|
||||||
|
package powder
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Manufacturer struct {
|
||||||
|
ID pgtype.UUID `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Url pgtype.Text `json:"url"`
|
||||||
|
CreatedAt pgtype.Timestamp `json:"created_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Powder struct {
|
||||||
|
ID pgtype.UUID `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Meta []byte `json:"meta"`
|
||||||
|
Photo []byte `json:"photo"`
|
||||||
|
ManufacturerID pgtype.UUID `json:"manufacturer_id"`
|
||||||
|
CreatedAt pgtype.Timestamp `json:"created_at"`
|
||||||
|
}
|
||||||
167
backend/models/powder/powder.sql.go
Normal file
167
backend/models/powder/powder.sql.go
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// sqlc v1.27.0
|
||||||
|
// source: powder.sql
|
||||||
|
|
||||||
|
package powder
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
|
)
|
||||||
|
|
||||||
|
const deletePowder = `-- name: DeletePowder :exec
|
||||||
|
DELETE
|
||||||
|
FROM powders
|
||||||
|
WHERE id = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) DeletePowder(ctx context.Context, id pgtype.UUID) error {
|
||||||
|
_, err := q.db.Exec(ctx, deletePowder, id)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const getPowderById = `-- name: GetPowderById :one
|
||||||
|
select powders.id as id,
|
||||||
|
powders.name as name,
|
||||||
|
powders.created_at,
|
||||||
|
powders.photo,
|
||||||
|
meta,
|
||||||
|
m.id as manufacturer_id,
|
||||||
|
m.name as manufacturer_name,
|
||||||
|
m.url as manufacturer_url
|
||||||
|
from powders
|
||||||
|
join manufacturers m on m.id = powders.manufacturer_id
|
||||||
|
where powders.id = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
type GetPowderByIdRow struct {
|
||||||
|
ID pgtype.UUID `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
CreatedAt pgtype.Timestamp `json:"created_at"`
|
||||||
|
Photo []byte `json:"photo"`
|
||||||
|
Meta []byte `json:"meta"`
|
||||||
|
ManufacturerID pgtype.UUID `json:"manufacturer_id"`
|
||||||
|
ManufacturerName string `json:"manufacturer_name"`
|
||||||
|
ManufacturerUrl pgtype.Text `json:"manufacturer_url"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) GetPowderById(ctx context.Context, id pgtype.UUID) (GetPowderByIdRow, error) {
|
||||||
|
row := q.db.QueryRow(ctx, getPowderById, id)
|
||||||
|
var i GetPowderByIdRow
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.Name,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.Photo,
|
||||||
|
&i.Meta,
|
||||||
|
&i.ManufacturerID,
|
||||||
|
&i.ManufacturerName,
|
||||||
|
&i.ManufacturerUrl,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const getPowders = `-- name: GetPowders :many
|
||||||
|
select powders.id as id,
|
||||||
|
powders.name as name,
|
||||||
|
powders.created_at,
|
||||||
|
meta,
|
||||||
|
m.id as manufacturer_id,
|
||||||
|
m.name as manufacturer_name,
|
||||||
|
m.url as manufacturer_url
|
||||||
|
from powders
|
||||||
|
join manufacturers m on m.id = powders.manufacturer_id
|
||||||
|
order by powders.name desc
|
||||||
|
`
|
||||||
|
|
||||||
|
type GetPowdersRow struct {
|
||||||
|
ID pgtype.UUID `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
CreatedAt pgtype.Timestamp `json:"created_at"`
|
||||||
|
Meta []byte `json:"meta"`
|
||||||
|
ManufacturerID pgtype.UUID `json:"manufacturer_id"`
|
||||||
|
ManufacturerName string `json:"manufacturer_name"`
|
||||||
|
ManufacturerUrl pgtype.Text `json:"manufacturer_url"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) GetPowders(ctx context.Context) ([]GetPowdersRow, error) {
|
||||||
|
rows, err := q.db.Query(ctx, getPowders)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var items []GetPowdersRow
|
||||||
|
for rows.Next() {
|
||||||
|
var i GetPowdersRow
|
||||||
|
if err := rows.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.Name,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.Meta,
|
||||||
|
&i.ManufacturerID,
|
||||||
|
&i.ManufacturerName,
|
||||||
|
&i.ManufacturerUrl,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const insertPowder = `-- name: InsertPowder :exec
|
||||||
|
insert into powders (id, name, manufacturer_id, meta, photo)
|
||||||
|
values ($1, $2, $3, $4, $5)
|
||||||
|
`
|
||||||
|
|
||||||
|
type InsertPowderParams struct {
|
||||||
|
ID pgtype.UUID `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
ManufacturerID pgtype.UUID `json:"manufacturer_id"`
|
||||||
|
Meta []byte `json:"meta"`
|
||||||
|
Photo []byte `json:"photo"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) InsertPowder(ctx context.Context, arg InsertPowderParams) error {
|
||||||
|
_, err := q.db.Exec(ctx, insertPowder,
|
||||||
|
arg.ID,
|
||||||
|
arg.Name,
|
||||||
|
arg.ManufacturerID,
|
||||||
|
arg.Meta,
|
||||||
|
arg.Photo,
|
||||||
|
)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatePowder = `-- name: UpdatePowder :exec
|
||||||
|
update powders
|
||||||
|
set name = $1,
|
||||||
|
manufacturer_id = $2,
|
||||||
|
meta = $3,
|
||||||
|
photo = $4
|
||||||
|
where id = $5
|
||||||
|
`
|
||||||
|
|
||||||
|
type UpdatePowderParams struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
ManufacturerID pgtype.UUID `json:"manufacturer_id"`
|
||||||
|
Meta []byte `json:"meta"`
|
||||||
|
Photo []byte `json:"photo"`
|
||||||
|
ID pgtype.UUID `json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) UpdatePowder(ctx context.Context, arg UpdatePowderParams) error {
|
||||||
|
_, err := q.db.Exec(ctx, updatePowder,
|
||||||
|
arg.Name,
|
||||||
|
arg.ManufacturerID,
|
||||||
|
arg.Meta,
|
||||||
|
arg.Photo,
|
||||||
|
arg.ID,
|
||||||
|
)
|
||||||
|
return err
|
||||||
|
}
|
||||||
19
backend/models/primers-schema.sql
Normal file
19
backend/models/primers-schema.sql
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
create table primers
|
||||||
|
(
|
||||||
|
id uuid primary key,
|
||||||
|
name varchar(255) not null,
|
||||||
|
meta json,
|
||||||
|
photo bytea,
|
||||||
|
manufacturer_id uuid not null,
|
||||||
|
created_at timestamp default NOW() not null,
|
||||||
|
constraint primers_manufacturers_id_fk
|
||||||
|
foreign key (manufacturer_id) references manufacturers (id)
|
||||||
|
);
|
||||||
|
|
||||||
|
create table manufacturers
|
||||||
|
(
|
||||||
|
id uuid primary key ,
|
||||||
|
name varchar(255) not null,
|
||||||
|
url varchar(255),
|
||||||
|
created_at timestamp default NOW() not null
|
||||||
|
);
|
||||||
41
backend/models/primers.sql
Normal file
41
backend/models/primers.sql
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
-- name: GetPrimers :many
|
||||||
|
SELECT primers.id as id,
|
||||||
|
primers.name as name,
|
||||||
|
primers.meta as meta,
|
||||||
|
primers.created_at as created_at,
|
||||||
|
m.id as manufacturer_id,
|
||||||
|
m.name as manufacturer_name,
|
||||||
|
m.url as manufacturer_url
|
||||||
|
FROM primers
|
||||||
|
join manufacturers m on m.id = primers.manufacturer_id
|
||||||
|
ORDER BY primers.name desc;
|
||||||
|
|
||||||
|
-- name: GetPrimerById :one
|
||||||
|
SELECT primers.id as id,
|
||||||
|
primers.name as name,
|
||||||
|
primers.meta as meta,
|
||||||
|
primers.photo as photo,
|
||||||
|
primers.created_at as created_at,
|
||||||
|
m.id as manufacturer_id,
|
||||||
|
m.name as manufacturer_name,
|
||||||
|
m.url as manufacturer_url
|
||||||
|
FROM primers
|
||||||
|
join manufacturers m on m.id = primers.manufacturer_id
|
||||||
|
WHERE primers.id = $1;
|
||||||
|
|
||||||
|
-- name: InsertPrimer :exec
|
||||||
|
INSERT INTO primers (id, name, manufacturer_id, meta, photo)
|
||||||
|
VALUES ($1, $2, $3, $4, $5);
|
||||||
|
|
||||||
|
-- name: DeletePrimer :exec
|
||||||
|
DELETE
|
||||||
|
FROM primers
|
||||||
|
WHERE id = $1;
|
||||||
|
|
||||||
|
-- name: UpdatePrimer :exec
|
||||||
|
UPDATE primers
|
||||||
|
SET name = $1,
|
||||||
|
manufacturer_id = $2,
|
||||||
|
meta = $3,
|
||||||
|
photo = $4
|
||||||
|
WHERE id = $5;
|
||||||
32
backend/models/primers/db.go
Normal file
32
backend/models/primers/db.go
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// sqlc v1.27.0
|
||||||
|
|
||||||
|
package primers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/jackc/pgx/v5"
|
||||||
|
"github.com/jackc/pgx/v5/pgconn"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DBTX interface {
|
||||||
|
Exec(context.Context, string, ...interface{}) (pgconn.CommandTag, error)
|
||||||
|
Query(context.Context, string, ...interface{}) (pgx.Rows, error)
|
||||||
|
QueryRow(context.Context, string, ...interface{}) pgx.Row
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(db DBTX) *Queries {
|
||||||
|
return &Queries{db: db}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Queries struct {
|
||||||
|
db DBTX
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) WithTx(tx pgx.Tx) *Queries {
|
||||||
|
return &Queries{
|
||||||
|
db: tx,
|
||||||
|
}
|
||||||
|
}
|
||||||
25
backend/models/primers/models.go
Normal file
25
backend/models/primers/models.go
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// sqlc v1.27.0
|
||||||
|
|
||||||
|
package primers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Manufacturer struct {
|
||||||
|
ID pgtype.UUID `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Url pgtype.Text `json:"url"`
|
||||||
|
CreatedAt pgtype.Timestamp `json:"created_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Primer struct {
|
||||||
|
ID pgtype.UUID `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Meta []byte `json:"meta"`
|
||||||
|
Photo []byte `json:"photo"`
|
||||||
|
ManufacturerID pgtype.UUID `json:"manufacturer_id"`
|
||||||
|
CreatedAt pgtype.Timestamp `json:"created_at"`
|
||||||
|
}
|
||||||
167
backend/models/primers/primers.sql.go
Normal file
167
backend/models/primers/primers.sql.go
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// sqlc v1.27.0
|
||||||
|
// source: primers.sql
|
||||||
|
|
||||||
|
package primers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
|
)
|
||||||
|
|
||||||
|
const deletePrimer = `-- name: DeletePrimer :exec
|
||||||
|
DELETE
|
||||||
|
FROM primers
|
||||||
|
WHERE id = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) DeletePrimer(ctx context.Context, id pgtype.UUID) error {
|
||||||
|
_, err := q.db.Exec(ctx, deletePrimer, id)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const getPrimerById = `-- name: GetPrimerById :one
|
||||||
|
SELECT primers.id as id,
|
||||||
|
primers.name as name,
|
||||||
|
primers.meta as meta,
|
||||||
|
primers.photo as photo,
|
||||||
|
primers.created_at as created_at,
|
||||||
|
m.id as manufacturer_id,
|
||||||
|
m.name as manufacturer_name,
|
||||||
|
m.url as manufacturer_url
|
||||||
|
FROM primers
|
||||||
|
join manufacturers m on m.id = primers.manufacturer_id
|
||||||
|
WHERE primers.id = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
type GetPrimerByIdRow struct {
|
||||||
|
ID pgtype.UUID `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Meta []byte `json:"meta"`
|
||||||
|
Photo []byte `json:"photo"`
|
||||||
|
CreatedAt pgtype.Timestamp `json:"created_at"`
|
||||||
|
ManufacturerID pgtype.UUID `json:"manufacturer_id"`
|
||||||
|
ManufacturerName string `json:"manufacturer_name"`
|
||||||
|
ManufacturerUrl pgtype.Text `json:"manufacturer_url"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) GetPrimerById(ctx context.Context, id pgtype.UUID) (GetPrimerByIdRow, error) {
|
||||||
|
row := q.db.QueryRow(ctx, getPrimerById, id)
|
||||||
|
var i GetPrimerByIdRow
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.Name,
|
||||||
|
&i.Meta,
|
||||||
|
&i.Photo,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.ManufacturerID,
|
||||||
|
&i.ManufacturerName,
|
||||||
|
&i.ManufacturerUrl,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const getPrimers = `-- name: GetPrimers :many
|
||||||
|
SELECT primers.id as id,
|
||||||
|
primers.name as name,
|
||||||
|
primers.meta as meta,
|
||||||
|
primers.created_at as created_at,
|
||||||
|
m.id as manufacturer_id,
|
||||||
|
m.name as manufacturer_name,
|
||||||
|
m.url as manufacturer_url
|
||||||
|
FROM primers
|
||||||
|
join manufacturers m on m.id = primers.manufacturer_id
|
||||||
|
ORDER BY primers.name desc
|
||||||
|
`
|
||||||
|
|
||||||
|
type GetPrimersRow struct {
|
||||||
|
ID pgtype.UUID `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Meta []byte `json:"meta"`
|
||||||
|
CreatedAt pgtype.Timestamp `json:"created_at"`
|
||||||
|
ManufacturerID pgtype.UUID `json:"manufacturer_id"`
|
||||||
|
ManufacturerName string `json:"manufacturer_name"`
|
||||||
|
ManufacturerUrl pgtype.Text `json:"manufacturer_url"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) GetPrimers(ctx context.Context) ([]GetPrimersRow, error) {
|
||||||
|
rows, err := q.db.Query(ctx, getPrimers)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var items []GetPrimersRow
|
||||||
|
for rows.Next() {
|
||||||
|
var i GetPrimersRow
|
||||||
|
if err := rows.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.Name,
|
||||||
|
&i.Meta,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.ManufacturerID,
|
||||||
|
&i.ManufacturerName,
|
||||||
|
&i.ManufacturerUrl,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const insertPrimer = `-- name: InsertPrimer :exec
|
||||||
|
INSERT INTO primers (id, name, manufacturer_id, meta, photo)
|
||||||
|
VALUES ($1, $2, $3, $4, $5)
|
||||||
|
`
|
||||||
|
|
||||||
|
type InsertPrimerParams struct {
|
||||||
|
ID pgtype.UUID `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
ManufacturerID pgtype.UUID `json:"manufacturer_id"`
|
||||||
|
Meta []byte `json:"meta"`
|
||||||
|
Photo []byte `json:"photo"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) InsertPrimer(ctx context.Context, arg InsertPrimerParams) error {
|
||||||
|
_, err := q.db.Exec(ctx, insertPrimer,
|
||||||
|
arg.ID,
|
||||||
|
arg.Name,
|
||||||
|
arg.ManufacturerID,
|
||||||
|
arg.Meta,
|
||||||
|
arg.Photo,
|
||||||
|
)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatePrimer = `-- name: UpdatePrimer :exec
|
||||||
|
UPDATE primers
|
||||||
|
SET name = $1,
|
||||||
|
manufacturer_id = $2,
|
||||||
|
meta = $3,
|
||||||
|
photo = $4
|
||||||
|
WHERE id = $5
|
||||||
|
`
|
||||||
|
|
||||||
|
type UpdatePrimerParams struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
ManufacturerID pgtype.UUID `json:"manufacturer_id"`
|
||||||
|
Meta []byte `json:"meta"`
|
||||||
|
Photo []byte `json:"photo"`
|
||||||
|
ID pgtype.UUID `json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) UpdatePrimer(ctx context.Context, arg UpdatePrimerParams) error {
|
||||||
|
_, err := q.db.Exec(ctx, updatePrimer,
|
||||||
|
arg.Name,
|
||||||
|
arg.ManufacturerID,
|
||||||
|
arg.Meta,
|
||||||
|
arg.Photo,
|
||||||
|
arg.ID,
|
||||||
|
)
|
||||||
|
return err
|
||||||
|
}
|
||||||
47
backend/models/sqlc.yaml
Normal file
47
backend/models/sqlc.yaml
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
version: "2"
|
||||||
|
sql:
|
||||||
|
- schema: "bullets-schema.sql"
|
||||||
|
engine: "postgresql"
|
||||||
|
queries: "bullets.sql"
|
||||||
|
gen:
|
||||||
|
go:
|
||||||
|
package: "bullets"
|
||||||
|
out: "bullets"
|
||||||
|
emit_json_tags: true
|
||||||
|
sql_package: "pgx/v5"
|
||||||
|
- schema: "manufacturer-schema.sql"
|
||||||
|
engine: "postgresql"
|
||||||
|
queries: "manufacturer.sql"
|
||||||
|
gen:
|
||||||
|
go:
|
||||||
|
package: "manufacturer"
|
||||||
|
out: "manufacturer"
|
||||||
|
emit_json_tags: true
|
||||||
|
sql_package: "pgx/v5"
|
||||||
|
- schema: "primers-schema.sql"
|
||||||
|
engine: "postgresql"
|
||||||
|
queries: "primers.sql"
|
||||||
|
gen:
|
||||||
|
go:
|
||||||
|
package: "primers"
|
||||||
|
out: "primers"
|
||||||
|
emit_json_tags: true
|
||||||
|
sql_package: "pgx/v5"
|
||||||
|
- schema: "powder-schema.sql"
|
||||||
|
engine: "postgresql"
|
||||||
|
queries: "powder.sql"
|
||||||
|
gen:
|
||||||
|
go:
|
||||||
|
package: "powder"
|
||||||
|
out: "powder"
|
||||||
|
emit_json_tags: true
|
||||||
|
sql_package: "pgx/v5"
|
||||||
|
- schema: "loads-schema.sql"
|
||||||
|
engine: "postgresql"
|
||||||
|
queries: "loads.sql"
|
||||||
|
gen:
|
||||||
|
go:
|
||||||
|
package: "loads"
|
||||||
|
out: "loads"
|
||||||
|
emit_json_tags: true
|
||||||
|
sql_package: "pgx/v5"
|
||||||
0
docker-compose.yml
Normal file
0
docker-compose.yml
Normal file
1
frontend/.env
Normal file
1
frontend/.env
Normal file
@@ -0,0 +1 @@
|
|||||||
|
VITE_API=http://localhost:8080
|
||||||
24
frontend/.gitignore
vendored
Normal file
24
frontend/.gitignore
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
*.local
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
.DS_Store
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
1
frontend/.nvmrc
Normal file
1
frontend/.nvmrc
Normal file
@@ -0,0 +1 @@
|
|||||||
|
v22.14.0
|
||||||
18
frontend/README.md
Normal file
18
frontend/README.md
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Vue 3 + TypeScript + Vite
|
||||||
|
|
||||||
|
This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
|
||||||
|
|
||||||
|
## Recommended IDE Setup
|
||||||
|
|
||||||
|
- [VS Code](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin).
|
||||||
|
|
||||||
|
## Type Support For `.vue` Imports in TS
|
||||||
|
|
||||||
|
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin) to make the TypeScript language service aware of `.vue` types.
|
||||||
|
|
||||||
|
If the standalone TypeScript plugin doesn't feel fast enough to you, Volar has also implemented a [Take Over Mode](https://github.com/johnsoncodehk/volar/discussions/471#discussioncomment-1361669) that is more performant. You can enable it by the following steps:
|
||||||
|
|
||||||
|
1. Disable the built-in TypeScript Extension
|
||||||
|
1. Run `Extensions: Show Built-in Extensions` from VSCode's command palette
|
||||||
|
2. Find `TypeScript and JavaScript Language Features`, right click and select `Disable (Workspace)`
|
||||||
|
2. Reload the VSCode window by running `Developer: Reload Window` from the command palette.
|
||||||
14
frontend/index.html
Normal file
14
frontend/index.html
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8"/>
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/vite.svg"/>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||||
|
<title>Load Data Manager</title>
|
||||||
|
<script src="https://kit.fontawesome.com/1a962b66c9.js" crossorigin="anonymous"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module" src="/src/main.ts"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
2951
frontend/package-lock.json
generated
Normal file
2951
frontend/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
31
frontend/package.json
Normal file
31
frontend/package.json
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"name": "frontend",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "vue-tsc && vite build",
|
||||||
|
"preview": "vite preview"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@primeuix/themes": "^1.0.0",
|
||||||
|
"@tailwindcss/postcss": "^4.0.15",
|
||||||
|
"@tailwindcss/vite": "^4.0.15",
|
||||||
|
"@vuelidate/core": "^2.0.3",
|
||||||
|
"@vuelidate/validators": "^2.0.4",
|
||||||
|
"axios": "^1.8.4",
|
||||||
|
"primevue": "^4.3.2",
|
||||||
|
"sass": "^1.86.0",
|
||||||
|
"vue": "^3.5.13",
|
||||||
|
"vue-router": "^4.5.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@vitejs/plugin-vue": "^5.2.3",
|
||||||
|
"autoprefixer": "^10.4.21",
|
||||||
|
"tailwindcss": "^4.0.15",
|
||||||
|
"typescript": "^5.8.2",
|
||||||
|
"vite": "^6.2.2",
|
||||||
|
"vue-tsc": "^2.2.8"
|
||||||
|
}
|
||||||
|
}
|
||||||
1
frontend/public/vite.svg
Normal file
1
frontend/public/vite.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
||||||
|
After Width: | Height: | Size: 1.5 KiB |
22
frontend/src/App.vue
Normal file
22
frontend/src/App.vue
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<template>
|
||||||
|
<Toast />
|
||||||
|
<TopMenubar />
|
||||||
|
<div class="flex flex-row m-10 justify-around">
|
||||||
|
<router-view />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { defineAsyncComponent } from 'vue'
|
||||||
|
|
||||||
|
const TopMenubar = defineAsyncComponent(() => import('./components/TopMenubar.vue'));
|
||||||
|
const Toast = defineAsyncComponent(() => import('primevue/toast'));
|
||||||
|
|
||||||
|
</script>
|
||||||
|
<style>
|
||||||
|
@import "tailwindcss";
|
||||||
|
|
||||||
|
html, body {
|
||||||
|
font-family: 'Nunito', sans-serif;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
1
frontend/src/assets/vue.svg
Normal file
1
frontend/src/assets/vue.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>
|
||||||
|
After Width: | Height: | Size: 496 B |
0
frontend/src/axios/index.ts
Normal file
0
frontend/src/axios/index.ts
Normal file
109
frontend/src/components/TopMenubar.vue
Normal file
109
frontend/src/components/TopMenubar.vue
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<Menubar :model="items">
|
||||||
|
<template #start>
|
||||||
|
<router-link to="/">
|
||||||
|
Load Data Manager
|
||||||
|
</router-link>
|
||||||
|
<i class="ml-3 mr-3 fa-thin fa-gun"></i>
|
||||||
|
::
|
||||||
|
</template>
|
||||||
|
</Menubar>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { defineAsyncComponent, ref } from 'vue'
|
||||||
|
import { MenuItem } from 'primevue/menuitem'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
import { icons } from '../lib/icons.ts'
|
||||||
|
|
||||||
|
const Menubar = defineAsyncComponent(() => import('primevue/menubar'))
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
const items = ref<MenuItem[]>([
|
||||||
|
{
|
||||||
|
label: 'Loads',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
label: 'New',
|
||||||
|
command: () => router.push('/loads/add'),
|
||||||
|
icon: icons.add
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Edit/Search',
|
||||||
|
command: () => router.push('/loads/search'),
|
||||||
|
icon: icons.edit
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Components',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
label: 'Bullets',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
label: 'New',
|
||||||
|
command: () => router.push('/bullets/add'),
|
||||||
|
icon: icons.add
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'List',
|
||||||
|
command: () => router.push('/bullets'),
|
||||||
|
icon: icons.list
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Powders',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
label: 'New',
|
||||||
|
command: () => router.push('/powders/add'),
|
||||||
|
icon: icons.add
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'List',
|
||||||
|
command: () => router.push('/powders'),
|
||||||
|
icon: icons.list
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Primers',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
label: 'New',
|
||||||
|
command: () => router.push('/primers/add'),
|
||||||
|
icon: icons.add
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'List',
|
||||||
|
command: () => router.push('/primers'),
|
||||||
|
icon: icons.list
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Manufacturers',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
label: 'Manage Manufacturers',
|
||||||
|
command: () => router.push('/manufacturers'),
|
||||||
|
icon: icons.add
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Add Manufacturer',
|
||||||
|
command: () => router.push('/manufacturers/add'),
|
||||||
|
icon: icons.edit
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
separator: true
|
||||||
|
}
|
||||||
|
])
|
||||||
|
</script>
|
||||||
8
frontend/src/lib/icons.ts
Normal file
8
frontend/src/lib/icons.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export const icons = {
|
||||||
|
add: 'fa-thin fa-sharp fa-plus',
|
||||||
|
edit: 'fa-thin fa-sharp fa-pencil',
|
||||||
|
list: 'fa-thin fa-sharp fa-list',
|
||||||
|
upload: 'fa-thin fa-sharp fa-upload',
|
||||||
|
save: 'fa-thin fa-sharp fa-floppy-disk',
|
||||||
|
delete: 'fa-thin fa-sharp fa-trash',
|
||||||
|
}
|
||||||
20
frontend/src/main.ts
Normal file
20
frontend/src/main.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { createApp } from 'vue'
|
||||||
|
import Aura from '@primeuix/themes/aura'
|
||||||
|
|
||||||
|
import App from './App.vue'
|
||||||
|
import router from './router'
|
||||||
|
import PrimeVue from 'primevue/config'
|
||||||
|
|
||||||
|
const app = createApp(App)
|
||||||
|
app.use(router)
|
||||||
|
app.use(PrimeVue, {
|
||||||
|
theme: {
|
||||||
|
preset: Aura,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
import ToastService from 'primevue/toastservice'
|
||||||
|
|
||||||
|
app.use(ToastService)
|
||||||
|
|
||||||
|
app.mount('#app')
|
||||||
16
frontend/src/pages/Index.vue
Normal file
16
frontend/src/pages/Index.vue
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<template>
|
||||||
|
<Card>
|
||||||
|
<template #title> Welcome to Your Load Database </template>
|
||||||
|
<template #content>
|
||||||
|
<p>
|
||||||
|
This is the place to track and create loads for all of your ammunition. Use the links at the top to navigate.
|
||||||
|
</p>
|
||||||
|
</template>
|
||||||
|
</Card>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { defineAsyncComponent } from 'vue'
|
||||||
|
|
||||||
|
const Card = defineAsyncComponent(() => import('primevue/card'))
|
||||||
|
|
||||||
|
</script>
|
||||||
166
frontend/src/pages/bullets/Add.vue
Normal file
166
frontend/src/pages/bullets/Add.vue
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
<template>
|
||||||
|
<Card class="w-full md:w-1/2">
|
||||||
|
<template #title>Add New Bullet</template>
|
||||||
|
<template #content>
|
||||||
|
<div class="w-full">
|
||||||
|
<label>Manufacturer</label>
|
||||||
|
<Dropdown :options="manufacturers" option-label="name" option-value="id" v-model="bullet.manufacturer_id"
|
||||||
|
class="w-full" />
|
||||||
|
</div>
|
||||||
|
<div class="m-5" v-if="bullet.manufacturer_id === '-- Add --'">
|
||||||
|
<div class="w-full m-1">
|
||||||
|
<label>Manufacturer Name</label>
|
||||||
|
<div class="w-full">
|
||||||
|
<InputText v-model="add.name" class="w-full" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="w-full m-1">
|
||||||
|
<label>Manufacturer Url</label>
|
||||||
|
<div class="w-full">
|
||||||
|
<InputText v-model="add.url" class="w-full" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="w-full mt-3">
|
||||||
|
<label>Bullet Name</label>
|
||||||
|
<InputText v-model="bullet.name" class="w-full" />
|
||||||
|
</div>
|
||||||
|
<div class="w-full mt-3">
|
||||||
|
<label>Bullet Diameter</label>
|
||||||
|
<InputNumber v-model="bullet.diameter" prefix="." :min="0" :max="999" class="w-full" />
|
||||||
|
</div>
|
||||||
|
<div class="w-full mt-3">
|
||||||
|
<label>Grain Weight</label>
|
||||||
|
<InputNumber v-model="bullet.weight" suffix=" gr" :min="0" :max="1000" class="w-full" />
|
||||||
|
</div>
|
||||||
|
<div class="w-full mt-3">
|
||||||
|
<label>Picture</label>
|
||||||
|
<FileUpload v-model="file" mode="basic" @select="fileSelected" customUpload />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #footer>
|
||||||
|
<div class="mt-3">
|
||||||
|
<Button :loading="loading" @click="create" label="Add" :icon="icons.add" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Card>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { defineAsyncComponent, ref } from 'vue'
|
||||||
|
import axios, { AxiosError } from 'axios'
|
||||||
|
import { Manufacturer } from '../../types/manufacturer'
|
||||||
|
import { Response } from '../../types/Response'
|
||||||
|
import { FileUploadSelectEvent } from 'primevue/fileupload'
|
||||||
|
import { Bullet } from '../../types/bullet'
|
||||||
|
import { ToastMessageOptions } from 'primevue/toast'
|
||||||
|
import { useToast } from 'primevue/usetoast'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
import { icons } from '../../lib/icons.ts'
|
||||||
|
|
||||||
|
const toast = useToast()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
const Dropdown = defineAsyncComponent(() => import('primevue/dropdown'))
|
||||||
|
const InputText = defineAsyncComponent(() => import('primevue/inputtext'))
|
||||||
|
const InputNumber = defineAsyncComponent(() => import('primevue/inputnumber'))
|
||||||
|
const FileUpload = defineAsyncComponent(() => import('primevue/fileupload'))
|
||||||
|
const Button = defineAsyncComponent(() => import('primevue/button'))
|
||||||
|
const Card = defineAsyncComponent(() => import('primevue/card'))
|
||||||
|
|
||||||
|
const add = ref({
|
||||||
|
name: '',
|
||||||
|
url: '',
|
||||||
|
})
|
||||||
|
|
||||||
|
const bullet = ref({
|
||||||
|
name: '',
|
||||||
|
diameter: 0,
|
||||||
|
manufacturer_id: '',
|
||||||
|
weight: 0,
|
||||||
|
meta: {},
|
||||||
|
})
|
||||||
|
|
||||||
|
const file = ref('')
|
||||||
|
const manufacturers = ref<Manufacturer[]>([])
|
||||||
|
const loading = ref(false)
|
||||||
|
|
||||||
|
const loadManufacturers = async () => {
|
||||||
|
await axios.get(`${import.meta.env.VITE_API}/manufacturer`).then((r: Response<Manufacturer[]>) => {
|
||||||
|
manufacturers.value.push({
|
||||||
|
name: '-- Add --',
|
||||||
|
id: '-- Add --',
|
||||||
|
} as Manufacturer)
|
||||||
|
|
||||||
|
if (!r.data.payload) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
r.data.payload.forEach((m: Manufacturer) => {
|
||||||
|
manufacturers.value.push(m)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const fileSelected = (event: FileUploadSelectEvent) => {
|
||||||
|
file.value = event.files[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
const createManufacturer = async () => {
|
||||||
|
return axios.post(`${import.meta.env.VITE_API}/manufacturer`, {
|
||||||
|
name: add.value.name,
|
||||||
|
url: add.value.url,
|
||||||
|
}, {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${jwt}`,
|
||||||
|
},
|
||||||
|
}).then((r: Response<Manufacturer>) => {
|
||||||
|
manufacturers.value.push(r.data.payload)
|
||||||
|
bullet.value.manufacturer_id = r.data.payload.id
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const create = async () => {
|
||||||
|
loading.value = true
|
||||||
|
if (bullet.value.manufacturer_id === '-- Add --') {
|
||||||
|
try {
|
||||||
|
await createManufacturer()
|
||||||
|
} catch (e) {
|
||||||
|
toast.add({
|
||||||
|
severity: 'error',
|
||||||
|
summary: 'Error',
|
||||||
|
detail: e,
|
||||||
|
life: 3000,
|
||||||
|
} as ToastMessageOptions)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let formData = new FormData()
|
||||||
|
formData.append('photo', file.value)
|
||||||
|
formData.append('manufacturer_id', bullet.value.manufacturer_id)
|
||||||
|
formData.append('weight', bullet.value.weight.toString())
|
||||||
|
formData.append('name', bullet.value.name)
|
||||||
|
formData.append('diameter', bullet.value.diameter.toString())
|
||||||
|
|
||||||
|
axios.post(`${import.meta.env.VITE_API}/bullet`,
|
||||||
|
formData, {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'multipart/form-data',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
).then((r: Response<string>) => {
|
||||||
|
router.push(`/bullets/edit/${r.data.payload}`)
|
||||||
|
}).catch((e: AxiosError<{ message: string }>) => {
|
||||||
|
loading.value = false
|
||||||
|
toast.add({
|
||||||
|
severity: 'error',
|
||||||
|
summary: 'Error',
|
||||||
|
detail: e.response?.data.message,
|
||||||
|
life: 3000,
|
||||||
|
} as ToastMessageOptions)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
loadManufacturers()
|
||||||
|
</script>
|
||||||
178
frontend/src/pages/bullets/Edit.vue
Normal file
178
frontend/src/pages/bullets/Edit.vue
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
<template>
|
||||||
|
<Card class="w-full md:w-1/2">
|
||||||
|
<template #title>
|
||||||
|
Edit Bullet
|
||||||
|
</template>
|
||||||
|
<template #content>
|
||||||
|
<div class="grid-cols-1">
|
||||||
|
<div>
|
||||||
|
<label>Manufacturer</label>
|
||||||
|
<Dropdown
|
||||||
|
class="w-full"
|
||||||
|
:options="manufacturers"
|
||||||
|
option-label="name"
|
||||||
|
option-value="id"
|
||||||
|
v-model="bullet.manufacturer_id"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="mt-3">
|
||||||
|
<label>Bullet Name</label>
|
||||||
|
<InputText class="w-full" v-model="bullet.name" />
|
||||||
|
</div>
|
||||||
|
<div class="w-full mt-3">
|
||||||
|
<label>Bullet Diameter</label>
|
||||||
|
<InputNumber v-model="bullet.diameter" prefix="." :min="0" :max="999" class="w-full" />
|
||||||
|
</div>
|
||||||
|
<div class="w-full mt-3">
|
||||||
|
<label>Grain Weight</label>
|
||||||
|
<InputNumber v-model="bullet.weight" suffix=" gr" :min="0" :max="1000" class="w-full" />
|
||||||
|
</div>
|
||||||
|
<div class="w-full mt-3">
|
||||||
|
<label>Picture</label>
|
||||||
|
<FileUpload v-model="file" mode="basic" @select="fileSelected" customUpload>
|
||||||
|
<template #uploadicon>
|
||||||
|
<i :class="icons.upload" class="mr-3" />
|
||||||
|
</template>
|
||||||
|
</FileUpload>
|
||||||
|
<div class="mt-3">
|
||||||
|
<Image v-if="!loading" :src="pictureUrl" alt="picture" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #footer>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
:loading="loading"
|
||||||
|
label="Save"
|
||||||
|
@click="save"
|
||||||
|
:icon="icons.save"
|
||||||
|
class="mr-3"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
severity="danger"
|
||||||
|
label="Delete"
|
||||||
|
:icon="icons.delete"
|
||||||
|
@click="deleteBullet"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</Card>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { computed, defineAsyncComponent, ref } from 'vue'
|
||||||
|
import axios from 'axios'
|
||||||
|
import { Manufacturer } from '../../types/manufacturer'
|
||||||
|
import { Response } from '../../types/Response'
|
||||||
|
import { FileUploadSelectEvent } from 'primevue/fileupload'
|
||||||
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
|
import { useToast } from 'primevue/usetoast'
|
||||||
|
import { icons } from '../../lib/icons.ts'
|
||||||
|
|
||||||
|
const Dropdown = defineAsyncComponent(() => import('primevue/dropdown'))
|
||||||
|
const InputText = defineAsyncComponent(() => import('primevue/inputtext'))
|
||||||
|
const InputNumber = defineAsyncComponent(() => import('primevue/inputnumber'))
|
||||||
|
const FileUpload = defineAsyncComponent(() => import('primevue/fileupload'))
|
||||||
|
const Image = defineAsyncComponent(() => import('primevue/image'))
|
||||||
|
const Button = defineAsyncComponent(() => import('primevue/button'))
|
||||||
|
const Card = defineAsyncComponent(() => import('primevue/card'))
|
||||||
|
|
||||||
|
interface BulletResponse {
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
diameter: number
|
||||||
|
weight: number
|
||||||
|
manufacturer: {
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
url: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
const router = useRouter()
|
||||||
|
const toast = useToast()
|
||||||
|
|
||||||
|
const loading = ref(false)
|
||||||
|
const bullet = ref({
|
||||||
|
diameter: 0,
|
||||||
|
weight: 0,
|
||||||
|
name: '',
|
||||||
|
manufacturer_id: '',
|
||||||
|
})
|
||||||
|
const file = ref('')
|
||||||
|
const fileSelected = (event: FileUploadSelectEvent) => {
|
||||||
|
console.log(event.files[0])
|
||||||
|
file.value = event.files[0]
|
||||||
|
}
|
||||||
|
const manufacturers = ref<Manufacturer[]>([])
|
||||||
|
|
||||||
|
const fetchManufacturers = async () => {
|
||||||
|
const response = await axios.get<any, Response<Manufacturer[]>>(`${import.meta.env.VITE_API}/manufacturer`)
|
||||||
|
manufacturers.value = response.data.payload
|
||||||
|
}
|
||||||
|
|
||||||
|
const pictureUrl = computed(() => {
|
||||||
|
const cache = new Date().getMilliseconds()
|
||||||
|
return import.meta.env.VITE_API + `/bullet/${route.params.id}/photo?cache=${cache}`
|
||||||
|
})
|
||||||
|
|
||||||
|
const deleteBullet = () => {
|
||||||
|
axios.delete(`${import.meta.env.VITE_API}/bullet/${route.params.id}`).then(() => {
|
||||||
|
router.push('/bullets')
|
||||||
|
}).catch((error) => {
|
||||||
|
toast.add({
|
||||||
|
severity: 'error',
|
||||||
|
summary: 'Error',
|
||||||
|
detail: error.response.data.message,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const get = async () => {
|
||||||
|
const response = await axios.get<any, Response<BulletResponse>>(
|
||||||
|
`${import.meta.env.VITE_API}/bullet/${route.params.id}`)
|
||||||
|
bullet.value.manufacturer_id = response.data.payload.manufacturer.id
|
||||||
|
bullet.value.name = response.data.payload.name
|
||||||
|
bullet.value.diameter = response.data.payload.diameter
|
||||||
|
bullet.value.weight = response.data.payload.weight
|
||||||
|
}
|
||||||
|
|
||||||
|
const save = async () => {
|
||||||
|
loading.value = true
|
||||||
|
|
||||||
|
let formData = new FormData()
|
||||||
|
formData.append('photo', file.value)
|
||||||
|
formData.append('manufacturer_id', bullet.value.manufacturer_id)
|
||||||
|
formData.append('weight', bullet.value.weight.toString())
|
||||||
|
formData.append('name', bullet.value.name)
|
||||||
|
formData.append('diameter', bullet.value.diameter.toString())
|
||||||
|
axios.post(`${import.meta.env.VITE_API}/bullet/${route.params.id}`,
|
||||||
|
formData, {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'multipart/form-data',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
).then(() => {
|
||||||
|
loading.value = false
|
||||||
|
toast.add({
|
||||||
|
severity: 'success',
|
||||||
|
summary: 'saved!',
|
||||||
|
detail: 'bullet saved',
|
||||||
|
life: 3000,
|
||||||
|
})
|
||||||
|
|
||||||
|
get()
|
||||||
|
}).catch((e) => {
|
||||||
|
toast.add({
|
||||||
|
severity: 'error',
|
||||||
|
summary: 'error',
|
||||||
|
detail: e.response?.data.message,
|
||||||
|
life: 3000,
|
||||||
|
})
|
||||||
|
loading.value = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchManufacturers()
|
||||||
|
get()
|
||||||
|
</script>
|
||||||
48
frontend/src/pages/bullets/List.vue
Normal file
48
frontend/src/pages/bullets/List.vue
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
<template>
|
||||||
|
<Card class="w-full md:w-2/3">
|
||||||
|
<template #title>
|
||||||
|
Bullets
|
||||||
|
</template>
|
||||||
|
<template #content>
|
||||||
|
<DataTable :value="bullets">
|
||||||
|
<template #empty>
|
||||||
|
<div class="p-text-center p-text-secondary p-text-bold p-text-uppercase">No bullets found</div>
|
||||||
|
</template>
|
||||||
|
<Column header="Bullet">
|
||||||
|
<template #body="slotProps">
|
||||||
|
{{ slotProps.data.manufacturer.name }}
|
||||||
|
{{ slotProps.data.weight }}gr
|
||||||
|
{{ slotProps.data.name }}
|
||||||
|
</template>
|
||||||
|
</Column>
|
||||||
|
<Column header="Edit">
|
||||||
|
<template #body="slotProps">
|
||||||
|
<router-link class="p-button p-button-link p-button-sm" :to="`/bullets/edit/${slotProps.data.id}`">
|
||||||
|
<i :class="icons.edit"></i>
|
||||||
|
</router-link>
|
||||||
|
</template>
|
||||||
|
</Column>
|
||||||
|
</DataTable>
|
||||||
|
</template>
|
||||||
|
</Card>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { defineAsyncComponent, ref } from 'vue'
|
||||||
|
import axios from 'axios'
|
||||||
|
import { Bullet } from '../../types/bullet'
|
||||||
|
import {Response} from "../../types/Response";
|
||||||
|
import Column from 'primevue/column'
|
||||||
|
import { icons } from '../../lib/icons.ts'
|
||||||
|
|
||||||
|
const DataTable = defineAsyncComponent(() => import('primevue/datatable'))
|
||||||
|
const Card = defineAsyncComponent(() => import('primevue/card'))
|
||||||
|
|
||||||
|
const bullets = ref<Bullet[]>([])
|
||||||
|
|
||||||
|
const fetchBullets = async () => {
|
||||||
|
const response = await axios.get<any, Response<Bullet[]>>(`${import.meta.env.VITE_API}/bullet`)
|
||||||
|
bullets.value = response.data.payload
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchBullets()
|
||||||
|
</script>
|
||||||
297
frontend/src/pages/loads/Add.vue
Normal file
297
frontend/src/pages/loads/Add.vue
Normal file
@@ -0,0 +1,297 @@
|
|||||||
|
<template>
|
||||||
|
<Card class="md:w-2/3 w-full">
|
||||||
|
<template #title>
|
||||||
|
Add New Load
|
||||||
|
</template>
|
||||||
|
<template #content>
|
||||||
|
<div class="grid grid-cols-1">
|
||||||
|
<div>
|
||||||
|
Cartridge
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-row items-center">
|
||||||
|
<div class="grow mr-3">
|
||||||
|
<Select v-model="load.cartridge" :options="cartridges" optionLabel="label" optionValue="value"
|
||||||
|
class="w-full" />
|
||||||
|
<Message v-if="v$.$dirty && v$.cartridge.$invalid" :value="false" size="small" severity="error"
|
||||||
|
variant="simple">Cartridge Required
|
||||||
|
</Message>
|
||||||
|
</div>
|
||||||
|
<div class="cursor-pointer" @click="addCartridgeDialog = true">
|
||||||
|
<i :class="icons.add" class="fa-2x" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-3 mr-3">
|
||||||
|
Bullet
|
||||||
|
</div>
|
||||||
|
<div class="w-full mt-3">
|
||||||
|
<Select v-model="load.bullet" :invalid="v$.$dirty && v$.bullet.$invalid" :options="bullets"
|
||||||
|
optionLabel="label" optionValue="value" class="w-full" />
|
||||||
|
<Message v-if="v$.$dirty && v$.bullet.$invalid" :value="false" size="small" severity="error" variant="simple">
|
||||||
|
Bullet Required
|
||||||
|
</Message>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-3 mr-3">
|
||||||
|
Powder
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-row">
|
||||||
|
<div class="grow mr-3">
|
||||||
|
<Select v-model="load.powder" :options="powders" optionLabel="label" optionValue="value" class="w-full" />
|
||||||
|
<Message v-if="v$.$dirty && v$.powder.$invalid" :value="false" size="small" severity="error"
|
||||||
|
variant="simple">Powder Required
|
||||||
|
</Message>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<InputNumber v-model="load.powderGrs" placeholder="00.0 gr" suffix=" gr" :minFractionDigits="1"
|
||||||
|
:maxFractionDigits="1" :min="0"
|
||||||
|
:max="1000" class="w-full" />
|
||||||
|
<Message v-if="v$.$dirty && v$.powderGrs.$invalid" :value="false" size="small" severity="error"
|
||||||
|
variant="simple">Grains Required
|
||||||
|
</Message>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-3 mr-3">
|
||||||
|
Primer
|
||||||
|
</div>
|
||||||
|
<div class="w-full mt-3">
|
||||||
|
<Select v-model="load.primer" :options="primers" optionLabel="label" optionValue="value" class="w-full" />
|
||||||
|
<Message v-if="v$.$dirty && v$.primer.$invalid" :value="false" size="small" severity="error"
|
||||||
|
variant="simple">Primer Required
|
||||||
|
</Message>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-3 mr-3">
|
||||||
|
COL
|
||||||
|
</div>
|
||||||
|
<div class="w-full mt-3">
|
||||||
|
<InputMask 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>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="w-full mt-5">
|
||||||
|
<label>Picture</label>
|
||||||
|
<Message v-if="v$.$dirty && !file" :value="false" size="small" severity="error"
|
||||||
|
variant="simple">Picture Required
|
||||||
|
</Message>
|
||||||
|
<FileUpload v-model="file" mode="basic" @select="fileSelected" customUpload />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #footer>
|
||||||
|
<Button label="Add" :icon="icons.add" @click="add" />
|
||||||
|
</template>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Dialog @update:visible="addCartridgeDialog = false" modal :visible="addCartridgeDialog" :style="{ width: '35rem' }">
|
||||||
|
<template #header>
|
||||||
|
Add Cartridge
|
||||||
|
</template>
|
||||||
|
<div class="grid grid-cols-1">
|
||||||
|
<div>
|
||||||
|
Name
|
||||||
|
</div>
|
||||||
|
<div class="w-full">
|
||||||
|
<InputText :invalid="cartridgeName === ''" v-model="cartridgeName" class="w-full" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<Button :disabled="cartridgeName === ''" label="Add" :icon="icons.add" @click="addCartridgeName" />
|
||||||
|
</template>
|
||||||
|
</Dialog>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
<script lang="ts">
|
||||||
|
interface Select {
|
||||||
|
label: string
|
||||||
|
value: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Cartridge {
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { Bullet } from '../../types/bullet'
|
||||||
|
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 { 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'
|
||||||
|
|
||||||
|
const Card = defineAsyncComponent(() => import('primevue/card'))
|
||||||
|
const Select = defineAsyncComponent(() => import('primevue/select'))
|
||||||
|
const Button = defineAsyncComponent(() => import('primevue/button'))
|
||||||
|
const InputNumber = defineAsyncComponent(() => import('primevue/inputnumber'))
|
||||||
|
const InputText = defineAsyncComponent(() => import('primevue/inputtext'))
|
||||||
|
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 toast = useToast()
|
||||||
|
|
||||||
|
const bullets = ref<Select[]>([])
|
||||||
|
const primers = ref<Select[]>([])
|
||||||
|
const powders = ref<Select[]>([])
|
||||||
|
const cartridges = ref<Select[]>([])
|
||||||
|
|
||||||
|
const cartridgeName = ref('')
|
||||||
|
const addCartridgeDialog = ref(false)
|
||||||
|
|
||||||
|
const file = ref<File | null>(null)
|
||||||
|
const fileSelected = (e: FileUploadSelectEvent) => {
|
||||||
|
file.value = e.files[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
const load = ref({
|
||||||
|
bullet: '',
|
||||||
|
cartridge: '',
|
||||||
|
powder: '',
|
||||||
|
powderGrs: 0,
|
||||||
|
primer: '',
|
||||||
|
col: '',
|
||||||
|
})
|
||||||
|
|
||||||
|
const v$ = useVuelidate({
|
||||||
|
bullet: { required },
|
||||||
|
cartridge: { required },
|
||||||
|
powder: { required },
|
||||||
|
powderGrs: {
|
||||||
|
required, min: function (val: number | null) {
|
||||||
|
if (!val) return false
|
||||||
|
|
||||||
|
return val > 0
|
||||||
|
},
|
||||||
|
},
|
||||||
|
primer: { required },
|
||||||
|
col: {
|
||||||
|
required, min: function (val: string | null) {
|
||||||
|
if (!val) return false
|
||||||
|
|
||||||
|
// regex to validate COL does not container _
|
||||||
|
return !val.includes('_')
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, load.value)
|
||||||
|
|
||||||
|
const fetchBullets = async () => {
|
||||||
|
const response = await axios.get<any, Response<Bullet[]>>(`${import.meta.env.VITE_API}/bullet`)
|
||||||
|
response.data.payload.forEach((bullet: Bullet) => {
|
||||||
|
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 = []
|
||||||
|
|
||||||
|
const response = await axios.get<any, Response<Cartridge[]>>(`${import.meta.env.VITE_API}/cartridge`)
|
||||||
|
response.data.payload.forEach((cartridge: Cartridge) => {
|
||||||
|
cartridges.value.push({ label: `${cartridge.name}`, value: cartridge.id })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const addCartridgeName = async () => {
|
||||||
|
if (cartridgeName.value === '') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await axios.post<any, Response<Cartridge>>(`${import.meta.env.VITE_API}/cartridge`, {
|
||||||
|
name: cartridgeName.value,
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
toast.add({
|
||||||
|
severity: 'error',
|
||||||
|
summary: 'Error',
|
||||||
|
detail: 'Error adding cartridge',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
await fetchCartridges()
|
||||||
|
|
||||||
|
cartridgeName.value = ''
|
||||||
|
addCartridgeDialog.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const add = async () => {
|
||||||
|
v$.value.$touch()
|
||||||
|
|
||||||
|
if (v$.value.$invalid || !file.value) {
|
||||||
|
toast.add({
|
||||||
|
severity: 'error',
|
||||||
|
summary: 'Error',
|
||||||
|
detail: 'Please correct all fields',
|
||||||
|
life: 3000,
|
||||||
|
})
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const formData = new FormData()
|
||||||
|
formData.append('bullet_id', load.value.bullet)
|
||||||
|
formData.append('cartridge_id', load.value.cartridge)
|
||||||
|
formData.append('powder_id', load.value.powder)
|
||||||
|
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)
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await axios.post<any, Response<string>>(`${import.meta.env.VITE_API}/load`, formData, {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'multipart/form-data',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
toast.add({
|
||||||
|
severity: 'success',
|
||||||
|
summary: 'Success',
|
||||||
|
detail: 'Load added',
|
||||||
|
life: 3000,
|
||||||
|
})
|
||||||
|
|
||||||
|
await router.push(`/loads/edit/${response.data.payload}`)
|
||||||
|
} catch (error) {
|
||||||
|
toast.add({
|
||||||
|
severity: 'error',
|
||||||
|
summary: 'Error',
|
||||||
|
detail: 'Error adding load',
|
||||||
|
life: 3000,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
fetchBullets()
|
||||||
|
fetchPrimers()
|
||||||
|
fetchPowders()
|
||||||
|
fetchCartridges()
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
140
frontend/src/pages/loads/Search.vue
Normal file
140
frontend/src/pages/loads/Search.vue
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
<template>
|
||||||
|
<Card class="w-11/12">
|
||||||
|
<template #title>
|
||||||
|
Loads
|
||||||
|
</template>
|
||||||
|
<template #content>
|
||||||
|
<DataTable
|
||||||
|
:value="loads"
|
||||||
|
filterDisplay="row"
|
||||||
|
paginator
|
||||||
|
lazy
|
||||||
|
:rows="50"
|
||||||
|
:totalRecords="total"
|
||||||
|
:loading="loading"
|
||||||
|
>
|
||||||
|
<template #empty>
|
||||||
|
<div class="p-4">
|
||||||
|
<p>No loads found</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<Column field="cartridge" header="Name" :sortable="true" :showFilterMenu="false">
|
||||||
|
<template #filter>
|
||||||
|
<Select />
|
||||||
|
</template>
|
||||||
|
<template #body="{ data }">
|
||||||
|
{{ data.cartridge }}
|
||||||
|
</template>
|
||||||
|
|
||||||
|
</Column>
|
||||||
|
|
||||||
|
<Column field="bulletManufacturer" header="Bullet Manufacturer" :sortable="true" :showFilterMenu="false">
|
||||||
|
<template #filter>
|
||||||
|
<Select />
|
||||||
|
</template>
|
||||||
|
<template #body="{ data }">
|
||||||
|
{{ data.bullet.manufacturer.name }}
|
||||||
|
</template>
|
||||||
|
</Column>
|
||||||
|
<Column header="Bullet" :sortable="true" :showFilterMenu="false">
|
||||||
|
<template #filter>
|
||||||
|
<Select />
|
||||||
|
</template>
|
||||||
|
<template #body="{ data }">
|
||||||
|
{{ data.bullet.name }}
|
||||||
|
{{ data.bullet.weight }}gr
|
||||||
|
.{{ data.bullet.diameter }}
|
||||||
|
</template>
|
||||||
|
</Column>
|
||||||
|
<Column field="name" header="Primer Manufacturer" :sortable="true">
|
||||||
|
<template #body="{ data }">
|
||||||
|
{{ data.primer.manufacturer.name }}
|
||||||
|
</template>
|
||||||
|
</Column>
|
||||||
|
<Column field="name" header="Primer" :sortable="true">
|
||||||
|
<template #body="{ data }">
|
||||||
|
{{ data.primer.name }}
|
||||||
|
</template>
|
||||||
|
</Column>
|
||||||
|
|
||||||
|
<Column field="name" header="Powder Manufacturer" :sortable="true">
|
||||||
|
<template #body="{ data }">
|
||||||
|
{{ data.powder.manufacturer.name }}
|
||||||
|
</template>
|
||||||
|
</Column>
|
||||||
|
|
||||||
|
<Column field="name" header="Powder" :sortable="true">
|
||||||
|
<template #body="{ data }">
|
||||||
|
{{ data.powder.name }}
|
||||||
|
</template>
|
||||||
|
</Column>
|
||||||
|
|
||||||
|
<Column field="name" header="Powder Grs" :sortable="true">
|
||||||
|
<template #body="{ data }">
|
||||||
|
{{ data.powder_gr }}gr
|
||||||
|
</template>
|
||||||
|
</Column>
|
||||||
|
|
||||||
|
<Column field="name" header="Edit">
|
||||||
|
<template #body="{ data }">
|
||||||
|
<Button size="small" text :icon="icons.edit" />
|
||||||
|
</template>
|
||||||
|
</Column>
|
||||||
|
</DataTable>
|
||||||
|
</template>
|
||||||
|
</Card>
|
||||||
|
</template>
|
||||||
|
<script lang="ts">
|
||||||
|
import { Bullet } from '../../types/bullet'
|
||||||
|
import { Powder } from '../../types/powder'
|
||||||
|
import { Primers } from '../../types/primers'
|
||||||
|
|
||||||
|
interface LoadResponse {
|
||||||
|
total: number
|
||||||
|
results: Load[]
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Load {
|
||||||
|
id: string
|
||||||
|
bullet: Bullet
|
||||||
|
cartridge: string
|
||||||
|
powder: Powder
|
||||||
|
powder_gr: number
|
||||||
|
primer: Primers
|
||||||
|
col: number
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { defineAsyncComponent, onMounted, ref } from 'vue'
|
||||||
|
import { Response } from '../../types/Response'
|
||||||
|
import axios from 'axios'
|
||||||
|
import Column from 'primevue/column'
|
||||||
|
import { icons } from '../../lib/icons.ts'
|
||||||
|
|
||||||
|
const DataTable = defineAsyncComponent(() => import('primevue/datatable'))
|
||||||
|
const Card = defineAsyncComponent(() => import('primevue/card'))
|
||||||
|
const Button = defineAsyncComponent(() => import('primevue/button'))
|
||||||
|
const Select = defineAsyncComponent(() => import('primevue/select'))
|
||||||
|
|
||||||
|
const loads = ref<Load[]>([])
|
||||||
|
const total = ref(0)
|
||||||
|
const page = ref(1)
|
||||||
|
const loading = ref(true)
|
||||||
|
|
||||||
|
const fetchLoads = async () => {
|
||||||
|
loading.value = true
|
||||||
|
|
||||||
|
const resp = await axios.get<any, Response<LoadResponse>>(`${import.meta.env.VITE_API}/load`)
|
||||||
|
|
||||||
|
loads.value = resp.data.payload.results
|
||||||
|
total.value = resp.data.payload.total
|
||||||
|
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
fetchLoads()
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
72
frontend/src/pages/manufacturers/Add.vue
Normal file
72
frontend/src/pages/manufacturers/Add.vue
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
<template>
|
||||||
|
<Card class="w-full md:w-1/2">
|
||||||
|
<template #title>
|
||||||
|
Add Manufacturer
|
||||||
|
</template>
|
||||||
|
<template #content>
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<label for="name">Name</label>
|
||||||
|
</div>
|
||||||
|
<InputText id="name" v-model="manufacturer.name" class="w-full" />
|
||||||
|
</div>
|
||||||
|
<div class="mt-4">
|
||||||
|
<label for="url">Url</label>
|
||||||
|
</div>
|
||||||
|
<InputText id="url" v-model="manufacturer.url" class="w-full" />
|
||||||
|
</template>
|
||||||
|
<template #footer>
|
||||||
|
<div class="flex justify-end mt-4">
|
||||||
|
<Button label="Add" @click="add" :loading="loading" :icon="icons.add" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Card>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { defineAsyncComponent, ref } from 'vue'
|
||||||
|
import axios from 'axios'
|
||||||
|
import { Response } from '../../types/Response'
|
||||||
|
import { useToast } from 'primevue/usetoast'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
import { icons } from '../../lib/icons.ts'
|
||||||
|
|
||||||
|
const Card = defineAsyncComponent(() => import('primevue/card'))
|
||||||
|
const InputText = defineAsyncComponent(() => import('primevue/inputtext'))
|
||||||
|
const Button = defineAsyncComponent(() => import('primevue/button'))
|
||||||
|
|
||||||
|
const toast = useToast()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
const loading = ref(false)
|
||||||
|
const manufacturer = ref({
|
||||||
|
name: '',
|
||||||
|
url: '',
|
||||||
|
})
|
||||||
|
|
||||||
|
const add = async () => {
|
||||||
|
loading.value = true
|
||||||
|
axios.post(`${import.meta.env.VITE_API}/manufacturer`, {
|
||||||
|
name: manufacturer.value.name,
|
||||||
|
url: manufacturer.value.url,
|
||||||
|
}).then((response: Response<string>) => {
|
||||||
|
toast.add({
|
||||||
|
severity: 'success',
|
||||||
|
summary: 'Added Manufacturer',
|
||||||
|
life: 3000,
|
||||||
|
})
|
||||||
|
if (response) {
|
||||||
|
router.push(`/manufacturers/edit/${response.data.payload}`)
|
||||||
|
}
|
||||||
|
}).finally(() => {
|
||||||
|
loading.value = false
|
||||||
|
}).catch((e) => {
|
||||||
|
toast.add({
|
||||||
|
severity: 'error',
|
||||||
|
summary: 'Something went wrong',
|
||||||
|
detail: e.response?.data.message,
|
||||||
|
life: 3000,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
100
frontend/src/pages/manufacturers/Edit.vue
Normal file
100
frontend/src/pages/manufacturers/Edit.vue
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
<template>
|
||||||
|
<Card class="w-full md:w-1/2">
|
||||||
|
<template #title>
|
||||||
|
Edit Manufacturer
|
||||||
|
</template>
|
||||||
|
<template #content>
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<label for="name">Name</label>
|
||||||
|
</div>
|
||||||
|
<InputText id="name" v-model="manufacturer.name" class="w-full" />
|
||||||
|
</div>
|
||||||
|
<div class="mt-4">
|
||||||
|
<label for="url">Url</label>
|
||||||
|
</div>
|
||||||
|
<InputText id="url" v-model="manufacturer.url" class="w-full" />
|
||||||
|
</template>
|
||||||
|
<template #footer>
|
||||||
|
<div class="flex justify-end mt-4">
|
||||||
|
<Button label="Save" @click="save" :loading="loading" :icon="icons.save" />
|
||||||
|
<Button severity="danger" label="Delete" @click="del" class="ml-2" :icon="icons.delete" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Card>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { defineAsyncComponent, onMounted, ref } from 'vue'
|
||||||
|
import axios from "axios";
|
||||||
|
import {Response} from "../../types/Response";
|
||||||
|
import {Manufacturer} from "../../types/manufacturer";
|
||||||
|
import { useToast } from 'primevue/usetoast'
|
||||||
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
|
import { icons } from '../../lib/icons.ts'
|
||||||
|
|
||||||
|
const Card = defineAsyncComponent(() => import('primevue/card'))
|
||||||
|
const InputText = defineAsyncComponent(() => import('primevue/inputtext'))
|
||||||
|
const Button = defineAsyncComponent(() => import('primevue/button'))
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
const toast = useToast()
|
||||||
|
const route = useRoute()
|
||||||
|
|
||||||
|
const loading = ref(false)
|
||||||
|
const manufacturer = ref({
|
||||||
|
name: '',
|
||||||
|
url: '',
|
||||||
|
})
|
||||||
|
|
||||||
|
const get = async () => {
|
||||||
|
loading.value = true
|
||||||
|
axios.get(`${import.meta.env.VITE_API}/manufacturer/${route.params.id}`).then((response: Response<Manufacturer>) => {
|
||||||
|
manufacturer.value.name = response.data.payload.name
|
||||||
|
manufacturer.value.url = response.data.payload.url
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
loading.value = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const del = async () => {
|
||||||
|
axios.delete(`${import.meta.env.VITE_API}/manufacturer/${route.params.id}`).then(() => {
|
||||||
|
router.push({path: '/manufacturers'})
|
||||||
|
}).catch((e) => {
|
||||||
|
toast.add({
|
||||||
|
severity: 'error',
|
||||||
|
summary: 'Something went wrong',
|
||||||
|
detail: e.response?.data.message,
|
||||||
|
life: 3000
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const save = async () => {
|
||||||
|
loading.value = true
|
||||||
|
axios.post(`${import.meta.env.VITE_API}/manufacturer/${route.params.id}`, {
|
||||||
|
name: manufacturer.value.name,
|
||||||
|
url: manufacturer.value.url,
|
||||||
|
}).finally(() => {
|
||||||
|
loading.value = false
|
||||||
|
}).catch((e) => {
|
||||||
|
toast.add({
|
||||||
|
severity: 'error',
|
||||||
|
summary: 'Something went wrong',
|
||||||
|
detail: e.response?.data.message,
|
||||||
|
life: 3000
|
||||||
|
})
|
||||||
|
}).then(() => {
|
||||||
|
toast.add({
|
||||||
|
severity: 'success',
|
||||||
|
summary: 'Successfully Saved',
|
||||||
|
life: 3000
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
get()
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
56
frontend/src/pages/manufacturers/List.vue
Normal file
56
frontend/src/pages/manufacturers/List.vue
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<DataTable size="small" :value="manufacturers" tableStyle="min-width: 50rem">
|
||||||
|
<template #empty>
|
||||||
|
<div class="p-text-center">
|
||||||
|
<p>No manufacturers found.</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<Column header="Name">
|
||||||
|
<template #body="slotProps">
|
||||||
|
<a v-if="slotProps.data.url" target="_blank" :href="slotProps.data.url">{{ slotProps.data.name }}</a>
|
||||||
|
<span v-else>{{ slotProps.data.name }}</span>
|
||||||
|
</template>
|
||||||
|
</Column>
|
||||||
|
<Column header="Edit">
|
||||||
|
<template #body="slotProps">
|
||||||
|
<router-link
|
||||||
|
:to="`/manufacturers/edit/${slotProps.data.id}`"
|
||||||
|
class="p-button p-button-link p-button-sm"
|
||||||
|
>Edit</router-link>
|
||||||
|
</template>
|
||||||
|
</Column>
|
||||||
|
</DataTable>
|
||||||
|
</div>
|
||||||
|
<div class="mt-5">
|
||||||
|
<Button @click="router.push('/manufacturers/add')" label="Add" :icon="icons.add" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { defineAsyncComponent, ref } from 'vue'
|
||||||
|
import axios from "axios";
|
||||||
|
import {Response} from "../../types/Response";
|
||||||
|
import {Manufacturer} from "../../types/manufacturer";
|
||||||
|
import Column from 'primevue/column';
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
import { icons } from '../../lib/icons.ts'
|
||||||
|
|
||||||
|
const DataTable = defineAsyncComponent(() => import('primevue/datatable'));
|
||||||
|
const Button = defineAsyncComponent(() => import('primevue/button'));
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
const manufacturers = ref<Manufacturer[]>([])
|
||||||
|
|
||||||
|
const fetchManufacturers = async () => {
|
||||||
|
axios.get(`${import.meta.env.VITE_API}/manufacturer`).then((response: Response<Manufacturer[]>) => {
|
||||||
|
if (response.data.payload) {
|
||||||
|
manufacturers.value = response.data.payload
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchManufacturers()
|
||||||
|
|
||||||
|
</script>
|
||||||
95
frontend/src/pages/powders/Add.vue
Normal file
95
frontend/src/pages/powders/Add.vue
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
<template>
|
||||||
|
<Card class="w-full md:w-1/2">
|
||||||
|
<template #title>
|
||||||
|
Add Powder
|
||||||
|
</template>
|
||||||
|
<template #content>
|
||||||
|
<div class="grid grid-cols-1 gap-6">
|
||||||
|
<div class="w-full">
|
||||||
|
<label>Manufacturer</label>
|
||||||
|
<Dropdown :options="manufacturers" option-label="name" option-value="id" v-model="powder.manufacturer_id"
|
||||||
|
class="w-full" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label>Name</label>
|
||||||
|
<InputText v-model="powder.name" class="w-full" />
|
||||||
|
</div>
|
||||||
|
<div class="w-full mt-3">
|
||||||
|
<label>Picture</label>
|
||||||
|
<FileUpload v-model="file" mode="basic" @select="fileSelected" customUpload />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #footer>
|
||||||
|
<Button :loading="loading" label="Add" @click="add" :icon="icons.save" />
|
||||||
|
</template>
|
||||||
|
</Card>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { defineAsyncComponent, onMounted, ref } from 'vue'
|
||||||
|
import { fetchManufacturers } from '../../services/manufacturers.ts'
|
||||||
|
import { Manufacturer } from '../../types/manufacturer'
|
||||||
|
import { FileUploadSelectEvent } from 'primevue/fileupload'
|
||||||
|
import axios from 'axios'
|
||||||
|
import router from '../../router'
|
||||||
|
import { useToast } from 'primevue/usetoast'
|
||||||
|
import { Response } from '../../types/Response'
|
||||||
|
import { icons } from '../../lib/icons.ts'
|
||||||
|
|
||||||
|
const Card = defineAsyncComponent(() => import('primevue/card'))
|
||||||
|
const Dropdown = defineAsyncComponent(() => import('primevue/dropdown'))
|
||||||
|
const InputText = defineAsyncComponent(() => import('primevue/inputtext'))
|
||||||
|
const Button = defineAsyncComponent(() => import('primevue/button'))
|
||||||
|
const FileUpload = defineAsyncComponent(() => import('primevue/fileupload'))
|
||||||
|
|
||||||
|
const toast = useToast()
|
||||||
|
|
||||||
|
const manufacturers = ref<Manufacturer[]>([])
|
||||||
|
const file = ref<File | null>(null)
|
||||||
|
const loading = ref(false)
|
||||||
|
|
||||||
|
const powder = ref({
|
||||||
|
manufacturer_id: '',
|
||||||
|
name: '',
|
||||||
|
})
|
||||||
|
|
||||||
|
const fileSelected = (event: FileUploadSelectEvent) => {
|
||||||
|
file.value = event.files[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
const add = () => {
|
||||||
|
loading.value = true
|
||||||
|
const formData = new FormData()
|
||||||
|
|
||||||
|
formData.append('manufacturer_id', powder.value.manufacturer_id)
|
||||||
|
formData.append('name', powder.value.name)
|
||||||
|
if (file.value) {
|
||||||
|
formData.append('photo', file.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
axios.post(`${import.meta.env.VITE_API}/powder`, formData, {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'multipart/form-data',
|
||||||
|
},
|
||||||
|
}).
|
||||||
|
then((r: Response<{ id: string }>) => {
|
||||||
|
router.push(`/powders/edit/${r.data.payload.id}`)
|
||||||
|
}).catch(() => {
|
||||||
|
toast.add({
|
||||||
|
severity: 'error',
|
||||||
|
summary: 'Error',
|
||||||
|
detail: 'An error occurred while adding the powder',
|
||||||
|
life: 3000,
|
||||||
|
})
|
||||||
|
}).finally(() => {
|
||||||
|
loading.value = false
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
const resp = await fetchManufacturers()
|
||||||
|
manufacturers.value.push(...resp.payload)
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
133
frontend/src/pages/powders/Edit.vue
Normal file
133
frontend/src/pages/powders/Edit.vue
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
<template>
|
||||||
|
<Card class="w-full md:w-1/2">
|
||||||
|
<template #title>
|
||||||
|
<h2>Edit Powder</h2>
|
||||||
|
</template>
|
||||||
|
<template #content>
|
||||||
|
<div v-if="powder" class="flex flex-wrap flex-col md:flex-row">
|
||||||
|
<div class="w-full mt-5">
|
||||||
|
<label>Manufacturer</label>
|
||||||
|
<Select :options="manufacturers" option-label="name" option-value="id" v-model="powder.manufacturer.id"
|
||||||
|
class="w-full" />
|
||||||
|
</div>
|
||||||
|
<div class="w-full mt-5">
|
||||||
|
<label for="name">Name</label>
|
||||||
|
<InputText v-model="powder.name" type="text" id="name" class="w-full" />
|
||||||
|
</div>
|
||||||
|
<div class="mt-3">
|
||||||
|
<label>Picture</label>
|
||||||
|
<FileUpload v-model="file" mode="basic" @select="fileSelected" customUpload>
|
||||||
|
<template #uploadicon>
|
||||||
|
<i :class="icons.upload" class="mr-3"></i>
|
||||||
|
</template>
|
||||||
|
</FileUpload>
|
||||||
|
<div class="mt-3">
|
||||||
|
<Image v-if="!loading" :src="pictureUrl" alt="picture" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #footer>
|
||||||
|
<Button class="mr-3" :loading="loading" @click="update" label="Save" :icon="icons.save" />
|
||||||
|
<Button severity="danger" :loading="loading" @click="deletePowder" label="Delete" :icon="icons.delete" />
|
||||||
|
</template>
|
||||||
|
</Card>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { computed, defineAsyncComponent, onMounted, ref } from 'vue'
|
||||||
|
import axios from 'axios'
|
||||||
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
|
import { Response } from '../../types/Response'
|
||||||
|
import { Powder } from '../../types/powder'
|
||||||
|
import { Manufacturer } from '../../types/manufacturer'
|
||||||
|
import { fetchManufacturers } from '../../services/manufacturers.ts'
|
||||||
|
import { FileUploadSelectEvent } from 'primevue/fileupload'
|
||||||
|
import { useToast } from 'primevue/usetoast'
|
||||||
|
import { icons } from '../../lib/icons.ts'
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
const router = useRouter()
|
||||||
|
const toast = useToast()
|
||||||
|
|
||||||
|
const Card = defineAsyncComponent(() => import('primevue/card'))
|
||||||
|
const InputText = defineAsyncComponent(() => import('primevue/inputtext'))
|
||||||
|
const Select = defineAsyncComponent(() => import('primevue/select'))
|
||||||
|
const Image = defineAsyncComponent(() => import('primevue/image'))
|
||||||
|
const FileUpload = defineAsyncComponent(() => import('primevue/fileupload'))
|
||||||
|
const Button = defineAsyncComponent(() => import('primevue/button'))
|
||||||
|
|
||||||
|
const powder = ref<Powder | null>(null)
|
||||||
|
const manufacturers = ref<Manufacturer[]>([])
|
||||||
|
const loading = ref(false)
|
||||||
|
|
||||||
|
const getPowder = async () => {
|
||||||
|
axios.get(`${import.meta.env.VITE_API}/powder/${route.params.id}`).then((response: Response<Powder>) => {
|
||||||
|
powder.value = response.data.payload
|
||||||
|
}).catch((error) => {
|
||||||
|
console.log(error)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const file = ref('')
|
||||||
|
const fileSelected = (event: FileUploadSelectEvent) => {
|
||||||
|
file.value = event.files[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
const pictureUrl = computed(() => {
|
||||||
|
const cache = new Date().getMilliseconds()
|
||||||
|
return import.meta.env.VITE_API + `/powder/${route.params.id}/photo?cache=${cache}`
|
||||||
|
})
|
||||||
|
|
||||||
|
const deletePowder = () => {
|
||||||
|
axios.delete(`${import.meta.env.VITE_API}/powder/${route.params.id}`).then(() => {
|
||||||
|
router.push('/powders')
|
||||||
|
}).catch((error) => {
|
||||||
|
toast.add({
|
||||||
|
severity: 'error',
|
||||||
|
summary: 'Error',
|
||||||
|
detail: error.response.data.message,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const update = () => {
|
||||||
|
loading.value = true
|
||||||
|
|
||||||
|
let formData = new FormData()
|
||||||
|
formData.append('photo', file.value)
|
||||||
|
formData.append('name', powder.value?.name || '')
|
||||||
|
formData.append('manufacturer_id', powder.value?.manufacturer.id || '')
|
||||||
|
|
||||||
|
axios.post(`${import.meta.env.VITE_API}/powder/${route.params.id}`,
|
||||||
|
formData, {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'multipart/form-data',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
).then(() => {
|
||||||
|
loading.value = false
|
||||||
|
toast.add({
|
||||||
|
severity: 'success',
|
||||||
|
summary: 'saved!',
|
||||||
|
detail: 'powder saved',
|
||||||
|
life: 3000,
|
||||||
|
})
|
||||||
|
|
||||||
|
getPowder()
|
||||||
|
}).catch(() => {
|
||||||
|
loading.value = false
|
||||||
|
toast.add({
|
||||||
|
severity: 'error',
|
||||||
|
summary: 'error!',
|
||||||
|
detail: 'powder not saved',
|
||||||
|
life: 3000,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
getPowder().then()
|
||||||
|
const resp = await fetchManufacturers()
|
||||||
|
manufacturers.value.push(...resp.payload)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
57
frontend/src/pages/powders/List.vue
Normal file
57
frontend/src/pages/powders/List.vue
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
<template>
|
||||||
|
<Card class="w-full md:w-2/3">
|
||||||
|
<template #title>
|
||||||
|
<h2>Powders</h2>
|
||||||
|
</template>
|
||||||
|
<template #content>
|
||||||
|
<DataTable :value="powders">
|
||||||
|
<template #empty>
|
||||||
|
<div class="p-4">
|
||||||
|
<p>No powders found</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<Column field="manufacturer" header="Name">
|
||||||
|
<template #body="{data}">
|
||||||
|
<span>{{ data.manufacturer.name }} {{ data.name }}</span>
|
||||||
|
</template>
|
||||||
|
</Column>
|
||||||
|
<Column field="actions" header="Actions">
|
||||||
|
<template #body="{data}">
|
||||||
|
<Button
|
||||||
|
text
|
||||||
|
size="small"
|
||||||
|
:icon="icons.edit"
|
||||||
|
@click="router.push(`/powders/edit/${data.id}`)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</Column>
|
||||||
|
</DataTable>
|
||||||
|
</template>
|
||||||
|
</Card>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import axios from 'axios'
|
||||||
|
import { Response } from '../../types/Response'
|
||||||
|
import { Powder } from '../../types/powder'
|
||||||
|
import { defineAsyncComponent, ref } from 'vue'
|
||||||
|
import Column from 'primevue/column'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
import { icons } from '../../lib/icons.ts'
|
||||||
|
|
||||||
|
const Card = defineAsyncComponent(() => import('primevue/card'))
|
||||||
|
const DataTable = defineAsyncComponent(() => import('primevue/datatable'))
|
||||||
|
const Button = defineAsyncComponent(() => import('primevue/button'))
|
||||||
|
|
||||||
|
const powders = ref<Powder[]>([])
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
const get = () => {
|
||||||
|
axios.get(`${import.meta.env.VITE_API}/powder`).then((r: Response<Powder[]>) => {
|
||||||
|
powders.value = r.data.payload
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
get()
|
||||||
|
|
||||||
|
</script>
|
||||||
93
frontend/src/pages/primers/Add.vue
Normal file
93
frontend/src/pages/primers/Add.vue
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
<template>
|
||||||
|
<Card class="w-full md:w-1/2">
|
||||||
|
<template #title>
|
||||||
|
Add Primer
|
||||||
|
</template>
|
||||||
|
<template #content>
|
||||||
|
<div class="grid grid-cols-1 gap-6">
|
||||||
|
<div class="w-full">
|
||||||
|
<label>Manufacturer</label>
|
||||||
|
<Select :options="manufacturers" option-label="name" option-value="id" v-model="primer.manufacturer_id"
|
||||||
|
class="w-full" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label>Name</label>
|
||||||
|
<InputText v-model="primer.name" class="w-full" />
|
||||||
|
</div>
|
||||||
|
<div class="w-full mt-3">
|
||||||
|
<label>Picture</label>
|
||||||
|
<FileUpload v-model="file" mode="basic" @select="fileSelected" customUpload />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #footer>
|
||||||
|
<Button :loading="loading" label="Add" :icon="icons.add" @click="add" />
|
||||||
|
</template>
|
||||||
|
</Card>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { defineAsyncComponent, onMounted, ref } from 'vue'
|
||||||
|
import { Manufacturer } from '../../types/manufacturer'
|
||||||
|
import { fetchManufacturers } from '../../services/manufacturers.ts'
|
||||||
|
import axios from 'axios'
|
||||||
|
import { icons } from '../../lib/icons.ts'
|
||||||
|
import { FileUploadSelectEvent } from 'primevue/fileupload'
|
||||||
|
import { Response } from '../../types/Response'
|
||||||
|
import router from '../../router'
|
||||||
|
import { useToast } from 'primevue/usetoast'
|
||||||
|
|
||||||
|
const toast = useToast()
|
||||||
|
|
||||||
|
const Card = defineAsyncComponent(() => import('primevue/card'))
|
||||||
|
const InputText = defineAsyncComponent(() => import('primevue/inputtext'))
|
||||||
|
const Select = defineAsyncComponent(() => import('primevue/select'))
|
||||||
|
const Button = defineAsyncComponent(() => import('primevue/button'))
|
||||||
|
const FileUpload = defineAsyncComponent(() => import('primevue/fileupload'))
|
||||||
|
|
||||||
|
const manufacturers = ref<Manufacturer[]>([])
|
||||||
|
const primer = ref({
|
||||||
|
manufacturer_id: '',
|
||||||
|
name: '',
|
||||||
|
})
|
||||||
|
|
||||||
|
const file = ref<File | null>(null)
|
||||||
|
const loading = ref(false)
|
||||||
|
const fileSelected = (event: FileUploadSelectEvent) => {
|
||||||
|
file.value = event.files[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
const add = () => {
|
||||||
|
loading.value = true
|
||||||
|
|
||||||
|
const formData = new FormData()
|
||||||
|
|
||||||
|
formData.append('manufacturer_id', primer.value.manufacturer_id)
|
||||||
|
formData.append('name', primer.value.name)
|
||||||
|
if (file.value) {
|
||||||
|
formData.append('photo', file.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
axios.post(`${import.meta.env.VITE_API}/primer`, formData, {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'multipart/form-data',
|
||||||
|
},
|
||||||
|
}).
|
||||||
|
then((r: Response<{ id: string }>) => {
|
||||||
|
router.push(`/primers/edit/${r.data.payload.id}`)
|
||||||
|
}).catch(() => {
|
||||||
|
toast.add({
|
||||||
|
severity: 'error',
|
||||||
|
summary: 'Error',
|
||||||
|
detail: 'An error occurred while adding the powder',
|
||||||
|
life: 3000,
|
||||||
|
})
|
||||||
|
}).finally(() => {
|
||||||
|
loading.value = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
const resp = await fetchManufacturers()
|
||||||
|
manufacturers.value.push(...resp.payload)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
142
frontend/src/pages/primers/Edit.vue
Normal file
142
frontend/src/pages/primers/Edit.vue
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
<template>
|
||||||
|
<Card class="w-full md:w-1/2">
|
||||||
|
<template #title>
|
||||||
|
Edit Primer
|
||||||
|
</template>
|
||||||
|
<template #content>
|
||||||
|
<div v-if="primer" class="flex flex-row flex-wrap">
|
||||||
|
<div class="w-full">
|
||||||
|
<label>Manufacturer</label>
|
||||||
|
<Select :options="manufacturers" option-label="name" option-value="id" v-model="primer.manufacturer.id"
|
||||||
|
class="w-full"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="w-full mt-5">
|
||||||
|
<label for="name">Name</label>
|
||||||
|
<InputText v-model="primer.name" type="text" id="name" class="w-full" />
|
||||||
|
</div>
|
||||||
|
<div class="mt-3">
|
||||||
|
<label>Picture</label>
|
||||||
|
<FileUpload v-model="file" mode="basic" @select="fileSelected" customUpload>
|
||||||
|
<template #uploadicon>
|
||||||
|
<i :class="icons.upload" class="mr-3"></i>
|
||||||
|
</template>
|
||||||
|
</FileUpload>
|
||||||
|
<div class="mt-3">
|
||||||
|
<Image v-if="!loading" :src="pictureUrl" alt="picture" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #footer>
|
||||||
|
<Button class="mr-3" label="Save" :icon="icons.save" @click="update" />
|
||||||
|
<Button severity="danger" label="Delete" :icon="icons.delete" @click="deletePrimer" />
|
||||||
|
</template>
|
||||||
|
</Card>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
|
||||||
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
|
import axios from 'axios'
|
||||||
|
import { computed, defineAsyncComponent, onMounted, ref } from 'vue'
|
||||||
|
import { Manufacturer } from '../../types/manufacturer'
|
||||||
|
import { fetchManufacturers } from '../../services/manufacturers.ts'
|
||||||
|
import { Response} from '../../types/Response'
|
||||||
|
import { Primers } from '../../types/primers'
|
||||||
|
import { icons } from '../../lib/icons.ts'
|
||||||
|
import { FileUploadSelectEvent } from 'primevue/fileupload'
|
||||||
|
import { useToast } from 'primevue/usetoast'
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
const router = useRouter()
|
||||||
|
const toast = useToast()
|
||||||
|
|
||||||
|
const Card = defineAsyncComponent(() => import('primevue/card'))
|
||||||
|
const Select = defineAsyncComponent(() => import('primevue/select'))
|
||||||
|
const InputText = defineAsyncComponent(() => import('primevue/inputtext'))
|
||||||
|
const Image = defineAsyncComponent(() => import('primevue/image'))
|
||||||
|
const FileUpload = defineAsyncComponent(() => import('primevue/fileupload'))
|
||||||
|
const Button = defineAsyncComponent(() => import('primevue/button'))
|
||||||
|
|
||||||
|
const manufacturers = ref<Manufacturer[]>([])
|
||||||
|
const primer = ref<Primers | null>(null)
|
||||||
|
const loading = ref(false)
|
||||||
|
const file = ref<File | null>(null)
|
||||||
|
const fileSelected = (event: FileUploadSelectEvent) => {
|
||||||
|
file.value = event.files[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
const pictureUrl = computed(() => {
|
||||||
|
const cache = new Date().getMilliseconds()
|
||||||
|
return import.meta.env.VITE_API + `/primer/${route.params.id}/photo?cache=${cache}`
|
||||||
|
})
|
||||||
|
|
||||||
|
const deletePrimer = () => {
|
||||||
|
axios.delete(`${import.meta.env.VITE_API}/primer/${route.params.id}`).then(() => {
|
||||||
|
router.push('/primers')
|
||||||
|
}).catch((error) => {
|
||||||
|
toast.add({
|
||||||
|
severity: 'error',
|
||||||
|
summary: 'Error',
|
||||||
|
detail: error.response.data.message,
|
||||||
|
life: 3000
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const getPrimer = () => {
|
||||||
|
axios.get(`${import.meta.env.VITE_API}/primer/${route.params.id}`)
|
||||||
|
.then((response: Response<Primers>) => {
|
||||||
|
primer.value = response.data.payload
|
||||||
|
}).catch((error) => {
|
||||||
|
console.log(error)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const update = () => {
|
||||||
|
loading.value = true
|
||||||
|
|
||||||
|
if (!primer.value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const formData = new FormData()
|
||||||
|
formData.append('name', primer.value.name)
|
||||||
|
formData.append('manufacturer_id', primer.value.manufacturer.id)
|
||||||
|
|
||||||
|
if (file.value) {
|
||||||
|
formData.append('photo', file.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
axios.post(`${import.meta.env.VITE_API}/primer/${route.params.id}`, formData, {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'multipart/form-data',
|
||||||
|
},
|
||||||
|
}).then(() => {
|
||||||
|
file.value = null
|
||||||
|
toast.add({
|
||||||
|
severity: 'success',
|
||||||
|
summary: 'Success',
|
||||||
|
detail: 'Primer updated successfully',
|
||||||
|
life: 3000
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
toast.add({
|
||||||
|
severity: 'error',
|
||||||
|
summary: 'Error',
|
||||||
|
detail: error.response.data.message,
|
||||||
|
life: 3000
|
||||||
|
})
|
||||||
|
}).finally(() => {
|
||||||
|
loading.value = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
getPrimer()
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
const response = await fetchManufacturers()
|
||||||
|
manufacturers.value = response.payload
|
||||||
|
})
|
||||||
|
</script>
|
||||||
61
frontend/src/pages/primers/List.vue
Normal file
61
frontend/src/pages/primers/List.vue
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
<template>
|
||||||
|
<Card class="w-2/3">
|
||||||
|
<template #title>
|
||||||
|
Primers
|
||||||
|
</template>
|
||||||
|
<template #content>
|
||||||
|
<DataTable :value="primers">
|
||||||
|
<template #empty>
|
||||||
|
<div class="p-4">
|
||||||
|
<p>No primers found</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<Column field="name" header="Name">
|
||||||
|
<template #body="{ data }">
|
||||||
|
{{ data.manufacturer.name }} {{ data.name }}
|
||||||
|
</template>
|
||||||
|
</Column>
|
||||||
|
<Column field="name" header="Edit">
|
||||||
|
<template #body="{ data }">
|
||||||
|
<Button
|
||||||
|
text
|
||||||
|
small
|
||||||
|
color="primary"
|
||||||
|
:icon="icons.edit"
|
||||||
|
@click="router.push(`/primers/edit/${data.id}`)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</Column>
|
||||||
|
</DataTable>
|
||||||
|
</template>
|
||||||
|
</Card>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import axios from 'axios'
|
||||||
|
import { defineAsyncComponent, ref } from 'vue'
|
||||||
|
import Column from 'primevue/column'
|
||||||
|
import { Response } from '../../types/Response'
|
||||||
|
import { Primers } from '../../types/primers'
|
||||||
|
import { icons } from '../../lib/icons.ts'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
const Card = defineAsyncComponent(() => import('primevue/card'))
|
||||||
|
const Button = defineAsyncComponent(() => import('primevue/button'))
|
||||||
|
const DataTable = defineAsyncComponent(() => import('primevue/datatable'))
|
||||||
|
|
||||||
|
const primers = ref<Primers[]>([])
|
||||||
|
|
||||||
|
const getPrimers = () => {
|
||||||
|
axios.get(`${import.meta.env.VITE_API}/primer`)
|
||||||
|
.then((response: Response<Primers[]>) => {
|
||||||
|
primers.value = response.data.payload
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error(error)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
getPrimers()
|
||||||
|
</script>
|
||||||
71
frontend/src/router/index.ts
Normal file
71
frontend/src/router/index.ts
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router'
|
||||||
|
|
||||||
|
const routes = [
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
component: () => import('../pages/Index.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/bullets/add',
|
||||||
|
component: () => import('../pages/bullets/Add.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/bullets/edit/:id',
|
||||||
|
component: () => import('../pages/bullets/Edit.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/bullets',
|
||||||
|
component: () => import('../pages/bullets/List.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/powders',
|
||||||
|
component: () => import('../pages/powders/List.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/powders/add',
|
||||||
|
component: () => import('../pages/powders/Add.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/powders/edit/:id',
|
||||||
|
component: () => import('../pages/powders/Edit.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/primers',
|
||||||
|
component: () => import('../pages/primers/List.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/primers/add',
|
||||||
|
component: () => import('../pages/primers/Add.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/primers/edit/:id',
|
||||||
|
component: () => import('../pages/primers/Edit.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/manufacturers',
|
||||||
|
component: () => import('../pages/manufacturers/List.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/manufacturers/add',
|
||||||
|
component: () => import('../pages/manufacturers/Add.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/manufacturers/edit/:id',
|
||||||
|
component: () => import('../pages/manufacturers/Edit.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/loads/add',
|
||||||
|
component: () => import('../pages/loads/Add.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/loads/search',
|
||||||
|
component: () => import('../pages/loads/Search.vue'),
|
||||||
|
},
|
||||||
|
] as RouteRecordRaw[]
|
||||||
|
|
||||||
|
const router = createRouter({
|
||||||
|
history: createWebHashHistory(),
|
||||||
|
routes,
|
||||||
|
})
|
||||||
|
|
||||||
|
export default router
|
||||||
10
frontend/src/services/manufacturers.ts
Normal file
10
frontend/src/services/manufacturers.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import axios from 'axios'
|
||||||
|
import { Response } from '../types/Response'
|
||||||
|
import { Manufacturer } from '../types/manufacturer'
|
||||||
|
|
||||||
|
export const fetchManufacturers = async () => {
|
||||||
|
const response = await axios.get<any, Response<Manufacturer[]>>(
|
||||||
|
`${import.meta.env.VITE_API}/manufacturer`)
|
||||||
|
|
||||||
|
return response.data
|
||||||
|
}
|
||||||
8
frontend/src/types/Response.d.ts
vendored
Normal file
8
frontend/src/types/Response.d.ts
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import {AxiosResponse} from "axios";
|
||||||
|
|
||||||
|
export interface Response<T> extends AxiosResponse {
|
||||||
|
data: {
|
||||||
|
payload: T
|
||||||
|
status: 'OK' | 'ERROR'
|
||||||
|
}
|
||||||
|
}
|
||||||
8
frontend/src/types/bullet.d.ts
vendored
Normal file
8
frontend/src/types/bullet.d.ts
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import { Manufacturer } from './manufacturer'
|
||||||
|
|
||||||
|
export interface Bullet {
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
weight: number
|
||||||
|
manufacturer: Manufacturer
|
||||||
|
}
|
||||||
5
frontend/src/types/manufacturer.d.ts
vendored
Normal file
5
frontend/src/types/manufacturer.d.ts
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
export interface Manufacturer {
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
url: string
|
||||||
|
}
|
||||||
8
frontend/src/types/powder.d.ts
vendored
Normal file
8
frontend/src/types/powder.d.ts
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import { Manufacturer } from './manufacturer'
|
||||||
|
|
||||||
|
export interface Powder {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
meta: string;
|
||||||
|
manufacturer: Manufacturer;
|
||||||
|
}
|
||||||
7
frontend/src/types/primers.d.ts
vendored
Normal file
7
frontend/src/types/primers.d.ts
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import { Manufacturer } from './manufacturer'
|
||||||
|
|
||||||
|
export interface Primers {
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
manufacturer: Manufacturer
|
||||||
|
}
|
||||||
10
frontend/src/vite-env.d.ts
vendored
Normal file
10
frontend/src/vite-env.d.ts
vendored
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
/// <reference types="vite/client" />
|
||||||
|
|
||||||
|
import {ComponentCustomProperties as GCS} from "@vue/runtime-core";
|
||||||
|
import {Account} from "appwrite";
|
||||||
|
|
||||||
|
declare module "@vue/runtime-core" {
|
||||||
|
interface ComponentCustomProperties extends GCS {
|
||||||
|
$account: Account
|
||||||
|
}
|
||||||
|
}
|
||||||
11
frontend/tailwind.config.js
Normal file
11
frontend/tailwind.config.js
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
/** @type {import('tailwindcss').Config} */
|
||||||
|
module.exports = {
|
||||||
|
content: [
|
||||||
|
"./index.html",
|
||||||
|
"./src/**/*.{vue,js,ts,jsx,tsx}",
|
||||||
|
],
|
||||||
|
theme: {
|
||||||
|
extend: {},
|
||||||
|
},
|
||||||
|
plugins: [],
|
||||||
|
}
|
||||||
26
frontend/tsconfig.json
Normal file
26
frontend/tsconfig.json
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"allowJs": true,
|
||||||
|
"target": "ES2020",
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||||
|
"skipLibCheck": true,
|
||||||
|
|
||||||
|
/* Bundler mode */
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"jsx": "preserve",
|
||||||
|
|
||||||
|
/* Linting */
|
||||||
|
"strict": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"noFallthroughCasesInSwitch": true
|
||||||
|
},
|
||||||
|
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
|
||||||
|
"references": [{ "path": "./tsconfig.node.json" }]
|
||||||
|
}
|
||||||
10
frontend/tsconfig.node.json
Normal file
10
frontend/tsconfig.node.json
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
},
|
||||||
|
"include": ["vite.config.ts"]
|
||||||
|
}
|
||||||
8
frontend/vite.config.ts
Normal file
8
frontend/vite.config.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import vue from '@vitejs/plugin-vue'
|
||||||
|
import tailwindcss from '@tailwindcss/vite'
|
||||||
|
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [vue(), tailwindcss()],
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user