From 94a6d41a6197ec54705c507eaebf1a31f9041659 Mon Sep 17 00:00:00 2001 From: "Hafizh A. Y" Date: Tue, 30 Sep 2025 14:45:54 +0700 Subject: [PATCH] fix: adjust docker and any file for starting project --- .DS_Store | Bin 8196 -> 8196 bytes .env.example | 25 +- .gitignore | 3 + .golangci.yml | 543 +++++++----------- Dockerfile.dev => Dockerfile.local | 0 Dockerfile.prod | 33 -- Makefile | 9 +- README.md | 9 +- cmd/api/main.go | 27 +- ...ompose.dev.yml => docker-compose.local.yml | 27 +- docker-compose.prod.yml | 50 -- go.mod | 2 - go.sum | 4 - internal/config/config.go | 132 +++-- internal/config/jwtRS256.key.pub | 14 + internal/config/oauth2.go | 27 - .../20250925040409_create_lti_tables.down.sql | 1 - .../20250925040409_create_lti_tables.up.sql | 8 - ...250925040409_create_master_tables.down.sql | 22 + ...20250925040409_create_master_tables.up.sql | 204 +++++++ internal/database/seed/seeder.go | 12 +- internal/modules/users/dto/user.dto.go | 22 +- internal/modules/users/models/user.model.go | 2 + .../users/repositories/user.repository.go | 1 - .../modules/users/services/user.service.go | 2 +- internal/route/route.go | 2 - tools/templates/dto.tmpl | 18 +- 27 files changed, 599 insertions(+), 600 deletions(-) rename Dockerfile.dev => Dockerfile.local (100%) delete mode 100644 Dockerfile.prod rename docker-compose.dev.yml => docker-compose.local.yml (73%) delete mode 100644 docker-compose.prod.yml create mode 100644 internal/config/jwtRS256.key.pub delete mode 100644 internal/config/oauth2.go delete mode 100644 internal/database/migrations/20250925040409_create_lti_tables.down.sql delete mode 100644 internal/database/migrations/20250925040409_create_lti_tables.up.sql create mode 100644 internal/database/migrations/20250925040409_create_master_tables.down.sql create mode 100644 internal/database/migrations/20250925040409_create_master_tables.up.sql diff --git a/.DS_Store b/.DS_Store index a01125ca651fb2b2a2afaf81c044be87eb849df0..762745b833fd0736d8845c0cbbaadc601e56427b 100644 GIT binary patch delta 45 zcmZp1XmOa}&nUeyU^hRb^kyD`CZ@@Eglsm43D04ioG;V2u|k-6GrPn$mW>tc%m829 B4;}yj delta 454 zcmZp1XmOa}I9U^hRb(qoopy9$~bRwvyeL@|KxrladD`uXHI@{Qcivn0|SEq0|R6C=G{W88S8;O zR)!Lw?tF$EhGH}|KY{A_{(}Kf9g51Fbi?4}{M-VtID>~Fs%&n)iwo2)j=#Sbo2lK diff --git a/.env.example b/.env.example index cb2c0623..a729d17f 100644 --- a/.env.example +++ b/.env.example @@ -1,5 +1,6 @@ # server configuration # Env value : prod || dev +VERSION=0.0.1 APP_ENV=dev APP_HOST=0.0.0.0 APP_PORT=8080 @@ -13,25 +14,19 @@ DB_NAME=db_lti_erp DB_PORT=5432 # JWT -# JWT secret key JWT_SECRET=changeme -# Number of minutes after which an access token expires JWT_ACCESS_EXP_MINUTES=30 -# Number of days after which a refresh token expires JWT_REFRESH_EXP_DAYS=30 -# Number of minutes after which a reset password token expires JWT_RESET_PASSWORD_EXP_MINUTES=10 -# Number of minutes after which a verify email token expires JWT_VERIFY_EMAIL_EXP_MINUTES=10 -# SMTP configuration options for the email service -SMTP_HOST=email-server -SMTP_PORT=587 -SMTP_USERNAME=email-server-username -SMTP_PASSWORD=changeme -EMAIL_FROM=support@yourapp.com +# CORS +CORS_ALLOW_ORIGINS=changeme +CORS_ALLOW_METHODS=GET,POST,PUT,PATCH,DELETE,OPTIONS +CORS_ALLOW_HEADERS=Authorization,Content-Type,X-Requested-With +CORS_EXPOSE_HEADERS=Link,Location +CORS_ALLOW_CREDENTIALS=true +CORS_MAX_AGE=600 -# OAuth2 configuration -GOOGLE_CLIENT_ID=yourapps.googleusercontent.com -GOOGLE_CLIENT_SECRET=changeme -REDIRECT_URL=http://localhost:3000/v1/auth/google-callback +# Redis +REDIS_URL=redis://redis:6379/0 diff --git a/.gitignore b/.gitignore index 57cc0755..90267937 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,9 @@ bin/ *.exe *.out +# Go build cache +.gocache/ + # Logs & reports *.log *.txt diff --git a/.golangci.yml b/.golangci.yml index 01781016..4debbdf1 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,346 +1,203 @@ -# ## Config for golangci-lint v1.60.1 +## Config for golangci-lint v2 schema +version: "2" -# run: -# # Timeout for analysis, e.g. 30s, 5m. -# # Default: 1m -# timeout: 3m +run: + timeout: 3m -# # This file contains only configs which differ from defaults. -# # All possible options can be found here https://github.com/golangci/golangci-lint/blob/master/.golangci.reference.yml -# linters-settings: -# cyclop: -# # The maximal code complexity to report. -# # Default: 10 -# max-complexity: 30 -# # The maximal average package complexity. -# # If it's higher than 0.0 (float) the check is enabled -# # Default: 0.0 -# package-average: 10.0 +linters: + default: none + enable: + ## enabled by default + - errcheck + - govet + - ineffassign + - staticcheck + - unused + ## disabled by default + - asasalint + - asciicheck + - bidichk + - bodyclose + - canonicalheader + - cyclop + - dupl + - durationcheck + - errname + - errorlint + - exhaustive + - fatcontext + - forbidigo + - funlen + - gocheckcompilerdirectives + #- gochecknoglobals + #- gochecknoinits + - gochecksumtype + - gocognit + - goconst + - gocritic + - gocyclo + #- godot + - gomoddirectives + - gomodguard + - goprintffuncname + - gosec + - intrange + - lll + - loggercheck + - makezero + - mirror + #- mnd + - musttag + - nakedret + - nestif + - nilerr + - nilnil + - noctx + - nolintlint + - nonamedreturns + - nosprintfhostport + - perfsprint + - predeclared + - promlinter + - protogetter + - reassign + - revive + - rowserrcheck + - sloglint + - spancheck + - sqlclosecheck + - testableexamples + #- testifylint + - testpackage + - tparallel + - unconvert + - unparam + - usestdlibvars + - usetesting + - wastedassign + - whitespace + settings: + cyclop: + max-complexity: 30 + package-average: 10.0 + errcheck: + check-type-assertions: true + exhaustive: + check: + - switch + - map + exhaustruct: + exclude: + - "^net/http.Client$" + - "^net/http.Cookie$" + - "^net/http.Request$" + - "^net/http.Response$" + - "^net/http.Server$" + - "^net/http.Transport$" + - "^net/url.URL$" + - "^os/exec.Cmd$" + - "^reflect.StructField$" + - "^github.com/Shopify/sarama.Config$" + - "^github.com/Shopify/sarama.ProducerMessage$" + - "^github.com/mitchellh/mapstructure.DecoderConfig$" + - "^github.com/prometheus/client_golang/.+Opts$" + - "^github.com/spf13/cobra.Command$" + - "^github.com/spf13/cobra.CompletionOptions$" + - "^github.com/stretchr/testify/mock.Mock$" + - "^github.com/testcontainers/testcontainers-go.+Request$" + - "^github.com/testcontainers/testcontainers-go.FromDockerfile$" + - "^golang.org/x/tools/go/analysis.Analyzer$" + - "^google.golang.org/protobuf/.+Options$" + - "^gopkg.in/yaml.v3.Node$" + funlen: + lines: 100 + statements: 50 + ignore-comments: true + gocognit: + min-complexity: 20 + gocritic: + settings: + captLocal: + paramsOnly: false + underef: + skipRecvDeref: false + gomodguard: + blocked: + modules: + - github.com/golang/protobuf: + recommendations: + - google.golang.org/protobuf + reason: "see https://developers.google.com/protocol-buffers/docs/reference/go/faq#modules" + - github.com/satori/go.uuid: + recommendations: + - github.com/google/uuid + reason: "satori's package is not maintained" + - github.com/gofrs/uuid: + recommendations: + - github.com/gofrs/uuid/v5 + reason: "gofrs' package was not go module before v5" + govet: + enable-all: true + disable: + - fieldalignment + settings: + shadow: + strict: true + inamedparam: + skip-single-param: true + mnd: + ignored-functions: + - args.Error + - flag.Arg + - flag.Duration.* + - flag.Float.* + - flag.Int.* + - flag.Uint.* + - os.Chmod + - os.Mkdir.* + - os.OpenFile + - os.WriteFile + - prometheus.ExponentialBuckets.* + - prometheus.LinearBuckets + nakedret: + max-func-lines: 0 + nolintlint: + allow-no-explanation: + - funlen + - gocognit + - lll + require-explanation: true + require-specific: true + perfsprint: + strconcat: false + rowserrcheck: + packages: + - github.com/jmoiron/sqlx + sloglint: + no-global: "all" + context: "scope" + exclusions: + rules: + - source: "(noinspection|TODO)" + linters: + - godot + - source: "//noinspection" + linters: + - gocritic + - path: "example\\.go" + linters: + - lll + - path: "_test\\.go" + linters: + - bodyclose + - dupl + - funlen + - goconst + - gosec + - noctx + - wrapcheck + - lll + - testpackage -# errcheck: -# # Report about not checking of errors in type assertions: `a := b.(MyStruct)`. -# # Such cases aren't reported by default. -# # Default: false -# check-type-assertions: true - -# exhaustive: -# # Program elements to check for exhaustiveness. -# # Default: [ switch ] -# check: -# - switch -# - map - -# exhaustruct: -# # List of regular expressions to exclude struct packages and their names from checks. -# # Regular expressions must match complete canonical struct package/name/structname. -# # Default: [] -# exclude: -# # std libs -# - "^net/http.Client$" -# - "^net/http.Cookie$" -# - "^net/http.Request$" -# - "^net/http.Response$" -# - "^net/http.Server$" -# - "^net/http.Transport$" -# - "^net/url.URL$" -# - "^os/exec.Cmd$" -# - "^reflect.StructField$" -# # public libs -# - "^github.com/Shopify/sarama.Config$" -# - "^github.com/Shopify/sarama.ProducerMessage$" -# - "^github.com/mitchellh/mapstructure.DecoderConfig$" -# - "^github.com/prometheus/client_golang/.+Opts$" -# - "^github.com/spf13/cobra.Command$" -# - "^github.com/spf13/cobra.CompletionOptions$" -# - "^github.com/stretchr/testify/mock.Mock$" -# - "^github.com/testcontainers/testcontainers-go.+Request$" -# - "^github.com/testcontainers/testcontainers-go.FromDockerfile$" -# - "^golang.org/x/tools/go/analysis.Analyzer$" -# - "^google.golang.org/protobuf/.+Options$" -# - "^gopkg.in/yaml.v3.Node$" - -# funlen: -# # Checks the number of lines in a function. -# # If lower than 0, disable the check. -# # Default: 60 -# lines: 100 -# # Checks the number of statements in a function. -# # If lower than 0, disable the check. -# # Default: 40 -# statements: 50 -# # Ignore comments when counting lines. -# # Default false -# ignore-comments: true - -# gocognit: -# # Minimal code complexity to report. -# # Default: 30 (but we recommend 10-20) -# min-complexity: 20 - -# gocritic: -# # Settings passed to gocritic. -# # The settings key is the name of a supported gocritic checker. -# # The list of supported checkers can be find in https://go-critic.github.io/overview. -# settings: -# captLocal: -# # Whether to restrict checker to params only. -# # Default: true -# paramsOnly: false -# underef: -# # Whether to skip (*x).method() calls where x is a pointer receiver. -# # Default: true -# skipRecvDeref: false - -# gomodguard: -# blocked: -# # List of blocked modules. -# # Default: [] -# modules: -# - github.com/golang/protobuf: -# recommendations: -# - google.golang.org/protobuf -# reason: "see https://developers.google.com/protocol-buffers/docs/reference/go/faq#modules" -# - github.com/satori/go.uuid: -# recommendations: -# - github.com/google/uuid -# reason: "satori's package is not maintained" -# - github.com/gofrs/uuid: -# recommendations: -# - github.com/gofrs/uuid/v5 -# reason: "gofrs' package was not go module before v5" - -# govet: -# # Enable all analyzers. -# # Default: false -# enable-all: true -# # Disable analyzers by name. -# # Run `go tool vet help` to see all analyzers. -# # Default: [] -# disable: -# - fieldalignment # too strict -# # Settings per analyzer. -# settings: -# shadow: -# # Whether to be strict about shadowing; can be noisy. -# # Default: false -# strict: true - -# inamedparam: -# # Skips check for interface methods with only a single parameter. -# # Default: false -# skip-single-param: true - -# mnd: -# # List of function patterns to exclude from analysis. -# # Values always ignored: `time.Date`, -# # `strconv.FormatInt`, `strconv.FormatUint`, `strconv.FormatFloat`, -# # `strconv.ParseInt`, `strconv.ParseUint`, `strconv.ParseFloat`. -# # Default: [] -# ignored-functions: -# - args.Error -# - flag.Arg -# - flag.Duration.* -# - flag.Float.* -# - flag.Int.* -# - flag.Uint.* -# - os.Chmod -# - os.Mkdir.* -# - os.OpenFile -# - os.WriteFile -# - prometheus.ExponentialBuckets.* -# - prometheus.LinearBuckets - -# nakedret: -# # Make an issue if func has more lines of code than this setting, and it has naked returns. -# # Default: 30 -# max-func-lines: 0 - -# nolintlint: -# # Exclude following linters from requiring an explanation. -# # Default: [] -# allow-no-explanation: [funlen, gocognit, lll] -# # Enable to require an explanation of nonzero length after each nolint directive. -# # Default: false -# require-explanation: true -# # Enable to require nolint directives to mention the specific linter being suppressed. -# # Default: false -# require-specific: true - -# perfsprint: -# # Optimizes into strings concatenation. -# # Default: true -# strconcat: false - -# rowserrcheck: -# # database/sql is always checked -# # Default: [] -# packages: -# - github.com/jmoiron/sqlx - -# sloglint: -# # Enforce not using global loggers. -# # Values: -# # - "": disabled -# # - "all": report all global loggers -# # - "default": report only the default slog logger -# # https://github.com/go-simpler/sloglint?tab=readme-ov-file#no-global -# # Default: "" -# no-global: "all" -# # Enforce using methods that accept a context. -# # Values: -# # - "": disabled -# # - "all": report all contextless calls -# # - "scope": report only if a context exists in the scope of the outermost function -# # https://github.com/go-simpler/sloglint?tab=readme-ov-file#context-only -# # Default: "" -# context: "scope" - -# tenv: -# # The option `all` will run against whole test files (`_test.go`) regardless of method/function signatures. -# # Otherwise, only methods that take `*testing.T`, `*testing.B`, and `testing.TB` as arguments are checked. -# # Default: false -# all: true - -# linters: -# disable-all: true -# enable: -# ## enabled by default -# - errcheck # checking for unchecked errors, these unchecked errors can be critical bugs in some cases -# - gosimple # specializes in simplifying a code -# - govet # reports suspicious constructs, such as Printf calls whose arguments do not align with the format string -# - ineffassign # detects when assignments to existing variables are not used -# - staticcheck # is a go vet on steroids, applying a ton of static analysis checks -# - typecheck # like the front-end of a Go compiler, parses and type-checks Go code -# - unused # checks for unused constants, variables, functions and types -# ## disabled by default -# - asasalint # checks for pass []any as any in variadic func(...any) -# - asciicheck # checks that your code does not contain non-ASCII identifiers -# - bidichk # checks for dangerous unicode character sequences -# - bodyclose # checks whether HTTP response body is closed successfully -# - canonicalheader # checks whether net/http.Header uses canonical header -# - cyclop # checks function and package cyclomatic complexity -# - dupl # tool for code clone detection -# - durationcheck # checks for two durations multiplied together -# - errname # checks that sentinel errors are prefixed with the Err and error types are suffixed with the Error -# - errorlint # finds code that will cause problems with the error wrapping scheme introduced in Go 1.13 -# - exhaustive # checks exhaustiveness of enum switch statements -# - fatcontext # detects nested contexts in loops -# - forbidigo # forbids identifiers -# - funlen # tool for detection of long functions -# - gocheckcompilerdirectives # validates go compiler directive comments (//go:) -# #- gochecknoglobals # checks that no global variables exist -# #- gochecknoinits # checks that no init functions are present in Go code -# - gochecksumtype # checks exhaustiveness on Go "sum types" -# - gocognit # computes and checks the cognitive complexity of functions -# - goconst # finds repeated strings that could be replaced by a constant -# - gocritic # provides diagnostics that check for bugs, performance and style issues -# - gocyclo # computes and checks the cyclomatic complexity of functions -# #- godot # checks if comments end in a period -# - goimports # in addition to fixing imports, goimports also formats your code in the same style as gofmt -# - gomoddirectives # manages the use of 'replace', 'retract', and 'excludes' directives in go.mod -# - gomodguard # allow and block lists linter for direct Go module dependencies. This is different from depguard where there are different block types for example version constraints and module recommendations -# - goprintffuncname # checks that printf-like functions are named with f at the end -# - gosec # inspects source code for security problems -# - intrange # finds places where for loops could make use of an integer range -# - lll # reports long lines -# - loggercheck # checks key value pairs for common logger libraries (kitlog,klog,logr,zap) -# - makezero # finds slice declarations with non-zero initial length -# - mirror # reports wrong mirror patterns of bytes/strings usage -# #- mnd # detects magic numbers -# - musttag # enforces field tags in (un)marshaled structs -# - nakedret # finds naked returns in functions greater than a specified function length -# - nestif # reports deeply nested if statements -# - nilerr # finds the code that returns nil even if it checks that the error is not nil -# - nilnil # checks that there is no simultaneous return of nil error and an invalid value -# - noctx # finds sending http request without context.Context -# - nolintlint # reports ill-formed or insufficient nolint directives -# - nonamedreturns # reports all named returns -# - nosprintfhostport # checks for misuse of Sprintf to construct a host with port in a URL -# - perfsprint # checks that fmt.Sprintf can be replaced with a faster alternative -# - predeclared # finds code that shadows one of Go's predeclared identifiers -# - promlinter # checks Prometheus metrics naming via promlint -# - protogetter # reports direct reads from proto message fields when getters should be used -# - reassign # checks that package variables are not reassigned -# - revive # fast, configurable, extensible, flexible, and beautiful linter for Go, drop-in replacement of golint -# - rowserrcheck # checks whether Err of rows is checked successfully -# - sloglint # ensure consistent code style when using log/slog -# - spancheck # checks for mistakes with OpenTelemetry/Census spans -# - sqlclosecheck # checks that sql.Rows and sql.Stmt are closed -# - stylecheck # is a replacement for golint -# - tenv # detects using os.Setenv instead of t.Setenv since Go1.17 -# - testableexamples # checks if examples are testable (have an expected output) -# #- testifylint # checks usage of github.com/stretchr/testify -# - testpackage # makes you use a separate _test package -# - tparallel # detects inappropriate usage of t.Parallel() method in your Go test codes -# - unconvert # removes unnecessary type conversions -# - unparam # reports unused function parameters -# - usestdlibvars # detects the possibility to use variables/constants from the Go standard library -# - wastedassign # finds wasted assignment statements -# - whitespace # detects leading and trailing whitespace - -# ## you may want to enable -# #- decorder # checks declaration order and count of types, constants, variables and functions -# #- exhaustruct # [highly recommend to enable] checks if all structure fields are initialized -# #- gci # controls golang package import order and makes it always deterministic -# #- ginkgolinter # [if you use ginkgo/gomega] enforces standards of using ginkgo and gomega -# #- godox # detects FIXME, TODO and other comment keywords -# #- goheader # checks is file header matches to pattern -# #- inamedparam # [great idea, but too strict, need to ignore a lot of cases by default] reports interfaces with unnamed method parameters -# #- interfacebloat # checks the number of methods inside an interface -# #- ireturn # accept interfaces, return concrete types -# #- prealloc # [premature optimization, but can be used in some cases] finds slice declarations that could potentially be preallocated -# #- tagalign # checks that struct tags are well aligned -# #- varnamelen # [great idea, but too many false positives] checks that the length of a variable's name matches its scope -# #- wrapcheck # checks that errors returned from external packages are wrapped -# #- zerologlint # detects the wrong usage of zerolog that a user forgets to dispatch zerolog.Event - -# ## disabled -# #- containedctx # detects struct contained context.Context field -# #- contextcheck # [too many false positives] checks the function whether use a non-inherited context -# #- copyloopvar # [not necessary from Go 1.22] detects places where loop variables are copied -# #- depguard # [replaced by gomodguard] checks if package imports are in a list of acceptable packages -# #- dogsled # checks assignments with too many blank identifiers (e.g. x, _, _, _, := f()) -# #- dupword # [useless without config] checks for duplicate words in the source code -# #- err113 # [too strict] checks the errors handling expressions -# #- errchkjson # [don't see profit + I'm against of omitting errors like in the first example https://github.com/breml/errchkjson] checks types passed to the json encoding functions. Reports unsupported types and optionally reports occasions, where the check for the returned error can be omitted -# #- execinquery # [deprecated] checks query string in Query function which reads your Go src files and warning it finds -# #- exportloopref # [not necessary from Go 1.22] checks for pointers to enclosing loop variables -# #- forcetypeassert # [replaced by errcheck] finds forced type assertions -# #- gofmt # [replaced by goimports] checks whether code was gofmt-ed -# #- gofumpt # [replaced by goimports, gofumports is not available yet] checks whether code was gofumpt-ed -# #- gosmopolitan # reports certain i18n/l10n anti-patterns in your Go codebase -# #- grouper # analyzes expression groups -# #- importas # enforces consistent import aliases -# #- maintidx # measures the maintainability index of each function -# #- misspell # [useless] finds commonly misspelled English words in comments -# #- nlreturn # [too strict and mostly code is not more readable] checks for a new line before return and branch statements to increase code clarity -# #- paralleltest # [too many false positives] detects missing usage of t.Parallel() method in your Go test -# #- tagliatelle # checks the struct tags -# #- thelper # detects golang test helpers without t.Helper() call and checks the consistency of test helpers -# #- wsl # [too strict and mostly code is not more readable] whitespace linter forces you to use empty lines - -# issues: -# # Maximum count of issues with the same text. -# # Set to 0 to disable. -# # Default: 3 -# max-same-issues: 50 - -# exclude-rules: -# - source: "(noinspection|TODO)" -# linters: [godot] -# - source: "//noinspection" -# linters: [gocritic] -# - path: "example\\.go" -# linters: -# - lll -# - path: "_test\\.go" -# linters: -# - bodyclose -# - dupl -# - funlen -# - goconst -# - gosec -# - noctx -# - wrapcheck -# - lll -# - testpackage +issues: + max-same-issues: 50 diff --git a/Dockerfile.dev b/Dockerfile.local similarity index 100% rename from Dockerfile.dev rename to Dockerfile.local diff --git a/Dockerfile.prod b/Dockerfile.prod deleted file mode 100644 index 5f6eea89..00000000 --- a/Dockerfile.prod +++ /dev/null @@ -1,33 +0,0 @@ -FROM golang:1.23-alpine AS builder - -# Install tools build -RUN apk add --no-cache git curl bash build-base - -WORKDIR /lti-api - -# Cache dependencies -COPY go.mod go.sum ./ -RUN go mod download - -# Copy source code -COPY . . - -# Build binary dari entrypoint (cmd/api/main.go) -RUN CGO_ENABLED=0 GOOS=linux go build -o app ./cmd/api - -FROM alpine:3.20 - -# Install tools -RUN apk add --no-cache curl - -WORKDIR /lti-api - -# Copy binary hasil build -COPY --from=builder /lti-api/app . - -# Copy file env example -COPY --from=builder /lti-api/.env.example .env - -EXPOSE 8080 - -CMD ["./app"] diff --git a/Makefile b/Makefile index 0534104b..324ecd76 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ export endif # --- Konfigurasi umum --- -COMPOSE ?= docker compose -f docker-compose.dev.yml +COMPOSE ?= docker compose -f docker-compose.local.yml NETWORK ?= lti-api_go-network MIGRATE_IMAGE ?= migrate/migrate MIGRATIONS_DIR := $(PWD)/internal/database/migrations @@ -31,7 +31,7 @@ WAIT_DB := docker run --rm --network $(NETWORK) postgres:alpine \ db-up wait-db \ migration-% migrate-up migrate-down migrate-fresh \ seed \ - docker-dev docker-prod docker-down docker-nuke docker-cache psql + docker-local docker-down docker-nuke docker-cache psql # --- Go workflow --- start: @@ -90,12 +90,9 @@ seed: db-up wait-db @$(COMPOSE) run --rm app go run cmd/seed/main.go # --- Docker orchestration convenience --- -docker-dev: +docker-local: @$(COMPOSE) up --build -d -docker-prod: - @docker compose -f docker-compose.prod.yml up --build -d - docker-down: @$(COMPOSE) down --remove-orphans diff --git a/README.md b/README.md index 8ceab057..da8394f1 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Lumbung Telur Indonesia ERP API (lti-api) -Lumbung Telur Indonesia RESTful API, built with **Go, Fiber, GORM** and **PostgreSQL**. +RESTful API for **Lumbung Telur Indonesia ERP**, built with **Go, Fiber, GORM** and **PostgreSQL**. --- @@ -15,6 +15,7 @@ Lumbung Telur Indonesia RESTful API, built with **Go, Fiber, GORM** and **Postgr - **Fiber middleware** — Rate limiting, CORS, recovery, logger - **Air** — Hot reload for development - **Docker + Docker Compose** — Containerization +- **golang-migrate** — Database migration tool --- @@ -46,7 +47,7 @@ cp .env.example .env Run initial docker. ```bash -make docker-dev +make docker-local ``` ### 4. Migrate Database @@ -105,8 +106,8 @@ internal/ ## ✨ Author -IT Development PT Mitra Berlian Unggas +IT Development PT Mitra Berlian Unggas Group ## 📃 License -Free to use --change here +This project is private. All rights reserved. diff --git a/cmd/api/main.go b/cmd/api/main.go index a06445f7..52f9ed73 100644 --- a/cmd/api/main.go +++ b/cmd/api/main.go @@ -5,6 +5,7 @@ import ( "fmt" "os" "os/signal" + "strings" "syscall" "time" @@ -43,11 +44,7 @@ func main() { } func setupRedis() *redis.Client { - redisURL := os.Getenv("REDIS_URL") - if redisURL == "" { - redisURL = "redis://redis:6379/0" - } - opt, err := redis.ParseURL(redisURL) + opt, err := redis.ParseURL(config.RedisURL) if err != nil { utils.Log.Fatalf("Redis URL parse error: %v", err) } @@ -55,7 +52,7 @@ func setupRedis() *redis.Client { if err := rdb.Ping(context.Background()).Err(); err != nil { utils.Log.Fatalf("Redis ping failed: %v", err) } - utils.Log.Infof("Redis connected: %s", redisURL) + utils.Log.Infof("Redis connected: %s", config.RedisURL) return rdb } @@ -70,6 +67,22 @@ func setupFiberApp() *fiber.App { app.Use(cors.New()) app.Use(middleware.RecoverConfig()) + origins := "*" + if len(config.CORSAllowOrigins) > 0 { + origins = strings.Join(config.CORSAllowOrigins, ",") + } + if config.CORSAllowCredentials && (origins == "" || origins == "*") { + origins = "http://localhost:3000" + } + app.Use(cors.New(cors.Config{ + AllowOrigins: origins, + AllowMethods: strings.Join(config.CORSAllowMethods, ","), + AllowHeaders: strings.Join(config.CORSAllowHeaders, ","), + ExposeHeaders: strings.Join(config.CORSExposeHeaders, ","), + AllowCredentials: config.CORSAllowCredentials, + MaxAge: config.CORSMaxAge, + })) + return app } @@ -86,7 +99,7 @@ func setupRoutes(app *fiber.App, db *gorm.DB, rdb *redis.Client) { return c.Status(fiber.StatusOK).JSON(fiber.Map{ "status": "ok", "service": "api", - "version": os.Getenv("VERSION"), + "version": config.Version, }) }) diff --git a/docker-compose.dev.yml b/docker-compose.local.yml similarity index 73% rename from docker-compose.dev.yml rename to docker-compose.local.yml index c3899882..c37c633f 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.local.yml @@ -13,7 +13,11 @@ services: - ./internal/database/init:/docker-entrypoint-initdb.d networks: [go-network] healthcheck: - test: ["CMD-SHELL", "pg_isready -U ${DB_USER:-postgres} -d ${DB_NAME:-db_lti_erp}"] + test: + [ + "CMD-SHELL", + "pg_isready -U ${DB_USER:-postgres} -d ${DB_NAME:-db_lti_erp}", + ] interval: 10s timeout: 5s retries: 5 @@ -29,29 +33,16 @@ services: retries: 10 networks: [go-network] - mailhog: - image: mailhog/mailhog:latest - restart: unless-stopped - ports: - - "1025:1025" # SMTP - - "8025:8025" # Web UI - healthcheck: - test: ["CMD-SHELL", "wget -qO- http://localhost:8025/ || exit 1"] - interval: 10s - timeout: 3s - retries: 5 - networks: [go-network] - app: build: context: . - dockerfile: Dockerfile.dev + dockerfile: Dockerfile.local image: cosmtrek/air:v1.52.3 working_dir: /lti-api volumes: - .:/lti-api - - ./internal/config/jwtRS256.key:/run/keys/jwtRS256.key:ro - - ./internal/config/jwtRS256.key.pub:/run/keys/jwtRS256.key.pub:ro + - ./internal/config/jwtRS256.key:/run/keys/jwtRS256.key + - ./internal/config/jwtRS256.key.pub:/run/keys/jwtRS256.key.pub command: air -c .air.toml env_file: - .env @@ -62,8 +53,6 @@ services: DB_PASSWORD: ${DB_PASSWORD:-postgres} DB_NAME: ${DB_NAME:-db_lti_erp} REDIS_URL: ${REDIS_URL:-redis://redis:6379/0} - SMTP_HOST: ${SMTP_HOST:-mailhog} - SMTP_PORT: ${SMTP_PORT:-1025} ports: - "${APP_PORT:-8080}:8080" depends_on: diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml deleted file mode 100644 index 94a284bc..00000000 --- a/docker-compose.prod.yml +++ /dev/null @@ -1,50 +0,0 @@ -services: - postgresdb: - image: postgres:alpine - restart: always - ports: - - "${DB_PORT:-5432}:5432" - environment: - POSTGRES_USER: ${DB_USER:-postgres} - POSTGRES_PASSWORD: ${DB_PASSWORD:-postgres} - POSTGRES_DB: ${DB_NAME:-db_lti_erp} - volumes: - - dbdata:/var/lib/postgresql/data - - ./internal/database/init:/docker-entrypoint-initdb.d - networks: [go-network] - healthcheck: - test: ["CMD-SHELL", "pg_isready -U ${DB_USER:-postgres} -d ${DB_NAME:-db_lti_erp}"] - interval: 10s - timeout: 5s - retries: 5 - - app: - build: - context: . - dockerfile: Dockerfile.prod - image: lti-api-app - working_dir: /lti-api - command: ./app # asumsi Dockerfile.prod menghasilkan binary bernama "app" - env_file: - - .env - environment: - DB_HOST: postgresdb - DB_PORT: 5432 - DB_USER: ${DB_USER:-postgres} - DB_PASSWORD: ${DB_PASSWORD:-postgres} - DB_NAME: ${DB_NAME:-db_lti_erp} - ports: - - "${APP_PORT:-8080}:8080" - depends_on: - postgresdb: - condition: service_healthy - restart: on-failure - networks: [go-network] - -volumes: - dbdata: - -networks: - go-network: - name: lti-api_go-network - driver: bridge diff --git a/go.mod b/go.mod index 0883015c..62841660 100644 --- a/go.mod +++ b/go.mod @@ -12,13 +12,11 @@ require ( github.com/sirupsen/logrus v1.9.3 github.com/spf13/viper v1.19.0 golang.org/x/crypto v0.33.0 - golang.org/x/oauth2 v0.22.0 gorm.io/driver/postgres v1.5.9 gorm.io/gorm v1.25.11 ) require ( - cloud.google.com/go/compute/metadata v0.3.0 // indirect github.com/MicahParks/keyfunc/v2 v2.1.0 // indirect github.com/andybalholm/brotli v1.1.0 // indirect github.com/bytedance/sonic/loader v0.2.0 // indirect diff --git a/go.sum b/go.sum index a7c25f83..5100b618 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,3 @@ -cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc= -cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= github.com/MicahParks/keyfunc/v2 v2.1.0 h1:6ZXKb9Rp6qp1bDbJefnG7cTH8yMN1IC/4nf+GVjO99k= github.com/MicahParks/keyfunc/v2 v2.1.0/go.mod h1:rW42fi+xgLJ2FRRXAfNx9ZA8WpD4OeE/yHVMteCkw9k= github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= @@ -160,8 +158,6 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= -golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA= -golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= diff --git a/internal/config/config.go b/internal/config/config.go index 65851394..2cd4987e 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -1,7 +1,8 @@ package config import ( - "fmt" + "encoding/json" + "strings" "gitlab.com/mbugroup/lti-api.git/internal/utils" @@ -9,32 +10,28 @@ import ( ) var ( - IsProd bool - AppHost string - Version string - LogLevel string - AppPort int - DBHost string - DBUser string - DBPassword string - DBName string - DBPort int - JWTSecret string - JWTAccessExp int - JWTRefreshExp int - JWTResetPasswordExp int - JWTVerifyEmailExp int - PostgresDSN string - RedisURL string - Issuer string - SMTPHost string - SMTPPort int - SMTPUsername string - SMTPPassword string - EmailFrom string - GoogleClientID string - GoogleClientSecret string - RedirectURL string + IsProd bool + AppHost string + Version string + LogLevel string + AppPort int + DBHost string + DBUser string + DBPassword string + DBName string + DBPort int + JWTSecret string + JWTAccessExp int + JWTRefreshExp int + JWTResetPasswordExp int + JWTVerifyEmailExp int + RedisURL string + CORSAllowOrigins []string + CORSAllowMethods []string + CORSAllowHeaders []string + CORSExposeHeaders []string + CORSAllowCredentials bool + CORSMaxAge int ) func init() { @@ -42,16 +39,8 @@ func init() { // server configuration IsProd = viper.GetString("APP_ENV") == "prod" - // AppHost = viper.GetString("APP_HOST") - // AppPort = viper.GetInt("APP_PORT") AppHost = viper.GetString("APP_HOST") - if AppHost == "" { - AppHost = "0.0.0.0" - } AppPort = viper.GetInt("APP_PORT") - if AppPort == 0 { - AppPort = 8080 - } Version = viper.GetString("VERSION") LogLevel = viper.GetString("LOG_LEVEL") @@ -61,13 +50,6 @@ func init() { DBPassword = viper.GetString("DB_PASSWORD") DBName = viper.GetString("DB_NAME") DBPort = viper.GetInt("DB_PORT") - PostgresDSN = viper.GetString("POSTGRES_DSN") - if PostgresDSN == "" { - PostgresDSN = fmt.Sprintf( - "postgres://%s:%s@%s:%d/%s?sslmode=disable", - DBUser, DBPassword, DBHost, DBPort, DBName, - ) - } // jwt configuration JWTSecret = viper.GetString("JWT_SECRET") @@ -76,27 +58,16 @@ func init() { JWTResetPasswordExp = viper.GetInt("JWT_RESET_PASSWORD_EXP_MINUTES") JWTVerifyEmailExp = viper.GetInt("JWT_VERIFY_EMAIL_EXP_MINUTES") - // Redis / OIDC - RedisURL = viper.GetString("REDIS_URL") - if RedisURL == "" { - RedisURL = "redis://redis:6379/0" - } - Issuer = viper.GetString("ISSUER") - if Issuer == "" { - // fallback ke SSO_ISSUER jika kamu sudah pakai itu sebelumnya - Issuer = viper.GetString("SSO_ISSUER") - } - // SMTP configuration - SMTPHost = viper.GetString("SMTP_HOST") - SMTPPort = viper.GetInt("SMTP_PORT") - SMTPUsername = viper.GetString("SMTP_USERNAME") - SMTPPassword = viper.GetString("SMTP_PASSWORD") - EmailFrom = viper.GetString("EMAIL_FROM") + //Cors + CORSAllowOrigins = parseList("CORS_ALLOW_ORIGINS") + CORSAllowMethods = parseListWithDefault("CORS_ALLOW_METHODS", "GET,POST,PUT,PATCH,DELETE,OPTIONS") + CORSAllowHeaders = parseListWithDefault("CORS_ALLOW_HEADERS", "Content-Type,Authorization,X-Requested-With") + CORSExposeHeaders = parseList("CORS_EXPOSE_HEADERS") + CORSAllowCredentials = viper.GetBool("CORS_ALLOW_CREDENTIALS") + CORSMaxAge = viper.GetInt("CORS_MAX_AGE") - // oauth2 configuration - GoogleClientID = viper.GetString("GOOGLE_CLIENT_ID") - GoogleClientSecret = viper.GetString("GOOGLE_CLIENT_SECRET") - RedirectURL = viper.GetString("REDIRECT_URL") + // Redis + RedisURL = viper.GetString("REDIS_URL") } func loadConfig() { @@ -109,3 +80,40 @@ func loadConfig() { utils.Log.Warn("No .env file found, using environment variables only") } } + +func parseList(key string) []string { + raw := strings.TrimSpace(viper.GetString(key)) + if raw == "" { + return nil + } + if strings.HasPrefix(raw, "[") { + var arr []string + if json.Unmarshal([]byte(raw), &arr) == nil { + for i := range arr { + arr[i] = strings.TrimSpace(arr[i]) + } + return arr + } + } + parts := strings.Split(raw, ",") + out := make([]string, 0, len(parts)) + for _, p := range parts { + p = strings.TrimSpace(p) + if p != "" { + out = append(out, p) + } + } + return out +} + +func parseListWithDefault(key, def string) []string { + if v := parseList(key); len(v) > 0 { + return v + } + // fallback ke default CSV + parts := strings.Split(def, ",") + for i := range parts { + parts[i] = strings.TrimSpace(parts[i]) + } + return parts +} diff --git a/internal/config/jwtRS256.key.pub b/internal/config/jwtRS256.key.pub new file mode 100644 index 00000000..e804862e --- /dev/null +++ b/internal/config/jwtRS256.key.pub @@ -0,0 +1,14 @@ +-----BEGIN PUBLIC KEY----- +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEArf9cLsf3m4TituVqDwvM +yaUwQ0rzDfOcmF/N+rHvgMMv1yyR4FcozoGk1NFfL/4jDIVm9FLUS68foPDo0iu5 +shNY0pwSsps9lcyWxQVhUVJzh489S53hU799PiDrUPBxYTcpy3EO/jX0HOZJs5dl +N/4C54LYrVdXyleG82NLNjcMnNGr3VGc6zE7B3YYd9/daPyr+QBpeUL5BIzUZbeu +sI0NMIxucaqxMKWF62CDWTrwfSSoFOubI9FZ9tkkWro01wVFK35GseQCsDtEmJ9v +kb81LvfM2AcPLr+g1kN8dVeZLNNQTMrmxaWXFiwwEgayJ8q01pHfgAxg42ariKEK +fX9kFx/3Rs80qsXhQNEkoCOwQBRNwrRxRzNfVkvuE0aRVoO6PVFE1gDOLUV2fJJs +QUpAWMzZ/+e/N+1gKMtbaCbz2dLqnA6KkdMdHe79dMFVGx2ZnRFbyALzM3S5XgNV +QtVvTri2PW/6ZH41T6MpLUANzuwaIEys1Az+8VLxOgBugb63xoORB2JDsebxEfsS +HBllECnBJVuBndkJRSnbqGjCKq4sl2xXo83nZ+2eNmZO/vkTxREl8aVp3DgaHWxp +OQIlZwbP9lsruTqSnQfH3/hLemrOhSh/hXfFguw3oOQjfeFwJBD8u7vGOl2vBi3C +hvb8hFdjzoUXAJLxWPl5+E0CAwEAAQ== +-----END PUBLIC KEY----- diff --git a/internal/config/oauth2.go b/internal/config/oauth2.go deleted file mode 100644 index 9e982113..00000000 --- a/internal/config/oauth2.go +++ /dev/null @@ -1,27 +0,0 @@ -package config - -import ( - "golang.org/x/oauth2" - "golang.org/x/oauth2/google" -) - -type Config struct { - GoogleLoginConfig oauth2.Config -} - -var AppConfig Config - -func GoogleConfig() oauth2.Config { - AppConfig.GoogleLoginConfig = oauth2.Config{ - RedirectURL: RedirectURL, - ClientID: GoogleClientID, - ClientSecret: GoogleClientSecret, - Scopes: []string{ - "https://www.googleapis.com/auth/userinfo.email", - "https://www.googleapis.com/auth/userinfo.profile", - }, - Endpoint: google.Endpoint, - } - - return AppConfig.GoogleLoginConfig -} diff --git a/internal/database/migrations/20250925040409_create_lti_tables.down.sql b/internal/database/migrations/20250925040409_create_lti_tables.down.sql deleted file mode 100644 index c99ddcdc..00000000 --- a/internal/database/migrations/20250925040409_create_lti_tables.down.sql +++ /dev/null @@ -1 +0,0 @@ -DROP TABLE IF EXISTS users; diff --git a/internal/database/migrations/20250925040409_create_lti_tables.up.sql b/internal/database/migrations/20250925040409_create_lti_tables.up.sql deleted file mode 100644 index 63397098..00000000 --- a/internal/database/migrations/20250925040409_create_lti_tables.up.sql +++ /dev/null @@ -1,8 +0,0 @@ --- Users -CREATE TABLE IF NOT EXISTS users ( - id BIGSERIAL PRIMARY KEY, - name VARCHAR, - created_at TIMESTAMPTZ NOT NULL DEFAULT now(), - updated_at TIMESTAMPTZ NOT NULL DEFAULT now(), - deleted_at TIMESTAMPTZ -); diff --git a/internal/database/migrations/20250925040409_create_master_tables.down.sql b/internal/database/migrations/20250925040409_create_master_tables.down.sql new file mode 100644 index 00000000..df78e492 --- /dev/null +++ b/internal/database/migrations/20250925040409_create_master_tables.down.sql @@ -0,0 +1,22 @@ + +DROP TABLE IF EXISTS fcr_standards; +DROP INDEX IF EXISTS products_sku_unique; +DROP TABLE IF EXISTS products; +DROP TABLE IF EXISTS flags; +DROP INDEX IF EXISTS customers_email_unique; +DROP TABLE IF EXISTS customers; +DROP INDEX IF EXISTS product_categories_code_unique; +DROP TABLE IF EXISTS product_categories; +DROP TABLE IF EXISTS nonstocks; +DROP TABLE IF EXISTS banks; +DROP TABLE IF EXISTS warehouses; +DROP TABLE IF EXISTS kandangs; +DROP TABLE IF EXISTS locations; +DROP TABLE IF EXISTS areas; +DROP TABLE IF EXISTS uom; +DROP TABLE IF EXISTS suppliers; +DROP TABLE IF EXISTS fcr; +DROP TABLE IF EXISTS projects; +DROP INDEX IF EXISTS users_id_user_unique; +DROP INDEX IF EXISTS users_email_unique; +DROP TABLE IF EXISTS users; diff --git a/internal/database/migrations/20250925040409_create_master_tables.up.sql b/internal/database/migrations/20250925040409_create_master_tables.up.sql new file mode 100644 index 00000000..2f6856b4 --- /dev/null +++ b/internal/database/migrations/20250925040409_create_master_tables.up.sql @@ -0,0 +1,204 @@ +-- USERS +CREATE TABLE users ( + id BIGSERIAL PRIMARY KEY, + id_user BIGINT NOT NULL, + name VARCHAR NOT NULL, + email VARCHAR NOT NULL, + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW(), + deleted_at TIMESTAMPTZ +); + +CREATE UNIQUE INDEX users_id_user_unique ON users (id_user) WHERE deleted_at IS NULL; +CREATE UNIQUE INDEX users_email_unique ON users (email) WHERE deleted_at IS NULL; + +-- FLAGS +CREATE TABLE flags ( + id BIGSERIAL PRIMARY KEY, + name VARCHAR NOT NULL, + flagable_id BIGINT NOT NULL, + flagable_type VARCHAR(50) NOT NULL, + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW(), + deleted_at TIMESTAMPTZ +); + +-- PRODUCT CATEGORIES +CREATE TABLE product_categories ( + id BIGSERIAL PRIMARY KEY, + name VARCHAR NOT NULL, + code VARCHAR(3) NOT NULL, + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW(), + deleted_at TIMESTAMPTZ, + created_by BIGINT NOT NULL REFERENCES users(id) +); +CREATE UNIQUE INDEX product_categories_code_unique ON product_categories (code) WHERE deleted_at IS NULL; + +-- UOM +CREATE TABLE uom ( + id BIGSERIAL PRIMARY KEY, + name VARCHAR NOT NULL, + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW(), + deleted_at TIMESTAMPTZ, + created_by BIGINT NOT NULL REFERENCES users(id) +); + +-- PRODUCTS +CREATE TABLE products ( + id BIGSERIAL PRIMARY KEY, + name VARCHAR NOT NULL, + brand VARCHAR NOT NULL, + sku VARCHAR(100), + uom_id BIGINT NOT NULL REFERENCES uom(id), + product_category_id BIGINT NOT NULL REFERENCES product_categories(id), + product_price NUMERIC(15,2) NOT NULL, + selling_price NUMERIC(15,2), + tax NUMERIC(15,2), + expiry_period INT, + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW(), + deleted_at TIMESTAMPTZ, + created_by BIGINT NOT NULL REFERENCES users(id) +); +CREATE UNIQUE INDEX products_sku_unique ON products (sku) WHERE deleted_at IS NULL; + +-- BANKS +CREATE TABLE banks ( + id BIGSERIAL PRIMARY KEY, + name VARCHAR NOT NULL, + alias VARCHAR(5) NOT NULL, + owner VARCHAR, + account_number VARCHAR(50) NOT NULL, + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW(), + deleted_at TIMESTAMPTZ, + created_by BIGINT NOT NULL REFERENCES users(id) +); + +-- AREAS +CREATE TABLE areas ( + id BIGSERIAL PRIMARY KEY, + name VARCHAR NOT NULL, + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW(), + deleted_at TIMESTAMPTZ, + created_by BIGINT NOT NULL REFERENCES users(id) +); + +-- LOCATIONS +CREATE TABLE locations ( + id BIGSERIAL PRIMARY KEY, + name VARCHAR NOT NULL, + address TEXT NOT NULL, + area_id BIGINT NOT NULL REFERENCES areas(id), + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW(), + deleted_at TIMESTAMPTZ, + created_by BIGINT NOT NULL REFERENCES users(id) +); + +-- KANDANG +CREATE TABLE kandangs ( + id BIGSERIAL PRIMARY KEY, + name VARCHAR(191) NOT NULL, + location_id BIGINT NOT NULL REFERENCES locations(id), + pic_id BIGINT NOT NULL REFERENCES users(id), + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW(), + deleted_at TIMESTAMPTZ, + created_by BIGINT NOT NULL REFERENCES users(id) +); + +-- WAREHOUSES +CREATE TABLE warehouses ( + id BIGSERIAL PRIMARY KEY, + name VARCHAR NOT NULL, + type VARCHAR(50) NOT NULL, + location_id BIGINT NOT NULL REFERENCES locations(id), + kandang_id BIGINT REFERENCES kandangs(id), + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW(), + deleted_at TIMESTAMPTZ, + created_by BIGINT NOT NULL REFERENCES users(id) +); + +-- CUSTOMERS +CREATE TABLE customers ( + id BIGSERIAL PRIMARY KEY, + name VARCHAR NOT NULL, + pic_id BIGINT REFERENCES users(id), + type VARCHAR(50) NOT NULL, + address TEXT NOT NULL, + phone VARCHAR(20) NOT NULL, + email VARCHAR NOT NULL, + account_number VARCHAR(50) NOT NULL, + balance NUMERIC(15,2) DEFAULT 0, + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW(), + deleted_at TIMESTAMPTZ, + created_by BIGINT NOT NULL REFERENCES users(id) +); + +-- NONSTOCK +CREATE TABLE nonstocks ( + id BIGSERIAL PRIMARY KEY, + name VARCHAR NOT NULL, + uom_id BIGINT NOT NULL REFERENCES uom(id), + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW(), + deleted_at TIMESTAMPTZ, + created_by BIGINT NOT NULL REFERENCES users(id) +); + +-- FCR +CREATE TABLE fcr ( + id BIGSERIAL PRIMARY KEY, + name VARCHAR NOT NULL, + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW(), + deleted_at TIMESTAMPTZ, + created_by BIGINT NOT NULL REFERENCES users(id) +); + +CREATE TABLE fcr_standards ( + id BIGSERIAL PRIMARY KEY, + fcr_id BIGINT NOT NULL REFERENCES fcr(id) ON DELETE CASCADE ON UPDATE CASCADE, + weight NUMERIC(15,2) NOT NULL, + fcr_number NUMERIC(15,2) NOT NULL, + mortality NUMERIC(15,2) NOT NULL, + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW(), + deleted_at TIMESTAMPTZ +); + +-- SUPPLIERS +CREATE TABLE suppliers ( + id BIGSERIAL PRIMARY KEY, + name VARCHAR NOT NULL, + alias VARCHAR(5) NOT NULL, + pic VARCHAR NOT NULL, + type VARCHAR(50) NOT NULL, + hatchery VARCHAR, + phone VARCHAR(20) NOT NULL, + email VARCHAR NOT NULL, + address TEXT NOT NULL, + npwp VARCHAR(50), + account_number VARCHAR(50), + balance NUMERIC(15,2) DEFAULT 0, + due_date INT NOT NULL, + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW(), + deleted_at TIMESTAMPTZ, + created_by BIGINT NOT NULL REFERENCES users(id) +); + +-- PROJECTS +CREATE TABLE projects ( + id BIGSERIAL PRIMARY KEY, + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW(), + deleted_at TIMESTAMPTZ, + created_by BIGINT NOT NULL REFERENCES users(id) +); diff --git a/internal/database/seed/seeder.go b/internal/database/seed/seeder.go index 37ce6e84..b570cccd 100644 --- a/internal/database/seed/seeder.go +++ b/internal/database/seed/seeder.go @@ -10,17 +10,13 @@ import ( func Run(db *gorm.DB) error { return db.Transaction(func(tx *gorm.DB) error { - // pw, err := secure.Hash("asdasdasd", nil) - - // if err != nil { - // return err - // } - // ===== Users (user) ===== user := mUser.User{ - Name: "Super Admin", + Email: "admin@mbugroup.id", + IdUser: 1, + Name: "Super Admin", } - if err := tx.Where("email = ?", user.Id).FirstOrCreate(&user).Error; err != nil { + if err := tx.Where("email = ?", user.Email).FirstOrCreate(&user).Error; err != nil { return err } diff --git a/internal/modules/users/dto/user.dto.go b/internal/modules/users/dto/user.dto.go index d06fb90c..3fc030b1 100644 --- a/internal/modules/users/dto/user.dto.go +++ b/internal/modules/users/dto/user.dto.go @@ -8,9 +8,15 @@ import ( // === DTO Structs === +type UserBaseDTO struct { + Id uint `json:"id"` + IdUser int64 `json:"id_user"` + Email string `json:"email"` + Name string `json:"name"` +} + type UserListDTO struct { - Id uint `json:"id"` - Name string `json:"name"` + UserBaseDTO CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } @@ -21,13 +27,21 @@ type UserDetailDTO struct { // === Mapper Functions === -func ToUserListDTO(m model.User) UserListDTO { - return UserListDTO{ +func ToUserBaseDTO(m model.User) UserBaseDTO { + return UserBaseDTO{ Id: m.Id, Name: m.Name, } } +func ToUserListDTO(m model.User) UserListDTO { + return UserListDTO{ + UserBaseDTO: ToUserBaseDTO(m), + CreatedAt: m.CreatedAt, + UpdatedAt: m.UpdatedAt, + } +} + func ToUserListDTOs(m []model.User) []UserListDTO { result := make([]UserListDTO, len(m)) for i, r := range m { diff --git a/internal/modules/users/models/user.model.go b/internal/modules/users/models/user.model.go index 6c37887c..17bf4080 100644 --- a/internal/modules/users/models/user.model.go +++ b/internal/modules/users/models/user.model.go @@ -8,6 +8,8 @@ import ( type User struct { Id uint `gorm:"primaryKey"` + IdUser int64 `gorm:"uniqueIndex"` + Email string `gorm:"uniqueIndex"` Name string `gorm:"not null"` CreatedAt time.Time UpdatedAt time.Time diff --git a/internal/modules/users/repositories/user.repository.go b/internal/modules/users/repositories/user.repository.go index c7001a73..7e5cef9c 100644 --- a/internal/modules/users/repositories/user.repository.go +++ b/internal/modules/users/repositories/user.repository.go @@ -3,7 +3,6 @@ package repository import ( model "gitlab.com/mbugroup/lti-api.git/internal/modules/users/models" "gitlab.com/mbugroup/lti-api.git/internal/repository" - "gorm.io/gorm" ) diff --git a/internal/modules/users/services/user.service.go b/internal/modules/users/services/user.service.go index 24b3ebc6..b1c5781d 100644 --- a/internal/modules/users/services/user.service.go +++ b/internal/modules/users/services/user.service.go @@ -74,7 +74,7 @@ func (s *userService) CreateOne(c *fiber.Ctx, req *validation.Create) (*model.Us } createBody := &model.User{ - Name: req.Name, + Name: req.Name, } if err := s.Repository.CreateOne(c.Context(), createBody, nil); err != nil { diff --git a/internal/route/route.go b/internal/route/route.go index bc1125d3..a3b12c0e 100644 --- a/internal/route/route.go +++ b/internal/route/route.go @@ -15,8 +15,6 @@ func Routes(app *fiber.App, db *gorm.DB) { validate := validation.Validator() api := app.Group("/api") - // masterRoute.Routes(api, db) - // root modules di sini allModules := []modules.Module{ users.UserModule{}, diff --git a/tools/templates/dto.tmpl b/tools/templates/dto.tmpl index 62550e6d..a3cdf89e 100644 --- a/tools/templates/dto.tmpl +++ b/tools/templates/dto.tmpl @@ -8,9 +8,13 @@ import ( // === DTO Structs === -type {{Pascal .Entity}}ListDTO struct { +type {{Pascal .Entity}}BaseDTO struct { Id uint `json:"id"` Name string `json:"name"` +} + +type {{Pascal .Entity}}ListDTO struct { + {{Pascal .Entity}}BaseDTO CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } @@ -21,10 +25,18 @@ type {{Pascal .Entity}}DetailDTO struct { // === Mapper Functions === +func To{{Pascal .Entity}}BaseDTO(m model.{{Pascal .Entity}}) {{Pascal .Entity}}BaseDTO { + return {{Pascal .Entity}}BaseDTO{ + Id: m.Id, + Name: m.Name, + } +} + func To{{Pascal .Entity}}ListDTO(m model.{{Pascal .Entity}}) {{Pascal .Entity}}ListDTO { return {{Pascal .Entity}}ListDTO{ - Id: m.Id, - Name: m.Name, + {{Pascal .Entity}}BaseDTO: To{{Pascal .Entity}}BaseDTO(m), + CreatedAt: m.CreatedAt, + UpdatedAt: m.UpdatedAt, } }