add project kontor-api-echo
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
# ---------- Stage 1: Build ----------
|
||||
FROM golang:1.25-alpine AS builder
|
||||
# Set the working directory
|
||||
WORKDIR /app
|
||||
# Ensure a portable, static-ish binary
|
||||
ENV CGO_ENABLED=0 GOOS=linux GOARCH=amd64
|
||||
# Copy and download dependencies
|
||||
COPY go.mod go.sum ./
|
||||
RUN go mod download
|
||||
# Copy the source code
|
||||
COPY . .
|
||||
# Build the Go application (strip debug info for smaller size)
|
||||
RUN go build -trimpath -ldflags="-s -w" -o kontor cmd/kontor/main.go
|
||||
|
||||
# ---------- Stage 2: Final ----------
|
||||
FROM alpine:latest
|
||||
# Set the working directory
|
||||
WORKDIR /app
|
||||
# Install runtime dependencies you actually need
|
||||
RUN apk add --no-cache ca-certificates tzdata curl
|
||||
# Create non-root user for security
|
||||
RUN addgroup -S kontor && adduser -S -G kontor -H -s /sbin/nologin kontor
|
||||
# Copy the binary and set ownership
|
||||
COPY --from=builder --chown=kontor:kontor /app/kontor /app/kontor
|
||||
# Run as non-root user
|
||||
USER kontor
|
||||
# Set the entrypoint command
|
||||
ENTRYPOINT ["/app/kontor"]
|
||||
@@ -0,0 +1,52 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"kontor-api-echo/pkg/handler"
|
||||
"kontor-api-echo/pkg/schema"
|
||||
"log"
|
||||
|
||||
// jwtware "github.com/gofiber/contrib/jwt"
|
||||
// "github.com/gofiber/fiber/v2/middleware/logger"
|
||||
echojwt "github.com/labstack/echo-jwt/v4"
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/labstack/echo/v4/middleware"
|
||||
)
|
||||
|
||||
func main() {
|
||||
log.Println("Kontor started")
|
||||
|
||||
if err := schema.Connect(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
e := echo.New()
|
||||
e.GET("/health", handler.GetHealth)
|
||||
e.POST("/login", handler.Login)
|
||||
|
||||
skipper := func(c echo.Context) bool {
|
||||
// Skip health check endpoint
|
||||
return c.Request().URL.Path == "/health"
|
||||
}
|
||||
e.Use(middleware.RequestLoggerWithConfig(middleware.RequestLoggerConfig{
|
||||
LogStatus: true,
|
||||
LogURI: true,
|
||||
Skipper: skipper,
|
||||
BeforeNextFunc: func(c echo.Context) {
|
||||
c.Set("customValueFromContext", 42)
|
||||
},
|
||||
LogValuesFunc: func(c echo.Context, v middleware.RequestLoggerValues) error {
|
||||
value, _ := c.Get("customValueFromContext").(int)
|
||||
fmt.Printf("REQUEST: uri: %v, status: %v, custom-value: %v\n", v.URI, v.Status, value)
|
||||
return nil
|
||||
},
|
||||
}))
|
||||
|
||||
group := e.Group("/api")
|
||||
group.Use(echojwt.WithConfig(echojwt.Config{SigningKey: []byte("secret")}))
|
||||
handler.SetupComicRoutes(group)
|
||||
handler.SetupMediaRoutes(group)
|
||||
|
||||
e.Logger.Fatal(e.Start(":8700"))
|
||||
log.Println("Kontor finished")
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
module kontor-api-echo
|
||||
|
||||
go 1.24.2
|
||||
|
||||
require (
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/fatih/color v1.18.0 // indirect
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/joho/godotenv v1.5.1 // indirect
|
||||
github.com/labstack/echo-jwt/v4 v4.4.0 // indirect
|
||||
github.com/labstack/echo/v4 v4.15.0 // indirect
|
||||
github.com/labstack/gommon v0.4.2 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/puzpuzpuz/xsync/v3 v3.5.1 // indirect
|
||||
github.com/stretchr/testify v1.11.1 // indirect
|
||||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect
|
||||
github.com/uptrace/bun v1.2.16 // indirect
|
||||
github.com/uptrace/bun/dialect/pgdialect v1.2.16 // indirect
|
||||
github.com/uptrace/bun/driver/pgdriver v1.2.16 // indirect
|
||||
github.com/uptrace/bun/extra/bundebug v1.2.16 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasttemplate v1.2.2 // indirect
|
||||
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
|
||||
go.opentelemetry.io/otel v1.39.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.39.0 // indirect
|
||||
golang.org/x/crypto v0.46.0 // indirect
|
||||
golang.org/x/net v0.48.0 // indirect
|
||||
golang.org/x/sys v0.39.0 // indirect
|
||||
golang.org/x/text v0.32.0 // indirect
|
||||
golang.org/x/time v0.14.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
mellium.im/sasl v0.3.2 // indirect
|
||||
)
|
||||
@@ -0,0 +1,68 @@
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
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/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
|
||||
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/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||
github.com/labstack/echo-jwt/v4 v4.4.0 h1:nrXaEnJupfc2R4XChcLRDyghhMZup77F8nIzHnBK19U=
|
||||
github.com/labstack/echo-jwt/v4 v4.4.0/go.mod h1:kYXWgWms9iFqI3ldR+HAEj/Zfg5rZtR7ePOgktG4Hjg=
|
||||
github.com/labstack/echo/v4 v4.15.0 h1:hoRTKWcnR5STXZFe9BmYun9AMTNeSbjHi2vtDuADJ24=
|
||||
github.com/labstack/echo/v4 v4.15.0/go.mod h1:xmw1clThob0BSVRX1CRQkGQ/vjwcpOMjQZSZa9fKA/c=
|
||||
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/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.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
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/puzpuzpuz/xsync/v3 v3.5.1 h1:GJYJZwO6IdxN/IKbneznS6yPkVC+c3zyY/j19c++5Fg=
|
||||
github.com/puzpuzpuz/xsync/v3 v3.5.1/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo=
|
||||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs=
|
||||
github.com/uptrace/bun v1.2.16 h1:QlObi6ZIK5Ao7kAALnh91HWYNZUBbVwye52fmlQM9kc=
|
||||
github.com/uptrace/bun v1.2.16/go.mod h1:jMoNg2n56ckaawi/O/J92BHaECmrz6IRjuMWqlMaMTM=
|
||||
github.com/uptrace/bun/dialect/pgdialect v1.2.16 h1:KFNZ0LxAyczKNfK/IJWMyaleO6eI9/Z5tUv3DE1NVL4=
|
||||
github.com/uptrace/bun/dialect/pgdialect v1.2.16/go.mod h1:IJdMeV4sLfh0LDUZl7TIxLI0LipF1vwTK3hBC7p5qLo=
|
||||
github.com/uptrace/bun/driver/pgdriver v1.2.16 h1:b1kpXKUxtTSGYow5Vlsb+dKV3z0R7aSAJNfMfKp61ZU=
|
||||
github.com/uptrace/bun/driver/pgdriver v1.2.16/go.mod h1:H6lUZ9CBfp1X5Vq62YGSV7q96/v94ja9AYFjKvdoTk0=
|
||||
github.com/uptrace/bun/extra/bundebug v1.2.16 h1:3OXAfHTU4ydu2+4j05oB1BxPx6+ypdWIVzTugl/7zl0=
|
||||
github.com/uptrace/bun/extra/bundebug v1.2.16/go.mod h1:vk6R/1i67/S2RvUI5AH/m3P5e67mOkfDCmmCsAPUumo=
|
||||
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/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8=
|
||||
github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok=
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
|
||||
go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=
|
||||
go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=
|
||||
go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
|
||||
go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
|
||||
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
|
||||
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
|
||||
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
|
||||
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
||||
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
|
||||
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
|
||||
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
||||
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
mellium.im/sasl v0.3.2 h1:PT6Xp7ccn9XaXAnJ03FcEjmAn7kK1x7aoXV6F+Vmrl0=
|
||||
mellium.im/sasl v0.3.2/go.mod h1:NKXDi1zkr+BlMHLQjY3ofYuU4KSPFxknb8mfEu6SveY=
|
||||
@@ -0,0 +1,66 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"kontor-api-echo/pkg/schema"
|
||||
"kontor-api-echo/pkg/utils"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
type jwtCustomClaims struct {
|
||||
Name string `json:"name"`
|
||||
Admin bool `json:"admin"`
|
||||
jwt.RegisteredClaims
|
||||
}
|
||||
|
||||
func Login(c echo.Context) error {
|
||||
user := c.FormValue("user")
|
||||
pass := c.FormValue("pass")
|
||||
|
||||
var profile schema.Profile
|
||||
var err error
|
||||
var db *bun.DB
|
||||
ctx := context.Background()
|
||||
|
||||
db, _ = schema.GetDatabase()
|
||||
err = db.NewSelect().Model(&profile).Where("email = ?", user).Scan(ctx)
|
||||
if err != nil {
|
||||
return c.String(http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
if !utils.ComparePassword(profile.Password, pass) {
|
||||
return echo.ErrUnauthorized
|
||||
}
|
||||
|
||||
// Set custom claims
|
||||
claims := &jwtCustomClaims{
|
||||
"Jon Snow",
|
||||
true,
|
||||
jwt.RegisteredClaims{
|
||||
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 72)),
|
||||
},
|
||||
}
|
||||
|
||||
// Create token with claims
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
|
||||
// Generate encoded token and send it as response.
|
||||
t, err := token.SignedString([]byte("secret"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, echo.Map{"token": t})
|
||||
}
|
||||
|
||||
func restricted(c echo.Context) error {
|
||||
user := c.Get("user").(*jwt.Token)
|
||||
claims := user.Claims.(*jwtCustomClaims)
|
||||
name := claims.Name
|
||||
return c.String(http.StatusOK, "Welcome "+name+"!")
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"kontor-api-echo/pkg/schema"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
func SetupComicRoutes(api *echo.Group) {
|
||||
comics := api.Group("/comics")
|
||||
comics.GET("/comics", GetAllComics)
|
||||
comics.GET("/publishers", GetAllPublishers)
|
||||
comics.GET("/comicworks", GetAllComicWorks)
|
||||
}
|
||||
|
||||
func GetAllComics(c echo.Context) error {
|
||||
var comics []schema.Comic
|
||||
var err error
|
||||
var db *bun.DB
|
||||
ctx := context.Background()
|
||||
|
||||
db, err = schema.GetDatabase()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
err = db.NewSelect().Model(&comics).Relation("Publisher").Scan(ctx)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
return echo.ErrInternalServerError
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, comics)
|
||||
}
|
||||
|
||||
func GetAllPublishers(c echo.Context) error {
|
||||
var publishers []schema.Publisher
|
||||
var err error
|
||||
var db *bun.DB
|
||||
ctx := context.Background()
|
||||
|
||||
db, err = schema.GetDatabase()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
err = db.NewSelect().Model(&publishers).Relation("ParentPublisher").Scan(ctx)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
return echo.ErrInternalServerError
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, publishers)
|
||||
}
|
||||
|
||||
func GetAllComicWorks(c echo.Context) error {
|
||||
var comic_works []schema.ComicWork
|
||||
var err error
|
||||
var db *bun.DB
|
||||
ctx := context.Background()
|
||||
|
||||
db, err = schema.GetDatabase()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
err = db.NewSelect().Model(&comic_works).Relation("Comic").Relation("Artist").Relation("WorkType").Scan(ctx)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
return echo.ErrInternalServerError
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, comic_works)
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
type Status struct {
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
func GetHealth(c echo.Context) error {
|
||||
status := new(Status)
|
||||
status.Status = "ok"
|
||||
return c.JSON(http.StatusOK, status)
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"kontor-api-echo/pkg/schema"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
func SetupMediaRoutes(api *echo.Group) {
|
||||
media := api.Group("/media")
|
||||
media.GET("/files", GetAllFiles)
|
||||
media.POST("/files", AddFile)
|
||||
}
|
||||
|
||||
func GetAllFiles(c echo.Context) error {
|
||||
var files []schema.MediaFile
|
||||
var err error
|
||||
var db *bun.DB
|
||||
ctx := context.Background()
|
||||
|
||||
db, err = schema.GetDatabase()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
err = db.NewSelect().Model(&files).Relation("MediaActorFiles").Scan(ctx)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
return echo.ErrInternalServerError
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, files)
|
||||
}
|
||||
|
||||
type Link struct {
|
||||
URL string `json:"url"`
|
||||
}
|
||||
|
||||
func AddFile(c echo.Context) error {
|
||||
var err error
|
||||
var db *bun.DB
|
||||
ctx := context.Background()
|
||||
link := new(Link)
|
||||
if err = c.Bind(link); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Printf("URL %s has been sent", link)
|
||||
|
||||
db, err = schema.GetDatabase()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
id := uuid.NewString()
|
||||
timestamp := time.Now()
|
||||
mediafile := &schema.MediaFile{ID: id, CreatedAt: timestamp, UpdatedAt: timestamp, WebLink: fmt.Sprintf("%s", link), Version: 1, ShouldDownload: true, Review: true}
|
||||
_, err = db.NewInsert().Model(mediafile).Exec(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.String(http.StatusCreated, "Link created")
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package schema
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
type Profile struct {
|
||||
bun.BaseModel `bun:"table:profile"`
|
||||
|
||||
ID string `bun:"id,pk"`
|
||||
CreatedAt time.Time `bun:"created_date,nullzero,notnull,default:current_timestamp"`
|
||||
UpdatedAt time.Time `bun:"last_modified_date,nullzero,notnull,default:current_timestamp"`
|
||||
Version int `bun:"version,default:0"`
|
||||
FirstName string `bun:"first_name"`
|
||||
LastName string `bun:"last_name"`
|
||||
UserName string `bun:"user_name,unique:user_name"`
|
||||
Email string `bun:"email"`
|
||||
Password string `bun:"password"`
|
||||
Enabled bool `bun:"enabled"`
|
||||
Assignments []Assignment `bun:"rel:has-many,join:id=profile_id"`
|
||||
Tokens []Token `bun:"rel:has-many,join:id=profile_id"`
|
||||
}
|
||||
|
||||
type Permission struct {
|
||||
bun.BaseModel `bun:"table:permission"`
|
||||
|
||||
ID string `bun:"id,pk"`
|
||||
CreatedAt time.Time `bun:"created_date,nullzero,notnull,default:current_timestamp"`
|
||||
UpdatedAt time.Time `bun:"last_modified_date,nullzero,notnull,default:current_timestamp"`
|
||||
Version int `bun:"version,default:0"`
|
||||
Name string `bun:"name,unique:name"`
|
||||
Assignments []Assignment `bun:"rel:has-many,join:id=permission_id"`
|
||||
}
|
||||
|
||||
type Token struct {
|
||||
bun.BaseModel `bun:"table:token"`
|
||||
|
||||
ID string `bun:"id,pk"`
|
||||
CreatedAt time.Time `bun:"created_date,nullzero,notnull,default:current_timestamp"`
|
||||
UpdatedAt time.Time `bun:"last_modified_date,nullzero,notnull,default:current_timestamp"`
|
||||
Version int `bun:"version,default:0"`
|
||||
Name string `bun:"name,unique:name"`
|
||||
LastUsedAt time.Time `bun:"last_used_date,nullzero,notnull,default:current_timestamp"`
|
||||
Enabled bool `bun:"enabled,default:true"`
|
||||
ProfileID *string `bun:"profile_id"`
|
||||
Profile *Profile `bun:"rel:belongs-to,join:profile_id=id"`
|
||||
}
|
||||
|
||||
type Assignment struct {
|
||||
bun.BaseModel `bun:"table:assignment"`
|
||||
|
||||
ID string `bun:"id,pk"`
|
||||
CreatedAt time.Time `bun:"created_date,nullzero,notnull,default:current_timestamp"`
|
||||
UpdatedAt time.Time `bun:"last_modified_date,nullzero,notnull,default:current_timestamp"`
|
||||
Version int `bun:"version,default:0"`
|
||||
ProfileID *string `bun:"profile_id"`
|
||||
Profile *Profile `bun:"rel:belongs-to,join:profile_id=id"`
|
||||
PermissionID *string `bun:"permission_id"`
|
||||
Permission *Permission `bun:"rel:belongs-to,join:permission_id=id"`
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
package schema
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
type Publisher struct {
|
||||
bun.BaseModel `bun:"table:publisher"`
|
||||
|
||||
ID string `bun:"id,pk"`
|
||||
CreatedAt time.Time `bun:"created_date,nullzero,notnull,default:current_timestamp"`
|
||||
UpdatedAt time.Time `bun:"last_modified_date,nullzero,notnull,default:current_timestamp"`
|
||||
Version int `bun:"version,default:0"`
|
||||
Name string `bun:"name,unique:name"`
|
||||
WebLink string `bun:"weblink"`
|
||||
|
||||
ParentPublisherID *string `bun:"parent_publisher_id"`
|
||||
ParentPublisher *Publisher `bun:"rel:belongs-to,join:parent_publisher_id=id"`
|
||||
Imprints []Publisher `bun:"rel:has-many,join:id=parent_publisher_id"`
|
||||
Comics []Comic `bun:"rel:has-many,join:id=publisher_id"`
|
||||
}
|
||||
|
||||
type Comic struct {
|
||||
bun.BaseModel `bun:"table:comic"`
|
||||
|
||||
ID string `bun:"id,pk"`
|
||||
CreatedAt time.Time `bun:"created_date,nullzero,notnull,default:current_timestamp"`
|
||||
UpdatedAt time.Time `bun:"last_modified_date,nullzero,notnull,default:current_timestamp"`
|
||||
Version int `bun:"version,default:0"`
|
||||
Title string `bun:"title,unique:title,notnull"`
|
||||
CurrentOrder bool `bun:"current_order"`
|
||||
Completed bool `bun:"completed"`
|
||||
WebLink string `bun:"weblink"`
|
||||
|
||||
PublisherID *string `bun:"publisher_id"`
|
||||
Publisher *Publisher `bun:"rel:belongs-to,join:publisher_id=id"`
|
||||
Issues []Issue `bun:"rel:has-many,join:id=comic_id"`
|
||||
StoryArcs []StoryArc `bun:"rel:has-many,join:id=comic_id"`
|
||||
TradePaperbacks []TradePaperback `bun:"rel:has-many,join:id=comic_id"`
|
||||
Volumes []Volume `bun:"rel:has-many,join:id=comic_id"`
|
||||
ComicWorks []ComicWork `bun:"rel:has-many,join:id=comic_id"`
|
||||
}
|
||||
|
||||
type Artist struct {
|
||||
bun.BaseModel `bun:"table:artist"`
|
||||
|
||||
ID string `bun:"id,pk"`
|
||||
CreatedAt time.Time `bun:"created_date,nullzero,notnull,default:current_timestamp"`
|
||||
UpdatedAt time.Time `bun:"last_modified_date,nullzero,notnull,default:current_timestamp"`
|
||||
Version int `bun:"version,default:0"`
|
||||
Name string `bun:"name,unique:title,notnull"`
|
||||
WebLink string `bun:"weblink"`
|
||||
}
|
||||
|
||||
type Issue struct {
|
||||
bun.BaseModel `bun:"table:issue"`
|
||||
|
||||
ID string `bun:"id,pk"`
|
||||
CreatedAt time.Time `bun:"created_date,nullzero,notnull,default:current_timestamp"`
|
||||
UpdatedAt time.Time `bun:"last_modified_date,nullzero,notnull,default:current_timestamp"`
|
||||
Version int `bun:"version,default:0"`
|
||||
InStock bool `bun:"in_stock"`
|
||||
IsRead bool `bun:"is_read"`
|
||||
IssueNumber string `bu:"issue_number"`
|
||||
Title string `bun:"title"`
|
||||
PublishedOn time.Time `bun:"published_on"`
|
||||
ComicID *string `bun:"comic_id"`
|
||||
Comic *Comic `bun:"rel:belongs-to,join:comic_id=id"`
|
||||
StoryArcID *string `bun:"story_arc_id"`
|
||||
StoryArc *StoryArc `bun:"rel:belongs-to,join:story_arc_id=id"`
|
||||
VolumeID *string `bun:"volume_id"`
|
||||
Volume *Volume `bun:"rel:belongs-to,join:volume_id=id"`
|
||||
}
|
||||
|
||||
type StoryArc struct {
|
||||
bun.BaseModel `bun:"table:story_arc"`
|
||||
|
||||
ID string `bun:"id,pk"`
|
||||
CreatedAt time.Time `bun:"created_date,nullzero,notnull,default:current_timestamp"`
|
||||
UpdatedAt time.Time `bun:"last_modified_date,nullzero,notnull,default:current_timestamp"`
|
||||
Version int `bun:"version,default:0"`
|
||||
Name string `bun:"name,unique:name,notnull"`
|
||||
ComicID *string `bun:"comic_id"`
|
||||
Comic *Comic `bun:"rel:belongs-to,join:comic_id=id"`
|
||||
VolumeID *string `bun:"volume_id"`
|
||||
Volume *Volume `bun:"rel:belongs-to,join:volume_id=id"`
|
||||
}
|
||||
|
||||
type TradePaperback struct {
|
||||
bun.BaseModel `bun:"table:trade_paperback"`
|
||||
|
||||
ID string `bun:"id,pk"`
|
||||
CreatedAt time.Time `bun:"created_date,nullzero,notnull,default:current_timestamp"`
|
||||
UpdatedAt time.Time `bun:"last_modified_date,nullzero,notnull,default:current_timestamp"`
|
||||
Version int `bun:"version,default:0"`
|
||||
Name string `bun:"name,unique:name,notnull"`
|
||||
IssueStart int `bun:"issue_start"`
|
||||
IssueEnd int `bun:"issue_end"`
|
||||
ComicID *string `bun:"comic_id"`
|
||||
Comic *Comic `bun:"rel:belongs-to,join:comic_id=id"`
|
||||
}
|
||||
|
||||
type Volume struct {
|
||||
bun.BaseModel `bun:"table:volume"`
|
||||
|
||||
ID string `bun:"id,pk"`
|
||||
CreatedAt time.Time `bun:"created_date,nullzero,notnull,default:current_timestamp"`
|
||||
UpdatedAt time.Time `bun:"last_modified_date,nullzero,notnull,default:current_timestamp"`
|
||||
Version int `bun:"version,default:0"`
|
||||
Name string `bun:"name,unique:name,notnull"`
|
||||
ComicID *string `bun:"comic_id"`
|
||||
Comic *Comic `bun:"rel:belongs-to,join:comic_id=id"`
|
||||
}
|
||||
|
||||
type WorkType struct {
|
||||
bun.BaseModel `bun:"table:worktype"`
|
||||
|
||||
ID string `bun:"id,pk"`
|
||||
CreatedAt time.Time `bun:"created_date,nullzero,notnull,default:current_timestamp"`
|
||||
UpdatedAt time.Time `bun:"last_modified_date,nullzero,notnull,default:current_timestamp"`
|
||||
Version int `bun:"version,default:0"`
|
||||
Name string `bun:"name,unique:name,notnull"`
|
||||
}
|
||||
|
||||
type ComicWork struct {
|
||||
bun.BaseModel `bun:"table:comic_work"`
|
||||
|
||||
ID string `bun:"id,pk"`
|
||||
CreatedAt time.Time `bun:"created_date,nullzero,notnull,default:current_timestamp"`
|
||||
UpdatedAt time.Time `bun:"last_modified_date,nullzero,notnull,default:current_timestamp"`
|
||||
Version int `bun:"version,default:0"`
|
||||
ArtistID *string `bun:"artist_id"`
|
||||
Artist *Artist `bun:"rel:belongs-to,join:artist_id=id"`
|
||||
ComicID *string `bun:"comic_id"`
|
||||
Comic *Comic `bun:"rel:belongs-to,join:comic_id=id"`
|
||||
WorkTypeID *string `bun:"work_type_id"`
|
||||
WorkType *WorkType `bun:"rel:belongs-to,join:work_type_id=id"`
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package schema
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"database/sql"
|
||||
|
||||
"github.com/uptrace/bun"
|
||||
"github.com/uptrace/bun/extra/bundebug"
|
||||
|
||||
"github.com/uptrace/bun/dialect/pgdialect"
|
||||
"github.com/uptrace/bun/driver/pgdriver"
|
||||
)
|
||||
|
||||
func GetTestDatabase() (*bun.DB, error) {
|
||||
var err error
|
||||
|
||||
dsn := "postgres://kontor:kontor@localhost:5432/kontor?sslmode=disable"
|
||||
sqldb := sql.OpenDB(pgdriver.NewConnector(pgdriver.WithDSN(dsn)))
|
||||
|
||||
sqldb.SetMaxOpenConns(4)
|
||||
sqldb.SetMaxIdleConns(4)
|
||||
|
||||
DB := bun.NewDB(sqldb, pgdialect.New())
|
||||
DB.AddQueryHook(bundebug.NewQueryHook(bundebug.WithVerbose(true)))
|
||||
|
||||
if err = DB.Ping(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Println("Returned Database Connection")
|
||||
return DB, nil
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
log.Println("Setup Test")
|
||||
exitCode := m.Run()
|
||||
os.Exit(exitCode)
|
||||
}
|
||||
|
||||
func TestSelectComics(t *testing.T) {
|
||||
var comics []Comic
|
||||
var err error
|
||||
var db *bun.DB
|
||||
ctx := context.Background()
|
||||
|
||||
db, err = GetTestDatabase()
|
||||
require.NoError(t, err)
|
||||
|
||||
err = db.NewSelect().Model(&comics).Relation("Publisher").Scan(ctx)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 168, len(comics))
|
||||
}
|
||||
|
||||
func TestSelectPublishers(t *testing.T) {
|
||||
var publishers []Publisher
|
||||
var err error
|
||||
var db *bun.DB
|
||||
ctx := context.Background()
|
||||
|
||||
db, err = GetTestDatabase()
|
||||
require.NoError(t, err)
|
||||
|
||||
err = db.NewSelect().Model(&publishers).Relation("ParentPublisher").Scan(ctx)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 19, len(publishers))
|
||||
}
|
||||
|
||||
func TestSelectWorkTypes(t *testing.T) {
|
||||
var comic_works []ComicWork
|
||||
var err error
|
||||
var db *bun.DB
|
||||
ctx := context.Background()
|
||||
|
||||
db, err = GetTestDatabase()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
err = db.NewSelect().Model(&comic_works).Relation("Comic").Relation("Artist").Relation("WorkType").Scan(ctx)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 59, len(comic_works))
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package schema
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"log"
|
||||
|
||||
"github.com/uptrace/bun"
|
||||
"github.com/uptrace/bun/dialect/pgdialect"
|
||||
"github.com/uptrace/bun/driver/pgdriver"
|
||||
)
|
||||
|
||||
var DB *bun.DB
|
||||
|
||||
func Connect() error {
|
||||
var err error
|
||||
|
||||
dsn := "postgres://kontor:kontor@postgres:5432/kontor?sslmode=disable"
|
||||
sqldb := sql.OpenDB(pgdriver.NewConnector(pgdriver.WithDSN(dsn)))
|
||||
|
||||
sqldb.SetMaxOpenConns(4)
|
||||
sqldb.SetMaxIdleConns(4)
|
||||
|
||||
DB := bun.NewDB(sqldb, pgdialect.New())
|
||||
|
||||
if err = DB.Ping(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Println("Connection Opened to Database")
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetDatabase() (*bun.DB, error) {
|
||||
var err error
|
||||
|
||||
dsn := "postgres://kontor:kontor@postgres:5432/kontor?sslmode=disable"
|
||||
sqldb := sql.OpenDB(pgdriver.NewConnector(pgdriver.WithDSN(dsn)))
|
||||
|
||||
sqldb.SetMaxOpenConns(4)
|
||||
sqldb.SetMaxIdleConns(4)
|
||||
|
||||
DB := bun.NewDB(sqldb, pgdialect.New())
|
||||
|
||||
if err = DB.Ping(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Println("Returned Database Connection")
|
||||
return DB, nil
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package schema
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
type MediaFile struct {
|
||||
bun.BaseModel `bun:"table:media_file"`
|
||||
|
||||
ID string `bun:"id,pk"`
|
||||
CreatedAt time.Time `bun:"created_date,nullzero,notnull,default:current_timestamp"`
|
||||
UpdatedAt time.Time `bun:"last_modified_date,nullzero,notnull,default:current_timestamp"`
|
||||
Version int `bun:"version,default:0"`
|
||||
CloudLink string `bun:"cloud_link"`
|
||||
FileName string `bun:"file_name"`
|
||||
Path string `bun:"path"`
|
||||
Review bool `bun:"review"`
|
||||
Title string `bun:"title"`
|
||||
WebLink string `bun:"url,unique:url"`
|
||||
ShouldDownload bool `bun:"should_download"`
|
||||
|
||||
MediaActorFiles []MediaActorFile `bun:"rel:has-many,join:id=media_file_id"`
|
||||
}
|
||||
|
||||
type MediaActor struct {
|
||||
bun.BaseModel `bun:"table:media_actor"`
|
||||
|
||||
ID string `bun:"id,pk"`
|
||||
CreatedAt time.Time `bun:"created_date,nullzero,notnull,default:current_timestamp"`
|
||||
UpdatedAt time.Time `bun:"last_modified_date,nullzero,notnull,default:current_timestamp"`
|
||||
Version int `bun:"version,default:0"`
|
||||
Name string `bun:"name,unique:name"`
|
||||
WebLink string `bun:"url,unique:url"`
|
||||
|
||||
MediaActorFiles []MediaActorFile `bun:"rel:has-many,join:id=media_actor_id"`
|
||||
}
|
||||
|
||||
type MediaActorFile struct {
|
||||
bun.BaseModel `bun:"table:media_actor_file"`
|
||||
|
||||
ID string `bun:"id,pk"`
|
||||
CreatedAt time.Time `bun:"created_date,nullzero,notnull,default:current_timestamp"`
|
||||
UpdatedAt time.Time `bun:"last_modified_date,nullzero,notnull,default:current_timestamp"`
|
||||
Version int `bun:"version,default:0"`
|
||||
MediaActorID *string `bun:"media_actor_id"`
|
||||
MediaActor *MediaActor `bun:"rel:belongs-to,join:media_actor_id=id"`
|
||||
MediaFileID *string `bun:"media_file_id"`
|
||||
MediaFile *MediaFile `bun:"rel:belongs-to,join:media_file_id=id"`
|
||||
}
|
||||
|
||||
type MediaArticle struct {
|
||||
bun.BaseModel `bun:"table:media_article"`
|
||||
|
||||
ID string `bun:"id,pk"`
|
||||
CreatedAt time.Time `bun:"created_date,nullzero,notnull,default:current_timestamp"`
|
||||
UpdatedAt time.Time `bun:"last_modified_date,nullzero,notnull,default:current_timestamp"`
|
||||
Version int `bun:"version,default:0"`
|
||||
Review bool `bun:"review"`
|
||||
Title string `bun:"title"`
|
||||
WebLink string `bun:"url,unique:url"`
|
||||
}
|
||||
|
||||
type MediaVideo struct {
|
||||
bun.BaseModel `bun:"table:media_article"`
|
||||
|
||||
ID string `bun:"id,pk"`
|
||||
CreatedAt time.Time `bun:"created_date,nullzero,notnull,default:current_timestamp"`
|
||||
UpdatedAt time.Time `bun:"last_modified_date,nullzero,notnull,default:current_timestamp"`
|
||||
Version int `bun:"version,default:0"`
|
||||
CloudLink string `bun:"cloud_link"`
|
||||
FileName string `bun:"file_name"`
|
||||
Path string `bun:"path"`
|
||||
Review bool `bun:"review"`
|
||||
Title string `bun:"title"`
|
||||
WebLink string `bun:"url,unique:url"`
|
||||
ShouldDownload bool `bun:"should_download"`
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package utils
|
||||
|
||||
import "golang.org/x/crypto/bcrypt"
|
||||
|
||||
func ComparePassword(hashedPassword, password string) bool {
|
||||
err := bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password))
|
||||
return err == nil
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"kontor-api-echo/pkg/schema"
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
)
|
||||
|
||||
func GenerateToken(user schema.Profile) (string, error) {
|
||||
// Create the Claims
|
||||
claims := jwt.MapClaims{
|
||||
"name": user.FirstName + ", " + user.LastName,
|
||||
"admin": true,
|
||||
"exp": time.Now().Add(time.Hour * 72).Unix(),
|
||||
}
|
||||
|
||||
// Create token
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
|
||||
// Generate encoded token and send it as response.
|
||||
t, err := token.SignedString([]byte("secret"))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
|
||||
func VerifyToken(tokenString string) (bool, error) {
|
||||
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
|
||||
return []byte("secret"), nil
|
||||
})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return token.Valid, nil
|
||||
}
|
||||
Reference in New Issue
Block a user