diff --git a/.air.toml b/.air.toml index 1b8c09a..619e647 100644 --- a/.air.toml +++ b/.air.toml @@ -7,7 +7,7 @@ tmp_dir = "tmp" bin = "./tmp/main" cmd = "go build -o ./tmp/main ./cmd/main.go" delay = 1000 - exclude_dir = ["assets", "tmp", "vendor", "testdata"] + exclude_dir = ["assets", "tmp", "vendor", "testdata", "web", "build"] exclude_file = [] exclude_regex = ["_test.go"] exclude_unchanged = false diff --git a/.gitignore b/.gitignore index d7fbdbd..6e2a9a3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ tmp -secret.sh \ No newline at end of file +secret.sh +build diff --git a/Dockerfile b/Dockerfile index b2451ac..68e7d0e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,15 +16,31 @@ COPY . ./ RUN go build -o /server ./cmd/main.go +## +## Build UI +## + +FROM oven/bun:1.0 AS ui + +WORKDIR /app + +COPY web ./ + +RUN bun install + +RUN bun run build ## ## Deploy ## -FROM gcr.io/distroless/base-debian12:nonroot +FROM busybox:1.35.0-uclibc AS deploy WORKDIR / +RUN mkdir -p /web + COPY --from=build /server . +COPY --from=ui /app/build /web/build EXPOSE 16321 EXPOSE 6321 diff --git a/Makefile b/Makefile index 0be8b57..78518e4 100644 --- a/Makefile +++ b/Makefile @@ -7,3 +7,9 @@ tidy: lint: @echo "Running linter..." @golangci-lint run + +ui: + @cd web && bun run build + +docker: + @docker build -t quickmq:latest . \ No newline at end of file diff --git a/cmd/main.go b/cmd/main.go index f2435d5..ae55bdc 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -7,18 +7,29 @@ import ( "time" "github.com/ochom/gutils/logs" + "github.com/ochom/quickmq/src/api" "github.com/ochom/quickmq/src/app" ) func main() { - svr := app.New() - port := ":16321" + coreServer := app.New() + webServer := api.New() + + // run core go func() { - if err := svr.Listen(port); err != nil { + if err := coreServer.Listen(":6321"); err != nil { panic(err) } }() + // run api and web + go func() { + if err := webServer.Listen(":16321"); err != nil { + panic(err) + } + }() + + // go run consumer daemon go func() { stopSignal := make(chan bool, 1) logs.Info("starting consumers daemon") @@ -34,7 +45,13 @@ func main() { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() - if err := svr.ShutdownWithContext(ctx); err != nil { + // shutdown core server + if err := coreServer.ShutdownWithContext(ctx); err != nil { + panic(err) + } + + // shutdown api server + if err := webServer.ShutdownWithContext(ctx); err != nil { panic(err) } diff --git a/go.mod b/go.mod index a0b67ae..7a02b8a 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( require ( github.com/andybalholm/brotli v1.1.0 // indirect github.com/gofiber/utils/v2 v2.0.0-beta.4 // indirect + github.com/golang-jwt/jwt v3.2.2+incompatible // indirect github.com/google/uuid v1.6.0 // indirect github.com/joho/godotenv v1.5.1 // indirect github.com/klauspost/compress v1.17.9 // indirect diff --git a/go.sum b/go.sum index 7af6623..66b2c15 100644 --- a/go.sum +++ b/go.sum @@ -6,6 +6,8 @@ github.com/gofiber/fiber/v3 v3.0.0-beta.2 h1:mVVgt8PTaHGup3NGl/+7U7nEoZaXJ5OComV github.com/gofiber/fiber/v3 v3.0.0-beta.2/go.mod h1:w7sdfTY0okjZ1oVH6rSOGvuACUIt0By1iK0HKUb3uqM= github.com/gofiber/utils/v2 v2.0.0-beta.4 h1:1gjbVFFwVwUb9arPcqiB6iEjHBwo7cHsyS41NeIW3co= github.com/gofiber/utils/v2 v2.0.0-beta.4/go.mod h1:sdRsPU1FXX6YiDGGxd+q2aPJRMzpsxdzCXo9dz+xtOY= +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/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/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= diff --git a/src/api/app.go b/src/api/app.go new file mode 100644 index 0000000..83b59af --- /dev/null +++ b/src/api/app.go @@ -0,0 +1,21 @@ +package api + +import ( + "github.com/gofiber/fiber/v3" +) + +func New() *fiber.App { + app := fiber.New() + + // rest apis + app.Post("/login", login) + app.Get("/user", loadUSer) + + // serve other static files + app.Static("/", "web/build") + app.Get("*", func(c fiber.Ctx) error { + return c.SendFile("web/build/index.html") + }) + + return app +} diff --git a/src/api/auth.go b/src/api/auth.go new file mode 100644 index 0000000..5da3dbb --- /dev/null +++ b/src/api/auth.go @@ -0,0 +1,61 @@ +package api + +import ( + "time" + + "github.com/gofiber/fiber/v3" + "github.com/ochom/gutils/auth" + "github.com/ochom/gutils/env" + "github.com/ochom/gutils/uuid" +) + +func login(c fiber.Ctx) error { + var data struct { + Username string `json:"username"` + Password string `json:"password"` + } + if err := c.Bind().Body(&data); err != nil { + return err + } + + username := env.Get("QUICK_MQ_USERNAME", "admin") + password := env.Get("QUICK_MQ_PASSWORD", "admin") + + if data.Username != username { + return c.Status(400).JSON(fiber.Map{"status": "error", "message": "Invalid username"}) + } + + if data.Password != password { + return c.Status(400).JSON(fiber.Map{"status": "error", "message": "Invalid password"}) + } + + token, err := auth.GenerateAuthTokens(map[string]string{"user": "admin", "session_id": uuid.New()}) + if err != nil { + return err + } + + // Set a cookie + cookie := fiber.Cookie{ + Name: "jwt", + Value: token["token"], + Expires: time.Now().Add(time.Hour * 24), + HTTPOnly: true, + } + c.Cookie(&cookie) + + return c.JSON(fiber.Map{"status": "success", "message": "Logged in", "username": "admin"}) +} + +func loadUSer(c fiber.Ctx) error { + token := c.Cookies("jwt") + claims, err := auth.GetAuthClaims(token) + if err != nil { + return c.Status(401).JSON(fiber.Map{"status": "error", "message": "Unauthorized", "data": err.Error()}) + } + + if claims["user"] != "admin" { + return c.Status(401).JSON(fiber.Map{"status": "error", "message": "Unauthorized"}) + } + + return c.JSON(fiber.Map{"status": "success", "message": "Logged in", "username": "admin"}) +} diff --git a/web/.eslintrc.cjs b/web/.eslintrc.cjs new file mode 100644 index 0000000..73cc6ce --- /dev/null +++ b/web/.eslintrc.cjs @@ -0,0 +1,14 @@ +module.exports = { + root: true, + env: { browser: true, es2020: true }, + extends: ['eslint:recommended', 'plugin:react/recommended', 'plugin:react/jsx-runtime', 'plugin:react-hooks/recommended'], + ignorePatterns: ['dist', '.eslintrc.cjs'], + parserOptions: { ecmaVersion: 'latest', sourceType: 'module' }, + settings: { react: { version: '18.2' } }, + plugins: ['react-refresh'], + rules: { + 'react/jsx-no-target-blank': 'off', + 'react-refresh/only-export-components': ['warn', { allowConstantExport: true }], + 'react/prop-types': 'off' + } +}; diff --git a/web/.gitignore b/web/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/web/.gitignore @@ -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? diff --git a/web/.prettierrc.json b/web/.prettierrc.json new file mode 100644 index 0000000..e7fe6d5 --- /dev/null +++ b/web/.prettierrc.json @@ -0,0 +1,7 @@ +{ + "singleQuote": true, + "jsxSingleQuote": true, + "arrowParens": "avoid", + "trailingComma": "none", + "printWidth": 200 +} \ No newline at end of file diff --git a/web/README.md b/web/README.md new file mode 100644 index 0000000..f768e33 --- /dev/null +++ b/web/README.md @@ -0,0 +1,8 @@ +# React + Vite + +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. + +Currently, two official plugins are available: + +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh diff --git a/web/bun.lockb b/web/bun.lockb new file mode 100755 index 0000000..3de9440 Binary files /dev/null and b/web/bun.lockb differ diff --git a/web/index.html b/web/index.html new file mode 100644 index 0000000..f853161 --- /dev/null +++ b/web/index.html @@ -0,0 +1,22 @@ + + + +
+ + + +