Big Refacotr -part1
Some checks are pending
continuous-integration/drone/push Build is pending

This commit is contained in:
2025-10-27 14:01:25 +01:00
parent 44edb080d8
commit 043f6be0b2
42 changed files with 601 additions and 574 deletions

4
.gitignore vendored
View File

@@ -2,10 +2,8 @@ deploy/.env
!deploy/.env.dist
deploy/.env.*
deploy/app.run
deploy/server
deploy/chronos
deploy/scheduler
!deploy/certs/.gitkeep
deploy/certs/*

View File

@@ -3,14 +3,13 @@ FROM golang:alpine
ARG BIN_OUTPUT=/go/bin
ARG GO_SERVER=cmd/server/main.go
ARG GO_CHRONOS=cmd/scheduler/main.go
ARG GO_MIGRATE=cmd/migrate/main.go
ARG GO_CHRONOS=cmd/chronos/main.go
WORKDIR /go/src/app
COPY src/ ./
RUN export CGO_ENABLED=0 ; export GOOS=linux ; export GOARCH=amd64 && \
go build -ldflags="-w -s" -o "$BIN_OUTPUT/server" $GO_SERVER && \
go build -ldflags="-w -s" -o "$BIN_OUTPUT/migrate" $GO_MIGRATE && \
go build -ldflags="-w -s" -o "$BIN_OUTPUT/chronos" $GO_CHRONOS
go build -ldflags="-w -s" -o "$BIN_OUTPUT/scheduler" $GO_CHRONOS && \
go build -ldflags="-w -s" -o "$BIN_OUTPUT/migrate" $GO_MIGRATE

View File

@@ -17,9 +17,11 @@ LABEL dev.egommerce.image.version=${SVC_VER}
LABEL dev.egommerce.image.build_time=${BUILD_TIME}
WORKDIR /
COPY --from=builder /go/bin/server /usr/local/bin/server
COPY --from=builder /go/bin/migrate /usr/local/bin/migrate
COPY --from=builder /go/bin/chronos /usr/local/bin/chronos
COPY --from=builder /go/bin/scheduler /usr/local/bin/scheduler
COPY deploy/.env.docker /.env
COPY ./bin/* /usr/local/bin/

View File

@@ -19,8 +19,8 @@ build-local-server:
run-local-server:
- cd deploy/ && ./server
build-local-chronos:
- go build -C ${SRC_DIR} -o ../deploy/chronos cmd/chronos/main.go
build-local-scheduler:
- go build -C ${SRC_DIR} -o ../deploy/scheduler cmd/scheduler/main.go
run-local-chronos:
- cd deploy/ && ./chronos
run-local-scheduler:
- cd deploy/ && ./scheduler

View File

@@ -2,9 +2,7 @@ SERVER_ADDR=:443
APP_NAME=identity-svc
APP_DOMAIN=identity.service.ego.io
APP_PATH_PREFIX=/identity
API_LOGGER_ADDR=api-logger:24224
API_DATABASE_URL=postgres://postgres:12345678@db-postgres:5432/egommerce
API_CACHE_ADDR=api-cache:6379
API_CACHE_USERNAME=default

View File

@@ -10,7 +10,7 @@ TARGET=${1:-latest}
[ ! -d "src/vendor" ] && sh -c "cd src; go mod vendor"
echo "Building tmp $IMAGE_PREFIX image..."
echo "Building target $IMAGE_PREFIX images..."
docker build --rm -t $BUILDER_IMAGE -f Dockerfile.builder .
if [ $TARGET = "latest" ]
@@ -23,7 +23,7 @@ then
--build-arg BUILD_TIME \
--rm --cache-from $SERVER_IMAGE:$TARGET \
-t $SERVER_IMAGE:$TARGET \
-f Dockerfile.target . >/dev/null 2>&1 && echo "Successfully tagged $SERVER_IMAGE:$TARGET"
-f Dockerfile.target . > /dev/null 2>&1 && echo "Successfully tagged $SERVER_IMAGE:$TARGET"
else
# DEV
docker build \
@@ -32,7 +32,7 @@ else
--build-arg BUILDER_IMAGE=$BUILDER_IMAGE \
--build-arg BUILD_TIME \
--rm --no-cache -t $SERVER_IMAGE:$TARGET \
-f Dockerfile.target . >/dev/null 2>&1 && echo "Successfully tagged $SERVER_IMAGE:$TARGET"
-f Dockerfile.target . > /dev/null 2>&1 && echo "Successfully tagged $SERVER_IMAGE:$TARGET"
fi
echo "Done."

View File

@@ -11,4 +11,4 @@ echo $DOCKER_PASSWORD | docker login git.ego.freeddns.org -u $DOCKER_USERNAME --
docker push "$SERVER_IMAGE:$TARGET"
# Restart container
curl -X POST http://127.0.0.1:9001/api/webhooks/64ea5d78-ae21-474c-ad4d-1d98f6b83acb
# TODO

40
src/app/app.go Normal file
View File

@@ -0,0 +1,40 @@
package app
import (
"log"
"os"
"os/signal"
"syscall"
)
type App struct {
worker Worker
}
func NewApp(worker Worker) *App {
return &App{
worker: worker,
}
}
func (app *App) Start(while chan struct{}) error {
go func(while chan struct{}) {
sigint := make(chan os.Signal, 1)
signal.Notify(sigint, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
<-sigint
log.Println("Received signal:", sigint)
app.Shutdown()
close(while)
}(while)
return app.worker.Start()
}
func (app *App) Shutdown() {
app.worker.OnShutdown()
}
func (app *App) RegisterPlugin(plugin Plugin) {
app.worker.addPlugin(plugin.name, plugin.fn)
}

View File

@@ -1,4 +1,4 @@
package common
package app
import (
"fmt"
@@ -53,21 +53,21 @@ func NewConfig(name string) *Config {
return c
}
func (c *Config) GetAppFullName() string {
// func (c *Config) GetArray() map[string]string { // FIXME fix types etc
// arr := make(map[string]string)
// arr["id"] = c.ID
// arr["name"] = c.Name
// arr["appFullname"] = c.getAppFullName()
// arr["domain"] = c.Domain
// arr["netAddr"] = c.NetAddr
// arr["cacheAddr"] = c.CacheAddr
// arr["cacheUsername"] = c.CacheUsername
// arr["cachePassword"] = c.CachePassword
// arr["dbURL"] = c.DbURL
// return arr
// }
func (c *Config) getAppFullName() string {
return fmt.Sprintf("%s_%s", c.Name, c.ID)
}
func (c *Config) GetArray() map[string]string { // FIXME fix types etc
arr := make(map[string]string)
arr["id"] = c.ID
arr["name"] = c.Name
arr["appFullname"] = c.GetAppFullName()
arr["domain"] = c.Domain
arr["netAddr"] = c.NetAddr
arr["cacheAddr"] = c.CacheAddr
arr["cacheUsername"] = c.CacheUsername
arr["cachePassword"] = c.CachePassword
arr["dbURL"] = c.DbURL
return arr
}

19
src/app/dependencies.go Normal file
View File

@@ -0,0 +1,19 @@
package app
import (
"github.com/go-redis/redis/v8"
"github.com/jackc/pgx/v5/pgxpool"
)
type Dependencies struct {
cache *redis.Client
db *pgxpool.Pool
}
func (d *Dependencies) GetCache() *redis.Client {
return d.cache
}
func (d *Dependencies) GetDatabase() *pgxpool.Pool {
return d.db
}

23
src/app/interface.go Normal file
View File

@@ -0,0 +1,23 @@
package app
type (
Application interface {
Start()
RegisterPlugin(Plugin)
Shutdown()
}
Worker interface {
Start() error
OnShutdown()
addPlugin(name string, fn PluginFn)
}
Plugin struct {
name string
fn PluginFn
}
PluginFn func() any
)

41
src/app/plugins.go Normal file
View File

@@ -0,0 +1,41 @@
package app
import (
"context"
"log"
"os"
"time"
redis "github.com/go-redis/redis/v8"
db "github.com/jackc/pgx/v5/pgxpool"
)
func CachePlugin(cnf *Config) Plugin {
return Plugin{
name: "cache",
fn: func() any {
return redis.NewClient(&redis.Options{
Addr: cnf.CacheAddr,
Username: cnf.CacheUsername,
Password: cnf.CachePassword,
DB: 0, // TODO
DialTimeout: 100 * time.Millisecond, // TODO
})
},
}
}
func DatabasePlugin(cnf *Config) Plugin {
return Plugin{
name: "database",
fn: func() any {
dbConn, err := db.New(context.Background(), cnf.DbURL)
if err != nil {
log.Fatalf("Failed to connect to the Database: %s. Err: %v\n", cnf.DbURL, err)
os.Exit(1)
}
return dbConn
},
}
}

1
src/app/scheduler.go Normal file
View File

@@ -0,0 +1 @@
package app

165
src/app/server.go Normal file
View File

@@ -0,0 +1,165 @@
package app
import (
"crypto/tls"
"log"
"net"
"time"
"github.com/go-redis/redis/v8"
jwt "github.com/gofiber/contrib/jwt"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/cors"
"github.com/google/uuid"
"github.com/jackc/pgx/v5/pgxpool"
commonDTO "git.ego.freeddns.org/egommerce/api-entities/common/dto"
cnf "git.ego.freeddns.org/egommerce/go-api-pkg/config"
"git.ego.freeddns.org/egommerce/identity-service/internal/http"
)
var defaultCORS = cors.New(
cors.Config{
// DEV CONFIG
AllowOrigins: "*",
AllowMethods: "GET, POST, PATCH, PUT, DELETE, OPTIONS",
AllowHeaders: "Accept, Authorization, Content-Type, Vary, X-Request-Id",
// PROD CONFIG
// AllowOrigins: "http://egommerce.io:3001", // client(reactjs) app url
// AllowCredentials: true,
// AllowMethods: "GET, POST, PATCH, PUT, DELETE",
},
)
type (
Server struct {
*fiber.App
ID string
addr string // e.g. "127.0.0.1:443"
plugins map[string]any
}
// HeaderRequestID struct {
// RequestID string `reqHeader:"x-request-id"`
// }
)
func NewServer(c *Config) *Server {
srv := &Server{
ID: c.ID,
App: fiber.New(fiber.Config{
AppName: c.ID,
ServerHeader: c.Name + ":" + c.ID,
ReadTimeout: c.ReadTimeout * time.Millisecond,
WriteTimeout: c.WriteTimeout * time.Millisecond,
IdleTimeout: c.IdleTimeout * time.Millisecond,
}),
addr: c.NetAddr,
plugins: make(map[string]any),
}
return srv
}
func (s *Server) Start() error {
s.setupMiddleware()
s.setupRouter()
crt, err := tls.LoadX509KeyPair("certs/identity-svc.crt", "certs/identity-svc.key")
if err != nil {
log.Fatal(err)
}
tlsCnf := &tls.Config{Certificates: []tls.Certificate{crt}}
ln, _ := net.Listen("tcp", s.addr)
ln = tls.NewListener(ln, tlsCnf)
return s.Listener(ln)
}
func (s *Server) OnShutdown() {
log.Printf("Server %s is going down...", s.ID)
s.getDatabase().Close()
s.getCache().Close()
s.Shutdown()
}
func (s *Server) addPlugin(name string, fn PluginFn) {
s.plugins[name] = fn()
}
// Plugin helper functions - using hardcoded keys because we rely on a specific implementation here...
func (s *Server) getCache() *redis.Client {
return (s.plugins["cache"]).(*redis.Client)
}
func (s *Server) getDatabase() *pgxpool.Pool {
return (s.plugins["database"]).(*pgxpool.Pool)
}
// func GetRequestID(c *fiber.Ctx) (string, error) {
// var hdr = new(HeaderRequestID)
// if err := c.ReqHeaderParser(hdr); err != nil {
// return "", err
// }
// return hdr.RequestID, nil
// }
func (s *Server) setupRouter() {
s.Options("*", defaultCORS)
s.Use(defaultCORS)
s.Get("/health", http.HealthHandlerFn(s.getDatabase(), s.getCache()))
s.Group("/v1").
Post("/login", http.LoginHandlerFn(s.getDatabase(), s.getCache())).
Post("/refresh", http.RefreshHandlerFn(s.getDatabase(), s.getCache())). // add JWTProtected() and get token from Auth Bearer header not from the body?
Post("/register", http.RegisterHandlerFn(s.getDatabase(), s.getCache())).
Get("/access", JWTProtected(), http.AccessHandlerFn(s.getDatabase(), s.getCache()))
}
func (s *Server) setupMiddleware() {
s.Use(LoggingMiddleware())
s.Use(XRequestIDMiddleware())
}
func LoggingMiddleware() func(c *fiber.Ctx) error {
return func(c *fiber.Ctx) error {
log.Printf("Request: %s, remote: %s, via: %s",
c.Request().URI().String(),
c.Context().RemoteIP().String(),
string(c.Context().UserAgent()),
)
return c.Next()
}
}
func XRequestIDMiddleware() func(c *fiber.Ctx) error {
return func(c *fiber.Ctx) error {
c.Set("X-Request-ID", uuid.New().String())
return c.Next()
}
}
func JWTProtected() func(c *fiber.Ctx) error {
secret := []byte(cnf.GetEnv("JWT_ACCESS_TOKEN_SECRET_KEY", "FallbackAccessTokenSecret"))
return jwt.New(jwt.Config{
SigningKey: jwt.SigningKey{Key: secret},
ContextKey: "jwt",
ErrorHandler: func(c *fiber.Ctx, err error) error {
return c.Status(fiber.StatusUnauthorized).JSON(commonDTO.ErrorResponseDTO{Error: "unauthorized"})
},
})
}
// func (s *Server) Error(c *fiber.Ctx, code int, msg string) error {
// return c.Status(code).JSON(dto.ErrorResponseDTO{Error: msg})
// }

View File

@@ -1,36 +0,0 @@
package main
import (
"log"
"os"
cnf "git.ego.freeddns.org/egommerce/go-api-pkg/config"
"git.ego.freeddns.org/egommerce/identity-service/common"
"git.ego.freeddns.org/egommerce/identity-service/internal/chronos"
)
func main() {
if cnf.ErrLoadingEnvs != nil {
log.Panicln(cnf.ErrLoadingEnvs)
}
c := common.NewConfig("identity-cronjob")
cArr := c.GetArray()
doer := chronos.New(c)
a := common.NewApp(doer)
a.RegisterPlugin(common.CachePlugin(cArr))
a.RegisterPlugin(common.DatabasePlugin(cArr))
while := make(chan struct{})
err := a.Start(while)
<-while
if err != nil {
log.Fatalf("Failed to chronos. Reason: %v\n", err)
os.Exit(1)
}
os.Exit(0)
}

36
src/cmd/scheduler/main.go Normal file
View File

@@ -0,0 +1,36 @@
package main
import (
"log"
"os"
cnf "git.ego.freeddns.org/egommerce/go-api-pkg/config"
"git.ego.freeddns.org/egommerce/identity-service/app"
"git.ego.freeddns.org/egommerce/identity-service/internal/cli/scheduler"
)
func main() {
if cnf.ErrLoadingEnvs != nil {
log.Panicln(cnf.ErrLoadingEnvs)
}
c := app.NewConfig("identity-cronjob")
cArr := c.GetArray()
doer := scheduler.New(c)
a := app.NewApp(doer)
a.RegisterPlugin(app.CachePlugin(cArr))
a.RegisterPlugin(app.DatabasePlugin(cArr))
while := make(chan struct{})
err := a.Start(while)
<-while
if err != nil {
log.Fatalf("Failed to scheduler. Reason: %v\n", err)
os.Exit(1)
}
os.Exit(0)
}

View File

@@ -6,25 +6,23 @@ import (
cnf "git.ego.freeddns.org/egommerce/go-api-pkg/config"
"git.ego.freeddns.org/egommerce/identity-service/common"
"git.ego.freeddns.org/egommerce/identity-service/internal/server"
"git.ego.freeddns.org/egommerce/identity-service/app"
)
func main() {
if cnf.ErrLoadingEnvs != nil {
log.Panicln(cnf.ErrLoadingEnvs)
log.Panicln(cnf.ErrLoadingEnvs.Error())
}
c := common.NewConfig("identity-svc")
cArr := c.GetArray()
cnf := app.NewConfig("identity-svc")
worker := app.NewServer(cnf)
doer := server.New(c)
a := common.NewApp(doer)
a.RegisterPlugin(common.CachePlugin(cArr))
a.RegisterPlugin(common.DatabasePlugin(cArr))
srv := app.NewApp(worker)
srv.RegisterPlugin(app.CachePlugin(cnf))
srv.RegisterPlugin(app.DatabasePlugin(cnf))
while := make(chan struct{})
err := a.Start(while)
err := srv.Start(while)
<-while
if err != nil {

View File

@@ -1,53 +0,0 @@
package common
import (
"fmt"
"log"
"os"
"os/signal"
"syscall"
)
type (
App struct {
doer Doer
}
)
func NewApp(d Doer) *App {
return &App{
doer: d,
}
}
func (a *App) Start(while chan struct{}) error {
go func() {
sigint := make(chan os.Signal, 1)
signal.Notify(sigint, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
<-sigint
fmt.Println("Received signal:", sigint)
a.Shutdown()
close(while)
}()
err := a.doer.Start()
if err != nil {
log.Fatalf("Failed to start app. Reason: %v\n", err)
close(while)
}
<-while
return err
}
func (a *App) RegisterPlugin(p Plugin) error {
a.doer.RegisterHandler(p.name, p.fn)
return nil
}
func (a *App) Shutdown() {
a.doer.OnShutdown()
}

View File

@@ -1,14 +0,0 @@
package common
type (
Doer interface {
Start() error
RegisterHandler(string, func() any)
OnShutdown()
}
Application interface {
Start(while chan struct{})
RegisterPlugin(PluginFn) error
Shutdown()
}
)

View File

@@ -1,49 +0,0 @@
package common
import (
"log"
"os"
"time"
db "git.ego.freeddns.org/egommerce/go-api-pkg/database"
redis "github.com/go-redis/redis/v8"
)
type (
Plugin struct {
name string
fn PluginFn
}
PluginFn func() any
)
func CachePlugin(cArr map[string]string) Plugin {
return Plugin{
name: "cache",
fn: func() any {
return redis.NewClient(&redis.Options{
Addr: cArr["cacheAddr"],
Username: cArr["cacheUsername"],
Password: cArr["cachePassword"],
DB: 0,
DialTimeout: 100 * time.Millisecond,
})
},
}
}
func DatabasePlugin(cArr map[string]string) Plugin {
return Plugin{
name: "database",
fn: func() any {
dbConn, err := db.Connect(cArr["dbURL"])
if err != nil {
log.Fatalf("Failed to connect to the Database: %s. Err: %v\n", cArr["dbURL"], err)
os.Exit(1)
}
return dbConn
},
}
}

View File

@@ -6,7 +6,7 @@ toolchain go1.24.1
require (
git.ego.freeddns.org/egommerce/api-entities v0.3.34
git.ego.freeddns.org/egommerce/go-api-pkg v0.4.9
git.ego.freeddns.org/egommerce/go-api-pkg v0.5.2
github.com/georgysavva/scany/v2 v2.1.4
github.com/go-pg/migrations/v8 v8.1.0
github.com/go-pg/pg/v10 v10.15.0

View File

@@ -1,8 +1,8 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
git.ego.freeddns.org/egommerce/api-entities v0.3.34 h1:WftM9cvV3JmbS1DlHCIiV3tsYIpALj9IXo90mkgZNWQ=
git.ego.freeddns.org/egommerce/api-entities v0.3.34/go.mod h1:IqynARw+06GOm4eZGZuepmbi7bUxWBnOB4jd5cI7jf8=
git.ego.freeddns.org/egommerce/go-api-pkg v0.4.9 h1:Y9MisGDhl/ti4gsegl9MC7KoY2aHuyA0LvIESPoiPkE=
git.ego.freeddns.org/egommerce/go-api-pkg v0.4.9/go.mod h1:Q4onxocNdFhzD9QnQK3ubd68chbJPexjDraEHoIEN3Y=
git.ego.freeddns.org/egommerce/go-api-pkg v0.5.2 h1:szfCwZ8S1Yf3b6LwpBs0DZYQZMsVl4Fe6VU1ou4LTOE=
git.ego.freeddns.org/egommerce/go-api-pkg v0.5.2/go.mod h1:T3ia8iprzlTRznPVXYCgEzQb/1UvIcdn9FHabE58vy0=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/MicahParks/keyfunc/v2 v2.1.0 h1:6ZXKb9Rp6qp1bDbJefnG7cTH8yMN1IC/4nf+GVjO99k=
github.com/MicahParks/keyfunc/v2 v2.1.0/go.mod h1:rW42fi+xgLJ2FRRXAfNx9ZA8WpD4OeE/yHVMteCkw9k=

View File

@@ -6,7 +6,7 @@ type UserRepositoryInterface interface {
FindAll() ([]entity.User, error)
FindByID(id string) (*entity.User, error)
FindByUsername(login string) (*entity.User, error)
Create(user *entity.User) (string, error)
Update(user *entity.User) (*entity.User, error)
Create(user *entity.User) (string, error) // FIXME types
Update(user *entity.User) (*entity.User, error) // FIXME types
Delete(id int64) (bool, error)
}

View File

@@ -7,7 +7,7 @@ import (
entity "git.ego.freeddns.org/egommerce/api-entities/identity/entity"
"git.ego.freeddns.org/egommerce/go-api-pkg/database"
database "git.ego.freeddns.org/egommerce/go-api-pkg/client/postgresql"
"github.com/jackc/pgx/v5/pgxpool"
)
@@ -87,7 +87,6 @@ func (r *URLAccessRepository) FindByURLAndServiceForRole(url, service, role stri
err := r.db.QueryRow(context.Background(), sql, url, service).
Scan(&entity.ID, &entity.Roles, &entity.URL, &entity.Service)
if err != nil {
fmt.Println(sql, url, service, role)
if err = database.NoRowsInQuerySet(err); err != nil {
return nil, errors.New("no url found for: " + url + " and role: " + role)
}

View File

@@ -6,7 +6,7 @@ import (
entity "git.ego.freeddns.org/egommerce/api-entities/identity/entity"
db "git.ego.freeddns.org/egommerce/go-api-pkg/database"
db "git.ego.freeddns.org/egommerce/go-api-pkg/client/postgresql"
"github.com/georgysavva/scany/v2/pgxscan"
"github.com/jackc/pgx/v5/pgxpool"

View File

@@ -1,52 +0,0 @@
package chronos
import (
"log"
"time"
"git.ego.freeddns.org/egommerce/identity-service/common"
"github.com/go-redis/redis/v8"
"github.com/jackc/pgx/v5/pgxpool"
"github.com/onatm/clockwerk"
)
type Chronos struct {
handlers map[string]any
}
func New(c *common.Config) *Chronos {
return &Chronos{
handlers: make(map[string]any),
}
}
func (c *Chronos) Start() error {
job := NewCachePermissionsJob(c)
sch := clockwerk.New()
sch.Every(30 * time.Minute).Do(job)
sch.Start()
return nil
}
func (c *Chronos) RegisterHandler(name string, fn func() any) {
c.handlers[name] = fn()
}
func (c *Chronos) OnShutdown() {
log.Println("Chronos is going down...")
c.GetDatabase().Close()
c.GetCache().Close()
}
// Plugin helper funcitons - refactor needed cause funcs are duplcated in server.go
// TODO: move functions below to some common place
func (c *Chronos) GetCache() *redis.Client {
return (c.handlers["cache"]).(*redis.Client)
}
func (c *Chronos) GetDatabase() *pgxpool.Pool {
return (c.handlers["database"]).(*pgxpool.Pool)
}

View File

@@ -1,4 +1,4 @@
package chronos
package scheduler
import (
"fmt"
@@ -9,10 +9,10 @@ import (
)
type CachePermissionsJob struct {
sch *Chronos
sch *Scheduler
}
func NewCachePermissionsJob(sch *Chronos) CachePermissionsJob {
func NewCachePermissionsJob(sch *Scheduler) CachePermissionsJob {
return CachePermissionsJob{sch: sch}
}

View File

@@ -0,0 +1,52 @@
package scheduler
import ( // REFACTOR IT LIKE A SERVER WAS
"log"
"time"
"git.ego.freeddns.org/egommerce/identity-service/app"
// "github.com/go-redis/redis/v8"
// "github.com/jackc/pgx/v5/pgxpool"
"github.com/onatm/clockwerk"
)
type Scheduler struct {
handlers map[string]any
}
func New(c *app.Config) *Scheduler {
return &Scheduler{
handlers: make(map[string]any),
}
}
func (c *Scheduler) Start() error {
job := NewCachePermissionsJob(c)
sch := clockwerk.New()
sch.Every(30 * time.Minute).Do(job)
sch.Start()
return nil
}
// func (c *Scheduler) RegisterHandler(name string, fn func() any) {
// c.handlers[name] = fn()
// }
func (c *Scheduler) OnShutdown() {
log.Println("Chronos is going down...")
// c.GetDatabase().Close()
// c.GetCache().Close()
}
// Plugin helper funcitons - refactor needed cause funcs are duplcated in server.go
// TODO: move functions below to some common place
// func (c *Scheduler) GetCache() *redis.Client {
// return (c.handlers["cache"]).(*redis.Client)
// }
// func (c *Scheduler) GetDatabase() *pgxpool.Pool {
// return (c.handlers["database"]).(*pgxpool.Pool)
// }

View File

@@ -0,0 +1,34 @@
package http
import (
commonDTO "git.ego.freeddns.org/egommerce/api-entities/common/dto"
identityDTO "git.ego.freeddns.org/egommerce/api-entities/identity/dto"
"git.ego.freeddns.org/egommerce/identity-service/infra/repository"
"git.ego.freeddns.org/egommerce/identity-service/internal/service"
"git.ego.freeddns.org/egommerce/identity-service/internal/ui"
"github.com/go-redis/redis/v8"
"github.com/gofiber/fiber/v2"
"github.com/jackc/pgx/v5/pgxpool"
)
func AccessHandlerFn(db *pgxpool.Pool, cache *redis.Client) fiber.Handler {
return func(c *fiber.Ctx) error {
userRepo := repository.NewUserRepository(db)
roleRepo := repository.NewRoleRepository(db)
urlRepo := repository.NewURLAccessRepository(db)
authSrv := service.NewAuthService(userRepo, cache)
guardSrv := service.NewGuardService(authSrv, cache, userRepo, roleRepo, urlRepo)
url, srvName := c.Query("q"), c.Query("srv")
header := new(identityDTO.AuthorizationHeaderDTO)
c.ReqHeaderParser(header)
if err := ui.NewAccessActionUI(guardSrv).Execute(header, url, srvName); err != nil {
return c.Status(fiber.StatusNotFound).JSON(&commonDTO.ErrorResponseDTO{Error: err.Error()})
}
return c.SendStatus(fiber.StatusNoContent)
}
}

View File

@@ -0,0 +1,33 @@
package http
import (
"context"
"net/http"
"github.com/go-redis/redis/v8"
"github.com/gofiber/fiber/v2"
"github.com/jackc/pgx/v5/pgxpool"
)
type HealthResponse struct {
Status string `json:"status,omitempty"`
}
func HealthHandlerFn(db *pgxpool.Pool, cache *redis.Client) fiber.Handler {
return func(c *fiber.Ctx) error {
// Only 404 indicate service as not-healthy
err := db.Ping(context.Background())
if err != nil {
return c.SendStatus(http.StatusNotFound)
}
err = cache.Ping(context.Background()).Err()
if err != nil {
return c.SendStatus(http.StatusNotFound)
}
return c.JSON(&HealthResponse{
Status: "OK",
})
}
}

View File

@@ -0,0 +1,35 @@
package http
import (
"github.com/go-redis/redis/v8"
"github.com/gofiber/fiber/v2"
"github.com/jackc/pgx/v5/pgxpool"
commonDTO "git.ego.freeddns.org/egommerce/api-entities/common/dto"
identityDTO "git.ego.freeddns.org/egommerce/api-entities/identity/dto"
"git.ego.freeddns.org/egommerce/identity-service/infra/repository"
"git.ego.freeddns.org/egommerce/identity-service/internal/service"
"git.ego.freeddns.org/egommerce/identity-service/internal/ui"
)
func LoginHandlerFn(db *pgxpool.Pool, cache *redis.Client) fiber.Handler {
return func(c *fiber.Ctx) error {
req := new(identityDTO.AuthLoginRequestDTO)
if err := c.BodyParser(req); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(&commonDTO.ErrorResponseDTO{Error: "error parsing input"})
// return srv.Error(c, fiber.StatusBadRequest, "error parsing input")
}
userRepo := repository.NewUserRepository(db)
authSrv := service.NewAuthService(userRepo, cache)
token, err := ui.NewLoginActionUI(authSrv).Execute(req)
if err != nil { // TODO: handle other response status codes -- add struct to decorate error with code and message
return c.Status(fiber.StatusBadRequest).JSON(commonDTO.ErrorResponseDTO{Error: err.Error()})
// return srv.Error(c, fiber.StatusBadRequest, err.Error())
}
return c.JSON(&identityDTO.AuthLoginResponseDTO{Token: token})
}
}

View File

@@ -0,0 +1,35 @@
package http
import (
commonDTO "git.ego.freeddns.org/egommerce/api-entities/common/dto"
identityDTO "git.ego.freeddns.org/egommerce/api-entities/identity/dto"
"git.ego.freeddns.org/egommerce/identity-service/infra/repository"
"git.ego.freeddns.org/egommerce/identity-service/internal/service"
"git.ego.freeddns.org/egommerce/identity-service/internal/ui"
"github.com/go-redis/redis/v8"
"github.com/gofiber/fiber/v2"
"github.com/jackc/pgx/v5/pgxpool"
)
func RefreshHandlerFn(db *pgxpool.Pool, cache *redis.Client) fiber.Handler {
return func(c *fiber.Ctx) error {
header := new(identityDTO.AuthorizationHeaderDTO)
if err := c.ReqHeaderParser(header); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(commonDTO.ErrorResponseDTO{Error: "Error parsing headers"})
// return srv.Error(c, fiber.StatusBadRequest, "Error parsing headers")
}
repo := repository.NewUserRepository(db)
authSrv := service.NewAuthService(repo, cache)
token, err := ui.NewRefreshTokenActionUI(authSrv).Execute(header)
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(&commonDTO.ErrorResponseDTO{Error: err.Error()})
// return srv.Error(c, fiber.StatusBadRequest, err.Error())
}
return c.JSON(&identityDTO.AuthRefreshTokenResponseDTO{Token: token})
}
}

View File

@@ -0,0 +1,33 @@
package http
import (
commonDTO "git.ego.freeddns.org/egommerce/api-entities/common/dto"
identityDTO "git.ego.freeddns.org/egommerce/api-entities/identity/dto"
"git.ego.freeddns.org/egommerce/identity-service/infra/repository"
"git.ego.freeddns.org/egommerce/identity-service/internal/ui"
"github.com/go-redis/redis/v8"
"github.com/gofiber/fiber/v2"
"github.com/jackc/pgx/v5/pgxpool"
)
func RegisterHandlerFn(db *pgxpool.Pool, cache *redis.Client) fiber.Handler {
return func(c *fiber.Ctx) error {
data := new(identityDTO.AuthRegisterRequestDTO)
if err := c.BodyParser(data); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(commonDTO.ErrorResponseDTO{Error: "Error parsing input"})
// return srv.Error(c, fiber.StatusBadRequest, "Error parsing input")
}
repo := repository.NewUserRepository(db)
id, err := ui.NewRegisterActionUI(repo, cache).Execute(data)
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(commonDTO.ErrorResponseDTO{Error: err.Error()})
// return srv.Error(c, fiber.StatusBadRequest, err.Error())
}
return c.JSON(&identityDTO.AuthRegisterResponseDTO{ID: id})
}
}

View File

@@ -1,28 +0,0 @@
package server
import (
dto "git.ego.freeddns.org/egommerce/api-entities/identity/dto"
"git.ego.freeddns.org/egommerce/identity-service/infra/repository"
"git.ego.freeddns.org/egommerce/identity-service/internal/service"
"git.ego.freeddns.org/egommerce/identity-service/internal/ui"
"github.com/gofiber/fiber/v2"
)
func (s *Server) AccessHandlerFn(c *fiber.Ctx) error {
userRepo := repository.NewUserRepository(s.GetDatabase())
roleRepo := repository.NewRoleRepository(s.GetDatabase())
urlRepo := repository.NewURLAccessRepository(s.GetDatabase())
authSrv := service.NewAuthService(userRepo, s.GetCache())
guardSrv := service.NewGuardService(authSrv, s.GetCache(), userRepo, roleRepo, urlRepo)
url, srvName := c.Query("q"), c.Query("srv")
header := new(dto.AuthorizationHeaderDTO)
c.ReqHeaderParser(header)
if err := ui.NewAccessActionUI(guardSrv).Execute(header, url, srvName); err != nil {
return s.Error(c, fiber.StatusNotFound, err.Error())
}
return c.SendStatus(fiber.StatusNoContent)
}

View File

@@ -1,29 +0,0 @@
package server
import (
"context"
"net/http"
"github.com/gofiber/fiber/v2"
)
type HealthResponse struct {
Status string `json:"status,omitempty"`
}
func (s *Server) HealthHandler(c *fiber.Ctx) error {
// Only 404 indicate service as not-healthy
err := s.GetDatabase().Ping(context.Background())
if err != nil {
return c.SendStatus(http.StatusNotFound)
}
err = s.GetCache().Ping(context.Background()).Err()
if err != nil {
return c.SendStatus(http.StatusNotFound)
}
return c.JSON(&HealthResponse{
Status: "OK",
})
}

View File

@@ -1,27 +0,0 @@
package server
import (
dto "git.ego.freeddns.org/egommerce/api-entities/identity/dto"
"git.ego.freeddns.org/egommerce/identity-service/infra/repository"
"git.ego.freeddns.org/egommerce/identity-service/internal/service"
"git.ego.freeddns.org/egommerce/identity-service/internal/ui"
"github.com/gofiber/fiber/v2"
)
func (s *Server) LoginHandlerFn(c *fiber.Ctx) error {
req := new(dto.AuthLoginRequestDTO)
if err := c.BodyParser(req); err != nil {
return s.Error(c, fiber.StatusBadRequest, "error parsing input")
}
userRepo := repository.NewUserRepository(s.GetDatabase())
authSrv := service.NewAuthService(userRepo, s.GetCache())
token, err := ui.NewLoginActionUI(authSrv).Execute(req)
if err != nil { // TODO: handle other response status codes -- add struct to decorate error with code and message
return s.Error(c, fiber.StatusBadRequest, err.Error())
}
return c.JSON(&dto.AuthLoginResponseDTO{Token: token})
}

View File

@@ -1,52 +0,0 @@
package server
import (
"log"
cnf "git.ego.freeddns.org/egommerce/go-api-pkg/config"
jwt "github.com/gofiber/contrib/jwt"
"github.com/gofiber/fiber/v2"
"github.com/google/uuid"
)
// "github.com/gofiber/fiber/v2"
// "github.com/gofiber/fiber/v2/middleware/cors"
func SetupMiddleware(s *Server) {
s.Use(LoggingMiddleware())
s.Use(XRequestIDMiddleware())
}
func LoggingMiddleware() func(c *fiber.Ctx) error {
return func(c *fiber.Ctx) error {
log.Printf("Request: %s, remote: %s, via: %s",
c.Request().URI().String(),
c.Context().RemoteIP().String(),
string(c.Context().UserAgent()),
)
return c.Next()
}
}
func XRequestIDMiddleware() func(c *fiber.Ctx) error {
return func(c *fiber.Ctx) error {
c.Set("X-Request-ID", uuid.New().String())
return c.Next()
}
}
func JWTProtected(s *Server) func(c *fiber.Ctx) error {
secret := []byte(cnf.GetEnv("JWT_ACCESS_TOKEN_SECRET_KEY", "FallbackAccessTokenSecret"))
return func(c *fiber.Ctx) error {
return jwt.New(jwt.Config{
SigningKey: jwt.SigningKey{Key: secret},
ContextKey: "jwt",
ErrorHandler: func(c *fiber.Ctx, err error) error {
return s.Error(c, fiber.StatusUnauthorized, "unauthorized")
},
})(c)
}
}

View File

@@ -1,27 +0,0 @@
package server
import (
dto "git.ego.freeddns.org/egommerce/api-entities/identity/dto"
"git.ego.freeddns.org/egommerce/identity-service/infra/repository"
"git.ego.freeddns.org/egommerce/identity-service/internal/service"
"git.ego.freeddns.org/egommerce/identity-service/internal/ui"
"github.com/gofiber/fiber/v2"
)
func (s *Server) RefreshHandlerFn(c *fiber.Ctx) error {
header := new(dto.AuthorizationHeaderDTO)
if err := c.ReqHeaderParser(header); err != nil {
return s.Error(c, fiber.StatusBadRequest, "Error parsing headers")
}
repo := repository.NewUserRepository(s.GetDatabase())
authSrv := service.NewAuthService(repo, s.GetCache())
token, err := ui.NewRefreshTokenActionUI(authSrv).Execute(header)
if err != nil {
return s.Error(c, fiber.StatusBadRequest, err.Error())
}
return c.JSON(&dto.AuthRefreshTokenResponseDTO{Token: token})
}

View File

@@ -1,25 +0,0 @@
package server
import (
dto "git.ego.freeddns.org/egommerce/api-entities/identity/dto"
"git.ego.freeddns.org/egommerce/identity-service/infra/repository"
"git.ego.freeddns.org/egommerce/identity-service/internal/ui"
"github.com/gofiber/fiber/v2"
)
func (s *Server) RegisterHandlerFn(c *fiber.Ctx) error {
data := new(dto.AuthRegisterRequestDTO)
if err := c.BodyParser(data); err != nil {
return s.Error(c, fiber.StatusBadRequest, "Error parsing input")
}
repo := repository.NewUserRepository(s.GetDatabase())
id, err := ui.NewRegisterActionUI(repo, s.GetCache()).Execute(data)
if err != nil {
return s.Error(c, fiber.StatusBadRequest, err.Error())
}
return c.JSON(&dto.AuthRegisterResponseDTO{ID: id})
}

View File

@@ -1,27 +0,0 @@
package server
import (
"github.com/gofiber/fiber/v2/middleware/cors"
)
var (
defaultCORS = cors.New(cors.Config{
AllowOrigins: "*",
// AllowCredentials: true,
AllowMethods: "GET, POST, PATCH, PUT, DELETE, OPTIONS",
AllowHeaders: "Accept, Authorization, Content-Type, Vary, X-Request-Id",
})
)
func SetupRouter(s *Server) {
s.Options("*", defaultCORS)
s.Use(defaultCORS)
s.Get("/health", s.HealthHandler)
s.Group("/v1").
Post("/login", s.LoginHandlerFn).
Post("/refresh", s.RefreshHandlerFn). // TODO: add JWTProtected() and get token from Auth Bearer Header not from the Body
Post("/register", s.RegisterHandlerFn).
Get("/access", JWTProtected(s), s.AccessHandlerFn)
}

View File

@@ -1,95 +0,0 @@
package server
import (
"crypto/tls"
"log"
"net"
"time"
"github.com/go-redis/redis/v8"
"github.com/gofiber/fiber/v2"
"github.com/jackc/pgx/v5/pgxpool"
dto "git.ego.freeddns.org/egommerce/api-entities/common/dto"
"git.ego.freeddns.org/egommerce/identity-service/common"
)
type (
Server struct {
*fiber.App
ID string
addr string // e.g. "127.0.0.1:443"
handlers map[string]any
}
HeaderRequestID struct {
RequestID string `reqHeader:"x-request-id"`
}
)
func New(c *common.Config) *Server {
return &Server{
ID: c.ID,
App: fiber.New(fiber.Config{
AppName: c.ID,
ServerHeader: c.Name + ":" + c.ID,
ReadTimeout: c.ReadTimeout * time.Millisecond,
WriteTimeout: c.WriteTimeout * time.Millisecond,
IdleTimeout: c.IdleTimeout * time.Millisecond,
}),
addr: c.NetAddr,
handlers: make(map[string]any),
}
}
func (s *Server) Start() error {
SetupMiddleware(s)
SetupRouter(s)
crt, err := tls.LoadX509KeyPair("certs/identity-svc.crt", "certs/identity-svc.key")
if err != nil {
log.Fatal(err)
}
tlsCnf := &tls.Config{Certificates: []tls.Certificate{crt}}
ln, _ := net.Listen("tcp", s.addr)
ln = tls.NewListener(ln, tlsCnf)
return s.Listener(ln)
}
func (s *Server) RegisterHandler(name string, fn func() any) {
s.handlers[name] = fn()
}
func (s *Server) OnShutdown() {
log.Printf("Server %s is going down...", s.ID)
s.GetDatabase().Close()
s.GetCache().Close()
s.Shutdown()
}
func (s *Server) GetRequestID(c *fiber.Ctx) (string, error) {
var hdr = new(HeaderRequestID)
if err := c.ReqHeaderParser(hdr); err != nil {
return "", err
}
return hdr.RequestID, nil
}
func (s *Server) Error(c *fiber.Ctx, code int, msg string) error {
return c.Status(code).JSON(dto.ErrorResponseDTO{Error: msg})
}
// Plugin helper funcitons - refactor needed cause funcs are duplcated in chronos.go
func (s *Server) GetCache() *redis.Client {
return (s.handlers["cache"]).(*redis.Client)
}
func (s *Server) GetDatabase() *pgxpool.Pool {
return (s.handlers["database"]).(*pgxpool.Pool)
}

View File

@@ -6,15 +6,15 @@ import (
"git.ego.freeddns.org/egommerce/identity-service/internal/service"
)
type AccessActionUI struct {
type CheckUserPermissionsUI struct {
guard *service.GuardService
}
func NewAccessActionUI(guard *service.GuardService) *AccessActionUI {
return &AccessActionUI{guard: guard}
func NewAccessActionUI(guard *service.GuardService) *CheckUserPermissionsUI {
return &CheckUserPermissionsUI{guard: guard}
}
func (ui *AccessActionUI) Execute(data *dto.AuthorizationHeaderDTO, url, srvName string) error { // TODO: rename to CheckAccess
func (ui *CheckUserPermissionsUI) Execute(data *dto.AuthorizationHeaderDTO, url, srvName string) error { // TODO: rename to CheckAccess
err := ui.guard.CheckUserPermissions(data, url, srvName)
if err != nil {
return err