This commit is contained in:
2024-07-19 17:04:42 +02:00
commit 5e0d0ec69f
71 changed files with 3316 additions and 0 deletions

View File

@@ -0,0 +1,32 @@
name: Build
on:
workflow_dispatch:
push:
branches:
- '**'
jobs:
build:
runs-on: ubuntu-latest
env:
GOPRIVATE: github.com/kratisto
GONOSUMDB: "*github.com/kratisto/mangezmieux-backend"
steps:
- uses: actions/checkout@v3
- name: Configure git for private modules
env:
TOKEN: ${{ secrets.GHA_TOKEN_PAT }}
run: git config --global url."https://YOUR_GITHUB_USERNAME:${TOKEN}@github.com".insteadOf "https://github.com"
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: '1.21'
- name: Build
run: go build -v ./...
- name: Test
run: go test -v ./...

View File

@@ -0,0 +1,21 @@
name: Release
on:
workflow_dispatch:
push:
branches:
- 'main'
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Semantic Release
id: semantic # Need an `id` for output variables
uses: cycjimmy/semantic-release-action@v4
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- uses: rickstaa/action-create-tag@v1
id: "tag_create"
with:
tag: "pkg/client/v${{ steps.semantic.outputs.new_release_version }}"

5
mangezmieux-backend/.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
mangezmieux-backend
vendor/
.idea/
*.iml

View File

@@ -0,0 +1,38 @@
{
"branches": ["main"],
"tagFormat":"v${version}",
"plugins": [
[
"@semantic-release/commit-analyzer",
{
"preset": "angular",
"releaseRules": [
{"type": "chore", "release": "patch"}
]
}
],
"@semantic-release/release-notes-generator",
[
"@semantic-release/changelog",
{
"changelogFile": "CHANGELOG.md"
}
],
[
"@semantic-release/git",
{
"assets": ["CHANGELOG.md"],
"message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
}
],
"@semantic-release/github",
[
"@semantic-release/git",
{
"assets": ["CHANGELOG.md"],
"message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
}
]
]
}

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 Jeffrey Duroyon
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,82 @@
# Set an output prefix, which is the local directory if not specified
PREFIX?=$(shell pwd)
.DEFAULT_GOAL := build
DOCKER_IMAGE_NAME := mangezmieux-backend
# Setup name variables for the package/tool
NAME := mangezmieux-backend
BIN_NAME := mangezmieux-backend
PKG := github.com/kratisto/mangezmieux-backend
# GO env vars
ifeq ($(GOPATH),)
GOPATH:=~/go
endif
GO=$(firstword $(subst :, ,$(GOPATH)))
.PHONY: ensure-vendor
ensure-vendor: ## Get all vendor dependencies
@echo "+ $@"
dep ensure
.PHONY: update-vendor
update-vendor: ## Get all vendor dependencies
@echo "+ $@"
dep ensure -update
.PHONY: clean
clean: local-clean ## Clean your generated files
@echo "+ $@"
docker image rm mangezmieux:snapshot || true
.PHONY: local-build
local-build: local-clean local-format ## Build locally the binary
@echo "+ $@"
go build -o $(GO)/bin/$(BIN_NAME) .
.PHONY: local-clean
local-clean: ## Cleanup locally any build binaries or packages
@echo "+ $@"
@$(RM) $(BIN_NAME)
.PHONY: local-format
local-format: ## format locally all files
@echo "+ $@"
gofmt -s -l -w .
.PHONY: build
build: sources-image ## Build the docker image with application binary
@echo "+ $@"
docker build --no-cache \
-f containers/Dockerfile \
--build-arg SOURCES_IMAGE=mangezmieux-sources:snapshot \
-t mangezmieux:snapshot .
GIT_CREDENTIALS?=$(shell cat ~/.git-credentials 2> /dev/null)
.PHONY: sources-image
sources-image: ## Generate a Docker image with only the sources
@echo "+ $@"
docker build -t mangezmieux-sources:snapshot -f containers/Dockerfile.sources .
.PHONY: liquibase
liquibase: ## Run the dependencies of the server (launch the containers/docker-compose.local.yml)
@echo "+ $@"
@docker run --network host --rm -v ./liquibase/changelogs:/liquibase/changelog liquibase/liquibase --defaults-file=/liquibase/changelog/liquibase.properties --changelog-file=changelog-master.xml --classpath=changelog update
.PHONY: local-run-golang
local-run-golang: ## Build the server and run it
@echo "+ $@"
BUILD_GOLANG_CMD=local-build \
LAUNCH_GOLANG_CMD="$(GO)/bin/$(BIN_NAME)" \
$(MAKE) local-launch-golang
.PHONY: local-launch-golang
local-launch-golang: ## Build the server and run it
@echo "+ $@"
PID=`ps -ax | egrep "\b$(BIN_NAME)"| cut -d " " -f 1`; \
kill $$PID || true
$(MAKE) $(BUILD_GOLANG_CMD)
DB_PORT=`` $(LAUNCH_GOLANG_CMD) serve --loglevel debug --logformat text --postgreshost localhost:$$DB_PORT
.PHONY: local-run
local-run: local-run-golang ## Run the server with its dependencies

View File

@@ -0,0 +1 @@
# mangezmieux-backend

View File

@@ -0,0 +1,106 @@
package cmd
import (
"fmt"
"mangezmieux-backend/configuration"
"mangezmieux-backend/internal/logger"
"os"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
const (
// Log.
parameterLogLevel = "loglevel"
parameterLogFormat = "logformat"
defaultLogLevel = "debug"
defaultLogFormat = "text"
// Mock.
parameterMock = "mock"
defaultMock = true
// Router.
parameterPort = "port"
defaultPort = "8080"
// DATABASE.
parameterPostgresDBName = "postgresdbname"
defaultPostgresDBName = "mangezmieux"
parameterPostgresDBSchema = "postgresdbschema"
defaultPostgresDBSchema = "mangezmieux"
parameterPostgresHost = "postgreshost"
defaultPostgresHost = "localhost"
parameterPostgresUser = "postgresuser"
defaultPostgresUser = "postgres"
parameterPostgresPwd = "postgrespwd"
defaultPostgresPwd = "mysecretpassword"
)
var (
config = &configuration.Config{}
cfgFile string
// GITHASH : Stores the git revision to be displayed.
GITHASH string
// VERSION : Stores the binary version to be displayed.
VERSION string
// rootCmd represents the base command when called without any subcommands.
rootCmd = &cobra.Command{
Use: "mangezmieux",
Short: "mangezmieux",
Version: fmt.Sprintf("%s (%s)", VERSION, GITHASH),
}
)
// Execute adds all child commands to the root command and sets flags appropriately.
// This is called by main.main(). It only needs to happen once to the rootCmd.
func Execute() {
if err := rootCmd.Execute(); err != nil {
logger.GetLogger().Error(err)
os.Exit(1)
}
}
func init() {
rootCmd.AddCommand(serveCmd)
// Here you will define your flags and configuration settings.
// Cobra supports persistent flags, which, if defined here,
// will be global for your application.
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.mangezmieux.yaml)")
}
// initConfig reads in config file and ENV variables if set.
func initConfig() {
if cfgFile != "" {
// Use config file from the flag.
viper.SetConfigFile(cfgFile)
}
viper.AutomaticEnv() // read in environment variables that match
// If a config file is found, read it in.
if err := viper.ReadInConfig(); err == nil {
logger.GetLogger().Info("Using config file:", viper.ConfigFileUsed())
}
config.Mock = viper.GetBool(parameterMock)
config.Port = viper.GetString(parameterPort)
config.LogLevel = viper.GetString(parameterLogLevel)
config.LogFormat = viper.GetString(parameterLogFormat)
config.PostgresDBName = viper.GetString(parameterPostgresDBName)
config.PostgresDBSchema = viper.GetString(parameterPostgresDBSchema)
config.PostgresHost = viper.GetString(parameterPostgresHost)
config.PostgresUser = viper.GetString(parameterPostgresUser)
config.PostgresPwd = viper.GetString(parameterPostgresPwd)
config.Version = VERSION
}

View File

@@ -0,0 +1,91 @@
package cmd
import (
"fmt"
"mangezmieux-backend/internal/acl"
aclKey "mangezmieux-backend/internal/acl/key"
"mangezmieux-backend/internal/acl/service"
"mangezmieux-backend/internal/ginserver"
"mangezmieux-backend/internal/health"
"mangezmieux-backend/internal/injector"
"mangezmieux-backend/internal/jwt"
"mangezmieux-backend/internal/logger"
"mangezmieux-backend/internal/postgres"
"mangezmieux-backend/internal/users"
service2 "mangezmieux-backend/internal/users/service"
coreValidator "mangezmieux-backend/internal/validator"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
// serveCmd represents the serve command.
var serveCmd = &cobra.Command{
Use: "serve",
Short: "Serve endpoints",
PreRun: initServeBindingsFlags,
Run: func(cmd *cobra.Command, args []string) {
serve()
},
}
func serve() {
initConfig()
logger.InitLogger(config.LogLevel, config.LogFormat)
logrus.
WithField(parameterLogLevel, config.LogLevel).
WithField(parameterLogFormat, config.LogFormat).
WithField(parameterPort, config.Port).
WithField(parameterPostgresHost, config.PostgresHost).
Warn("Configuration")
inj := &injector.Injector{}
authMiddleware := users.AuthMiddleware // <= its not configured yet
jwt.Setup(inj)
jwtService := injector.Get[*jwt.Service](inj, jwt.JWTKey)
inj.Set("AuthenticationMiddleware", authMiddleware.GinMiddleware(jwtService))
ginserver.Setup(inj)
coreValidator.Setup(inj)
psqlInfo := fmt.Sprintf("host=localhost port=5432 user=%s "+
"password=mysecretpassword dbname=%s sslmode=disable",
config.PostgresUser, config.PostgresDBName)
postgres.Setup(inj, psqlInfo)
health.Setup(inj)
acl.SetupDao(inj)
users.Setup(inj)
acl.Setup(inj)
authMiddleware.Service = injector.Get[*service2.Service](inj, users.ServiceKey)
authMiddleware.RoleService = injector.Get[service.Service](inj, aclKey.ServiceKey)
ginserver.Start(inj, config.Port)
}
func init() {
serveCmd.Flags().String(parameterLogLevel, defaultLogLevel, "Use this flag to set the logging level")
serveCmd.Flags().String(parameterLogFormat, defaultLogFormat, "Use this flag to set the logging format")
serveCmd.Flags().Bool(parameterMock, defaultMock, "Use this flag to mock external services")
serveCmd.Flags().String(parameterPort, defaultPort, "Use this flag to set the listening port of the api")
serveCmd.Flags().String(parameterPostgresDBName, defaultPostgresDBName, "Use this flag to set database name")
serveCmd.Flags().String(parameterPostgresDBSchema, defaultPostgresDBSchema, "Use this flag to set database schema name")
serveCmd.Flags().String(parameterPostgresHost, defaultPostgresHost, "Use this flag to set database host")
serveCmd.Flags().String(parameterPostgresUser, defaultPostgresUser, "Use this flag to set database user name")
serveCmd.Flags().String(parameterPostgresPwd, defaultPostgresPwd, "Use this flag to set database user password")
}
func initServeBindingsFlags(cmd *cobra.Command, args []string) {
_ = viper.BindPFlag(parameterLogLevel, cmd.Flags().Lookup(parameterLogLevel))
_ = viper.BindPFlag(parameterLogFormat, cmd.Flags().Lookup(parameterLogFormat))
_ = viper.BindPFlag(parameterMock, cmd.Flags().Lookup(parameterMock))
_ = viper.BindPFlag(parameterPort, cmd.Flags().Lookup(parameterPort))
_ = viper.BindPFlag(parameterPostgresDBName, cmd.Flags().Lookup(parameterPostgresDBName))
_ = viper.BindPFlag(parameterPostgresDBSchema, cmd.Flags().Lookup(parameterPostgresDBSchema))
_ = viper.BindPFlag(parameterPostgresHost, cmd.Flags().Lookup(parameterPostgresHost))
_ = viper.BindPFlag(parameterPostgresUser, cmd.Flags().Lookup(parameterPostgresUser))
_ = viper.BindPFlag(parameterPostgresPwd, cmd.Flags().Lookup(parameterPostgresPwd))
}

View File

@@ -0,0 +1,19 @@
package configuration
// Config holds all the configuration of the application.
type Config struct {
Mock bool
Port string
LogLevel string
LogFormat string
PostgresDBName string
PostgresDBSchema string
PostgresHost string
PostgresUser string
PostgresPwd string
Version string
}

View File

@@ -0,0 +1,67 @@
module mangezmieux-backend
go 1.22
require (
github.com/cucumber/godog v0.14.1
github.com/gin-contrib/cors v1.7.2
github.com/gin-gonic/gin v1.10.0
github.com/go-playground/validator/v10 v10.22.0
github.com/gofrs/uuid v4.3.1+incompatible
github.com/golang-jwt/jwt/v5 v5.2.1
github.com/google/uuid v1.6.0
github.com/lib/pq v1.10.9
github.com/ohler55/ojg v1.22.0
github.com/sirupsen/logrus v1.9.3
github.com/spf13/cobra v1.8.1
github.com/spf13/viper v1.19.0
golang.org/x/crypto v0.24.0
)
require (
github.com/bytedance/sonic v1.11.6 // indirect
github.com/bytedance/sonic/loader v0.1.1 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect
github.com/cucumber/gherkin/go/v26 v26.2.0 // indirect
github.com/cucumber/messages/go/v21 v21.0.1 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
github.com/hashicorp/go-memdb v1.3.4 // indirect
github.com/hashicorp/golang-lru v0.5.4 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/sagikazarmark/locafero v0.4.0 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spf13/afero v1.11.0 // indirect
github.com/spf13/cast v1.6.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.9.0 // indirect
golang.org/x/arch v0.8.0 // indirect
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
golang.org/x/net v0.25.0 // indirect
golang.org/x/sys v0.21.0 // indirect
golang.org/x/text v0.16.0 // indirect
google.golang.org/protobuf v1.34.1 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

176
mangezmieux-backend/go.sum Normal file
View File

@@ -0,0 +1,176 @@
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cucumber/gherkin/go/v26 v26.2.0 h1:EgIjePLWiPeslwIWmNQ3XHcypPsWAHoMCz/YEBKP4GI=
github.com/cucumber/gherkin/go/v26 v26.2.0/go.mod h1:t2GAPnB8maCT4lkHL99BDCVNzCh1d7dBhCLt150Nr/0=
github.com/cucumber/godog v0.14.1 h1:HGZhcOyyfaKclHjJ+r/q93iaTJZLKYW6Tv3HkmUE6+M=
github.com/cucumber/godog v0.14.1/go.mod h1:FX3rzIDybWABU4kuIXLZ/qtqEe1Ac5RdXmqvACJOces=
github.com/cucumber/messages/go/v21 v21.0.1 h1:wzA0LxwjlWQYZd32VTlAVDTkW6inOFmSM+RuOwHZiMI=
github.com/cucumber/messages/go/v21 v21.0.1/go.mod h1:zheH/2HS9JLVFukdrsPWoPdmUtmYQAQPLk7w5vWsk5s=
github.com/cucumber/messages/go/v22 v22.0.0/go.mod h1:aZipXTKc0JnjCsXrJnuZpWhtay93k7Rn3Dee7iyPJjs=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
github.com/gin-contrib/cors v1.7.2 h1:oLDHxdg8W/XDoN/8zamqk/Drgt4oVZDvaV0YmvVICQw=
github.com/gin-contrib/cors v1.7.2/go.mod h1:SUJVARKgQ40dmrzgXEVxj2m7Ig1v1qIboQkPDTQ9t2E=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
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/v10 v10.22.0 h1:k6HsTZ0sTnROkhS//R0O+55JgM8C4Bx7ia+JlgcnOao=
github.com/go-playground/validator/v10 v10.22.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gofrs/uuid v4.3.1+incompatible h1:0/KbAdpx3UXAx1kEOWHJeOkpbgRFGHVgv+CFIY7dBJI=
github.com/gofrs/uuid v4.3.1+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
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/hashicorp/go-immutable-radix v1.3.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc=
github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-memdb v1.3.4 h1:XSL3NR682X/cVk2IeV0d70N4DZ9ljI885xAEU8IoK3c=
github.com/hashicorp/go-memdb v1.3.4/go.mod h1:uBTr1oQbtuMgd1SSGoR8YV27eT3sBHbYiNm53bMpgSg=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE=
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
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/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
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/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/ohler55/ojg v1.22.0 h1:McZObj3cD/Zz/ojzk5Pi5VvgQcagxmT1bVKNzhE5ihI=
github.com/ohler55/ojg v1.22.0/go.mod h1:gQhDVpQLqrmnd2eqGAvJtn+NfKoYJbe/A4Sj3/Vro4o=
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI=
github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg=
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/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
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.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
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=
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=

View File

@@ -0,0 +1,71 @@
package acl
import (
"errors"
"github.com/go-playground/validator/v10"
"mangezmieux-backend/internal/acl/service"
"mangezmieux-backend/internal/logger"
"mangezmieux-backend/internal/middleware"
"mangezmieux-backend/internal/responses"
"mangezmieux-backend/internal/users/model"
"github.com/gin-gonic/gin"
"net/http"
)
type Handler struct {
service service.Service
Validator *validator.Validate
}
const (
rolePathParam = "roleName"
roleIdPathParam = "roleId"
)
func NewHandler(service service.Service, validator *validator.Validate) *Handler {
return &Handler{
service: service,
Validator: validator,
}
}
func (h Handler) GetAllRole(context *gin.Context) {
roles, err := h.service.GetAllRole()
if err != nil {
logger.GetLogger().Error(err)
var apiError *responses.APIError
if errors.As(err, &apiError) {
responses.JSONError(context.Writer, *apiError)
return
}
responses.JSONErrorWithMessage(context.Writer, responses.ErrInternalServer, err.Error())
return
}
responses.JSON(context.Writer, http.StatusOK, roles)
}
func (h Handler) GetMyRoles(context *gin.Context) {
user, exists := context.Get(middleware.CtxUser)
if !exists {
responses.JSONErrorWithMessage(context.Writer, responses.ErrInternalServer, "User not found in context")
return
}
userModel := user.(*model.User)
userRight, err := h.service.GetRoleForCurrentUser(userModel)
if err != nil {
logger.GetLogger().Error(err)
var apiError *responses.APIError
if errors.As(err, &apiError) {
responses.JSONError(context.Writer, *apiError)
return
}
responses.JSONErrorWithMessage(context.Writer, responses.ErrInternalServer, err.Error())
return
}
responses.JSON(context.Writer, http.StatusOK, userRight)
}

View File

@@ -0,0 +1,6 @@
package key
const (
ServiceKey = "RoleService"
DaoKey = "DaoService"
)

View File

@@ -0,0 +1,48 @@
package model
import (
"github.com/google/uuid"
"mangezmieux-backend/internal/model"
)
type UserRight struct {
UserRole []*UserRole `json:"userRole"`
}
type Resource struct {
Id uuid.UUID `json:"id"`
Name string `json:"name"`
model.Metadata
}
type RoleVerbResource struct {
Id uuid.UUID `json:"id"`
Verb string `json:"verb"`
RoleId uuid.UUID `json:"role"`
ResourceId uuid.UUID `json:"resource"`
model.Metadata
}
type RoleEditable struct {
Id uuid.UUID `json:"id"`
Name string `json:"name"`
model.Metadata
}
type Role struct {
RoleEditable
ResourceVerb map[string][]*Verb
}
type Verb struct {
Id uuid.UUID `json:"id"`
Verb string `json:"verb"`
model.Metadata
}
type UserRole struct {
Id uuid.UUID `json:"id"`
RoleId uuid.UUID `json:"role"`
UserId uuid.UUID `json:"user"`
model.Metadata
}

View File

@@ -0,0 +1,60 @@
package service
import (
"github.com/google/uuid"
"mangezmieux-backend/internal/acl/model"
"mangezmieux-backend/internal/acl/sql"
model2 "mangezmieux-backend/internal/users/model"
)
type service struct {
dao sql.Dao
}
func (s service) GetRoleForCurrentUser(user *model2.User) (*model.UserRight, error) {
userId, err := uuid.Parse(user.ID.String())
if err != nil {
return nil, err
}
userRole, err := s.GetUserRoleByUser(userId)
if err != nil {
return nil, err
}
userRight := &model.UserRight{
UserRole: userRole,
}
return userRight, nil
}
func (s service) GetAllRole() ([]*model.Role, error) {
roles, err := s.dao.GetAllRole()
return roles, err
}
func (s service) GetUserRoleByUser(id uuid.UUID) ([]*model.UserRole, error) {
userRoles, err := s.dao.GetUserRoleByUser(id)
return userRoles, err
}
func (s service) GetRole(id uuid.UUID) (*model.Role, error) {
role, err := s.dao.GetRole(id)
if err != nil {
return nil, err
}
return role, nil
}
type Service interface {
GetAllRole() ([]*model.Role, error)
GetRoleForCurrentUser(user *model2.User) (*model.UserRight, error)
GetUserRoleByUser(id uuid.UUID) ([]*model.UserRole, error)
}
func NewService(dao sql.Dao) Service {
return &service{
dao: dao,
}
}

View File

@@ -0,0 +1,39 @@
package acl
import (
"database/sql"
"mangezmieux-backend/internal/acl/key"
"mangezmieux-backend/internal/acl/service"
aclSql "mangezmieux-backend/internal/acl/sql"
"mangezmieux-backend/internal/ginserver"
"mangezmieux-backend/internal/injector"
"mangezmieux-backend/internal/postgres"
"mangezmieux-backend/internal/validator"
"net/http"
"github.com/gin-gonic/gin"
validatorv10 "github.com/go-playground/validator/v10"
)
func SetupDao(inj *injector.Injector) {
client := injector.Get[*sql.DB](inj, postgres.DatabaseKey)
dao := aclSql.NewDao(client)
inj.Set(key.DaoKey, dao)
}
func Setup(inj *injector.Injector) {
securedRoute := injector.Get[*gin.RouterGroup](inj, ginserver.SecuredRouterInjectorKey)
validatorCli := injector.Get[*validatorv10.Validate](inj, validator.ValidatorInjectorKey)
dao := injector.Get[aclSql.Dao](inj, key.DaoKey)
aclService := service.NewService(dao)
handler := NewHandler(aclService, validatorCli)
inj.Set(key.ServiceKey, aclService)
aclRoute := securedRoute.Group("/roles")
aclRoute.Handle(http.MethodGet, "/", handler.GetAllRole)
securedRoute.Handle(http.MethodGet, "/users/me/roles", handler.GetMyRoles)
}

View File

@@ -0,0 +1,39 @@
package sql
import (
"github.com/google/uuid"
"mangezmieux-backend/internal/acl/model"
model2 "mangezmieux-backend/internal/model"
)
type Dao interface {
//Role
GetRole(id uuid.UUID) (*model.Role, error)
GetRoleByName(name string) (*model.Role, error)
AddRole(roleName string, metadata model2.Metadata) (*model.Role, error)
DeleteRole(id uuid.UUID) error
GetAllRole() ([]*model.Role, error)
//Resource
AddResource(resourceName string, metadata model2.Metadata) (*model.Resource, error)
DeleteResource(id uuid.UUID) error
GetResource(id uuid.UUID) (*model.Resource, error)
GetResourceByName(name string) (*model.Resource, error)
GetAllResource() ([]*model.Resource, error)
//RoleVerbResource
GetRoleVerbResource(id uuid.UUID) (*model.RoleVerbResource, error)
GetRoleVerbResourceByRoleResourceAndVerb(roleId, resourceId uuid.UUID, verb string) (*model.RoleVerbResource, error)
GetRoleVerbResourceByRoleResource(roleId, resourceId uuid.UUID) ([]*model.RoleVerbResource, error)
GetRoleVerbResourceByRole(roleId uuid.UUID) ([]*model.RoleVerbResource, error)
AddRoleVerbResource(roleId, resourceId uuid.UUID, verb string, metadata model2.Metadata) (*model.RoleVerbResource, error)
DeleteRoleVerbResource(id uuid.UUID) error
//UserRole
GetUserRole(id uuid.UUID) (*model.UserRole, error)
GetUserRoleByUserAndRole(userId, roleId uuid.UUID) (*model.UserRole, error)
GetUserRoleByUser(userId uuid.UUID) ([]*model.UserRole, error)
GetUserRoleByRole(role uuid.UUID) ([]*model.UserRole, error)
AddUserRole(userId, roleId uuid.UUID, metadata model2.Metadata) (*model.UserRole, error)
DeleteUserRole(id uuid.UUID) error
}

View File

@@ -0,0 +1,13 @@
package sql
import (
"database/sql"
)
type dao struct {
client *sql.DB
}
func NewDao(client *sql.DB) Dao {
return &dao{client: client}
}

View File

@@ -0,0 +1,121 @@
package sql
import (
"database/sql"
"errors"
"github.com/google/uuid"
"github.com/lib/pq"
"mangezmieux-backend/internal/acl/model"
model2 "mangezmieux-backend/internal/model"
"mangezmieux-backend/internal/postgres"
)
func (sqlDAO dao) AddResource(resourceName string, metadata model2.Metadata) (*model.Resource, error) {
var Id uuid.UUID
q := `
INSERT INTO mangezmieux.resource
(name, creation_date, creation_user)
VALUES
($1, $2, $3)
RETURNING
Id`
err := sqlDAO.client.QueryRow(q, resourceName, metadata.CreationDate, metadata.CreationUser).Scan(&Id)
var errPq *pq.Error
if errors.As(err, &errPq) {
return nil, postgres.HandlePgError(errPq)
}
resource, err := sqlDAO.GetResource(Id)
if errors.As(err, &errPq) {
return nil, postgres.HandlePgError(errPq)
}
return resource, nil
}
func (sqlDAO dao) DeleteResource(Id uuid.UUID) error {
q := `
DELETE FROM mangezmieux.resource
WHERE Id = $1
`
_, err := sqlDAO.client.Exec(q, Id.String())
var errPq *pq.Error
if errors.As(err, &errPq) {
return postgres.HandlePgError(errPq)
}
return err
}
func (sqlDAO dao) GetResource(Id uuid.UUID) (*model.Resource, error) {
q := `
SELECT Id, name, creation_date, last_update_date
FROM mangezmieux.resource r
WHERE r.Id = $1
`
row := sqlDAO.client.QueryRow(q, Id.String())
resource := &model.Resource{}
err := row.Scan(&resource.Id, &resource.Name, &resource.CreationDate, &resource.LastUpdateDate)
var errPq *pq.Error
if errors.As(err, &errPq) {
return nil, postgres.HandlePgError(errPq)
}
if errors.Is(err, sql.ErrNoRows) {
return nil, postgres.NewDAOError(postgres.ErrTypeNotFound, err)
}
return resource, nil
}
func (sqlDAO dao) GetResourceByName(name string) (*model.Resource, error) {
q := `
SELECT Id, name, creation_date, last_update_date
FROM mangezmieux.resource r
WHERE r.name = $1
`
row := sqlDAO.client.QueryRow(q, name)
resource := &model.Resource{}
err := row.Scan(&resource.Id, &resource.Name, &resource.CreationDate, &resource.LastUpdateDate)
var errPq *pq.Error
if errors.As(err, &errPq) {
return nil, postgres.HandlePgError(errPq)
}
if errors.Is(err, sql.ErrNoRows) {
return nil, postgres.NewDAOError(postgres.ErrTypeNotFound, err)
}
return resource, nil
}
func (sqlDAO dao) GetAllResource() ([]*model.Resource, error) {
q := `
SELECT Id, name, creation_date, last_update_date
FROM mangezmieux.resource r
`
rows, err := sqlDAO.client.Query(q)
if err != nil {
return nil, err
}
if rows.Err() != nil {
return nil, rows.Err()
}
defer rows.Close()
resources := make([]*model.Resource, 0)
for rows.Next() {
resource := &model.Resource{}
err := rows.Scan(&resource.Id, &resource.Name, &resource.CreationDate, &resource.LastUpdateDate)
var errPq *pq.Error
if errors.As(err, &errPq) {
return nil, postgres.HandlePgError(errPq)
}
if errors.Is(err, sql.ErrNoRows) {
return nil, postgres.NewDAOError(postgres.ErrTypeNotFound, err)
}
resources = append(resources, resource)
}
return resources, nil
}

View File

@@ -0,0 +1,121 @@
package sql
import (
"database/sql"
"errors"
"github.com/google/uuid"
"github.com/lib/pq"
"mangezmieux-backend/internal/acl/model"
model2 "mangezmieux-backend/internal/model"
"mangezmieux-backend/internal/postgres"
)
func (sqlDAO dao) GetRole(id uuid.UUID) (*model.Role, error) {
q := `
SELECT id, name, creation_date, last_update_date
FROM mangezmieux.role r
WHERE r.id = $1
`
row := sqlDAO.client.QueryRow(q, id.String())
role := &model.Role{}
err := row.Scan(&role.Id, &role.Name, &role.CreationDate, &role.LastUpdateDate)
var errPq *pq.Error
if errors.As(err, &errPq) {
return nil, postgres.HandlePgError(errPq)
}
if errors.Is(err, sql.ErrNoRows) {
return nil, postgres.NewDAOError(postgres.ErrTypeNotFound, err)
}
return role, nil
}
func (sqlDAO dao) GetRoleByName(name string) (*model.Role, error) {
q := `
SELECT id, name, creation_date, last_update_date
FROM mangezmieux.role r
WHERE r.name = $1
`
row := sqlDAO.client.QueryRow(q, name)
role := &model.Role{}
err := row.Scan(&role.Id, &role.Name, &role.CreationDate, &role.LastUpdateDate)
var errPq *pq.Error
if errors.As(err, &errPq) {
return nil, postgres.HandlePgError(errPq)
}
if errors.Is(err, sql.ErrNoRows) {
return nil, postgres.NewDAOError(postgres.ErrTypeNotFound, err)
}
return role, nil
}
func (sqlDAO dao) AddRole(roleName string, metadata model2.Metadata) (*model.Role, error) {
var id uuid.UUID
q := `
INSERT INTO mangezmieux.role
(name, creation_date, creation_user)
VALUES
($1,$2,$3)
RETURNING
id`
err := sqlDAO.client.QueryRow(q, roleName, metadata.CreationDate, metadata.CreationUser).Scan(&id)
var errPq *pq.Error
if errors.As(err, &errPq) {
return nil, postgres.HandlePgError(errPq)
}
role, err := sqlDAO.GetRole(id)
if errors.As(err, &errPq) {
return nil, postgres.HandlePgError(errPq)
}
return role, nil
}
func (sqlDAO dao) DeleteRole(id uuid.UUID) error {
q := `
DELETE FROM mangezmieux.role
WHERE id = $1
`
_, err := sqlDAO.client.Exec(q, id.String())
var errPq *pq.Error
if errors.As(err, &errPq) {
return postgres.HandlePgError(errPq)
}
return err
}
func (sqlDAO dao) GetAllRole() ([]*model.Role, error) {
q := `
SELECT id, name, creation_date, last_update_date
FROM mangezmieux.role r
`
rows, err := sqlDAO.client.Query(q)
if err != nil {
return nil, err
}
if rows.Err() != nil {
return nil, rows.Err()
}
defer rows.Close()
roles := make([]*model.Role, 0)
for rows.Next() {
role := &model.Role{}
err := rows.Scan(&role.Id, &role.Name, &role.CreationDate, &role.LastUpdateDate)
var errPq *pq.Error
if errors.As(err, &errPq) {
return nil, postgres.HandlePgError(errPq)
}
if errors.Is(err, sql.ErrNoRows) {
return nil, postgres.NewDAOError(postgres.ErrTypeNotFound, err)
}
roles = append(roles, role)
}
return roles, nil
}

View File

@@ -0,0 +1,191 @@
package sql
import (
"database/sql"
"errors"
"mangezmieux-backend/internal/acl/model"
model2 "mangezmieux-backend/internal/model"
"mangezmieux-backend/internal/postgres"
"github.com/google/uuid"
"github.com/lib/pq"
)
func (sqlDAO dao) GetRoleVerbResource(id uuid.UUID) (*model.RoleVerbResource, error) {
q := `
SELECT id, role_id, verb, resource_id, creation_date, last_update_date
FROM mangezmieux.role_verb_resource r
WHERE r.id = $1
`
row := sqlDAO.client.QueryRow(q, id.String())
roleVerbResource := &model.RoleVerbResource{}
err := row.Scan(&roleVerbResource.Id, &roleVerbResource.RoleId, &roleVerbResource.Verb, &roleVerbResource.ResourceId, &roleVerbResource.CreationDate, &roleVerbResource.LastUpdateDate)
var errPq *pq.Error
if errors.As(err, &errPq) {
return nil, postgres.HandlePgError(errPq)
}
if errors.Is(err, sql.ErrNoRows) {
return nil, postgres.NewDAOError(postgres.ErrTypeNotFound, err)
}
return roleVerbResource, nil
}
func (sqlDAO dao) GetRoleVerbResourceByRoleResourceAndVerb(roleId, resourceId uuid.UUID, verb string) (*model.RoleVerbResource, error) {
q := `
SELECT id, role_id, verb, resource_id, creation_date, last_update_date
FROM mangezmieux.role_verb_resource r
WHERE r.role_id = $1
AND r.resource_id = $2
AND r.verb = $3
`
row := sqlDAO.client.QueryRow(q, roleId.String(), resourceId.String(), verb)
roleVerbResource := &model.RoleVerbResource{}
err := row.Scan(&roleVerbResource.Id, &roleVerbResource.RoleId, &roleVerbResource.Verb, &roleVerbResource.ResourceId, &roleVerbResource.CreationDate, &roleVerbResource.LastUpdateDate)
var errPq *pq.Error
if errors.As(err, &errPq) {
return nil, postgres.HandlePgError(errPq)
}
if errors.Is(err, sql.ErrNoRows) {
return nil, postgres.NewDAOError(postgres.ErrTypeNotFound, err)
}
return roleVerbResource, nil
}
func (sqlDAO dao) GetRoleVerbResourceByResourceAndVerb(resourceId uuid.UUID, verb string) ([]*model.RoleVerbResource, error) {
q := `
SELECT id, role_id, verb, resource_id, creation_date, last_update_date
FROM mangezmieux.role_verb_resource r
WHERE r.resource_id = $1
AND r.verb = $2
`
rows, err := sqlDAO.client.Query(q, resourceId.String(), verb)
if err != nil {
return nil, err
}
if rows.Err() != nil {
return nil, rows.Err()
}
defer rows.Close()
roleVerResources := make([]*model.RoleVerbResource, 0)
for rows.Next() {
roleVerbResource := &model.RoleVerbResource{}
err := rows.Scan(&roleVerbResource.Id, &roleVerbResource.RoleId, &roleVerbResource.Verb, &roleVerbResource.ResourceId, &roleVerbResource.CreationDate, &roleVerbResource.LastUpdateDate)
var errPq *pq.Error
if errors.As(err, &errPq) {
return nil, postgres.HandlePgError(errPq)
}
if errors.Is(err, sql.ErrNoRows) {
return nil, postgres.NewDAOError(postgres.ErrTypeNotFound, err)
}
roleVerResources = append(roleVerResources, roleVerbResource)
}
return roleVerResources, nil
}
func (sqlDAO dao) GetRoleVerbResourceByRoleResource(roleId, resourceId uuid.UUID) ([]*model.RoleVerbResource, error) {
q := `
SELECT id, role_id, verb, resource_id, creation_date, last_update_date
FROM mangezmieux.role_verb_resource r
WHERE r.role_id = $1
AND r.resource_id = $2
`
rows, err := sqlDAO.client.Query(q, roleId.String(), resourceId.String())
if err != nil {
return nil, err
}
if rows.Err() != nil {
return nil, rows.Err()
}
defer rows.Close()
roleVerResources := make([]*model.RoleVerbResource, 0)
for rows.Next() {
roleVerbResource := &model.RoleVerbResource{}
err := rows.Scan(&roleVerbResource.Id, &roleVerbResource.RoleId, &roleVerbResource.Verb, &roleVerbResource.ResourceId, &roleVerbResource.CreationDate, &roleVerbResource.LastUpdateDate)
var errPq *pq.Error
if errors.As(err, &errPq) {
return nil, postgres.HandlePgError(errPq)
}
if errors.Is(err, sql.ErrNoRows) {
return nil, postgres.NewDAOError(postgres.ErrTypeNotFound, err)
}
roleVerResources = append(roleVerResources, roleVerbResource)
}
return roleVerResources, nil
}
func (sqlDAO dao) GetRoleVerbResourceByRole(roleId uuid.UUID) ([]*model.RoleVerbResource, error) {
q := `
SELECT id, role_id, verb, resource_id, creation_date, last_update_date
FROM mangezmieux.role_verb_resource r
WHERE r.role_id = $1
`
rows, err := sqlDAO.client.Query(q, roleId.String())
if err != nil {
return nil, err
}
if rows.Err() != nil {
return nil, rows.Err()
}
defer rows.Close()
roleVerResources := make([]*model.RoleVerbResource, 0)
for rows.Next() {
roleVerbResource := &model.RoleVerbResource{}
err := rows.Scan(&roleVerbResource.Id, &roleVerbResource.RoleId, &roleVerbResource.Verb, &roleVerbResource.ResourceId, &roleVerbResource.CreationDate, &roleVerbResource.LastUpdateDate)
var errPq *pq.Error
if errors.As(err, &errPq) {
return nil, postgres.HandlePgError(errPq)
}
if errors.Is(err, sql.ErrNoRows) {
return nil, postgres.NewDAOError(postgres.ErrTypeNotFound, err)
}
roleVerResources = append(roleVerResources, roleVerbResource)
}
return roleVerResources, nil
}
func (sqlDAO dao) AddRoleVerbResource(roleId, resourceId uuid.UUID, verb string, metadata model2.Metadata) (*model.RoleVerbResource, error) {
var Id uuid.UUID
q := `
INSERT INTO mangezmieux.role_verb_resource
(role_id, verb, resource_id, creation_date, creation_user)
VALUES
($1,$2,$3,$4,$5)
RETURNING
Id`
err := sqlDAO.client.QueryRow(q, roleId.String(), verb, resourceId.String(), metadata.CreationDate, metadata.CreationUser).Scan(&Id)
var errPq *pq.Error
if errors.As(err, &errPq) {
return nil, postgres.HandlePgError(errPq)
}
roleVerbResource, err := sqlDAO.GetRoleVerbResource(Id)
if errors.As(err, &errPq) {
return nil, postgres.HandlePgError(errPq)
}
return roleVerbResource, nil
}
func (sqlDAO dao) DeleteRoleVerbResource(id uuid.UUID) error {
q := `
DELETE FROM mangezmieux.role_verb_resource
WHERE Id = $1
`
_, err := sqlDAO.client.Exec(q, id.String())
var errPq *pq.Error
if errors.As(err, &errPq) {
return postgres.HandlePgError(errPq)
}
return err
}

View File

@@ -0,0 +1,161 @@
package sql
import (
"database/sql"
"errors"
"mangezmieux-backend/internal/acl/model"
model2 "mangezmieux-backend/internal/model"
"mangezmieux-backend/internal/postgres"
"github.com/google/uuid"
"github.com/lib/pq"
)
func (sqlDAO dao) GetUserRole(id uuid.UUID) (*model.UserRole, error) {
q := `
SELECT id, role_id, user_id, creation_date, last_update_date
FROM mangezmieux.user_role r
WHERE r.id = $1
`
row := sqlDAO.client.QueryRow(q, id.String())
userRole := &model.UserRole{}
err := row.Scan(&userRole.Id, &userRole.RoleId, &userRole.UserId, &userRole.CreationDate, &userRole.LastUpdateDate)
var errPq *pq.Error
if errors.As(err, &errPq) {
return nil, postgres.HandlePgError(errPq)
}
if errors.Is(err, sql.ErrNoRows) {
return nil, postgres.NewDAOError(postgres.ErrTypeNotFound, err)
}
return userRole, nil
}
func (sqlDAO dao) GetUserRoleByUserAndRole(userId, roleId uuid.UUID) (*model.UserRole, error) {
q := `
SELECT id, role_id, user_id, creation_date, last_update_date
FROM mangezmieux.user_role r
WHERE r.role_id = $1
AND r.user_id = $2
`
row := sqlDAO.client.QueryRow(q, roleId.String(), userId.String())
userRole := &model.UserRole{}
err := row.Scan(&userRole.Id, &userRole.RoleId, &userRole.UserId, &userRole.CreationDate, &userRole.LastUpdateDate)
var errPq *pq.Error
if errors.As(err, &errPq) {
return nil, postgres.HandlePgError(errPq)
}
if errors.Is(err, sql.ErrNoRows) {
return nil, postgres.NewDAOError(postgres.ErrTypeNotFound, err)
}
return userRole, nil
}
func (sqlDAO dao) GetUserRoleByUser(userId uuid.UUID) ([]*model.UserRole, error) {
q := `
SELECT id, role_id, user_id, creation_date, last_update_date
FROM mangezmieux.user_role r
WHERE r.user_id = $1
`
rows, err := sqlDAO.client.Query(q, userId.String())
if errors.Is(err, sql.ErrNoRows) {
return make([]*model.UserRole, 0), nil
}
var errPq *pq.Error
if errors.As(err, &errPq) {
return nil, postgres.HandlePgError(errPq)
}
if rows.Err() != nil {
return nil, rows.Err()
}
defer rows.Close()
userRoles := make([]*model.UserRole, 0)
for rows.Next() {
userRole := &model.UserRole{}
err := rows.Scan(&userRole.Id, &userRole.RoleId, &userRole.UserId, &userRole.CreationDate, &userRole.LastUpdateDate)
var errPq *pq.Error
if errors.As(err, &errPq) {
return nil, postgres.HandlePgError(errPq)
}
if errors.Is(err, sql.ErrNoRows) {
return nil, postgres.NewDAOError(postgres.ErrTypeNotFound, err)
}
userRoles = append(userRoles, userRole)
}
return userRoles, nil
}
func (sqlDAO dao) GetUserRoleByRole(role uuid.UUID) ([]*model.UserRole, error) {
q := `
SELECT id, role_id, user_id, creation_date, last_update_date
FROM mangezmieux.user_role r
WHERE r.role_id = $1
`
rows, err := sqlDAO.client.Query(q, role.String())
if err != nil {
return nil, err
}
if rows.Err() != nil {
return nil, rows.Err()
}
defer rows.Close()
userRoles := make([]*model.UserRole, 0)
for rows.Next() {
userRole := &model.UserRole{}
err := rows.Scan(&userRole.Id, &userRole.RoleId, &userRole.UserId, &userRole.CreationDate, &userRole.LastUpdateDate)
var errPq *pq.Error
if errors.As(err, &errPq) {
return nil, postgres.HandlePgError(errPq)
}
if errors.Is(err, sql.ErrNoRows) {
return nil, postgres.NewDAOError(postgres.ErrTypeNotFound, err)
}
userRoles = append(userRoles, userRole)
}
return userRoles, nil
}
func (sqlDAO dao) AddUserRole(userId, roleId uuid.UUID, metadata model2.Metadata) (*model.UserRole, error) {
var Id uuid.UUID
q := `
INSERT INTO mangezmieux.user_role
(user_id, role_id, creation_date, creation_user)
VALUES
($1,$2,$3,$4)
RETURNING
Id`
err := sqlDAO.client.QueryRow(q, userId.String(), roleId.String(), metadata.CreationDate, metadata.CreationUser).Scan(&Id)
var errPq *pq.Error
if errors.As(err, &errPq) {
return nil, postgres.HandlePgError(errPq)
}
userRole, err := sqlDAO.GetUserRole(Id)
if errors.As(err, &errPq) {
return nil, postgres.HandlePgError(errPq)
}
return userRole, nil
}
func (sqlDAO dao) DeleteUserRole(id uuid.UUID) error {
q := `
DELETE FROM mangezmieux.user_role
WHERE Id = $1
`
_, err := sqlDAO.client.Exec(q, id.String())
var errPq *pq.Error
if errors.As(err, &errPq) {
return postgres.HandlePgError(errPq)
}
return err
}

View File

@@ -0,0 +1,8 @@
package ginserver
const (
HeaderNameContentType = "content-type"
HeaderNameCorrelationID = "correlationID"
HeaderValueApplicationJSONUTF8 = "application/json; charset=UTF-8"
)

View File

@@ -0,0 +1,70 @@
package ginserver
import (
"mangezmieux-backend/internal/logger"
"math/rand"
"time"
"github.com/gin-gonic/gin"
)
const (
letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
letterIdxBits = 6 // 6 bits to represent a letter index
letterIdxMask = 1<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits
letterIdxMax = 63 / letterIdxBits // # of letter indices fitting in 63 bits
)
var src = rand.NewSource(time.Now().UnixNano())
func randStringBytesMaskImprSrc(n int) string {
b := make([]byte, n)
// A src.Int63() generates 63 random bits, enough for letterIdxMax characters!
for i, cache, remain := n-1, src.Int63(), letterIdxMax; i >= 0; {
if remain == 0 {
cache, remain = src.Int63(), letterIdxMax
}
if idx := int(cache & letterIdxMask); idx < len(letterBytes) {
b[i] = letterBytes[idx]
i--
}
cache >>= letterIdxBits
remain--
}
return string(b)
}
func GetLoggerMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
correlationID := c.Request.Header.Get(HeaderNameCorrelationID)
if correlationID == "" {
correlationID = randStringBytesMaskImprSrc(30)
c.Writer.Header().Set(HeaderNameCorrelationID, correlationID)
}
logEntry := logger.GetLogger().WithField(HeaderNameCorrelationID, correlationID)
c.Set(logger.ContextKeyLogger, logEntry)
}
}
func GetHTTPLoggerMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
logger.GetLoggerFromCtx(c).
WithField("method", c.Request.Method).
WithField("url", c.Request.RequestURI).
WithField("from", c.ClientIP()).
Info("start handling HTTP request")
c.Next()
d := time.Since(start)
logger.GetLoggerFromCtx(c).
WithField("status", c.Writer.Status()).
WithField("duration", d.String()).
Info("end handling HTTP request")
}
}

View File

@@ -0,0 +1,45 @@
package ginserver
import (
"mangezmieux-backend/internal/injector"
"time"
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
)
var (
routerInjectorKey = "ROUTER"
SecuredRouterInjectorKey = "SECURED_ROUTER"
UnsecuredRouterInjectorKey = "UNSECURED_ROUTER"
)
func Setup(inj *injector.Injector) {
gin.SetMode(gin.ReleaseMode)
router := gin.New()
router.HandleMethodNotAllowed = true
router.Use(cors.New(cors.Config{
AllowOrigins: []string{"http://localhost:3000/", "http://localhost:3000"},
AllowMethods: []string{"*"},
AllowHeaders: []string{"*"},
ExposeHeaders: []string{"*"},
AllowCredentials: true,
MaxAge: 12 * time.Hour,
}))
router.Use(gin.Recovery())
router.Use(GetLoggerMiddleware())
router.Use(GetHTTPLoggerMiddleware())
public := router.Group("")
inj.Set(UnsecuredRouterInjectorKey, public)
authMiddleware := injector.Get[gin.HandlerFunc](inj, "AuthenticationMiddleware")
securedUserRoute := public.Group("/api/v1")
securedUserRoute.Use(authMiddleware)
inj.Set(SecuredRouterInjectorKey, securedUserRoute)
inj.Set(routerInjectorKey, router)
}

View File

@@ -0,0 +1,41 @@
package ginserver
import (
"errors"
"log"
"mangezmieux-backend/internal/injector"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"github.com/gin-gonic/gin"
)
func Start(inj *injector.Injector, port string) {
router := injector.Get[*gin.Engine](inj, routerInjectorKey)
srv := &http.Server{
Addr: ":" + port,
Handler: router,
ReadHeaderTimeout: 4 * time.Second,
}
go func() {
// service connections
if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
log.Fatalf("listen: %s\n", err)
}
}()
// Wait for interrupt signal to gracefully shutdown the server with
// a timeout of 5 seconds.
quit := make(chan os.Signal, 1)
// kill (no param) default send syscanll.SIGTERM
// kill -2 is syscall.SIGINT
// kill -9 is syscall. SIGKILL but can"t be catch, so don't need add it
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
log.Println("Shutdown Server ...")
}

View File

@@ -0,0 +1,18 @@
package godog
import "context"
type TestCenter interface {
GetFunctionalityContext() any
}
type genericTestCenter struct {
testCenter TestCenter
}
type ResourceHandler interface {
Create(ctx context.Context, val string) (context.Context, error)
Read(ctx context.Context, val string) (context.Context, error)
Update(ctx context.Context, val string) (context.Context, error)
Patch(ctx context.Context, val string) (context.Context, error)
Delete(ctx context.Context, val string) (context.Context, error)
}

View File

@@ -0,0 +1,23 @@
package godog
import (
"fmt"
"log"
)
var resourcesHandler = map[string]ResourceHandler{}
func RegisterResourceHandler(resourceName string, handler ResourceHandler) {
if _, ok := resourcesHandler[resourceName]; ok {
log.Fatalf("Can't add an already existing handler for resource %s", resourceName)
return
}
resourcesHandler[resourceName] = handler
}
func GetResourceHandler(resourceName string) ResourceHandler {
if val, ok := resourcesHandler[resourceName]; ok {
return val
}
panic(fmt.Sprintf("no handler found for resource type %s", resourceName))
}

View File

@@ -0,0 +1,92 @@
package godog
import (
"context"
"encoding/json"
"fmt"
"github.com/cucumber/godog"
"github.com/ohler55/ojg/jp"
"net/http/httptest"
)
func (gtc *genericTestCenter) theResourceIsCreated(ctx context.Context) (context.Context, error) {
return gtc.theResponseHasStatus(ctx, 201)
}
func (gtc *genericTestCenter) theResourceAlreadyExists(ctx context.Context) (context.Context, error) {
return gtc.theResponseHasStatus(ctx, 409)
}
func (gtc *genericTestCenter) theUserMissRight(ctx context.Context) (context.Context, error) {
return gtc.theResponseHasStatus(ctx, 403)
}
func (gtc *genericTestCenter) badRequest(ctx context.Context) (context.Context, error) {
return gtc.theResponseHasStatus(ctx, 400)
}
func (gtc *genericTestCenter) forbiddenRequest(ctx context.Context) (context.Context, error) {
return gtc.theResponseHasStatus(ctx, 403)
}
func (gtc *genericTestCenter) theResponseHasStatus(ctx context.Context, status int) (context.Context, error) {
httpRecorder := ctx.Value("recorder").(*httptest.ResponseRecorder)
if httpRecorder.Code != status {
return ctx, fmt.Errorf("got code %d with body %s", httpRecorder.Code, httpRecorder.Body.String())
}
ctx = context.WithValue(ctx, "OBJECT_RESPONSE", httpRecorder.Body.String())
return ctx, nil
}
func (gtc *genericTestCenter) theFieldHasValue(ctx context.Context, fieldName, value string) (context.Context, error) {
compiledPath, err := jp.ParseString(fieldName)
body := ctx.Value("OBJECT_RESPONSE").(string)
if err != nil {
return ctx, err
}
var resourceAsMap interface{}
err = json.Unmarshal([]byte(body), &resourceAsMap)
if err != nil {
return ctx, err
}
datas := compiledPath.Get(resourceAsMap)
if len(datas) != 1 {
return ctx, fmt.Errorf("Found %v data. Expected only one", len(datas))
}
if datas[0] != value {
return ctx, fmt.Errorf("The field %s has value %s . Expected %s", fieldName, datas[0], value)
}
return ctx, nil
}
func (gtc *genericTestCenter) resourceDoesntExist(ctx context.Context, resourceType string, resourceName string) (context.Context, error) {
handler := GetResourceHandler(resourceType)
ctx, _ = handler.Delete(ctx, resourceName)
return ctx, nil
}
func (gtc *genericTestCenter) theUserCreatesAResourcesWithTheFollowingData(ctx context.Context, resourceType string, content *godog.DocString) (context.Context, error) {
handler := GetResourceHandler(resourceType)
ctx, _ = handler.Create(ctx, content.Content)
return ctx, nil
}
func (gtc *genericTestCenter) theUserPatchesAResourcesWithTheFollowingData(ctx context.Context, resourceType string, content *godog.DocString) (context.Context, error) {
handler := GetResourceHandler(resourceType)
ctx, _ = handler.Patch(ctx, content.Content)
return ctx, nil
}
func (gtc *genericTestCenter) theUserUpdatesAResourcesWithTheFollowingData(ctx context.Context, resourceType string, content *godog.DocString) (context.Context, error) {
handler := GetResourceHandler(resourceType)
ctx, _ = handler.Update(ctx, content.Content)
return ctx, nil
}
func (gtc *genericTestCenter) theResourceExistWithTheFollowingData(ctx context.Context, resourceType string, content *godog.DocString) (context.Context, error) {
handler := GetResourceHandler(resourceType)
ctx, _ = handler.Create(ctx, content.Content)
return ctx, nil
}

View File

@@ -0,0 +1,20 @@
package godog
import cucumber "github.com/cucumber/godog"
func Setup(ctx *cucumber.ScenarioContext, testCenter TestCenter) {
gtc := genericTestCenter{testCenter: testCenter}
ctx.Step(`^the resource is created`, gtc.theResourceIsCreated)
ctx.Step(`^the response indicates that the ([^\s]+) already exists`, gtc.theResourceAlreadyExists)
ctx.Step(`^the response indicates that the user doesn't have right`, gtc.theUserMissRight)
ctx.Step(`^the response indicates that this a bad request`, gtc.badRequest)
ctx.Step(`^the response indicates that this a forbidden request`, gtc.forbiddenRequest)
ctx.Step(`^the response has status (\d+)$`, gtc.theResponseHasStatus)
ctx.Step(`^the ([^\s]+) ([^\s]+) doesn\'t exist yet`, gtc.resourceDoesntExist)
ctx.Step(`^the field ([^\s]+) is (\d+)$`, gtc.theFieldHasValue)
ctx.Step(`^the field ([^\s]+) has value "([^"]*)"$`, gtc.theFieldHasValue)
ctx.Step(`^the user create a[n]* ([^\s]+) with the following data:$`, gtc.theUserCreatesAResourcesWithTheFollowingData)
ctx.Step(`^the user update a[n]* ([^\s]+) with the following data:$`, gtc.theUserUpdatesAResourcesWithTheFollowingData)
ctx.Step(`^the user patch a[n]* ([^\s]+) with the following data:$`, gtc.theUserUpdatesAResourcesWithTheFollowingData)
ctx.Step(`^the ([^\s]+) exists with the following data:$`, gtc.theResourceExistWithTheFollowingData)
}

View File

@@ -0,0 +1,27 @@
package health
import (
"net/http"
"github.com/gin-gonic/gin"
)
// @openapi:path
// /_health:
//
// get:
// tags:
// - "Monitoring"
// summary: Health check
// description: Health check
// responses:
// 200:
// description: "Health response"
// content:
// application/json:
// schema:
// $ref: "#/components/schemas/Health"
func GetHealth(c *gin.Context) {
health := &Health{Alive: true}
c.JSON(http.StatusOK, health)
}

View File

@@ -0,0 +1,8 @@
package health
// Health struct
// @openapi:schema.
type Health struct {
Alive bool `json:"alive"`
Version string `json:"version"`
}

View File

@@ -0,0 +1,15 @@
package health
import (
"mangezmieux-backend/internal/ginserver"
"mangezmieux-backend/internal/injector"
"net/http"
"github.com/gin-gonic/gin"
)
func Setup(inj *injector.Injector) {
publicRoute := injector.Get[*gin.RouterGroup](inj, ginserver.UnsecuredRouterInjectorKey)
publicRoute.Handle(http.MethodGet, "/health", GetHealth)
}

View File

@@ -0,0 +1,42 @@
package injector
import "fmt"
type Injector struct {
content map[string]any
}
func (i *Injector) Get(key string) any {
val, ok := i.content[key]
if !ok {
panic(fmt.Sprintf("Can't get key %s from injector", key))
}
return val
}
func (i *Injector) GetWithDefault(key string, defaultValue any) any {
val, ok := i.content[key]
if !ok {
return defaultValue
}
return val
}
func Get[T any](i *Injector, key string) T {
return i.Get(key).(T)
}
func GetWithDefault[T any](i *Injector, key string, defaultValue any) T {
return i.GetWithDefault(key, defaultValue).(T)
}
func (i *Injector) Set(key string, content any) {
if i.content == nil {
i.content = map[string]any{}
}
if _, ok := i.content[key]; ok {
panic(fmt.Sprintf("Key %s already have content", key))
}
i.content[key] = content
}

View File

@@ -0,0 +1,78 @@
package jwt
import (
"mangezmieux-backend/internal/responses"
"time"
jwtLib "github.com/golang-jwt/jwt/v5"
)
type Service struct {
SecretKey string
}
func NewService() *Service {
return &Service{SecretKey: "hard-coded-temp"}
}
type Claims struct {
ID string `json:"username"`
jwtLib.RegisteredClaims
}
func (s *Service) ValidateToken(token string) (*Claims, error) {
claims := &Claims{}
tkn, err := jwtLib.ParseWithClaims(token, claims, func(token *jwtLib.Token) (any, error) {
return []byte(s.SecretKey), nil
})
if err != nil {
return nil, err
}
if !tkn.Valid {
return nil, err
}
return claims, nil
}
func (s *Service) GenerateJWTToken(userId string) (string, error) {
expirationTime := time.Now().Add(10 * time.Minute)
claims := &Claims{
ID: userId,
RegisteredClaims: jwtLib.RegisteredClaims{
// In JWT, the expiry time is expressed as unix milliseconds
ExpiresAt: jwtLib.NewNumericDate(expirationTime),
},
}
token := jwtLib.NewWithClaims(jwtLib.SigningMethodHS256, claims)
// Create the JWT string
tokenString, err := token.SignedString([]byte(s.SecretKey))
if err != nil {
return "", &responses.ErrInternalServer
}
return tokenString, nil
}
func (s *Service) Refresh(oldToken string) (string, error) {
claims := &Claims{}
tkn, err := jwtLib.ParseWithClaims(oldToken, claims, func(token *jwtLib.Token) (any, error) {
return []byte(s.SecretKey), nil
})
if err != nil {
return "", err
}
if !tkn.Valid {
return "", err
}
// Now, create a new token for the current use, with a renewed expiration time
expirationTime := time.Now().Add(10 * time.Minute)
claims.ExpiresAt = jwtLib.NewNumericDate(expirationTime)
token := jwtLib.NewWithClaims(jwtLib.SigningMethodHS256, claims)
tokenString, err := token.SignedString([]byte(s.SecretKey))
return tokenString, nil
}

View File

@@ -0,0 +1,16 @@
package jwt
import (
"mangezmieux-backend/internal/injector"
)
const JWTKey = "JWT"
func Setup(inj *injector.Injector) {
// build components
service := NewService()
// register provided components
inj.Set(JWTKey, service)
}

View File

@@ -0,0 +1,74 @@
package logger
import (
"io"
"os"
"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
)
const (
LogFormatText = "text"
LogFormatJSON = "json"
ContextKeyLogger = "logger"
)
var (
logLevel = logrus.DebugLevel
logFormat logrus.Formatter = &logrus.TextFormatter{}
logOut io.Writer
)
func InitLogger(ll, lf string) {
logLevel = parseLogrusLevel(ll)
logrus.SetLevel(logLevel)
logFormat = parseLogrusFormat(lf)
logrus.SetFormatter(logFormat)
logOut = os.Stdout
logrus.SetOutput(logOut)
}
func GetLoggerFromCtx(c *gin.Context) *logrus.Entry {
if logger, ok := c.Get(ContextKeyLogger); ok {
logEntry, assertionOk := logger.(*logrus.Entry)
if assertionOk {
return logEntry
}
}
return logrus.NewEntry(GetLogger())
}
func GetLogger() *logrus.Logger {
logger := logrus.New()
logger.Formatter = logFormat
logger.Level = logLevel
logger.Out = logOut
return logger
}
func parseLogrusLevel(logLevelStr string) logrus.Level {
logLevel, err := logrus.ParseLevel(logLevelStr)
if err != nil {
logrus.WithError(err).Errorf("error while parsing log level. %v is set as default.", logLevel)
logLevel = logrus.DebugLevel
}
return logLevel
}
func parseLogrusFormat(logFormatStr string) logrus.Formatter {
var formatter logrus.Formatter
switch logFormatStr {
case LogFormatText:
formatter = &logrus.TextFormatter{ForceColors: true, FullTimestamp: true}
case LogFormatJSON:
formatter = &logrus.JSONFormatter{}
default:
logrus.Errorf("error while parsing log format. %v is set as default.", formatter)
formatter = &logrus.TextFormatter{ForceColors: true, FullTimestamp: true}
}
return formatter
}

View File

@@ -0,0 +1,102 @@
package middleware
import (
"context"
"errors"
model2 "mangezmieux-backend/internal/acl/model"
"mangezmieux-backend/internal/jwt"
"mangezmieux-backend/internal/logger"
"mangezmieux-backend/internal/responses"
"mangezmieux-backend/internal/users/model"
"strings"
"github.com/gin-gonic/gin"
)
const CtxUser = "user"
const CtxUserRight = "userRight"
const CtxRole = "role"
const CtxToken = "token"
type IntrospectService interface {
Introspect(token string) (user *model.User, err error)
GetRole(ctx context.Context, user *model.User) (userRight *model2.UserRight, err error)
GetAllRole(ctx context.Context) ([]*model2.Role, error)
}
func GetAuthenticationMiddleware(introspectService IntrospectService, jwtService *jwt.Service) gin.HandlerFunc {
return func(c *gin.Context) {
token, err := getTokenFromGinCtx(c)
c.Set(CtxToken, token)
ctx := c.Request.Context()
ctx = context.WithValue(ctx, CtxToken, token)
if err != nil {
logger.GetLogger().WithError(err).Debug("no token found")
responses.JSONErrorWithMessage(c.Writer, responses.ErrBadRequestFormat, err.Error())
c.Abort()
return
}
_, err = jwtService.ValidateToken(token)
if err != nil {
logger.GetLogger().WithError(err).Debug("error during token validation")
responses.JSONErrorWithMessage(c.Writer, responses.ErrBadRequestFormat, err.Error())
c.Abort()
return
}
user, err := introspectService.Introspect(token)
if err != nil {
logger.GetLogger().WithError(err).Debug("error during introspect")
responses.JSONErrorWithMessage(c.Writer, responses.ErrBadRequestFormat, err.Error())
c.Abort()
return
}
c.Set(CtxUser, user)
userRight, err := introspectService.GetRole(ctx, user)
if err != nil {
logger.GetLogger().WithError(err).Debug("error during getting role for user")
responses.JSONErrorWithMessage(c.Writer, responses.ErrBadRequestFormat, err.Error())
c.Abort()
return
}
c.Set(CtxUserRight, userRight)
roles, err := introspectService.GetAllRole(ctx)
if err != nil {
logger.GetLogger().WithError(err).Debug("error during getting role map")
responses.JSONErrorWithMessage(c.Writer, responses.ErrBadRequestFormat, err.Error())
c.Abort()
return
}
c.Set(CtxRole, roles)
c.Next()
}
}
// getTokenFromGinCtx allow to get the access token of the request in the Authorization request header.
// It will split the header and remove the Bearer part to extract only the token.
func getTokenFromGinCtx(c *gin.Context) (string, error) {
auth := c.GetHeader("Authorization")
if auth != "" {
authSplitted := strings.SplitN(auth, " ", 2)
if len(authSplitted) != 2 {
return "", errors.New("malformed authorization header")
}
if strings.ToUpper(authSplitted[0]) != strings.ToUpper("Bearer") && strings.ToUpper(authSplitted[0]) != strings.ToUpper("JWT") {
return "", errors.New("unsupported authentication scheme")
}
return authSplitted[1], nil
}
if cookie, err := c.Cookie("token"); err == nil {
return cookie, nil
}
return "", errors.New("no token found in the request")
}

View File

@@ -0,0 +1,16 @@
package middleware
import (
"mangezmieux-backend/internal/injector"
"mangezmieux-backend/internal/jwt"
)
const AuthenticationMiddlewareKey = "AuthenticationMiddleware"
const IntrospectServiceKey = "AuthCli"
func Setup(inj *injector.Injector) {
jwtService := injector.Get[*jwt.Service](inj, jwt.JWTKey)
introspectService := injector.Get[IntrospectService](inj, IntrospectServiceKey)
inj.Set(AuthenticationMiddlewareKey, GetAuthenticationMiddleware(introspectService, jwtService))
}

View File

@@ -0,0 +1,10 @@
package model
import "time"
type Metadata struct {
CreationDate time.Time `json:"creation_date"`
LastUpdateDate time.Time `json:"last_update_date"`
CreationUser string `json:"creation_user"`
LastUpdateUser string `json:"last_update_user"`
}

View File

@@ -0,0 +1,32 @@
package postgres
import (
"fmt"
)
type Type int
const (
ErrTypeNotFound Type = iota
ErrTypeDuplicate
ErrTypeForeignKeyViolation
)
type Error struct {
Cause error
Type Type
}
func NewDAOError(t Type, cause error) error {
return &Error{
Type: t,
Cause: cause,
}
}
func (e *Error) Error() string {
if e.Cause != nil {
return fmt.Sprintf("Type %d: %s", e.Type, e.Cause.Error())
}
return fmt.Sprintf("Type %d: no cause given", e.Type)
}

View File

@@ -0,0 +1,35 @@
package postgres
import (
"database/sql"
"github.com/lib/pq"
"mangezmieux-backend/internal/logger"
)
const (
pgCodeUniqueViolation = "23505"
pgCodeForeingKeyViolation = "23503"
)
func HandlePgError(e *pq.Error) error {
if e.Code == pgCodeUniqueViolation {
return NewDAOError(ErrTypeDuplicate, e)
}
if e.Code == pgCodeForeingKeyViolation {
return NewDAOError(ErrTypeForeignKeyViolation, e)
}
return e
}
func NewDatabasePostgreSQL(connectionURI string) *sql.DB {
db, err := sql.Open("postgres", connectionURI)
if err != nil {
logger.GetLogger().WithError(err).Fatal("Unable to get a connection to the postgres db")
}
err = db.Ping()
if err != nil {
logger.GetLogger().WithError(err).Fatal("Unable to ping the postgres db")
}
return db
}

View File

@@ -0,0 +1,11 @@
package postgres
import "mangezmieux-backend/internal/injector"
const DatabaseKey = "POSTGRES"
func Setup(inj *injector.Injector, connectionURI string) {
client := NewDatabasePostgreSQL(connectionURI)
inj.Set(DatabaseKey, client)
}

View File

@@ -0,0 +1,62 @@
package responses
import (
"fmt"
"net/http"
)
var (
ErrBadRequestFormat = APIError{
Type: "bad_format",
HTTPCode: http.StatusBadRequest,
Description: "unable to read request body, please check that the json is valid",
}
ErrDataValidation = APIError{
Type: "data_validation",
HTTPCode: http.StatusBadRequest,
Description: "the data are not valid",
}
ErrNotFound = APIError{
Type: "not_found",
HTTPCode: http.StatusNotFound,
}
ErrAlreadyExists = APIError{
Type: "already_exists",
HTTPCode: http.StatusConflict,
}
ErrUnauthorized = APIError{
Type: "unauthorized",
HTTPCode: http.StatusUnauthorized,
}
ErrForbidden = APIError{
Type: "forbidden",
HTTPCode: http.StatusForbidden,
}
ErrInternalServer = APIError{
Type: "internal_server_error",
HTTPCode: http.StatusInternalServerError,
}
)
type APIError struct {
HTTPCode int `json:"-"`
Type string `json:"error"`
Description string `json:"errorDescription"`
Details []FieldError `json:"errorDetails,omitempty"`
Headers map[string][]string `json:"-"`
}
type FieldError struct {
Field string `json:"field"`
Constraint string `json:"constraint"`
Description string `json:"description"`
}
func (e *APIError) Error() string {
return fmt.Sprintf("error : %d, %s, %s, %v", e.HTTPCode, e.Type, e.Description, e.Details)
}

View File

@@ -0,0 +1,34 @@
package responses
import (
"encoding/json"
"mangezmieux-backend/internal/ginserver"
"net/http"
)
func JSON(w http.ResponseWriter, status int, data interface{}) {
w.Header().Set(ginserver.HeaderNameContentType, ginserver.HeaderValueApplicationJSONUTF8)
w.WriteHeader(status)
if data != nil {
err := json.NewEncoder(w).Encode(data)
if err != nil {
return
}
}
}
func JSONError(w http.ResponseWriter, e APIError) {
if e.Headers != nil {
for k, headers := range e.Headers {
for _, headerValue := range headers {
w.Header().Add(k, headerValue)
}
}
}
JSON(w, e.HTTPCode, e)
}
func JSONErrorWithMessage(w http.ResponseWriter, e APIError, message string) {
e.Description = message
JSONError(w, e)
}

View File

@@ -0,0 +1,123 @@
package users
import (
"errors"
"github.com/go-playground/validator/v10"
"mangezmieux-backend/internal/logger"
"mangezmieux-backend/internal/middleware"
"mangezmieux-backend/internal/responses"
"mangezmieux-backend/internal/users/model"
"mangezmieux-backend/internal/users/service"
"net/http"
"strings"
"github.com/gin-gonic/gin"
coreValidator "mangezmieux-backend/internal/validator"
)
type Handler struct {
Service *service.Service
Validator *validator.Validate
}
func NewHandler(service *service.Service, validator *validator.Validate) *Handler {
return &Handler{
Service: service,
Validator: validator,
}
}
func (h Handler) CreateUser(context *gin.Context) {
userEditable := model.UserEditable{}
if err := context.BindJSON(&userEditable); err != nil {
responses.JSONError(context.Writer, coreValidator.NewDataValidationAPIError(err))
return
}
user, err := h.Service.CreateUser(&userEditable)
if err != nil {
logger.GetLogger().Error(err)
var apiError *responses.APIError
if errors.As(err, &apiError) {
responses.JSONError(context.Writer, *apiError)
return
}
responses.JSONErrorWithMessage(context.Writer, responses.ErrInternalServer, err.Error())
return
}
responses.JSON(context.Writer, http.StatusCreated, user)
}
func (h Handler) Login(context *gin.Context) {
userLoginRequest := model.UserLoginRequest{}
if err := context.BindJSON(&userLoginRequest); err != nil {
responses.JSONError(context.Writer, coreValidator.NewDataValidationAPIError(err))
return
}
token, err := h.Service.Login(userLoginRequest)
if err != nil {
logger.GetLogger().Error(err)
var apiError *responses.APIError
if errors.As(err, &apiError) {
responses.JSONError(context.Writer, *apiError)
return
}
responses.JSONErrorWithMessage(context.Writer, responses.ErrInternalServer, err.Error())
return
}
context.SetCookie("token", token, 10, "/", "localhost", true, false)
userLoginResponse := model.UserLoginResponse{
AccessToken: token,
TokenType: "Bearer",
}
responses.JSON(context.Writer, http.StatusOK, userLoginResponse)
}
func (h Handler) IntrospectToken(context *gin.Context) {
authorization := context.Request.Header.Get("Authorization")
splitToken := strings.Split(authorization, "Bearer ")
reqToken := splitToken[1]
user, err := h.Service.Introspect(reqToken)
if err != nil {
logger.GetLogger().Error(err)
var apiError *responses.APIError
if errors.As(err, &apiError) {
responses.JSONError(context.Writer, *apiError)
return
}
responses.JSONErrorWithMessage(context.Writer, responses.ErrInternalServer, err.Error())
return
}
responses.JSON(context.Writer, http.StatusOK, user)
}
func (h Handler) RefreshToken(context *gin.Context) {
authorization := context.Request.Header.Get("Authorization")
splitToken := strings.Split(authorization, "Bearer ")
reqToken := splitToken[1]
refreshedToken, err := h.Service.Refresh(reqToken)
if err != nil {
logger.GetLogger().Error(err)
var apiError *responses.APIError
if errors.As(err, &apiError) {
responses.JSONError(context.Writer, *apiError)
return
}
responses.JSONErrorWithMessage(context.Writer, responses.ErrInternalServer, err.Error())
return
}
context.SetCookie("token", refreshedToken, 10, "/", "localhost", true, false)
}
func (h Handler) GetMe(context *gin.Context) {
usr, exists := context.Get(middleware.CtxUser)
if !exists {
responses.JSONErrorWithMessage(context.Writer, responses.ErrInternalServer, "User not found in context")
return
}
responses.JSON(context.Writer, http.StatusOK, usr)
}

View File

@@ -0,0 +1,44 @@
package users
import (
"context"
"github.com/gin-gonic/gin"
model2 "mangezmieux-backend/internal/acl/model"
"mangezmieux-backend/internal/acl/service"
"mangezmieux-backend/internal/jwt"
"mangezmieux-backend/internal/middleware"
"mangezmieux-backend/internal/users/model"
service2 "mangezmieux-backend/internal/users/service"
)
var AuthMiddleware = newMiddleware()
type internalAuthMiddleware struct {
Service *service2.Service
RoleService service.Service
UserService service.Service
}
func newMiddleware() *internalAuthMiddleware {
return &internalAuthMiddleware{}
}
func (m *internalAuthMiddleware) GinMiddleware(jwtService *jwt.Service) gin.HandlerFunc {
return middleware.GetAuthenticationMiddleware(m, jwtService)
}
// delegate useful for deferred binding (when the middleware is installed, GinMiddleware() is called, the service m.Service is not yet created :-( )
// see cmd/app.go for deferred binding at the end.
func (m *internalAuthMiddleware) Introspect(token string) (*model.User, error) {
return m.Service.Introspect(token)
}
// delegate useful for deferred binding (when the middleware is installed, GinMiddleware() is called, the service m.Service is not yet created :-( )
// see cmd/app.go for deferred binding at the end.
func (m *internalAuthMiddleware) GetRole(ctx context.Context, user *model.User) (*model2.UserRight, error) {
return m.RoleService.GetRoleForCurrentUser(user)
}
func (m *internalAuthMiddleware) GetAllRole(ctx context.Context) ([]*model2.Role, error) {
return m.RoleService.GetAllRole()
}

View File

@@ -0,0 +1,31 @@
package model
import (
"github.com/gofrs/uuid"
"time"
)
type User struct {
ID *uuid.UUID `json:"ID"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt *time.Time `json:"updatedAt"`
UserEditable
}
type UserEditable struct {
Firstname string `json:"first_name" binding:"required"`
Lastname string `json:"last_name" binding:"required"`
Email string `json:"email" binding:"required"`
Password string `json:"password" binding:"required"`
}
type UserLoginRequest struct {
Email string `json:"email" binding:"required"`
Password string `json:"password" binding:"required"`
}
type UserLoginResponse struct {
AccessToken string `json:"accessToken"`
TokenType string `json:"tokenType"`
ExpiresIn string `json:"expiresIn"`
}

View File

@@ -0,0 +1,98 @@
package service
import (
"mangezmieux-backend/internal/jwt"
"mangezmieux-backend/internal/responses"
"mangezmieux-backend/internal/users/model"
"mangezmieux-backend/internal/users/sql"
"time"
"golang.org/x/crypto/bcrypt"
)
type Service struct {
dao sql.Dao
jwt *jwt.Service
}
func NewService(dao sql.Dao, jwt *jwt.Service) *Service {
return &Service{dao: dao, jwt: jwt}
}
func (s *Service) CreateUser(userEditable *model.UserEditable) (*model.User, error) {
now := time.Now()
user := &model.User{
ID: nil,
CreatedAt: now,
UpdatedAt: &now,
UserEditable: model.UserEditable{
Firstname: userEditable.Firstname,
Lastname: userEditable.Lastname,
Email: userEditable.Email,
Password: userEditable.Password,
},
}
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(userEditable.Password), 8)
if err != nil {
return nil, &responses.ErrInternalServer
}
user.Password = string(hashedPassword)
err = s.dao.Create(user)
if err != nil {
return nil, err
}
user.Password = ""
return user, nil
}
func (s *Service) Login(request model.UserLoginRequest) (string, error) {
user, err := s.dao.FindByMail(request.Email)
if err != nil {
return "", &responses.ErrUnauthorized
}
err = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(request.Password))
if err != nil {
return "", &responses.ErrUnauthorized
}
return s.jwt.GenerateJWTToken(user.ID.String())
}
func (s *Service) Introspect(token string) (*model.User, error) {
claims, err := s.jwt.ValidateToken(token)
if err != nil {
return nil, err
}
usr, err := s.dao.FindByID(claims.ID)
if err != nil {
return nil, err
}
userModel := s.transformEntityToResponse(usr, false)
return userModel, nil
}
func (s *Service) Refresh(oldToken string) (string, error) {
return s.jwt.Refresh(oldToken)
}
func (s *Service) transformEntityToResponse(user *model.User, withPassword bool) *model.User {
password := ""
if withPassword {
password = user.Password
}
return &model.User{
ID: user.ID,
CreatedAt: user.CreatedAt,
UpdatedAt: user.UpdatedAt,
UserEditable: model.UserEditable{
Firstname: user.Firstname,
Lastname: user.Lastname,
Email: user.Email,
Password: password,
},
}
}

View File

@@ -0,0 +1,42 @@
package users
import (
"database/sql"
"mangezmieux-backend/internal/ginserver"
"mangezmieux-backend/internal/injector"
"mangezmieux-backend/internal/jwt"
"mangezmieux-backend/internal/postgres"
service2 "mangezmieux-backend/internal/users/service"
sql2 "mangezmieux-backend/internal/users/sql"
"mangezmieux-backend/internal/validator"
"net/http"
"github.com/gin-gonic/gin"
validatorv10 "github.com/go-playground/validator/v10"
)
const ServiceKey = "UsersService"
func Setup(inj *injector.Injector) {
publicRoute := injector.Get[*gin.RouterGroup](inj, ginserver.UnsecuredRouterInjectorKey)
validatorCli := injector.Get[*validatorv10.Validate](inj, validator.ValidatorInjectorKey)
jwtService := injector.Get[*jwt.Service](inj, jwt.JWTKey)
client := injector.Get[*sql.DB](inj, postgres.DatabaseKey)
dao := sql2.NewDao(client)
service := service2.NewService(dao, jwtService)
handler := NewHandler(service, validatorCli)
inj.Set(ServiceKey, service)
publicRoute.Handle(http.MethodPost, "/api/v1/users", handler.CreateUser)
publicRoute.Handle(http.MethodPost, "/oauth2/token", handler.Login)
publicRoute.Handle(http.MethodPost, "/oauth2/introspect", handler.IntrospectToken)
publicRoute.Handle(http.MethodPost, "/oauth2/refresh", handler.RefreshToken)
securedRoute := injector.Get[*gin.RouterGroup](inj, ginserver.SecuredRouterInjectorKey)
securedRoute.Handle(http.MethodGet, "/users/me", handler.GetMe)
}

View File

@@ -0,0 +1,11 @@
package sql
import "mangezmieux-backend/internal/users/model"
type Dao interface {
FindByMail(mail string) (*model.User, error)
Create(user *model.User) error
Delete(mail string) error
FindByMailAndPassword(mail string, password string) (*model.User, error)
FindByID(id string) (*model.User, error)
}

View File

@@ -0,0 +1,111 @@
package sql
import (
"database/sql"
"errors"
"mangezmieux-backend/internal/postgres"
"mangezmieux-backend/internal/users/model"
"github.com/lib/pq"
)
type SQLDao struct {
client *sql.DB
}
func NewDao(client *sql.DB) Dao {
return &SQLDao{client: client}
}
func (sqlDAO *SQLDao) FindByMailAndPassword(mail string, password string) (*model.User, error) {
q := `
SELECT u.ID, u.first_name, u.last_name, u.creation_date, u.last_update_date
FROM mangezmieux.user u
WHERE u.email = $1 AND u.password = $2
`
row := sqlDAO.client.QueryRow(q, mail, password)
u := model.User{}
err := row.Scan(&u.Email, &u.Firstname, &u.Lastname, &u.CreatedAt, &u.UpdatedAt)
var errPq *pq.Error
if errors.As(err, &errPq) {
return nil, postgres.HandlePgError(errPq)
}
if errors.Is(err, sql.ErrNoRows) {
return nil, postgres.NewDAOError(postgres.ErrTypeNotFound, err)
}
return &u, err
}
func (sqlDAO *SQLDao) FindByMail(mail string) (*model.User, error) {
q := `
SELECT u.ID, u.email, u.first_name, u.last_name, u.creation_date, u.last_update_date, u.password
FROM mangezmieux.user u
WHERE u.email = $1
`
row := sqlDAO.client.QueryRow(q, mail)
u := model.User{}
err := row.Scan(&u.ID, &u.Email, &u.Firstname, &u.Lastname, &u.CreatedAt, &u.UpdatedAt, &u.Password)
var errPq *pq.Error
if errors.As(err, &errPq) {
return nil, postgres.HandlePgError(errPq)
}
if errors.Is(err, sql.ErrNoRows) {
return nil, postgres.NewDAOError(postgres.ErrTypeNotFound, err)
}
return &u, err
}
func (sqlDAO *SQLDao) FindByID(id string) (*model.User, error) {
q := `
SELECT u.ID, u.email, u.first_name, u.last_name, u.creation_date, u.last_update_date
FROM mangezmieux.user u
WHERE u.id = $1
`
row := sqlDAO.client.QueryRow(q, id)
u := model.User{}
err := row.Scan(&u.ID, &u.Email, &u.Firstname, &u.Lastname, &u.CreatedAt, &u.UpdatedAt)
var errPq *pq.Error
if errors.As(err, &errPq) {
return nil, postgres.HandlePgError(errPq)
}
if errors.Is(err, sql.ErrNoRows) {
return nil, postgres.NewDAOError(postgres.ErrTypeNotFound, err)
}
return &u, err
}
func (sqlDAO *SQLDao) Create(user *model.User) error {
q := `
INSERT INTO mangezmieux.user
(email, password, first_name, last_name, creation_date, last_update_date)
VALUES
($1, $2, $3, $4, $5, $6)
RETURNING id, creation_date
`
err := sqlDAO.client.
QueryRow(q, user.Email, user.Password, user.Firstname, user.Lastname, user.CreatedAt, user.UpdatedAt).
Scan(&user.ID, &user.CreatedAt)
var errPq *pq.Error
if errors.As(err, &errPq) {
return postgres.HandlePgError(errPq)
}
return err
}
func (sqlDAO *SQLDao) Delete(id string) error {
q := `
DELETE FROM mangezmieux.user
WHERE id = $1
`
_, err := sqlDAO.client.Exec(q, id)
var errPq *pq.Error
if errors.As(err, &errPq) {
return postgres.HandlePgError(errPq)
}
return err
}

View File

@@ -0,0 +1,51 @@
package validator
import (
"errors"
"fmt"
"mangezmieux-backend/internal/logger"
"mangezmieux-backend/internal/responses"
"regexp"
"strings"
validatorLib "github.com/go-playground/validator/v10"
)
var regexpValidatorNamespacePrefix = regexp.MustCompile(`^\w+\.`)
func NewDataValidationAPIError(err error) responses.APIError {
apiErr := responses.ErrDataValidation
if err != nil {
var invalidValidationErrror *validatorLib.InvalidValidationError
if errors.As(err, &invalidValidationErrror) {
logger.GetLogger().WithError(invalidValidationErrror).WithField("templateAPIErr", apiErr).Error("InvalidValidationError")
} else {
var validationErrors validatorLib.ValidationErrors
if errors.As(err, &validationErrors) {
for _, e := range validationErrors {
reason := e.Tag()
if _, ok := CustomValidators[e.Tag()]; ok {
reason = truncatingSprintf(CustomValidators[e.Tag()].Message, e.Param())
}
namespaceWithoutStructName := regexpValidatorNamespacePrefix.ReplaceAllString(e.Namespace(), "")
fe := responses.FieldError{
Field: namespaceWithoutStructName,
Constraint: e.Tag(),
Description: reason,
}
apiErr.Details = append(apiErr.Details, fe)
}
} else {
apiErr.Description = err.Error()
}
}
}
return apiErr
}
// truncatingSprintf is used as fmt.Sprintf but allow to truncate the additional parameters given when there is more parameters than %v in str.
func truncatingSprintf(str string, args ...interface{}) string {
n := strings.Count(str, "%v")
return fmt.Sprintf(str, args[:n]...)
}

View File

@@ -0,0 +1,9 @@
package validator
import "mangezmieux-backend/internal/injector"
const ValidatorInjectorKey = "VALIDATOR"
func Setup(inj *injector.Injector) {
inj.Set(ValidatorInjectorKey, newValidator())
}

View File

@@ -0,0 +1,56 @@
package validator
import (
"context"
"reflect"
"strings"
validatorLib "github.com/go-playground/validator/v10"
)
var CustomValidators = map[string]customValidator{
"enum": {
Message: "This field should be in: %v",
Validator: validateEnum,
},
"required": {
Message: "This field is required and cannot be empty",
},
}
type customValidator struct {
Message string
Validator validatorLib.FuncCtx
}
func validateEnum(ctx context.Context, fl validatorLib.FieldLevel) bool {
for _, v := range strings.Split(fl.Param(), " ") {
if v == fl.Field().String() {
return true
}
}
return false
}
func newValidator() *validatorLib.Validate {
va := validatorLib.New()
va.RegisterTagNameFunc(func(fld reflect.StructField) string {
name := strings.SplitN(fld.Tag.Get("json"), ",", 2)
if len(name) < 1 {
return ""
}
return name[0]
})
for k, v := range CustomValidators {
if v.Validator != nil {
err := va.RegisterValidationCtx(k, v.Validator)
if err != nil {
return nil
}
}
}
return va
}

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.5.xsd">
<!-- Define default schema name used by changesets-->
<property name="schemaName" value="mangezmieux"/>
<include file="prepare-database.sql" relativeToChangelogFile="true"/>
<include file="changesets/create-user-table.xml" relativeToChangelogFile="true" />
<include file="changesets/create-role-table.xml" relativeToChangelogFile="true" />
<include file="changesets/create-resource-table.xml" relativeToChangelogFile="true" />
<include file="changesets/create-role-verb-table.xml" relativeToChangelogFile="true" />
<include file="changesets/create-user-role-table.xml" relativeToChangelogFile="true" />
<include file="changesets/create-ingredient-table.xml" relativeToChangelogFile="true" />
<include file="changesets/create-recipe-table.xml" relativeToChangelogFile="true" />
<include file="changesets/insert-resource.xml" relativeToChangelogFile="true" />
<include file="changesets/insert-admin-role.xml" relativeToChangelogFile="true" />
<include file="changesets/insert-admin-user.xml" relativeToChangelogFile="true" />
<include file="changesets/insert-mangezmieux-admin-role.xml" relativeToChangelogFile="true" />
</databaseChangeLog>

View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.5.xsd">
<changeSet id="create-user-table" author="Jeffrey Duroyon">
<createTable tableName="user" schemaName="${schemaName}">
<column name="id" type="uuid" defaultValueComputed="gen_random_uuid()">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="email" type="varchar(50)">
<constraints unique="true"/>
</column>
<column name="password" type="varchar(255)">
</column>
<column name="first_name" type="varchar(45)">
</column>
<column name="last_name" type="varchar(45)">
</column>
<column name="creation_date" type="timestamp" defaultValueComputed="now()">
<constraints nullable="true"/>
</column>
<column name="last_update_date" type="timestamp" defaultValueComputed="now()">
<constraints nullable="true"/>
</column>
<column name="last_update_user" type="varchar(100)">
</column>
<column name="creation_user" type="varchar(100)">
</column>
</createTable>
</changeSet>
</databaseChangeLog>

View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.5.xsd">
<changeSet id="create-ingredient-table" author="Jeffrey Duroyon">
<createTable tableName="ingredient" schemaName="${schemaName}">
<column name="id" type="uuid" defaultValueComputed="gen_random_uuid()">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="name" type="varchar(255)">
<constraints unique="true"/>
</column>
<column name="rarity" type="varchar(255)">
</column>
<column name="creation_date" type="timestamp" defaultValueComputed="now()">
<constraints nullable="true"/>
</column>
<column name="last_update_date" type="timestamp" defaultValueComputed="now()">
<constraints nullable="true"/>
</column>
<column name="last_update_user" type="varchar(100)">
</column>
<column name="creation_user" type="varchar(100)">
</column>
</createTable>
</changeSet>
</databaseChangeLog>

View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.5.xsd"
>
<changeSet id="create-resource-table" author="Jeffrey Duroyon">
<createTable tableName="resource" schemaName="${schemaName}">
<column name="id" type="uuid" defaultValueComputed="gen_random_uuid()">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="name" type="varchar(40)">
<constraints unique="true"/>
</column>
<column name="creation_date" type="timestamp" defaultValueComputed="now()">
<constraints nullable="true"/>
</column>
<column name="last_update_date" type="timestamp">
<constraints nullable="true"/>
</column>
<column name="last_update_user" type="varchar(100)">
</column>
<column name="creation_user" type="varchar(100)">
</column>
</createTable>
</changeSet>
</databaseChangeLog>

View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.5.xsd"
>
<changeSet id="create-role-table" author="Jeffrey Duroyon">
<createTable tableName="role" schemaName="${schemaName}">
<column name="id" type="uuid" defaultValueComputed="gen_random_uuid()">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="name" type="varchar(40)">
<constraints unique="true"/>
</column>
<column name="creation_date" type="timestamp" defaultValueComputed="now()">
<constraints nullable="true"/>
</column>
<column name="last_update_date" type="timestamp">
<constraints nullable="true"/>
</column>
<column name="last_update_user" type="varchar(100)">
</column>
<column name="creation_user" type="varchar(100)">
</column>
</createTable>
</changeSet>
</databaseChangeLog>

View File

@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.5.xsd"
>
<changeSet id="create-role-verb-table" author="Jeffrey Duroyon">
<createTable tableName="role_verb_resource" schemaName="${schemaName}">
<column name="id" type="uuid" defaultValueComputed="gen_random_uuid()">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="role_id" type="uuid">
<constraints nullable="false" foreignKeyName="fk_role_verb_resource_role" references="${schemaName}.role(id)"/>
</column>
<column name="verb" type="varchar(40)">
</column>
<column name="resource_id" type="uuid">
<constraints nullable="false" foreignKeyName="fk_role_verb_resource_resource" references="${schemaName}.resource(id)"/>
</column>
<column name="creation_date" type="timestamp" defaultValueComputed="now()">
<constraints nullable="true"/>
</column>
<column name="last_update_date" type="timestamp">
<constraints nullable="true"/>
</column>
<column name="last_update_user" type="varchar(100)">
</column>
<column name="creation_user" type="varchar(100)">
</column>
</createTable>
<addUniqueConstraint
columnNames="role_id, verb, resource_id"
constraintName="unique_role_verb_resource"
schemaName="${schemaName}"
tableName="role_verb_resource"
/>
</changeSet>
</databaseChangeLog>

View File

@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.5.xsd"
>
<changeSet id="create-user-role-table" author="Jeffrey Duroyon">
<createTable tableName="user_role" schemaName="${schemaName}">
<column name="id" type="uuid" defaultValueComputed="gen_random_uuid()">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="user_id" type="uuid">
<constraints nullable="false" foreignKeyName="fk_user_role_box_user" references="${schemaName}.user(id)"/>
</column>
<column name="role_id" type="uuid">
<constraints nullable="false" foreignKeyName="fk_user_role_box_role" references="${schemaName}.role(id)"/>
</column>
<column name="creation_date" type="timestamp" defaultValueComputed="now()">
<constraints nullable="true"/>
</column>
<column name="last_update_date" type="timestamp">
<constraints nullable="true"/>
</column>
<column name="last_update_user" type="varchar(100)">
</column>
<column name="creation_user" type="varchar(100)">
</column>
</createTable>
<addUniqueConstraint
columnNames="role_id, user_id"
constraintName="unique_user_role"
schemaName="${schemaName}"
tableName="user_role"
/>
</changeSet>
</databaseChangeLog>

View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.5.xsd">
<changeSet id="create-user-table" author="Jeffrey Duroyon">
<createTable tableName="user" schemaName="${schemaName}">
<column name="id" type="uuid" defaultValueComputed="gen_random_uuid()">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="email" type="varchar(50)">
<constraints unique="true"/>
</column>
<column name="password" type="varchar(255)">
</column>
<column name="first_name" type="varchar(45)">
</column>
<column name="last_name" type="varchar(45)">
</column>
<column name="creation_date" type="timestamp" defaultValueComputed="now()">
<constraints nullable="true"/>
</column>
<column name="last_update_date" type="timestamp" defaultValueComputed="now()">
<constraints nullable="true"/>
</column>
<column name="last_update_user" type="varchar(100)">
</column>
<column name="creation_user" type="varchar(100)">
</column>
</createTable>
</changeSet>
</databaseChangeLog>

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.5.xsd"
>
<changeSet id="insert-role-and-verb" author="Jeffrey Duroyon">
<insert tableName="role" schemaName="${schemaName}">
<column name="name" value="ADMIN"/>
</insert>
<insert tableName="role_verb_resource" schemaName="${schemaName}">
<column name="verb" value="ADMIN"/>
<column name="role_id" valueComputed="(SELECT id from ${schemaName}.role where name='ADMIN')"/>
<column name="resource_id" valueComputed="(SELECT id from ${schemaName}.resource where name='ADMIN')"/>
</insert>
</changeSet>
</databaseChangeLog>

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.5.xsd"
>
<changeSet id="insert-admin-user" author="Jeffrey Duroyon">
<insert tableName="user" schemaName="${schemaName}">
<column name="email" value="admin@mangezmieux.com"/>
<column name="password" value="$2a$10$zLk6vcP7dKQ3KZ1c5icu3OiT68AeGyW.rxGL54AqOoUhoId2hmrrK"/>
</insert>
</changeSet>
</databaseChangeLog>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.5.xsd"
>
<changeSet id="insert-mangezmieux-admin-role" author="Jeffrey Duroyon">
<insert tableName="role" schemaName="${schemaName}">
<column name="name" value="MANGEZMIEUX_ADMIN"/>
</insert>
<insert tableName="role_verb_resource" schemaName="${schemaName}">
<column name="verb" value="ADMIN"/>
<column name="role_id" valueComputed="(SELECT id from ${schemaName}.role where name='MANGEZMIEUX_ADMIN')"/>
<column name="resource_id" valueComputed="(SELECT id from ${schemaName}.resource where name='ADMIN')"/>
</insert>
<insert tableName="user_role" schemaName="${schemaName}">
<column name="user_id" valueComputed="(SELECT id from ${schemaName}.user where email='admin@mangezmieux.com')"/>
<column name="role_id" valueComputed="(SELECT id from ${schemaName}.role where name='MANGEZMIEUX_ADMIN')"/>
</insert>
<!-- <column name="studentId" valueComputed="(SELECT sId from StudentMaster where studentName='Jay Parikh')"/>-->
</changeSet>
</databaseChangeLog>

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.5.xsd"
>
<changeSet id="insert-resource" author="Jeffrey Duroyon">
<insert tableName="resource" schemaName="${schemaName}">
<column name="name" value="ADMIN"/>
</insert>
<insert tableName="resource" schemaName="${schemaName}">
<column name="name" value="USER"/>
</insert>
<insert tableName="resource" schemaName="${schemaName}">
<column name="name" value="RECIPE"/>
</insert>
<insert tableName="resource" schemaName="${schemaName}">
<column name="name" value="ROLE"/>
</insert>
</changeSet>
</databaseChangeLog>

View File

@@ -0,0 +1,4 @@
url: jdbc:postgresql://127.0.0.1:5432/mangezmieux
username: postgres
password: mysecretpassword
logLevel: info

View File

@@ -0,0 +1,2 @@
CREATE SCHEMA IF NOT EXISTS mangezmieux;
CREATE EXTENSION IF NOT EXISTS pgcrypto;

View File

@@ -0,0 +1,7 @@
package main
import "mangezmieux-backend/cmd"
func main() {
cmd.Execute()
}