diff --git a/cmd/api-key/main.go b/cmd/api-key/main.go new file mode 100644 index 00000000..77b21748 --- /dev/null +++ b/cmd/api-key/main.go @@ -0,0 +1,132 @@ +package main + +import ( + "context" + "flag" + "fmt" + "os" + "strings" + + "gitlab.com/mbugroup/lti-api.git/internal/apikeys" + "gitlab.com/mbugroup/lti-api.git/internal/config" + "gitlab.com/mbugroup/lti-api.git/internal/database" +) + +func main() { + if len(os.Args) < 2 { + usage() + os.Exit(1) + } + + db := database.Connect(config.DBHost, config.DBName) + service := apikeys.NewService(db) + ctx := context.Background() + + switch os.Args[1] { + case "create": + fs := flag.NewFlagSet("create", flag.ExitOnError) + name := fs.String("name", "dashboard-read-api", "integration client name") + environment := fs.String("env", config.AppEnv, "environment label") + permissions := fs.String("permissions", "", "comma separated permission codes") + allArea := fs.Bool("all-area", true, "grant all areas") + areaIDs := fs.String("area-ids", "", "comma separated area ids") + allLocation := fs.Bool("all-location", true, "grant all locations") + locationIDs := fs.String("location-ids", "", "comma separated location ids") + fs.Parse(os.Args[2:]) + + permissionCodes := apikeys.DefaultDashboardPermissions() + if strings.TrimSpace(*permissions) != "" { + permissionCodes = splitCSV(*permissions) + } + + issued, err := service.Create(ctx, apikeys.CreateInput{ + Name: *name, + Environment: *environment, + PermissionCodes: permissionCodes, + AllArea: *allArea, + AreaIDs: parseUintCSV(*areaIDs), + AllLocation: *allLocation, + LocationIDs: parseUintCSV(*locationIDs), + }) + if err != nil { + panic(err) + } + + fmt.Printf("name: %s\n", issued.Record.Name) + fmt.Printf("environment: %s\n", issued.Record.Environment) + fmt.Printf("prefix: %s\n", issued.Record.KeyPrefix) + fmt.Printf("status: %s\n", issued.Record.Status) + fmt.Printf("api_key: %s\n", issued.Key) + case "list": + fs := flag.NewFlagSet("list", flag.ExitOnError) + environment := fs.String("env", "", "filter by environment") + fs.Parse(os.Args[2:]) + + records, err := service.List(ctx, *environment) + if err != nil { + panic(err) + } + + for _, record := range records { + fmt.Printf("%s\t%s\t%s\t%s\tareas=%t\tlocations=%t\n", + record.Environment, + record.KeyPrefix, + record.Status, + record.Name, + record.AllArea, + record.AllLocation, + ) + } + case "revoke": + fs := flag.NewFlagSet("revoke", flag.ExitOnError) + environment := fs.String("env", config.AppEnv, "environment label") + prefix := fs.String("prefix", "", "key prefix to revoke") + fs.Parse(os.Args[2:]) + + if err := service.Revoke(ctx, *environment, *prefix); err != nil { + panic(err) + } + fmt.Printf("revoked %s/%s\n", *environment, *prefix) + default: + usage() + os.Exit(1) + } +} + +func usage() { + fmt.Println("usage:") + fmt.Println(" go run ./cmd/api-key create [flags]") + fmt.Println(" go run ./cmd/api-key list [flags]") + fmt.Println(" go run ./cmd/api-key revoke -env -prefix ") +} + +func splitCSV(raw string) []string { + if strings.TrimSpace(raw) == "" { + return nil + } + parts := strings.Split(raw, ",") + out := make([]string, 0, len(parts)) + for _, part := range parts { + part = strings.TrimSpace(part) + if part != "" { + out = append(out, part) + } + } + return out +} + +func parseUintCSV(raw string) []uint { + parts := splitCSV(raw) + if len(parts) == 0 { + return nil + } + + values := make([]uint, 0, len(parts)) + for _, part := range parts { + var value uint + if _, err := fmt.Sscanf(part, "%d", &value); err == nil && value > 0 { + values = append(values, value) + } + } + return values +} diff --git a/cmd/api/main.go b/cmd/api/main.go index 76de7729..9b2f6d9b 100644 --- a/cmd/api/main.go +++ b/cmd/api/main.go @@ -9,12 +9,14 @@ import ( "syscall" "time" + "gitlab.com/mbugroup/lti-api.git/internal/apikeys" "gitlab.com/mbugroup/lti-api.git/internal/cache" "gitlab.com/mbugroup/lti-api.git/internal/config" "gitlab.com/mbugroup/lti-api.git/internal/database" "gitlab.com/mbugroup/lti-api.git/internal/middleware" "gitlab.com/mbugroup/lti-api.git/internal/modules/sso/session" sso "gitlab.com/mbugroup/lti-api.git/internal/modules/sso/verifier" + "gitlab.com/mbugroup/lti-api.git/internal/readapi" "gitlab.com/mbugroup/lti-api.git/internal/route" "gitlab.com/mbugroup/lti-api.git/internal/utils" @@ -131,6 +133,7 @@ func setupDatabase() *gorm.DB { } func setupRoutes(app *fiber.App, db *gorm.DB, rdb *redis.Client) { + middleware.SetAPIKeyAuthenticator(apikeys.NewService(db)) // route.Routes(app, db) // app.Use(utils.NotFoundHandler) @@ -169,6 +172,8 @@ func setupRoutes(app *fiber.App, db *gorm.DB, rdb *redis.Client) { return c.Status(status).JSON(body) }) + readAPIRoutes := app.Group("/api") + readapi.RegisterRoutes(readAPIRoutes) route.Routes(app, db) app.Use(utils.NotFoundHandler) } diff --git a/cmd/generate-read-api/main.go b/cmd/generate-read-api/main.go new file mode 100644 index 00000000..bbce6601 --- /dev/null +++ b/cmd/generate-read-api/main.go @@ -0,0 +1,74 @@ +package main + +import ( + "fmt" + "os" + "path/filepath" + + "gitlab.com/mbugroup/lti-api.git/internal/cache" + "gitlab.com/mbugroup/lti-api.git/internal/config" + "gitlab.com/mbugroup/lti-api.git/internal/readapi" + "gitlab.com/mbugroup/lti-api.git/internal/route" + + "github.com/gofiber/fiber/v2" + "github.com/redis/go-redis/v9" +) + +func main() { + root, err := findRepoRoot() + if err != nil { + panic(err) + } + + readapi.PrimeBuildConfig() + cache.SetRedis(redis.NewClient(&redis.Options{Addr: "127.0.0.1:6379"})) + app := fiber.New(config.FiberConfig()) + app.Get("/healthz", func(c *fiber.Ctx) error { + return c.JSON(fiber.Map{"status": "ok", "service": "api", "version": config.Version}) + }) + app.Get("/readyz", func(c *fiber.Ctx) error { + return c.JSON(fiber.Map{"status": "ok", "db": "up", "redis": "up"}) + }) + route.Routes(app, nil) + + artifacts, err := readapi.BuildArtifactsFromApp(app) + if err != nil { + panic(err) + } + + files := map[string][]byte{ + filepath.Join(root, "docs", "openapi", "read-api.json"): artifacts.OpenAPIJSON, + filepath.Join(root, "docs", "openapi", "read-api.yaml"): artifacts.OpenAPIYAML, + filepath.Join(root, "docs", "postman", "read-api.collection.json"): artifacts.PostmanCollectionJSON, + filepath.Join(root, "docs", "postman", "read-api.environment.json"): artifacts.PostmanEnvironmentJSON, + } + + for path, body := range files { + if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { + panic(err) + } + if err := os.WriteFile(path, body, 0o644); err != nil { + panic(err) + } + fmt.Printf("wrote %s\n", path) + } +} + +func findRepoRoot() (string, error) { + wd, err := os.Getwd() + if err != nil { + return "", err + } + + current := wd + for { + if _, err := os.Stat(filepath.Join(current, "go.mod")); err == nil { + return current, nil + } + parent := filepath.Dir(current) + if parent == current { + return "", fmt.Errorf("go.mod not found from %s", wd) + } + current = parent + } +} diff --git a/docs/openapi/read-api.json b/docs/openapi/read-api.json new file mode 100644 index 00000000..b047d432 --- /dev/null +++ b/docs/openapi/read-api.json @@ -0,0 +1,6850 @@ +{ + "components": { + "schemas": { + "ErrorEnvelope": { + "properties": { + "code": { + "example": 401, + "type": "integer" + }, + "errors": { + "additionalProperties": true, + "type": "object" + }, + "message": { + "example": "Please authenticate", + "type": "string" + }, + "status": { + "example": "error", + "type": "string" + } + }, + "type": "object" + }, + "PaginatedEnvelope": { + "properties": { + "code": { + "example": 200, + "type": "integer" + }, + "data": { + "items": { + "additionalProperties": true, + "type": "object" + }, + "type": "array" + }, + "message": { + "example": "Request completed successfully", + "type": "string" + }, + "meta": { + "properties": { + "limit": { + "example": 10, + "type": "integer" + }, + "page": { + "example": 1, + "type": "integer" + }, + "total_pages": { + "example": 1, + "type": "integer" + }, + "total_results": { + "example": 0, + "type": "integer" + } + }, + "type": "object" + }, + "status": { + "example": "success", + "type": "string" + } + }, + "type": "object" + }, + "SuccessEnvelope": { + "properties": { + "code": { + "example": 200, + "type": "integer" + }, + "data": { + "additionalProperties": true, + "type": "object" + }, + "message": { + "example": "Request completed successfully", + "type": "string" + }, + "status": { + "example": "success", + "type": "string" + } + }, + "type": "object" + } + }, + "securitySchemes": { + "ApiKeyAuth": { + "in": "header", + "name": "X-API-Key", + "type": "apiKey" + }, + "BearerAuth": { + "scheme": "bearer", + "type": "http" + } + } + }, + "info": { + "description": "Read-only OpenAPI surface for dashboard integrations and GET endpoint exploration.", + "title": "LTI ERP Read API", + "version": "v1" + }, + "openapi": "3.1.0", + "paths": { + "/api/approvals/": { + "get": { + "description": "Read access to `/api/approvals/`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / approvals", + "tags": [ + "API" + ] + } + }, + "/api/closings/": { + "get": { + "description": "Read access to `/api/closings/`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / closings", + "tags": [ + "Closings" + ] + } + }, + "/api/closings/{projectFlockId}": { + "get": { + "description": "Read access to `/api/closings/:projectFlockId`.", + "parameters": [ + { + "description": "Path parameter `projectFlockId`.", + "in": "path", + "name": "projectFlockId", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / closings / :projectFlockId", + "tags": [ + "Closings" + ] + } + }, + "/api/closings/{projectFlockId}/keuangan": { + "get": { + "description": "Read access to `/api/closings/:projectFlockId/keuangan`.", + "parameters": [ + { + "description": "Path parameter `projectFlockId`.", + "in": "path", + "name": "projectFlockId", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + }, + { + "description": "Optional kandang id filter.", + "example": 1, + "in": "query", + "name": "kandang_id", + "required": false, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / closings / :projectFlockId / keuangan", + "tags": [ + "Closings" + ] + } + }, + "/api/closings/{projectFlockId}/production-data": { + "get": { + "description": "Read access to `/api/closings/:projectFlockId/production-data`.", + "parameters": [ + { + "description": "Path parameter `projectFlockId`.", + "in": "path", + "name": "projectFlockId", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + }, + { + "description": "Optional kandang id filter.", + "example": 1, + "in": "query", + "name": "kandang_id", + "required": false, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / closings / :projectFlockId / production data", + "tags": [ + "Closings" + ] + } + }, + "/api/closings/{projectFlockId}/sapronak": { + "get": { + "description": "Read access to `/api/closings/:projectFlockId/sapronak`.", + "parameters": [ + { + "description": "Path parameter `projectFlockId`.", + "in": "path", + "name": "projectFlockId", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + }, + { + "description": "Required sapronak direction.", + "example": "incoming", + "in": "query", + "name": "type", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Search keyword.", + "example": "pakan", + "in": "query", + "name": "search", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Optional kandang id filter.", + "example": 1, + "in": "query", + "name": "kandang_id", + "required": false, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / closings / :projectFlockId / sapronak", + "tags": [ + "Closings" + ] + } + }, + "/api/closings/{projectFlockId}/sapronak/summary": { + "get": { + "description": "Read access to `/api/closings/:projectFlockId/sapronak/summary`.", + "parameters": [ + { + "description": "Path parameter `projectFlockId`.", + "in": "path", + "name": "projectFlockId", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + }, + { + "description": "Required sapronak direction.", + "example": "incoming", + "in": "query", + "name": "type", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Search keyword.", + "example": "pakan", + "in": "query", + "name": "search", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Optional kandang id filter.", + "example": 1, + "in": "query", + "name": "kandang_id", + "required": false, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / closings / :projectFlockId / sapronak / summary", + "tags": [ + "Closings" + ] + } + }, + "/api/closings/{project_flock_id}/expedition-hpp": { + "get": { + "description": "Read access to `/api/closings/:project_flock_id/expedition-hpp`.", + "parameters": [ + { + "description": "Path parameter `project_flock_id`.", + "in": "path", + "name": "project_flock_id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + }, + { + "description": "Optional project flock kandang id filter.", + "example": 1, + "in": "query", + "name": "project_flock_kandang_id", + "required": false, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / closings / :project_flock_id / expedition hpp", + "tags": [ + "Closings" + ] + } + }, + "/api/closings/{project_flock_id}/overhead": { + "get": { + "description": "Read access to `/api/closings/:project_flock_id/overhead`.", + "parameters": [ + { + "description": "Path parameter `project_flock_id`.", + "in": "path", + "name": "project_flock_id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / closings / :project_flock_id / overhead", + "tags": [ + "Closings" + ] + } + }, + "/api/closings/{project_flock_id}/penjualan": { + "get": { + "description": "Read access to `/api/closings/:project_flock_id/penjualan`.", + "parameters": [ + { + "description": "Path parameter `project_flock_id`.", + "in": "path", + "name": "project_flock_id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / closings / :project_flock_id / penjualan", + "tags": [ + "Closings" + ] + } + }, + "/api/closings/{project_flock_id}/perhitungan_sapronak": { + "get": { + "description": "Read access to `/api/closings/:project_flock_id/perhitungan_sapronak`.", + "parameters": [ + { + "description": "Path parameter `project_flock_id`.", + "in": "path", + "name": "project_flock_id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / closings / :project_flock_id / perhitungan_sapronak", + "tags": [ + "Closings" + ] + } + }, + "/api/closings/{project_flock_id}/{project_flock_kandang_id}/expedition-hpp": { + "get": { + "description": "Read access to `/api/closings/:project_flock_id/:project_flock_kandang_id/expedition-hpp`.", + "parameters": [ + { + "description": "Path parameter `project_flock_id`.", + "in": "path", + "name": "project_flock_id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + }, + { + "description": "Path parameter `project_flock_kandang_id`.", + "in": "path", + "name": "project_flock_kandang_id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / closings / :project_flock_id / :project_flock_kandang_id / expedition hpp", + "tags": [ + "Closings" + ] + } + }, + "/api/closings/{project_flock_id}/{project_flock_kandang_id}/keuangan": { + "get": { + "description": "Read access to `/api/closings/:project_flock_id/:project_flock_kandang_id/keuangan`.", + "parameters": [ + { + "description": "Path parameter `project_flock_id`.", + "in": "path", + "name": "project_flock_id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + }, + { + "description": "Path parameter `project_flock_kandang_id`.", + "in": "path", + "name": "project_flock_kandang_id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / closings / :project_flock_id / :project_flock_kandang_id / keuangan", + "tags": [ + "Closings" + ] + } + }, + "/api/closings/{project_flock_id}/{project_flock_kandang_id}/overhead": { + "get": { + "description": "Read access to `/api/closings/:project_flock_id/:project_flock_kandang_id/overhead`.", + "parameters": [ + { + "description": "Path parameter `project_flock_id`.", + "in": "path", + "name": "project_flock_id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + }, + { + "description": "Path parameter `project_flock_kandang_id`.", + "in": "path", + "name": "project_flock_kandang_id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / closings / :project_flock_id / :project_flock_kandang_id / overhead", + "tags": [ + "Closings" + ] + } + }, + "/api/closings/{project_flock_id}/{project_flock_kandang_id}/penjualan": { + "get": { + "description": "Read access to `/api/closings/:project_flock_id/:project_flock_kandang_id/penjualan`.", + "parameters": [ + { + "description": "Path parameter `project_flock_id`.", + "in": "path", + "name": "project_flock_id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + }, + { + "description": "Path parameter `project_flock_kandang_id`.", + "in": "path", + "name": "project_flock_kandang_id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / closings / :project_flock_id / :project_flock_kandang_id / penjualan", + "tags": [ + "Closings" + ] + } + }, + "/api/closings/{project_flock_id}/{project_flock_kandang_id}/perhitungan_sapronak": { + "get": { + "description": "Read access to `/api/closings/:project_flock_id/:project_flock_kandang_id/perhitungan_sapronak`.", + "parameters": [ + { + "description": "Path parameter `project_flock_id`.", + "in": "path", + "name": "project_flock_id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + }, + { + "description": "Path parameter `project_flock_kandang_id`.", + "in": "path", + "name": "project_flock_kandang_id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / closings / :project_flock_id / :project_flock_kandang_id / perhitungan_sapronak", + "tags": [ + "Closings" + ] + } + }, + "/api/constants/": { + "get": { + "description": "Read access to `/api/constants/`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / constants", + "tags": [ + "API" + ] + } + }, + "/api/daily-checklists/": { + "get": { + "description": "Read access to `/api/daily-checklists/`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / daily checklists", + "tags": [ + "Daily Checklists" + ] + } + }, + "/api/daily-checklists/phase/{idDailyChecklist}": { + "get": { + "description": "Read access to `/api/daily-checklists/phase/:idDailyChecklist`.", + "parameters": [ + { + "description": "Path parameter `idDailyChecklist`.", + "in": "path", + "name": "idDailyChecklist", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / daily checklists / phase / :idDailyChecklist", + "tags": [ + "Daily Checklists" + ] + } + }, + "/api/daily-checklists/relation/{idDailyChecklist}": { + "get": { + "description": "Read access to `/api/daily-checklists/relation/:idDailyChecklist`.", + "parameters": [ + { + "description": "Path parameter `idDailyChecklist`.", + "in": "path", + "name": "idDailyChecklist", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / daily checklists / relation / :idDailyChecklist", + "tags": [ + "Daily Checklists" + ] + } + }, + "/api/daily-checklists/report": { + "get": { + "description": "Read access to `/api/daily-checklists/report`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / daily checklists / report", + "tags": [ + "Daily Checklists" + ] + } + }, + "/api/daily-checklists/summary": { + "get": { + "description": "Read access to `/api/daily-checklists/summary`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / daily checklists / summary", + "tags": [ + "Daily Checklists" + ] + } + }, + "/api/daily-checklists/tasks": { + "get": { + "description": "Read access to `/api/daily-checklists/tasks`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / daily checklists / tasks", + "tags": [ + "Daily Checklists" + ] + } + }, + "/api/dashboards/": { + "get": { + "description": "Read access to `/api/dashboards/`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / dashboards", + "tags": [ + "Dashboards" + ] + } + }, + "/api/expenses/": { + "get": { + "description": "Read access to `/api/expenses/`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / expenses", + "tags": [ + "Expenses" + ] + } + }, + "/api/expenses/{id}": { + "get": { + "description": "Read access to `/api/expenses/:id`.", + "parameters": [ + { + "description": "Path parameter `id`.", + "in": "path", + "name": "id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / expenses / :id", + "tags": [ + "Expenses" + ] + } + }, + "/api/finance/initial-balances/{id}": { + "get": { + "description": "Read access to `/api/finance/initial-balances/:id`.", + "parameters": [ + { + "description": "Path parameter `id`.", + "in": "path", + "name": "id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / finance / initial balances / :id", + "tags": [ + "Finance" + ] + } + }, + "/api/finance/injections/{id}": { + "get": { + "description": "Read access to `/api/finance/injections/:id`.", + "parameters": [ + { + "description": "Path parameter `id`.", + "in": "path", + "name": "id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / finance / injections / :id", + "tags": [ + "Finance" + ] + } + }, + "/api/finance/payments/{id}": { + "get": { + "description": "Read access to `/api/finance/payments/:id`.", + "parameters": [ + { + "description": "Path parameter `id`.", + "in": "path", + "name": "id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / finance / payments / :id", + "tags": [ + "Finance" + ] + } + }, + "/api/finance/transactions/": { + "get": { + "description": "Read access to `/api/finance/transactions/`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / finance / transactions", + "tags": [ + "Finance" + ] + } + }, + "/api/finance/transactions/{id}": { + "get": { + "description": "Read access to `/api/finance/transactions/:id`.", + "parameters": [ + { + "description": "Path parameter `id`.", + "in": "path", + "name": "id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / finance / transactions / :id", + "tags": [ + "Finance" + ] + } + }, + "/api/inventory/adjustments/": { + "get": { + "description": "Read access to `/api/inventory/adjustments/`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / inventory / adjustments", + "tags": [ + "Inventory" + ] + } + }, + "/api/inventory/adjustments/{id}": { + "get": { + "description": "Read access to `/api/inventory/adjustments/:id`.", + "parameters": [ + { + "description": "Path parameter `id`.", + "in": "path", + "name": "id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / inventory / adjustments / :id", + "tags": [ + "Inventory" + ] + } + }, + "/api/inventory/product-stocks/": { + "get": { + "description": "Read access to `/api/inventory/product-stocks/`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / inventory / product stocks", + "tags": [ + "Inventory" + ] + } + }, + "/api/inventory/product-stocks/{id}": { + "get": { + "description": "Read access to `/api/inventory/product-stocks/:id`.", + "parameters": [ + { + "description": "Path parameter `id`.", + "in": "path", + "name": "id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / inventory / product stocks / :id", + "tags": [ + "Inventory" + ] + } + }, + "/api/inventory/product-warehouses/": { + "get": { + "description": "Read access to `/api/inventory/product-warehouses/`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / inventory / product warehouses", + "tags": [ + "Inventory" + ] + } + }, + "/api/inventory/product-warehouses/{id}": { + "get": { + "description": "Read access to `/api/inventory/product-warehouses/:id`.", + "parameters": [ + { + "description": "Path parameter `id`.", + "in": "path", + "name": "id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / inventory / product warehouses / :id", + "tags": [ + "Inventory" + ] + } + }, + "/api/inventory/transfers/": { + "get": { + "description": "Read access to `/api/inventory/transfers/`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / inventory / transfers", + "tags": [ + "Inventory" + ] + } + }, + "/api/inventory/transfers/{id}": { + "get": { + "description": "Read access to `/api/inventory/transfers/:id`.", + "parameters": [ + { + "description": "Path parameter `id`.", + "in": "path", + "name": "id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / inventory / transfers / :id", + "tags": [ + "Inventory" + ] + } + }, + "/api/marketing/": { + "get": { + "description": "Read access to `/api/marketing/`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / marketing", + "tags": [ + "Marketing" + ] + } + }, + "/api/marketing/{id}": { + "get": { + "description": "Read access to `/api/marketing/:id`.", + "parameters": [ + { + "description": "Path parameter `id`.", + "in": "path", + "name": "id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / marketing / :id", + "tags": [ + "Marketing" + ] + } + }, + "/api/master-data/areas/": { + "get": { + "description": "Read access to `/api/master-data/areas/`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / master data / areas", + "tags": [ + "Master Data" + ] + } + }, + "/api/master-data/areas/{id}": { + "get": { + "description": "Read access to `/api/master-data/areas/:id`.", + "parameters": [ + { + "description": "Path parameter `id`.", + "in": "path", + "name": "id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / master data / areas / :id", + "tags": [ + "Master Data" + ] + } + }, + "/api/master-data/banks/": { + "get": { + "description": "Read access to `/api/master-data/banks/`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / master data / banks", + "tags": [ + "Master Data" + ] + } + }, + "/api/master-data/banks/{id}": { + "get": { + "description": "Read access to `/api/master-data/banks/:id`.", + "parameters": [ + { + "description": "Path parameter `id`.", + "in": "path", + "name": "id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / master data / banks / :id", + "tags": [ + "Master Data" + ] + } + }, + "/api/master-data/config-checklists/": { + "get": { + "description": "Read access to `/api/master-data/config-checklists/`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / master data / config checklists", + "tags": [ + "Master Data" + ] + } + }, + "/api/master-data/config-checklists/{id}": { + "get": { + "description": "Read access to `/api/master-data/config-checklists/:id`.", + "parameters": [ + { + "description": "Path parameter `id`.", + "in": "path", + "name": "id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / master data / config checklists / :id", + "tags": [ + "Master Data" + ] + } + }, + "/api/master-data/customers/": { + "get": { + "description": "Read access to `/api/master-data/customers/`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / master data / customers", + "tags": [ + "Master Data" + ] + } + }, + "/api/master-data/customers/{id}": { + "get": { + "description": "Read access to `/api/master-data/customers/:id`.", + "parameters": [ + { + "description": "Path parameter `id`.", + "in": "path", + "name": "id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / master data / customers / :id", + "tags": [ + "Master Data" + ] + } + }, + "/api/master-data/employees/": { + "get": { + "description": "Read access to `/api/master-data/employees/`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / master data / employees", + "tags": [ + "Master Data" + ] + } + }, + "/api/master-data/employees/{id}": { + "get": { + "description": "Read access to `/api/master-data/employees/:id`.", + "parameters": [ + { + "description": "Path parameter `id`.", + "in": "path", + "name": "id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / master data / employees / :id", + "tags": [ + "Master Data" + ] + } + }, + "/api/master-data/fcrs/": { + "get": { + "description": "Read access to `/api/master-data/fcrs/`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / master data / fcrs", + "tags": [ + "Master Data" + ] + } + }, + "/api/master-data/fcrs/{id}": { + "get": { + "description": "Read access to `/api/master-data/fcrs/:id`.", + "parameters": [ + { + "description": "Path parameter `id`.", + "in": "path", + "name": "id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / master data / fcrs / :id", + "tags": [ + "Master Data" + ] + } + }, + "/api/master-data/flocks/": { + "get": { + "description": "Read access to `/api/master-data/flocks/`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / master data / flocks", + "tags": [ + "Master Data" + ] + } + }, + "/api/master-data/flocks/{id}": { + "get": { + "description": "Read access to `/api/master-data/flocks/:id`.", + "parameters": [ + { + "description": "Path parameter `id`.", + "in": "path", + "name": "id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / master data / flocks / :id", + "tags": [ + "Master Data" + ] + } + }, + "/api/master-data/kandang-groups/": { + "get": { + "description": "Read access to `/api/master-data/kandang-groups/`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / master data / kandang groups", + "tags": [ + "Master Data" + ] + } + }, + "/api/master-data/kandang-groups/{id}": { + "get": { + "description": "Read access to `/api/master-data/kandang-groups/:id`.", + "parameters": [ + { + "description": "Path parameter `id`.", + "in": "path", + "name": "id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / master data / kandang groups / :id", + "tags": [ + "Master Data" + ] + } + }, + "/api/master-data/kandangs/": { + "get": { + "description": "Read access to `/api/master-data/kandangs/`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / master data / kandangs", + "tags": [ + "Master Data" + ] + } + }, + "/api/master-data/kandangs/{id}": { + "get": { + "description": "Read access to `/api/master-data/kandangs/:id`.", + "parameters": [ + { + "description": "Path parameter `id`.", + "in": "path", + "name": "id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / master data / kandangs / :id", + "tags": [ + "Master Data" + ] + } + }, + "/api/master-data/locations/": { + "get": { + "description": "Read access to `/api/master-data/locations/`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / master data / locations", + "tags": [ + "Master Data" + ] + } + }, + "/api/master-data/locations/{id}": { + "get": { + "description": "Read access to `/api/master-data/locations/:id`.", + "parameters": [ + { + "description": "Path parameter `id`.", + "in": "path", + "name": "id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / master data / locations / :id", + "tags": [ + "Master Data" + ] + } + }, + "/api/master-data/nonstocks/": { + "get": { + "description": "Read access to `/api/master-data/nonstocks/`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / master data / nonstocks", + "tags": [ + "Master Data" + ] + } + }, + "/api/master-data/nonstocks/{id}": { + "get": { + "description": "Read access to `/api/master-data/nonstocks/:id`.", + "parameters": [ + { + "description": "Path parameter `id`.", + "in": "path", + "name": "id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / master data / nonstocks / :id", + "tags": [ + "Master Data" + ] + } + }, + "/api/master-data/phase-activities/": { + "get": { + "description": "Read access to `/api/master-data/phase-activities/`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / master data / phase activities", + "tags": [ + "Master Data" + ] + } + }, + "/api/master-data/phase-activities/{id}": { + "get": { + "description": "Read access to `/api/master-data/phase-activities/:id`.", + "parameters": [ + { + "description": "Path parameter `id`.", + "in": "path", + "name": "id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / master data / phase activities / :id", + "tags": [ + "Master Data" + ] + } + }, + "/api/master-data/phases/": { + "get": { + "description": "Read access to `/api/master-data/phases/`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / master data / phases", + "tags": [ + "Master Data" + ] + } + }, + "/api/master-data/phases/{id}": { + "get": { + "description": "Read access to `/api/master-data/phases/:id`.", + "parameters": [ + { + "description": "Path parameter `id`.", + "in": "path", + "name": "id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / master data / phases / :id", + "tags": [ + "Master Data" + ] + } + }, + "/api/master-data/product-categories/": { + "get": { + "description": "Read access to `/api/master-data/product-categories/`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / master data / product categories", + "tags": [ + "Master Data" + ] + } + }, + "/api/master-data/product-categories/{id}": { + "get": { + "description": "Read access to `/api/master-data/product-categories/:id`.", + "parameters": [ + { + "description": "Path parameter `id`.", + "in": "path", + "name": "id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / master data / product categories / :id", + "tags": [ + "Master Data" + ] + } + }, + "/api/master-data/production-standards/": { + "get": { + "description": "Read access to `/api/master-data/production-standards/`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / master data / production standards", + "tags": [ + "Master Data" + ] + } + }, + "/api/master-data/production-standards/{id}": { + "get": { + "description": "Read access to `/api/master-data/production-standards/:id`.", + "parameters": [ + { + "description": "Path parameter `id`.", + "in": "path", + "name": "id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / master data / production standards / :id", + "tags": [ + "Master Data" + ] + } + }, + "/api/master-data/products/": { + "get": { + "description": "Read access to `/api/master-data/products/`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / master data / products", + "tags": [ + "Master Data" + ] + } + }, + "/api/master-data/products/{id}": { + "get": { + "description": "Read access to `/api/master-data/products/:id`.", + "parameters": [ + { + "description": "Path parameter `id`.", + "in": "path", + "name": "id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / master data / products / :id", + "tags": [ + "Master Data" + ] + } + }, + "/api/master-data/suppliers/": { + "get": { + "description": "Read access to `/api/master-data/suppliers/`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / master data / suppliers", + "tags": [ + "Master Data" + ] + } + }, + "/api/master-data/suppliers/{id}": { + "get": { + "description": "Read access to `/api/master-data/suppliers/:id`.", + "parameters": [ + { + "description": "Path parameter `id`.", + "in": "path", + "name": "id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / master data / suppliers / :id", + "tags": [ + "Master Data" + ] + } + }, + "/api/master-data/uoms/": { + "get": { + "description": "Read access to `/api/master-data/uoms/`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / master data / uoms", + "tags": [ + "Master Data" + ] + } + }, + "/api/master-data/uoms/{id}": { + "get": { + "description": "Read access to `/api/master-data/uoms/:id`.", + "parameters": [ + { + "description": "Path parameter `id`.", + "in": "path", + "name": "id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / master data / uoms / :id", + "tags": [ + "Master Data" + ] + } + }, + "/api/master-data/warehouses/": { + "get": { + "description": "Read access to `/api/master-data/warehouses/`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / master data / warehouses", + "tags": [ + "Master Data" + ] + } + }, + "/api/master-data/warehouses/{id}": { + "get": { + "description": "Read access to `/api/master-data/warehouses/:id`.", + "parameters": [ + { + "description": "Path parameter `id`.", + "in": "path", + "name": "id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / master data / warehouses / :id", + "tags": [ + "Master Data" + ] + } + }, + "/api/production/chickins/{id}": { + "get": { + "description": "Read access to `/api/production/chickins/:id`.", + "parameters": [ + { + "description": "Path parameter `id`.", + "in": "path", + "name": "id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / production / chickins / :id", + "tags": [ + "Production" + ] + } + }, + "/api/production/project-flock-kandangs/": { + "get": { + "description": "Read access to `/api/production/project-flock-kandangs/`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / production / project flock kandangs", + "tags": [ + "Production" + ] + } + }, + "/api/production/project-flock-kandangs/{id}": { + "get": { + "description": "Read access to `/api/production/project-flock-kandangs/:id`.", + "parameters": [ + { + "description": "Path parameter `id`.", + "in": "path", + "name": "id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / production / project flock kandangs / :id", + "tags": [ + "Production" + ] + } + }, + "/api/production/project-flock-kandangs/{id}/closing/check": { + "get": { + "description": "Read access to `/api/production/project-flock-kandangs/:id/closing/check`.", + "parameters": [ + { + "description": "Path parameter `id`.", + "in": "path", + "name": "id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / production / project flock kandangs / :id / closing / check", + "tags": [ + "Production" + ] + } + }, + "/api/production/project-flocks/": { + "get": { + "description": "Read access to `/api/production/project-flocks/`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / production / project flocks", + "tags": [ + "Production" + ] + } + }, + "/api/production/project-flocks/kandangs/lookup": { + "get": { + "description": "Read access to `/api/production/project-flocks/kandangs/lookup`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / production / project flocks / kandangs / lookup", + "tags": [ + "Production" + ] + } + }, + "/api/production/project-flocks/locations/{location_id}/periods": { + "get": { + "description": "Read access to `/api/production/project-flocks/locations/:location_id/periods`.", + "parameters": [ + { + "description": "Path parameter `location_id`.", + "in": "path", + "name": "location_id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / production / project flocks / locations / :location_id / periods", + "tags": [ + "Production" + ] + } + }, + "/api/production/project-flocks/{id}": { + "get": { + "description": "Read access to `/api/production/project-flocks/:id`.", + "parameters": [ + { + "description": "Path parameter `id`.", + "in": "path", + "name": "id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / production / project flocks / :id", + "tags": [ + "Production" + ] + } + }, + "/api/production/recordings/": { + "get": { + "description": "Read access to `/api/production/recordings/`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / production / recordings", + "tags": [ + "Production" + ] + } + }, + "/api/production/recordings/next-day": { + "get": { + "description": "Read access to `/api/production/recordings/next-day`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / production / recordings / next day", + "tags": [ + "Production" + ] + } + }, + "/api/production/recordings/{id}": { + "get": { + "description": "Read access to `/api/production/recordings/:id`.", + "parameters": [ + { + "description": "Path parameter `id`.", + "in": "path", + "name": "id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / production / recordings / :id", + "tags": [ + "Production" + ] + } + }, + "/api/production/transfer_layings/": { + "get": { + "description": "Read access to `/api/production/transfer_layings/`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / production / transfer_layings", + "tags": [ + "Production" + ] + } + }, + "/api/production/transfer_layings/project-flocks/{project_flock_id}/available-qty": { + "get": { + "description": "Read access to `/api/production/transfer_layings/project-flocks/:project_flock_id/available-qty`.", + "parameters": [ + { + "description": "Path parameter `project_flock_id`.", + "in": "path", + "name": "project_flock_id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / production / transfer_layings / project flocks / :project_flock_id / available qty", + "tags": [ + "Production" + ] + } + }, + "/api/production/transfer_layings/project-flocks/{project_flock_id}/max-target-qty": { + "get": { + "description": "Read access to `/api/production/transfer_layings/project-flocks/:project_flock_id/max-target-qty`.", + "parameters": [ + { + "description": "Path parameter `project_flock_id`.", + "in": "path", + "name": "project_flock_id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / production / transfer_layings / project flocks / :project_flock_id / max target qty", + "tags": [ + "Production" + ] + } + }, + "/api/production/transfer_layings/{id}": { + "get": { + "description": "Read access to `/api/production/transfer_layings/:id`.", + "parameters": [ + { + "description": "Path parameter `id`.", + "in": "path", + "name": "id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / production / transfer_layings / :id", + "tags": [ + "Production" + ] + } + }, + "/api/production/uniformities/": { + "get": { + "description": "Read access to `/api/production/uniformities/`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / production / uniformities", + "tags": [ + "Production" + ] + } + }, + "/api/production/uniformities/{id}": { + "get": { + "description": "Read access to `/api/production/uniformities/:id`.", + "parameters": [ + { + "description": "Path parameter `id`.", + "in": "path", + "name": "id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / production / uniformities / :id", + "tags": [ + "Production" + ] + } + }, + "/api/purchases/": { + "get": { + "description": "Read access to `/api/purchases/`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / purchases", + "tags": [ + "Purchases" + ] + } + }, + "/api/purchases/{id}": { + "get": { + "description": "Read access to `/api/purchases/:id`.", + "parameters": [ + { + "description": "Path parameter `id`.", + "in": "path", + "name": "id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / purchases / :id", + "tags": [ + "Purchases" + ] + } + }, + "/api/reports/customer-payment": { + "get": { + "description": "Read access to `/api/reports/customer-payment`.", + "parameters": [ + { + "description": "Page number.", + "example": 1, + "in": "query", + "name": "page", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Page size.", + "example": 10, + "in": "query", + "name": "limit", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Period start date (YYYY-MM-DD).", + "example": "2026-01-01", + "in": "query", + "name": "start_date", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Period end date (YYYY-MM-DD).", + "example": "2026-01-31", + "in": "query", + "name": "end_date", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Comma separated customer ids.", + "example": "1,2", + "in": "query", + "name": "customer_ids", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / reports / customer payment", + "tags": [ + "Reports" + ] + } + }, + "/api/reports/debt-supplier": { + "get": { + "description": "Read access to `/api/reports/debt-supplier`.", + "parameters": [ + { + "description": "Page number.", + "example": 1, + "in": "query", + "name": "page", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Page size.", + "example": 10, + "in": "query", + "name": "limit", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Period start date (YYYY-MM-DD).", + "example": "2026-01-01", + "in": "query", + "name": "start_date", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Period end date (YYYY-MM-DD).", + "example": "2026-01-31", + "in": "query", + "name": "end_date", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Comma separated supplier ids.", + "example": "1,2", + "in": "query", + "name": "supplier_ids", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / reports / debt supplier", + "tags": [ + "Reports" + ] + } + }, + "/api/reports/expense": { + "get": { + "description": "Read access to `/api/reports/expense`.", + "parameters": [ + { + "description": "Page number.", + "example": 1, + "in": "query", + "name": "page", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Page size.", + "example": 10, + "in": "query", + "name": "limit", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Search keyword.", + "example": "operasional", + "in": "query", + "name": "search", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Expense category filter.", + "example": "BOP", + "in": "query", + "name": "category", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Supplier id filter.", + "example": 1, + "in": "query", + "name": "supplier_id", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Location id filter.", + "example": 1, + "in": "query", + "name": "location_id", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Area id filter.", + "example": 1, + "in": "query", + "name": "area_id", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Realization date filter (YYYY-MM-DD).", + "example": "2026-01-15", + "in": "query", + "name": "realization_date", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / reports / expense", + "tags": [ + "Reports" + ] + } + }, + "/api/reports/hpp-per-kandang": { + "get": { + "description": "Read access to `/api/reports/hpp-per-kandang`.", + "parameters": [ + { + "description": "Page number.", + "example": 1, + "in": "query", + "name": "page", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Page size.", + "example": 10, + "in": "query", + "name": "limit", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Daily period filter (YYYY-MM).", + "example": "2026-01", + "in": "query", + "name": "period", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Location id filter.", + "example": 1, + "in": "query", + "name": "location_id", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Kandang id filter.", + "example": 1, + "in": "query", + "name": "kandang_id", + "required": false, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / reports / hpp per kandang", + "tags": [ + "Reports" + ] + } + }, + "/api/reports/marketing": { + "get": { + "description": "Read access to `/api/reports/marketing`.", + "parameters": [ + { + "description": "Page number.", + "example": 1, + "in": "query", + "name": "page", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Page size.", + "example": 10, + "in": "query", + "name": "limit", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Period start date (YYYY-MM-DD).", + "example": "2026-01-01", + "in": "query", + "name": "start_date", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Period end date (YYYY-MM-DD).", + "example": "2026-01-31", + "in": "query", + "name": "end_date", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Customer id filter.", + "example": 1, + "in": "query", + "name": "customer_id", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Location id filter.", + "example": 1, + "in": "query", + "name": "location_id", + "required": false, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / reports / marketing", + "tags": [ + "Reports" + ] + } + }, + "/api/reports/production-result/{idProjectFlockKandang}": { + "get": { + "description": "Read access to `/api/reports/production-result/:idProjectFlockKandang`.", + "parameters": [ + { + "description": "Path parameter `idProjectFlockKandang`.", + "in": "path", + "name": "idProjectFlockKandang", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / reports / production result / :idProjectFlockKandang", + "tags": [ + "Reports" + ] + } + }, + "/api/reports/purchase-supplier": { + "get": { + "description": "Read access to `/api/reports/purchase-supplier`.", + "parameters": [ + { + "description": "Page number.", + "example": 1, + "in": "query", + "name": "page", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Page size.", + "example": 10, + "in": "query", + "name": "limit", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Period start date (YYYY-MM-DD).", + "example": "2026-01-01", + "in": "query", + "name": "start_date", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Period end date (YYYY-MM-DD).", + "example": "2026-01-31", + "in": "query", + "name": "end_date", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Comma separated supplier ids.", + "example": "1,2", + "in": "query", + "name": "supplier_id", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Comma separated area ids.", + "example": "1,2", + "in": "query", + "name": "area_id", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / reports / purchase supplier", + "tags": [ + "Reports" + ] + } + }, + "/api/sso/callback": { + "get": { + "description": "Read access to `/api/sso/callback`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "summary": "GET api / sso / callback", + "tags": [ + "SSO" + ] + } + }, + "/api/sso/master/areas": { + "get": { + "description": "Read access to `/api/sso/master/areas`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "summary": "GET api / sso / master / areas", + "tags": [ + "SSO" + ] + } + }, + "/api/sso/master/locations": { + "get": { + "description": "Read access to `/api/sso/master/locations`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "summary": "GET api / sso / master / locations", + "tags": [ + "SSO" + ] + } + }, + "/api/sso/start": { + "get": { + "description": "Read access to `/api/sso/start`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "summary": "GET api / sso / start", + "tags": [ + "SSO" + ] + } + }, + "/api/sso/userinfo": { + "get": { + "description": "Read access to `/api/sso/userinfo`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "BearerAuth": [] + } + ], + "summary": "GET api / sso / userinfo", + "tags": [ + "SSO" + ] + } + }, + "/api/users/": { + "get": { + "description": "Read access to `/api/users/`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / users", + "tags": [ + "Users" + ] + } + }, + "/api/users/{id}": { + "get": { + "description": "Read access to `/api/users/:id`.", + "parameters": [ + { + "description": "Path parameter `id`.", + "in": "path", + "name": "id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / users / :id", + "tags": [ + "Users" + ] + } + }, + "/healthz": { + "get": { + "description": "Simple liveness probe.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "summary": "Health check", + "tags": [ + "System" + ] + } + }, + "/readyz": { + "get": { + "description": "Readiness probe for database and Redis.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "summary": "Readiness check", + "tags": [ + "System" + ] + } + } + }, + "servers": [ + { + "url": "http://localhost:8081" + } + ] +} \ No newline at end of file diff --git a/docs/openapi/read-api.yaml b/docs/openapi/read-api.yaml new file mode 100644 index 00000000..01a4e811 --- /dev/null +++ b/docs/openapi/read-api.yaml @@ -0,0 +1,4066 @@ +components: + schemas: + ErrorEnvelope: + properties: + code: + example: 401 + type: integer + errors: + additionalProperties: true + type: object + message: + example: Please authenticate + type: string + status: + example: error + type: string + type: object + PaginatedEnvelope: + properties: + code: + example: 200 + type: integer + data: + items: + additionalProperties: true + type: object + type: array + message: + example: Request completed successfully + type: string + meta: + properties: + limit: + example: 10 + type: integer + page: + example: 1 + type: integer + total_pages: + example: 1 + type: integer + total_results: + example: 0 + type: integer + type: object + status: + example: success + type: string + type: object + SuccessEnvelope: + properties: + code: + example: 200 + type: integer + data: + additionalProperties: true + type: object + message: + example: Request completed successfully + type: string + status: + example: success + type: string + type: object + securitySchemes: + ApiKeyAuth: + in: header + name: X-API-Key + type: apiKey + BearerAuth: + scheme: bearer + type: http +info: + description: Read-only OpenAPI surface for dashboard integrations and GET endpoint exploration. + title: LTI ERP Read API + version: v1 +openapi: 3.1.0 +paths: + /api/approvals/: + get: + description: Read access to `/api/approvals/`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / approvals + tags: + - API + /api/closings/: + get: + description: Read access to `/api/closings/`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / closings + tags: + - Closings + /api/closings/{project_flock_id}/{project_flock_kandang_id}/expedition-hpp: + get: + description: Read access to `/api/closings/:project_flock_id/:project_flock_kandang_id/expedition-hpp`. + parameters: + - description: Path parameter `project_flock_id`. + in: path + name: project_flock_id + required: true + schema: + example: "1" + type: string + - description: Path parameter `project_flock_kandang_id`. + in: path + name: project_flock_kandang_id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / closings / :project_flock_id / :project_flock_kandang_id / expedition hpp + tags: + - Closings + /api/closings/{project_flock_id}/{project_flock_kandang_id}/keuangan: + get: + description: Read access to `/api/closings/:project_flock_id/:project_flock_kandang_id/keuangan`. + parameters: + - description: Path parameter `project_flock_id`. + in: path + name: project_flock_id + required: true + schema: + example: "1" + type: string + - description: Path parameter `project_flock_kandang_id`. + in: path + name: project_flock_kandang_id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / closings / :project_flock_id / :project_flock_kandang_id / keuangan + tags: + - Closings + /api/closings/{project_flock_id}/{project_flock_kandang_id}/overhead: + get: + description: Read access to `/api/closings/:project_flock_id/:project_flock_kandang_id/overhead`. + parameters: + - description: Path parameter `project_flock_id`. + in: path + name: project_flock_id + required: true + schema: + example: "1" + type: string + - description: Path parameter `project_flock_kandang_id`. + in: path + name: project_flock_kandang_id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / closings / :project_flock_id / :project_flock_kandang_id / overhead + tags: + - Closings + /api/closings/{project_flock_id}/{project_flock_kandang_id}/penjualan: + get: + description: Read access to `/api/closings/:project_flock_id/:project_flock_kandang_id/penjualan`. + parameters: + - description: Path parameter `project_flock_id`. + in: path + name: project_flock_id + required: true + schema: + example: "1" + type: string + - description: Path parameter `project_flock_kandang_id`. + in: path + name: project_flock_kandang_id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / closings / :project_flock_id / :project_flock_kandang_id / penjualan + tags: + - Closings + /api/closings/{project_flock_id}/{project_flock_kandang_id}/perhitungan_sapronak: + get: + description: Read access to `/api/closings/:project_flock_id/:project_flock_kandang_id/perhitungan_sapronak`. + parameters: + - description: Path parameter `project_flock_id`. + in: path + name: project_flock_id + required: true + schema: + example: "1" + type: string + - description: Path parameter `project_flock_kandang_id`. + in: path + name: project_flock_kandang_id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / closings / :project_flock_id / :project_flock_kandang_id / perhitungan_sapronak + tags: + - Closings + /api/closings/{project_flock_id}/expedition-hpp: + get: + description: Read access to `/api/closings/:project_flock_id/expedition-hpp`. + parameters: + - description: Path parameter `project_flock_id`. + in: path + name: project_flock_id + required: true + schema: + example: "1" + type: string + - description: Optional project flock kandang id filter. + example: 1 + in: query + name: project_flock_kandang_id + required: false + schema: + type: integer + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / closings / :project_flock_id / expedition hpp + tags: + - Closings + /api/closings/{project_flock_id}/overhead: + get: + description: Read access to `/api/closings/:project_flock_id/overhead`. + parameters: + - description: Path parameter `project_flock_id`. + in: path + name: project_flock_id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / closings / :project_flock_id / overhead + tags: + - Closings + /api/closings/{project_flock_id}/penjualan: + get: + description: Read access to `/api/closings/:project_flock_id/penjualan`. + parameters: + - description: Path parameter `project_flock_id`. + in: path + name: project_flock_id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / closings / :project_flock_id / penjualan + tags: + - Closings + /api/closings/{project_flock_id}/perhitungan_sapronak: + get: + description: Read access to `/api/closings/:project_flock_id/perhitungan_sapronak`. + parameters: + - description: Path parameter `project_flock_id`. + in: path + name: project_flock_id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / closings / :project_flock_id / perhitungan_sapronak + tags: + - Closings + /api/closings/{projectFlockId}: + get: + description: Read access to `/api/closings/:projectFlockId`. + parameters: + - description: Path parameter `projectFlockId`. + in: path + name: projectFlockId + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / closings / :projectFlockId + tags: + - Closings + /api/closings/{projectFlockId}/keuangan: + get: + description: Read access to `/api/closings/:projectFlockId/keuangan`. + parameters: + - description: Path parameter `projectFlockId`. + in: path + name: projectFlockId + required: true + schema: + example: "1" + type: string + - description: Optional kandang id filter. + example: 1 + in: query + name: kandang_id + required: false + schema: + type: integer + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / closings / :projectFlockId / keuangan + tags: + - Closings + /api/closings/{projectFlockId}/production-data: + get: + description: Read access to `/api/closings/:projectFlockId/production-data`. + parameters: + - description: Path parameter `projectFlockId`. + in: path + name: projectFlockId + required: true + schema: + example: "1" + type: string + - description: Optional kandang id filter. + example: 1 + in: query + name: kandang_id + required: false + schema: + type: integer + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / closings / :projectFlockId / production data + tags: + - Closings + /api/closings/{projectFlockId}/sapronak: + get: + description: Read access to `/api/closings/:projectFlockId/sapronak`. + parameters: + - description: Path parameter `projectFlockId`. + in: path + name: projectFlockId + required: true + schema: + example: "1" + type: string + - description: Required sapronak direction. + example: incoming + in: query + name: type + required: true + schema: + type: string + - description: Search keyword. + example: pakan + in: query + name: search + required: false + schema: + type: string + - description: Optional kandang id filter. + example: 1 + in: query + name: kandang_id + required: false + schema: + type: integer + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / closings / :projectFlockId / sapronak + tags: + - Closings + /api/closings/{projectFlockId}/sapronak/summary: + get: + description: Read access to `/api/closings/:projectFlockId/sapronak/summary`. + parameters: + - description: Path parameter `projectFlockId`. + in: path + name: projectFlockId + required: true + schema: + example: "1" + type: string + - description: Required sapronak direction. + example: incoming + in: query + name: type + required: true + schema: + type: string + - description: Search keyword. + example: pakan + in: query + name: search + required: false + schema: + type: string + - description: Optional kandang id filter. + example: 1 + in: query + name: kandang_id + required: false + schema: + type: integer + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / closings / :projectFlockId / sapronak / summary + tags: + - Closings + /api/constants/: + get: + description: Read access to `/api/constants/`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / constants + tags: + - API + /api/daily-checklists/: + get: + description: Read access to `/api/daily-checklists/`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / daily checklists + tags: + - Daily Checklists + /api/daily-checklists/phase/{idDailyChecklist}: + get: + description: Read access to `/api/daily-checklists/phase/:idDailyChecklist`. + parameters: + - description: Path parameter `idDailyChecklist`. + in: path + name: idDailyChecklist + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / daily checklists / phase / :idDailyChecklist + tags: + - Daily Checklists + /api/daily-checklists/relation/{idDailyChecklist}: + get: + description: Read access to `/api/daily-checklists/relation/:idDailyChecklist`. + parameters: + - description: Path parameter `idDailyChecklist`. + in: path + name: idDailyChecklist + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / daily checklists / relation / :idDailyChecklist + tags: + - Daily Checklists + /api/daily-checklists/report: + get: + description: Read access to `/api/daily-checklists/report`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / daily checklists / report + tags: + - Daily Checklists + /api/daily-checklists/summary: + get: + description: Read access to `/api/daily-checklists/summary`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / daily checklists / summary + tags: + - Daily Checklists + /api/daily-checklists/tasks: + get: + description: Read access to `/api/daily-checklists/tasks`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / daily checklists / tasks + tags: + - Daily Checklists + /api/dashboards/: + get: + description: Read access to `/api/dashboards/`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / dashboards + tags: + - Dashboards + /api/expenses/: + get: + description: Read access to `/api/expenses/`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / expenses + tags: + - Expenses + /api/expenses/{id}: + get: + description: Read access to `/api/expenses/:id`. + parameters: + - description: Path parameter `id`. + in: path + name: id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / expenses / :id + tags: + - Expenses + /api/finance/initial-balances/{id}: + get: + description: Read access to `/api/finance/initial-balances/:id`. + parameters: + - description: Path parameter `id`. + in: path + name: id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / finance / initial balances / :id + tags: + - Finance + /api/finance/injections/{id}: + get: + description: Read access to `/api/finance/injections/:id`. + parameters: + - description: Path parameter `id`. + in: path + name: id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / finance / injections / :id + tags: + - Finance + /api/finance/payments/{id}: + get: + description: Read access to `/api/finance/payments/:id`. + parameters: + - description: Path parameter `id`. + in: path + name: id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / finance / payments / :id + tags: + - Finance + /api/finance/transactions/: + get: + description: Read access to `/api/finance/transactions/`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / finance / transactions + tags: + - Finance + /api/finance/transactions/{id}: + get: + description: Read access to `/api/finance/transactions/:id`. + parameters: + - description: Path parameter `id`. + in: path + name: id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / finance / transactions / :id + tags: + - Finance + /api/inventory/adjustments/: + get: + description: Read access to `/api/inventory/adjustments/`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / inventory / adjustments + tags: + - Inventory + /api/inventory/adjustments/{id}: + get: + description: Read access to `/api/inventory/adjustments/:id`. + parameters: + - description: Path parameter `id`. + in: path + name: id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / inventory / adjustments / :id + tags: + - Inventory + /api/inventory/product-stocks/: + get: + description: Read access to `/api/inventory/product-stocks/`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / inventory / product stocks + tags: + - Inventory + /api/inventory/product-stocks/{id}: + get: + description: Read access to `/api/inventory/product-stocks/:id`. + parameters: + - description: Path parameter `id`. + in: path + name: id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / inventory / product stocks / :id + tags: + - Inventory + /api/inventory/product-warehouses/: + get: + description: Read access to `/api/inventory/product-warehouses/`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / inventory / product warehouses + tags: + - Inventory + /api/inventory/product-warehouses/{id}: + get: + description: Read access to `/api/inventory/product-warehouses/:id`. + parameters: + - description: Path parameter `id`. + in: path + name: id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / inventory / product warehouses / :id + tags: + - Inventory + /api/inventory/transfers/: + get: + description: Read access to `/api/inventory/transfers/`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / inventory / transfers + tags: + - Inventory + /api/inventory/transfers/{id}: + get: + description: Read access to `/api/inventory/transfers/:id`. + parameters: + - description: Path parameter `id`. + in: path + name: id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / inventory / transfers / :id + tags: + - Inventory + /api/marketing/: + get: + description: Read access to `/api/marketing/`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / marketing + tags: + - Marketing + /api/marketing/{id}: + get: + description: Read access to `/api/marketing/:id`. + parameters: + - description: Path parameter `id`. + in: path + name: id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / marketing / :id + tags: + - Marketing + /api/master-data/areas/: + get: + description: Read access to `/api/master-data/areas/`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / master data / areas + tags: + - Master Data + /api/master-data/areas/{id}: + get: + description: Read access to `/api/master-data/areas/:id`. + parameters: + - description: Path parameter `id`. + in: path + name: id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / master data / areas / :id + tags: + - Master Data + /api/master-data/banks/: + get: + description: Read access to `/api/master-data/banks/`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / master data / banks + tags: + - Master Data + /api/master-data/banks/{id}: + get: + description: Read access to `/api/master-data/banks/:id`. + parameters: + - description: Path parameter `id`. + in: path + name: id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / master data / banks / :id + tags: + - Master Data + /api/master-data/config-checklists/: + get: + description: Read access to `/api/master-data/config-checklists/`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / master data / config checklists + tags: + - Master Data + /api/master-data/config-checklists/{id}: + get: + description: Read access to `/api/master-data/config-checklists/:id`. + parameters: + - description: Path parameter `id`. + in: path + name: id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / master data / config checklists / :id + tags: + - Master Data + /api/master-data/customers/: + get: + description: Read access to `/api/master-data/customers/`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / master data / customers + tags: + - Master Data + /api/master-data/customers/{id}: + get: + description: Read access to `/api/master-data/customers/:id`. + parameters: + - description: Path parameter `id`. + in: path + name: id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / master data / customers / :id + tags: + - Master Data + /api/master-data/employees/: + get: + description: Read access to `/api/master-data/employees/`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / master data / employees + tags: + - Master Data + /api/master-data/employees/{id}: + get: + description: Read access to `/api/master-data/employees/:id`. + parameters: + - description: Path parameter `id`. + in: path + name: id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / master data / employees / :id + tags: + - Master Data + /api/master-data/fcrs/: + get: + description: Read access to `/api/master-data/fcrs/`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / master data / fcrs + tags: + - Master Data + /api/master-data/fcrs/{id}: + get: + description: Read access to `/api/master-data/fcrs/:id`. + parameters: + - description: Path parameter `id`. + in: path + name: id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / master data / fcrs / :id + tags: + - Master Data + /api/master-data/flocks/: + get: + description: Read access to `/api/master-data/flocks/`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / master data / flocks + tags: + - Master Data + /api/master-data/flocks/{id}: + get: + description: Read access to `/api/master-data/flocks/:id`. + parameters: + - description: Path parameter `id`. + in: path + name: id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / master data / flocks / :id + tags: + - Master Data + /api/master-data/kandang-groups/: + get: + description: Read access to `/api/master-data/kandang-groups/`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / master data / kandang groups + tags: + - Master Data + /api/master-data/kandang-groups/{id}: + get: + description: Read access to `/api/master-data/kandang-groups/:id`. + parameters: + - description: Path parameter `id`. + in: path + name: id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / master data / kandang groups / :id + tags: + - Master Data + /api/master-data/kandangs/: + get: + description: Read access to `/api/master-data/kandangs/`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / master data / kandangs + tags: + - Master Data + /api/master-data/kandangs/{id}: + get: + description: Read access to `/api/master-data/kandangs/:id`. + parameters: + - description: Path parameter `id`. + in: path + name: id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / master data / kandangs / :id + tags: + - Master Data + /api/master-data/locations/: + get: + description: Read access to `/api/master-data/locations/`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / master data / locations + tags: + - Master Data + /api/master-data/locations/{id}: + get: + description: Read access to `/api/master-data/locations/:id`. + parameters: + - description: Path parameter `id`. + in: path + name: id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / master data / locations / :id + tags: + - Master Data + /api/master-data/nonstocks/: + get: + description: Read access to `/api/master-data/nonstocks/`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / master data / nonstocks + tags: + - Master Data + /api/master-data/nonstocks/{id}: + get: + description: Read access to `/api/master-data/nonstocks/:id`. + parameters: + - description: Path parameter `id`. + in: path + name: id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / master data / nonstocks / :id + tags: + - Master Data + /api/master-data/phase-activities/: + get: + description: Read access to `/api/master-data/phase-activities/`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / master data / phase activities + tags: + - Master Data + /api/master-data/phase-activities/{id}: + get: + description: Read access to `/api/master-data/phase-activities/:id`. + parameters: + - description: Path parameter `id`. + in: path + name: id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / master data / phase activities / :id + tags: + - Master Data + /api/master-data/phases/: + get: + description: Read access to `/api/master-data/phases/`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / master data / phases + tags: + - Master Data + /api/master-data/phases/{id}: + get: + description: Read access to `/api/master-data/phases/:id`. + parameters: + - description: Path parameter `id`. + in: path + name: id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / master data / phases / :id + tags: + - Master Data + /api/master-data/product-categories/: + get: + description: Read access to `/api/master-data/product-categories/`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / master data / product categories + tags: + - Master Data + /api/master-data/product-categories/{id}: + get: + description: Read access to `/api/master-data/product-categories/:id`. + parameters: + - description: Path parameter `id`. + in: path + name: id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / master data / product categories / :id + tags: + - Master Data + /api/master-data/production-standards/: + get: + description: Read access to `/api/master-data/production-standards/`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / master data / production standards + tags: + - Master Data + /api/master-data/production-standards/{id}: + get: + description: Read access to `/api/master-data/production-standards/:id`. + parameters: + - description: Path parameter `id`. + in: path + name: id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / master data / production standards / :id + tags: + - Master Data + /api/master-data/products/: + get: + description: Read access to `/api/master-data/products/`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / master data / products + tags: + - Master Data + /api/master-data/products/{id}: + get: + description: Read access to `/api/master-data/products/:id`. + parameters: + - description: Path parameter `id`. + in: path + name: id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / master data / products / :id + tags: + - Master Data + /api/master-data/suppliers/: + get: + description: Read access to `/api/master-data/suppliers/`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / master data / suppliers + tags: + - Master Data + /api/master-data/suppliers/{id}: + get: + description: Read access to `/api/master-data/suppliers/:id`. + parameters: + - description: Path parameter `id`. + in: path + name: id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / master data / suppliers / :id + tags: + - Master Data + /api/master-data/uoms/: + get: + description: Read access to `/api/master-data/uoms/`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / master data / uoms + tags: + - Master Data + /api/master-data/uoms/{id}: + get: + description: Read access to `/api/master-data/uoms/:id`. + parameters: + - description: Path parameter `id`. + in: path + name: id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / master data / uoms / :id + tags: + - Master Data + /api/master-data/warehouses/: + get: + description: Read access to `/api/master-data/warehouses/`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / master data / warehouses + tags: + - Master Data + /api/master-data/warehouses/{id}: + get: + description: Read access to `/api/master-data/warehouses/:id`. + parameters: + - description: Path parameter `id`. + in: path + name: id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / master data / warehouses / :id + tags: + - Master Data + /api/production/chickins/{id}: + get: + description: Read access to `/api/production/chickins/:id`. + parameters: + - description: Path parameter `id`. + in: path + name: id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / production / chickins / :id + tags: + - Production + /api/production/project-flock-kandangs/: + get: + description: Read access to `/api/production/project-flock-kandangs/`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / production / project flock kandangs + tags: + - Production + /api/production/project-flock-kandangs/{id}: + get: + description: Read access to `/api/production/project-flock-kandangs/:id`. + parameters: + - description: Path parameter `id`. + in: path + name: id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / production / project flock kandangs / :id + tags: + - Production + /api/production/project-flock-kandangs/{id}/closing/check: + get: + description: Read access to `/api/production/project-flock-kandangs/:id/closing/check`. + parameters: + - description: Path parameter `id`. + in: path + name: id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / production / project flock kandangs / :id / closing / check + tags: + - Production + /api/production/project-flocks/: + get: + description: Read access to `/api/production/project-flocks/`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / production / project flocks + tags: + - Production + /api/production/project-flocks/{id}: + get: + description: Read access to `/api/production/project-flocks/:id`. + parameters: + - description: Path parameter `id`. + in: path + name: id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / production / project flocks / :id + tags: + - Production + /api/production/project-flocks/kandangs/lookup: + get: + description: Read access to `/api/production/project-flocks/kandangs/lookup`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / production / project flocks / kandangs / lookup + tags: + - Production + /api/production/project-flocks/locations/{location_id}/periods: + get: + description: Read access to `/api/production/project-flocks/locations/:location_id/periods`. + parameters: + - description: Path parameter `location_id`. + in: path + name: location_id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / production / project flocks / locations / :location_id / periods + tags: + - Production + /api/production/recordings/: + get: + description: Read access to `/api/production/recordings/`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / production / recordings + tags: + - Production + /api/production/recordings/{id}: + get: + description: Read access to `/api/production/recordings/:id`. + parameters: + - description: Path parameter `id`. + in: path + name: id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / production / recordings / :id + tags: + - Production + /api/production/recordings/next-day: + get: + description: Read access to `/api/production/recordings/next-day`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / production / recordings / next day + tags: + - Production + /api/production/transfer_layings/: + get: + description: Read access to `/api/production/transfer_layings/`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / production / transfer_layings + tags: + - Production + /api/production/transfer_layings/{id}: + get: + description: Read access to `/api/production/transfer_layings/:id`. + parameters: + - description: Path parameter `id`. + in: path + name: id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / production / transfer_layings / :id + tags: + - Production + /api/production/transfer_layings/project-flocks/{project_flock_id}/available-qty: + get: + description: Read access to `/api/production/transfer_layings/project-flocks/:project_flock_id/available-qty`. + parameters: + - description: Path parameter `project_flock_id`. + in: path + name: project_flock_id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / production / transfer_layings / project flocks / :project_flock_id / available qty + tags: + - Production + /api/production/transfer_layings/project-flocks/{project_flock_id}/max-target-qty: + get: + description: Read access to `/api/production/transfer_layings/project-flocks/:project_flock_id/max-target-qty`. + parameters: + - description: Path parameter `project_flock_id`. + in: path + name: project_flock_id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / production / transfer_layings / project flocks / :project_flock_id / max target qty + tags: + - Production + /api/production/uniformities/: + get: + description: Read access to `/api/production/uniformities/`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / production / uniformities + tags: + - Production + /api/production/uniformities/{id}: + get: + description: Read access to `/api/production/uniformities/:id`. + parameters: + - description: Path parameter `id`. + in: path + name: id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / production / uniformities / :id + tags: + - Production + /api/purchases/: + get: + description: Read access to `/api/purchases/`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / purchases + tags: + - Purchases + /api/purchases/{id}: + get: + description: Read access to `/api/purchases/:id`. + parameters: + - description: Path parameter `id`. + in: path + name: id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / purchases / :id + tags: + - Purchases + /api/reports/customer-payment: + get: + description: Read access to `/api/reports/customer-payment`. + parameters: + - description: Page number. + example: 1 + in: query + name: page + required: false + schema: + type: integer + - description: Page size. + example: 10 + in: query + name: limit + required: false + schema: + type: integer + - description: Period start date (YYYY-MM-DD). + example: "2026-01-01" + in: query + name: start_date + required: false + schema: + type: string + - description: Period end date (YYYY-MM-DD). + example: "2026-01-31" + in: query + name: end_date + required: false + schema: + type: string + - description: Comma separated customer ids. + example: 1,2 + in: query + name: customer_ids + required: false + schema: + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / reports / customer payment + tags: + - Reports + /api/reports/debt-supplier: + get: + description: Read access to `/api/reports/debt-supplier`. + parameters: + - description: Page number. + example: 1 + in: query + name: page + required: false + schema: + type: integer + - description: Page size. + example: 10 + in: query + name: limit + required: false + schema: + type: integer + - description: Period start date (YYYY-MM-DD). + example: "2026-01-01" + in: query + name: start_date + required: false + schema: + type: string + - description: Period end date (YYYY-MM-DD). + example: "2026-01-31" + in: query + name: end_date + required: false + schema: + type: string + - description: Comma separated supplier ids. + example: 1,2 + in: query + name: supplier_ids + required: false + schema: + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / reports / debt supplier + tags: + - Reports + /api/reports/expense: + get: + description: Read access to `/api/reports/expense`. + parameters: + - description: Page number. + example: 1 + in: query + name: page + required: false + schema: + type: integer + - description: Page size. + example: 10 + in: query + name: limit + required: false + schema: + type: integer + - description: Search keyword. + example: operasional + in: query + name: search + required: false + schema: + type: string + - description: Expense category filter. + example: BOP + in: query + name: category + required: false + schema: + type: string + - description: Supplier id filter. + example: 1 + in: query + name: supplier_id + required: false + schema: + type: integer + - description: Location id filter. + example: 1 + in: query + name: location_id + required: false + schema: + type: integer + - description: Area id filter. + example: 1 + in: query + name: area_id + required: false + schema: + type: integer + - description: Realization date filter (YYYY-MM-DD). + example: "2026-01-15" + in: query + name: realization_date + required: false + schema: + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / reports / expense + tags: + - Reports + /api/reports/hpp-per-kandang: + get: + description: Read access to `/api/reports/hpp-per-kandang`. + parameters: + - description: Page number. + example: 1 + in: query + name: page + required: false + schema: + type: integer + - description: Page size. + example: 10 + in: query + name: limit + required: false + schema: + type: integer + - description: Daily period filter (YYYY-MM). + example: 2026-01 + in: query + name: period + required: false + schema: + type: string + - description: Location id filter. + example: 1 + in: query + name: location_id + required: false + schema: + type: integer + - description: Kandang id filter. + example: 1 + in: query + name: kandang_id + required: false + schema: + type: integer + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / reports / hpp per kandang + tags: + - Reports + /api/reports/marketing: + get: + description: Read access to `/api/reports/marketing`. + parameters: + - description: Page number. + example: 1 + in: query + name: page + required: false + schema: + type: integer + - description: Page size. + example: 10 + in: query + name: limit + required: false + schema: + type: integer + - description: Period start date (YYYY-MM-DD). + example: "2026-01-01" + in: query + name: start_date + required: false + schema: + type: string + - description: Period end date (YYYY-MM-DD). + example: "2026-01-31" + in: query + name: end_date + required: false + schema: + type: string + - description: Customer id filter. + example: 1 + in: query + name: customer_id + required: false + schema: + type: integer + - description: Location id filter. + example: 1 + in: query + name: location_id + required: false + schema: + type: integer + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / reports / marketing + tags: + - Reports + /api/reports/production-result/{idProjectFlockKandang}: + get: + description: Read access to `/api/reports/production-result/:idProjectFlockKandang`. + parameters: + - description: Path parameter `idProjectFlockKandang`. + in: path + name: idProjectFlockKandang + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / reports / production result / :idProjectFlockKandang + tags: + - Reports + /api/reports/purchase-supplier: + get: + description: Read access to `/api/reports/purchase-supplier`. + parameters: + - description: Page number. + example: 1 + in: query + name: page + required: false + schema: + type: integer + - description: Page size. + example: 10 + in: query + name: limit + required: false + schema: + type: integer + - description: Period start date (YYYY-MM-DD). + example: "2026-01-01" + in: query + name: start_date + required: false + schema: + type: string + - description: Period end date (YYYY-MM-DD). + example: "2026-01-31" + in: query + name: end_date + required: false + schema: + type: string + - description: Comma separated supplier ids. + example: 1,2 + in: query + name: supplier_id + required: false + schema: + type: string + - description: Comma separated area ids. + example: 1,2 + in: query + name: area_id + required: false + schema: + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / reports / purchase supplier + tags: + - Reports + /api/sso/callback: + get: + description: Read access to `/api/sso/callback`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + summary: GET api / sso / callback + tags: + - SSO + /api/sso/master/areas: + get: + description: Read access to `/api/sso/master/areas`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + summary: GET api / sso / master / areas + tags: + - SSO + /api/sso/master/locations: + get: + description: Read access to `/api/sso/master/locations`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + summary: GET api / sso / master / locations + tags: + - SSO + /api/sso/start: + get: + description: Read access to `/api/sso/start`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + summary: GET api / sso / start + tags: + - SSO + /api/sso/userinfo: + get: + description: Read access to `/api/sso/userinfo`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - BearerAuth: [] + summary: GET api / sso / userinfo + tags: + - SSO + /api/users/: + get: + description: Read access to `/api/users/`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / users + tags: + - Users + /api/users/{id}: + get: + description: Read access to `/api/users/:id`. + parameters: + - description: Path parameter `id`. + in: path + name: id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / users / :id + tags: + - Users + /healthz: + get: + description: Simple liveness probe. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + summary: Health check + tags: + - System + /readyz: + get: + description: Readiness probe for database and Redis. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + summary: Readiness check + tags: + - System +servers: + - url: http://localhost:8081 diff --git a/docs/postman/read-api.collection.json b/docs/postman/read-api.collection.json new file mode 100644 index 00000000..4b52da17 --- /dev/null +++ b/docs/postman/read-api.collection.json @@ -0,0 +1,1619 @@ +{ + "info": { + "name": "LTI ERP Read API", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" + }, + "item": [ + { + "item": [ + { + "item": [ + { + "name": "Health check", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/healthz" + } + }, + { + "name": "Readiness check", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/readyz" + } + } + ], + "name": "System" + } + ], + "name": "Public" + }, + { + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const apiKey = pm.environment.get('api_key');", + "const bearerToken = pm.environment.get('bearer_token');", + "if (apiKey) {", + " pm.request.headers.upsert({ key: 'X-API-Key', value: apiKey });", + " pm.request.headers.remove('Authorization');", + "} else if (bearerToken) {", + " pm.request.headers.upsert({ key: 'Authorization', value: 'Bearer ' + bearerToken });", + " pm.request.headers.remove('X-API-Key');", + "} else {", + " pm.request.headers.remove('Authorization');", + " pm.request.headers.remove('X-API-Key');", + "}" + ], + "type": "text/javascript" + } + } + ], + "item": [ + { + "item": [ + { + "name": "GET api / approvals", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/approvals/" + } + }, + { + "name": "GET api / constants", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/constants/" + } + } + ], + "name": "API" + }, + { + "item": [ + { + "name": "GET api / closings", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/closings/" + } + }, + { + "name": "GET api / closings / :projectFlockId", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/closings/{{projectFlockId}}" + } + }, + { + "name": "GET api / closings / :projectFlockId / keuangan", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/closings/{{projectFlockId}}/keuangan" + } + }, + { + "name": "GET api / closings / :projectFlockId / production data", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/closings/{{projectFlockId}}/production-data" + } + }, + { + "name": "GET api / closings / :projectFlockId / sapronak", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/closings/{{projectFlockId}}/sapronak?type=incoming" + } + }, + { + "name": "GET api / closings / :projectFlockId / sapronak / summary", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/closings/{{projectFlockId}}/sapronak/summary?type=incoming" + } + }, + { + "name": "GET api / closings / :project_flock_id / :project_flock_kandang_id / expedition hpp", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/closings/{{project_flock_id}}/{{project_flock_kandang_id}}/expedition-hpp" + } + }, + { + "name": "GET api / closings / :project_flock_id / :project_flock_kandang_id / keuangan", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/closings/{{project_flock_id}}/{{project_flock_kandang_id}}/keuangan" + } + }, + { + "name": "GET api / closings / :project_flock_id / :project_flock_kandang_id / overhead", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/closings/{{project_flock_id}}/{{project_flock_kandang_id}}/overhead" + } + }, + { + "name": "GET api / closings / :project_flock_id / :project_flock_kandang_id / penjualan", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/closings/{{project_flock_id}}/{{project_flock_kandang_id}}/penjualan" + } + }, + { + "name": "GET api / closings / :project_flock_id / :project_flock_kandang_id / perhitungan_sapronak", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/closings/{{project_flock_id}}/{{project_flock_kandang_id}}/perhitungan_sapronak" + } + }, + { + "name": "GET api / closings / :project_flock_id / expedition hpp", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/closings/{{project_flock_id}}/expedition-hpp" + } + }, + { + "name": "GET api / closings / :project_flock_id / overhead", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/closings/{{project_flock_id}}/overhead" + } + }, + { + "name": "GET api / closings / :project_flock_id / penjualan", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/closings/{{project_flock_id}}/penjualan" + } + }, + { + "name": "GET api / closings / :project_flock_id / perhitungan_sapronak", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/closings/{{project_flock_id}}/perhitungan_sapronak" + } + } + ], + "name": "Closings" + }, + { + "item": [ + { + "name": "GET api / daily checklists", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/daily-checklists/" + } + }, + { + "name": "GET api / daily checklists / phase / :idDailyChecklist", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/daily-checklists/phase/{{idDailyChecklist}}" + } + }, + { + "name": "GET api / daily checklists / relation / :idDailyChecklist", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/daily-checklists/relation/{{idDailyChecklist}}" + } + }, + { + "name": "GET api / daily checklists / report", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/daily-checklists/report" + } + }, + { + "name": "GET api / daily checklists / summary", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/daily-checklists/summary" + } + }, + { + "name": "GET api / daily checklists / tasks", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/daily-checklists/tasks" + } + } + ], + "name": "Daily Checklists" + }, + { + "item": [ + { + "name": "GET api / dashboards", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/dashboards/" + } + } + ], + "name": "Dashboards" + }, + { + "item": [ + { + "name": "GET api / expenses", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/expenses/" + } + }, + { + "name": "GET api / expenses / :id", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/expenses/{{expense_id}}" + } + } + ], + "name": "Expenses" + }, + { + "item": [ + { + "name": "GET api / finance / initial balances / :id", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/finance/initial-balances/{{initial_balance_id}}" + } + }, + { + "name": "GET api / finance / injections / :id", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/finance/injections/{{injection_id}}" + } + }, + { + "name": "GET api / finance / payments / :id", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/finance/payments/{{payment_id}}" + } + }, + { + "name": "GET api / finance / transactions", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/finance/transactions/" + } + }, + { + "name": "GET api / finance / transactions / :id", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/finance/transactions/{{transaction_id}}" + } + } + ], + "name": "Finance" + }, + { + "item": [ + { + "name": "GET api / inventory / adjustments", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/inventory/adjustments/" + } + }, + { + "name": "GET api / inventory / adjustments / :id", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/inventory/adjustments/{{adjustment_id}}" + } + }, + { + "name": "GET api / inventory / product stocks", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/inventory/product-stocks/" + } + }, + { + "name": "GET api / inventory / product stocks / :id", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/inventory/product-stocks/{{id}}" + } + }, + { + "name": "GET api / inventory / product warehouses", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/inventory/product-warehouses/" + } + }, + { + "name": "GET api / inventory / product warehouses / :id", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/inventory/product-warehouses/{{id}}" + } + }, + { + "name": "GET api / inventory / transfers", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/inventory/transfers/" + } + }, + { + "name": "GET api / inventory / transfers / :id", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/inventory/transfers/{{transfer_id}}" + } + } + ], + "name": "Inventory" + }, + { + "item": [ + { + "name": "GET api / marketing", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/marketing/" + } + }, + { + "name": "GET api / marketing / :id", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/marketing/{{id}}" + } + } + ], + "name": "Marketing" + }, + { + "item": [ + { + "name": "GET api / master data / areas", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/master-data/areas/" + } + }, + { + "name": "GET api / master data / areas / :id", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/master-data/areas/{{area_id}}" + } + }, + { + "name": "GET api / master data / banks", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/master-data/banks/" + } + }, + { + "name": "GET api / master data / banks / :id", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/master-data/banks/{{bank_id}}" + } + }, + { + "name": "GET api / master data / config checklists", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/master-data/config-checklists/" + } + }, + { + "name": "GET api / master data / config checklists / :id", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/master-data/config-checklists/{{id}}" + } + }, + { + "name": "GET api / master data / customers", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/master-data/customers/" + } + }, + { + "name": "GET api / master data / customers / :id", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/master-data/customers/{{customer_id}}" + } + }, + { + "name": "GET api / master data / employees", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/master-data/employees/" + } + }, + { + "name": "GET api / master data / employees / :id", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/master-data/employees/{{employee_id}}" + } + }, + { + "name": "GET api / master data / fcrs", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/master-data/fcrs/" + } + }, + { + "name": "GET api / master data / fcrs / :id", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/master-data/fcrs/{{id}}" + } + }, + { + "name": "GET api / master data / flocks", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/master-data/flocks/" + } + }, + { + "name": "GET api / master data / flocks / :id", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/master-data/flocks/{{flock_id}}" + } + }, + { + "name": "GET api / master data / kandang groups", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/master-data/kandang-groups/" + } + }, + { + "name": "GET api / master data / kandang groups / :id", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/master-data/kandang-groups/{{id}}" + } + }, + { + "name": "GET api / master data / kandangs", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/master-data/kandangs/" + } + }, + { + "name": "GET api / master data / kandangs / :id", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/master-data/kandangs/{{id}}" + } + }, + { + "name": "GET api / master data / locations", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/master-data/locations/" + } + }, + { + "name": "GET api / master data / locations / :id", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/master-data/locations/{{location_id}}" + } + }, + { + "name": "GET api / master data / nonstocks", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/master-data/nonstocks/" + } + }, + { + "name": "GET api / master data / nonstocks / :id", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/master-data/nonstocks/{{nonstock_id}}" + } + }, + { + "name": "GET api / master data / phase activities", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/master-data/phase-activities/" + } + }, + { + "name": "GET api / master data / phase activities / :id", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/master-data/phase-activities/{{id}}" + } + }, + { + "name": "GET api / master data / phases", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/master-data/phases/" + } + }, + { + "name": "GET api / master data / phases / :id", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/master-data/phases/{{id}}" + } + }, + { + "name": "GET api / master data / product categories", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/master-data/product-categories/" + } + }, + { + "name": "GET api / master data / product categories / :id", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/master-data/product-categories/{{product_category_id}}" + } + }, + { + "name": "GET api / master data / production standards", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/master-data/production-standards/" + } + }, + { + "name": "GET api / master data / production standards / :id", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/master-data/production-standards/{{id}}" + } + }, + { + "name": "GET api / master data / products", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/master-data/products/" + } + }, + { + "name": "GET api / master data / products / :id", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/master-data/products/{{product_id}}" + } + }, + { + "name": "GET api / master data / suppliers", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/master-data/suppliers/" + } + }, + { + "name": "GET api / master data / suppliers / :id", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/master-data/suppliers/{{supplier_id}}" + } + }, + { + "name": "GET api / master data / uoms", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/master-data/uoms/" + } + }, + { + "name": "GET api / master data / uoms / :id", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/master-data/uoms/{{uom_id}}" + } + }, + { + "name": "GET api / master data / warehouses", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/master-data/warehouses/" + } + }, + { + "name": "GET api / master data / warehouses / :id", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/master-data/warehouses/{{warehouse_id}}" + } + } + ], + "name": "Master Data" + }, + { + "item": [ + { + "name": "GET api / production / chickins / :id", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/production/chickins/{{chickin_id}}" + } + }, + { + "name": "GET api / production / project flock kandangs", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/production/project-flock-kandangs/" + } + }, + { + "name": "GET api / production / project flock kandangs / :id", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/production/project-flock-kandangs/{{id}}" + } + }, + { + "name": "GET api / production / project flock kandangs / :id / closing / check", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/production/project-flock-kandangs/{{id}}/closing/check" + } + }, + { + "name": "GET api / production / project flocks", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/production/project-flocks/" + } + }, + { + "name": "GET api / production / project flocks / :id", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/production/project-flocks/{{id}}" + } + }, + { + "name": "GET api / production / project flocks / kandangs / lookup", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/production/project-flocks/kandangs/lookup" + } + }, + { + "name": "GET api / production / project flocks / locations / :location_id / periods", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/production/project-flocks/locations/{{location_id}}/periods" + } + }, + { + "name": "GET api / production / recordings", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/production/recordings/" + } + }, + { + "name": "GET api / production / recordings / :id", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/production/recordings/{{recording_id}}" + } + }, + { + "name": "GET api / production / recordings / next day", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/production/recordings/next-day" + } + }, + { + "name": "GET api / production / transfer_layings", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/production/transfer_layings/" + } + }, + { + "name": "GET api / production / transfer_layings / :id", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/production/transfer_layings/{{id}}" + } + }, + { + "name": "GET api / production / transfer_layings / project flocks / :project_flock_id / available qty", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/production/transfer_layings/project-flocks/{{project_flock_id}}/available-qty" + } + }, + { + "name": "GET api / production / transfer_layings / project flocks / :project_flock_id / max target qty", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/production/transfer_layings/project-flocks/{{project_flock_id}}/max-target-qty" + } + }, + { + "name": "GET api / production / uniformities", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/production/uniformities/" + } + }, + { + "name": "GET api / production / uniformities / :id", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/production/uniformities/{{uniformity_id}}" + } + } + ], + "name": "Production" + }, + { + "item": [ + { + "name": "GET api / purchases", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/purchases/" + } + }, + { + "name": "GET api / purchases / :id", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/purchases/{{purchase_id}}" + } + } + ], + "name": "Purchases" + }, + { + "item": [ + { + "name": "GET api / reports / customer payment", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/reports/customer-payment?customer_ids={{customer_id}}" + } + }, + { + "name": "GET api / reports / debt supplier", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/reports/debt-supplier?supplier_ids={{supplier_id}}" + } + }, + { + "name": "GET api / reports / expense", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/reports/expense" + } + }, + { + "name": "GET api / reports / hpp per kandang", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/reports/hpp-per-kandang" + } + }, + { + "name": "GET api / reports / marketing", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/reports/marketing" + } + }, + { + "name": "GET api / reports / production result / :idProjectFlockKandang", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/reports/production-result/{{idProjectFlockKandang}}" + } + }, + { + "name": "GET api / reports / purchase supplier", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/reports/purchase-supplier" + } + } + ], + "name": "Reports" + }, + { + "item": [ + { + "name": "GET api / users", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/users/" + } + }, + { + "name": "GET api / users / :id", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/users/{{user_id}}" + } + } + ], + "name": "Users" + } + ], + "name": "Dashboard API Key" + }, + { + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const bearerToken = pm.environment.get('bearer_token');", + "pm.request.headers.remove('X-API-Key');", + "if (bearerToken) {", + " pm.request.headers.upsert({ key: 'Authorization', value: 'Bearer ' + bearerToken });", + "} else {", + " pm.request.headers.remove('Authorization');", + "}" + ], + "type": "text/javascript" + } + } + ], + "item": [ + { + "item": [ + { + "name": "GET api / sso / callback", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/sso/callback" + } + }, + { + "name": "GET api / sso / master / areas", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/sso/master/areas" + } + }, + { + "name": "GET api / sso / master / locations", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/sso/master/locations" + } + }, + { + "name": "GET api / sso / start", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/sso/start" + } + }, + { + "name": "GET api / sso / userinfo", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/sso/userinfo" + } + } + ], + "name": "SSO" + } + ], + "name": "Internal/OAuth Reference" + } + ] +} \ No newline at end of file diff --git a/docs/postman/read-api.environment.json b/docs/postman/read-api.environment.json new file mode 100644 index 00000000..2eba4d52 --- /dev/null +++ b/docs/postman/read-api.environment.json @@ -0,0 +1,174 @@ +{ + "_postman_exported_at": "2026-04-14T00:00:00Z", + "_postman_exported_using": "Codex", + "_postman_variable_scope": "environment", + "id": "lti-read-api-local", + "name": "LTI ERP Read API.local", + "values": [ + { + "enabled": true, + "key": "adjustment_id", + "value": "1" + }, + { + "enabled": true, + "key": "api_key", + "value": "" + }, + { + "enabled": true, + "key": "area_id", + "value": "1" + }, + { + "enabled": true, + "key": "bank_id", + "value": "1" + }, + { + "enabled": true, + "key": "base_url", + "value": "http://localhost:8081" + }, + { + "enabled": true, + "key": "bearer_token", + "value": "" + }, + { + "enabled": true, + "key": "chickin_id", + "value": "1" + }, + { + "enabled": true, + "key": "customer_id", + "value": "1" + }, + { + "enabled": true, + "key": "employee_id", + "value": "1" + }, + { + "enabled": true, + "key": "expense_id", + "value": "1" + }, + { + "enabled": true, + "key": "flock_id", + "value": "1" + }, + { + "enabled": true, + "key": "id", + "value": "1" + }, + { + "enabled": true, + "key": "idDailyChecklist", + "value": "1" + }, + { + "enabled": true, + "key": "idProjectFlockKandang", + "value": "1" + }, + { + "enabled": true, + "key": "initial_balance_id", + "value": "1" + }, + { + "enabled": true, + "key": "injection_id", + "value": "1" + }, + { + "enabled": true, + "key": "location_id", + "value": "1" + }, + { + "enabled": true, + "key": "nonstock_id", + "value": "1" + }, + { + "enabled": true, + "key": "payment_id", + "value": "1" + }, + { + "enabled": true, + "key": "product_category_id", + "value": "1" + }, + { + "enabled": true, + "key": "product_id", + "value": "1" + }, + { + "enabled": true, + "key": "projectFlockId", + "value": "1" + }, + { + "enabled": true, + "key": "project_flock_id", + "value": "1" + }, + { + "enabled": true, + "key": "project_flock_kandang_id", + "value": "1" + }, + { + "enabled": true, + "key": "purchase_id", + "value": "1" + }, + { + "enabled": true, + "key": "recording_id", + "value": "1" + }, + { + "enabled": true, + "key": "supplier_id", + "value": "1" + }, + { + "enabled": true, + "key": "transaction_id", + "value": "1" + }, + { + "enabled": true, + "key": "transfer_id", + "value": "1" + }, + { + "enabled": true, + "key": "uniformity_id", + "value": "1" + }, + { + "enabled": true, + "key": "uom_id", + "value": "1" + }, + { + "enabled": true, + "key": "user_id", + "value": "1" + }, + { + "enabled": true, + "key": "warehouse_id", + "value": "1" + } + ] +} \ No newline at end of file diff --git a/internal/apikeys/defaults.go b/internal/apikeys/defaults.go new file mode 100644 index 00000000..29daeda5 --- /dev/null +++ b/internal/apikeys/defaults.go @@ -0,0 +1,92 @@ +package apikeys + +func DefaultDashboardPermissions() []string { + return []string{ + "lti.approval.list", + "lti.closing.list", + "lti.closing.detail", + "lti.daily_checklist.create", + "lti.daily_checklist.dashboard.list", + "lti.daily_checklist.detail", + "lti.daily_checklist.list", + "lti.daily_checklist.master_data.activity", + "lti.daily_checklist.master_data.configuration", + "lti.daily_checklist.master_data.employee", + "lti.daily_checklist.reports", + "lti.dashboard.list", + "lti.expense.detail", + "lti.expense.list", + "lti.finance.initial_balances.detail", + "lti.finance.injections.detail", + "lti.finance.payments.detail", + "lti.finance.transactions.detail", + "lti.finance.transactions.list", + "lti.inventory.detail", + "lti.inventory.list", + "lti.inventory.product_stock.detail", + "lti.inventory.product_stock.list", + "lti.inventory.product_warehouses.detail", + "lti.inventory.product_warehouses.list", + "lti.inventory.transfer.detail", + "lti.inventory.transfer.list", + "lti.marketing.delivery_order.detail", + "lti.marketing.delivery_order.list", + "lti.master.area.detail", + "lti.master.area.list", + "lti.master.banks.detail", + "lti.master.banks.list", + "lti.master.customer.detail", + "lti.master.customer.list", + "lti.master.fcr.detail", + "lti.master.fcr.list", + "lti.master.flocks.detail", + "lti.master.flocks.list", + "lti.master.kandangs.detail", + "lti.master.kandangs.list", + "lti.master.locations.detail", + "lti.master.locations.list", + "lti.master.nonstocks.detail", + "lti.master.nonstocks.list", + "lti.master.product_categories.detail", + "lti.master.product_categories.list", + "lti.master.products.detail", + "lti.master.products.list", + "lti.master.production_standards.detail", + "lti.master.production_standards.list", + "lti.master.suppliers.detail", + "lti.master.suppliers.list", + "lti.master.uoms.detail", + "lti.master.uoms.list", + "lti.master.warehouses.detail", + "lti.master.warehouses.list", + "lti.production.chickins.detail", + "lti.production.project_flock_kandangs.closing.detail", + "lti.production.project_flock_kandangs.detail", + "lti.production.project_flock_kandangs.list", + "lti.production.project_flocks.detail", + "lti.production.project_flocks.list", + "lti.production.project_flocks.lookup", + "lti.production.project_flocks.next_period", + "lti.production.recording.detail", + "lti.production.recording.list", + "lti.production.recording.next_day", + "lti.production.transfer_to_laying.create", + "lti.production.transfer_to_laying.detail", + "lti.production.transfer_to_laying.getavailableqty", + "lti.production.transfer_to_laying.list", + "lti.production.uniformity.detail", + "lti.production.uniformity.list", + "lti.purchase.detail", + "lti.purchase.list", + "lti.repport.customerpayment.list", + "lti.repport.debtsupplier.list", + "lti.repport.delivery.list", + "lti.repport.expense.list", + "lti.repport.gethppperkandang.list", + "lti.repport.production_result.list", + "lti.repport.purchasesupplier.list", + "lti.users.detail", + "lti.users.list", + "lti.daily_checklist.master_data.kandang", + } +} diff --git a/internal/apikeys/repository.go b/internal/apikeys/repository.go new file mode 100644 index 00000000..7348d7c5 --- /dev/null +++ b/internal/apikeys/repository.go @@ -0,0 +1,107 @@ +package apikeys + +import ( + "context" + "errors" + "time" + + entity "gitlab.com/mbugroup/lti-api.git/internal/entities" + "gorm.io/gorm" +) + +type Repository interface { + Create(ctx context.Context, record *entity.IntegrationAPIKey) error + GetByEnvironmentAndPrefix(ctx context.Context, environment, prefix string) (*entity.IntegrationAPIKey, error) + List(ctx context.Context, environment string) ([]entity.IntegrationAPIKey, error) + Revoke(ctx context.Context, environment, prefix string, revokedAt time.Time) error + TouchLastUsed(ctx context.Context, id uint, usedAt time.Time, usedFrom string) error +} + +type repository struct { + db *gorm.DB +} + +func NewRepository(db *gorm.DB) Repository { + return &repository{db: db} +} + +func (r *repository) Create(ctx context.Context, record *entity.IntegrationAPIKey) error { + if r.db == nil { + return errors.New("database not configured") + } + return r.db.WithContext(ctx).Create(record).Error +} + +func (r *repository) GetByEnvironmentAndPrefix(ctx context.Context, environment, prefix string) (*entity.IntegrationAPIKey, error) { + if r.db == nil { + return nil, errors.New("database not configured") + } + + var record entity.IntegrationAPIKey + if err := r.db.WithContext(ctx). + Where("environment = ?", environment). + Where("key_prefix = ?", prefix). + First(&record).Error; err != nil { + return nil, err + } + + return &record, nil +} + +func (r *repository) List(ctx context.Context, environment string) ([]entity.IntegrationAPIKey, error) { + if r.db == nil { + return nil, errors.New("database not configured") + } + + query := r.db.WithContext(ctx).Model(&entity.IntegrationAPIKey{}) + if environment != "" { + query = query.Where("environment = ?", environment) + } + + var records []entity.IntegrationAPIKey + if err := query.Order("environment ASC").Order("name ASC").Find(&records).Error; err != nil { + return nil, err + } + + return records, nil +} + +func (r *repository) Revoke(ctx context.Context, environment, prefix string, revokedAt time.Time) error { + if r.db == nil { + return errors.New("database not configured") + } + + updates := map[string]any{ + "status": entity.IntegrationAPIKeyStatusRevoked, + "revoked_at": revokedAt, + "updated_at": revokedAt, + } + + result := r.db.WithContext(ctx). + Model(&entity.IntegrationAPIKey{}). + Where("environment = ?", environment). + Where("key_prefix = ?", prefix). + Updates(updates) + if result.Error != nil { + return result.Error + } + if result.RowsAffected == 0 { + return gorm.ErrRecordNotFound + } + return nil +} + +func (r *repository) TouchLastUsed(ctx context.Context, id uint, usedAt time.Time, usedFrom string) error { + if r.db == nil { + return errors.New("database not configured") + } + + return r.db.WithContext(ctx). + Model(&entity.IntegrationAPIKey{}). + Where("id = ?", id). + Updates(map[string]any{ + "last_used_at": usedAt, + "last_used_from": usedFrom, + "updated_at": usedAt, + }).Error +} diff --git a/internal/apikeys/service.go b/internal/apikeys/service.go new file mode 100644 index 00000000..5059b18a --- /dev/null +++ b/internal/apikeys/service.go @@ -0,0 +1,233 @@ +package apikeys + +import ( + "context" + "crypto/rand" + "encoding/base32" + "errors" + "fmt" + "strings" + "time" + + entity "gitlab.com/mbugroup/lti-api.git/internal/entities" + "gitlab.com/mbugroup/lti-api.git/internal/utils" + "gitlab.com/mbugroup/lti-api.git/internal/utils/secure" + "gorm.io/gorm" +) + +var ( + ErrInvalidAPIKey = errors.New("invalid api key") + ErrInactiveKey = errors.New("inactive api key") +) + +type Principal struct { + ID uint + Name string + Environment string + Permissions []string + AllArea bool + AreaIDs []uint + AllLocation bool + LocationIDs []uint +} + +type Authenticator interface { + Authenticate(ctx context.Context, rawKey, source string) (*Principal, error) +} + +type Service interface { + Authenticator + Create(ctx context.Context, input CreateInput) (*IssuedKey, error) + List(ctx context.Context, environment string) ([]entity.IntegrationAPIKey, error) + Revoke(ctx context.Context, environment, prefix string) error +} + +type CreateInput struct { + Name string + Environment string + PermissionCodes []string + AllArea bool + AreaIDs []uint + AllLocation bool + LocationIDs []uint +} + +type IssuedKey struct { + Key string + Record *entity.IntegrationAPIKey +} + +type service struct { + repo Repository + now func() time.Time +} + +func NewService(db *gorm.DB) Service { + return &service{ + repo: NewRepository(db), + now: time.Now, + } +} + +func (s *service) Authenticate(ctx context.Context, rawKey, source string) (*Principal, error) { + environment, prefix, secret, err := parseRawKey(rawKey) + if err != nil { + return nil, ErrInvalidAPIKey + } + + record, err := s.repo.GetByEnvironmentAndPrefix(ctx, environment, prefix) + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, ErrInvalidAPIKey + } + return nil, err + } + + if !strings.EqualFold(record.Status, entity.IntegrationAPIKeyStatusActive) || record.RevokedAt != nil { + return nil, ErrInactiveKey + } + if !secure.Verify(record.KeyHash, secret) { + return nil, ErrInvalidAPIKey + } + + usedAt := s.now().UTC() + if err := s.repo.TouchLastUsed(ctx, record.ID, usedAt, strings.TrimSpace(source)); err != nil { + utils.Log.WithError(err).Warn("api key: failed to update last_used fields") + } + + return &Principal{ + ID: record.ID, + Name: record.Name, + Environment: record.Environment, + Permissions: canonicalPermissions(record.PermissionCodes), + AllArea: record.AllArea, + AreaIDs: uniqueUint(record.AreaIDs), + AllLocation: record.AllLocation, + LocationIDs: uniqueUint(record.LocationIDs), + }, nil +} + +func (s *service) Create(ctx context.Context, input CreateInput) (*IssuedKey, error) { + name := strings.TrimSpace(input.Name) + environment := strings.ToLower(strings.TrimSpace(input.Environment)) + if name == "" || environment == "" { + return nil, fmt.Errorf("name and environment are required") + } + + prefix, err := randomToken(10) + if err != nil { + return nil, err + } + secret, err := randomToken(24) + if err != nil { + return nil, err + } + + hash, err := secure.Hash(secret, nil) + if err != nil { + return nil, err + } + + record := &entity.IntegrationAPIKey{ + Name: name, + Environment: environment, + Status: entity.IntegrationAPIKeyStatusActive, + KeyPrefix: prefix, + KeyHash: hash, + PermissionCodes: canonicalPermissions(input.PermissionCodes), + AllArea: input.AllArea, + AreaIDs: uniqueUint(input.AreaIDs), + AllLocation: input.AllLocation, + LocationIDs: uniqueUint(input.LocationIDs), + } + + if err := s.repo.Create(ctx, record); err != nil { + return nil, err + } + + return &IssuedKey{ + Key: fmt.Sprintf("lti_%s_%s_%s", environment, prefix, secret), + Record: record, + }, nil +} + +func (s *service) List(ctx context.Context, environment string) ([]entity.IntegrationAPIKey, error) { + return s.repo.List(ctx, strings.ToLower(strings.TrimSpace(environment))) +} + +func (s *service) Revoke(ctx context.Context, environment, prefix string) error { + environment = strings.ToLower(strings.TrimSpace(environment)) + prefix = strings.TrimSpace(prefix) + if environment == "" || prefix == "" { + return fmt.Errorf("environment and prefix are required") + } + return s.repo.Revoke(ctx, environment, prefix, s.now().UTC()) +} + +func parseRawKey(rawKey string) (environment string, prefix string, secret string, err error) { + rawKey = strings.TrimSpace(rawKey) + parts := strings.Split(rawKey, "_") + if len(parts) != 4 || parts[0] != "lti" { + return "", "", "", ErrInvalidAPIKey + } + + environment = strings.ToLower(strings.TrimSpace(parts[1])) + prefix = strings.TrimSpace(parts[2]) + secret = strings.TrimSpace(parts[3]) + if environment == "" || prefix == "" || secret == "" { + return "", "", "", ErrInvalidAPIKey + } + + return environment, prefix, secret, nil +} + +func randomToken(size int) (string, error) { + buf := make([]byte, size) + if _, err := rand.Read(buf); err != nil { + return "", err + } + + encoder := base32.StdEncoding.WithPadding(base32.NoPadding) + return strings.ToLower(encoder.EncodeToString(buf)), nil +} + +func canonicalPermissions(perms []string) []string { + if len(perms) == 0 { + return nil + } + + seen := make(map[string]struct{}, len(perms)) + result := make([]string, 0, len(perms)) + for _, perm := range perms { + perm = strings.ToLower(strings.TrimSpace(perm)) + if perm == "" { + continue + } + if _, ok := seen[perm]; ok { + continue + } + seen[perm] = struct{}{} + result = append(result, perm) + } + return result +} + +func uniqueUint(values []uint) []uint { + if len(values) == 0 { + return nil + } + + seen := make(map[uint]struct{}, len(values)) + result := make([]uint, 0, len(values)) + for _, value := range values { + if value == 0 { + continue + } + if _, ok := seen[value]; ok { + continue + } + seen[value] = struct{}{} + result = append(result, value) + } + return result +} diff --git a/internal/config/config.go b/internal/config/config.go index 040e0fdd..2a5d640e 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -23,6 +23,7 @@ type SSOClientConfig struct { var ( IsProd bool + AppEnv string AppHost string Version string LogLevel string @@ -84,7 +85,8 @@ func init() { loadConfig() // server configuration - IsProd = viper.GetString("APP_ENV") == "prod" + AppEnv = defaultString(strings.TrimSpace(viper.GetString("APP_ENV")), "development") + IsProd = AppEnv == "prod" AppHost = viper.GetString("APP_HOST") AppPort = viper.GetInt("APP_PORT") Version = viper.GetString("VERSION") @@ -111,7 +113,7 @@ func init() { // 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") + CORSAllowHeaders = parseListWithDefault("CORS_ALLOW_HEADERS", "Content-Type,Authorization,X-API-Key,X-Requested-With") CORSExposeHeaders = parseList("CORS_EXPOSE_HEADERS") CORSAllowCredentials = viper.GetBool("CORS_ALLOW_CREDENTIALS") CORSMaxAge = viper.GetInt("CORS_MAX_AGE") diff --git a/internal/database/migrations/20260414090000_create_integration_api_keys.down.sql b/internal/database/migrations/20260414090000_create_integration_api_keys.down.sql new file mode 100644 index 00000000..874026a5 --- /dev/null +++ b/internal/database/migrations/20260414090000_create_integration_api_keys.down.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS integration_api_keys; diff --git a/internal/database/migrations/20260414090000_create_integration_api_keys.up.sql b/internal/database/migrations/20260414090000_create_integration_api_keys.up.sql new file mode 100644 index 00000000..ce18a84f --- /dev/null +++ b/internal/database/migrations/20260414090000_create_integration_api_keys.up.sql @@ -0,0 +1,23 @@ +CREATE TABLE IF NOT EXISTS integration_api_keys ( + id BIGSERIAL PRIMARY KEY, + name VARCHAR(100) NOT NULL, + environment VARCHAR(50) NOT NULL, + status VARCHAR(20) NOT NULL DEFAULT 'active', + key_prefix VARCHAR(64) NOT NULL, + key_hash TEXT NOT NULL, + permission_codes JSONB NOT NULL DEFAULT '[]'::jsonb, + all_area BOOLEAN NOT NULL DEFAULT FALSE, + area_ids JSONB NOT NULL DEFAULT '[]'::jsonb, + all_location BOOLEAN NOT NULL DEFAULT FALSE, + location_ids JSONB NOT NULL DEFAULT '[]'::jsonb, + last_used_at TIMESTAMPTZ NULL, + last_used_from VARCHAR(128) NULL, + revoked_at TIMESTAMPTZ NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + deleted_at TIMESTAMPTZ NULL, + CONSTRAINT uq_integration_api_keys_environment_prefix UNIQUE (environment, key_prefix) +); + +CREATE INDEX idx_integration_api_keys_status ON integration_api_keys (status); +CREATE INDEX idx_integration_api_keys_deleted_at ON integration_api_keys (deleted_at); diff --git a/internal/entities/integration_api_key.go b/internal/entities/integration_api_key.go new file mode 100644 index 00000000..dbaecdc6 --- /dev/null +++ b/internal/entities/integration_api_key.go @@ -0,0 +1,36 @@ +package entities + +import ( + "time" + + "gorm.io/gorm" +) + +const ( + IntegrationAPIKeyStatusActive = "active" + IntegrationAPIKeyStatusRevoked = "revoked" +) + +type IntegrationAPIKey struct { + ID uint `gorm:"primaryKey"` + Name string `gorm:"type:varchar(100);not null"` + Environment string `gorm:"type:varchar(50);not null;uniqueIndex:idx_integration_api_keys_env_prefix,priority:1"` + Status string `gorm:"type:varchar(20);not null;default:active;index"` + KeyPrefix string `gorm:"type:varchar(64);not null;uniqueIndex:idx_integration_api_keys_env_prefix,priority:2"` + KeyHash string `gorm:"type:text;not null"` + PermissionCodes []string `gorm:"type:jsonb;serializer:json;not null"` + AllArea bool `gorm:"not null;default:false"` + AreaIDs []uint `gorm:"type:jsonb;serializer:json;not null"` + AllLocation bool `gorm:"not null;default:false"` + LocationIDs []uint `gorm:"type:jsonb;serializer:json;not null"` + LastUsedAt *time.Time + LastUsedFrom string `gorm:"type:varchar(128)"` + RevokedAt *time.Time + CreatedAt time.Time `gorm:"autoCreateTime"` + UpdatedAt time.Time `gorm:"autoUpdateTime"` + DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` +} + +func (IntegrationAPIKey) TableName() string { + return "integration_api_keys" +} diff --git a/internal/middleware/auth.go b/internal/middleware/auth.go index e7640e7b..eddb7afe 100644 --- a/internal/middleware/auth.go +++ b/internal/middleware/auth.go @@ -1,9 +1,13 @@ package middleware import ( + "context" + "errors" "strings" + "sync" "github.com/gofiber/fiber/v2" + "gitlab.com/mbugroup/lti-api.git/internal/apikeys" "gitlab.com/mbugroup/lti-api.git/internal/config" entity "gitlab.com/mbugroup/lti-api.git/internal/entities" "gitlab.com/mbugroup/lti-api.git/internal/modules/sso/session" @@ -17,11 +21,21 @@ const ( authUserLocalsKey = "auth.user" ) +var ( + verifyAccessTokenFunc = sso.VerifyAccessToken + fetchProfileFunc = sso.FetchProfile + + apiKeyAuthMu sync.RWMutex + apiKeyAuthenticator apikeys.Authenticator +) + // AuthContext keeps authentication details captured by the middleware. type AuthContext struct { Token string Verification *sso.VerificationResult User *entity.User + PrincipalType string + PrincipalName string Roles []sso.Role Permissions map[string]struct{} UserAreaIDs []uint @@ -30,6 +44,13 @@ type AuthContext struct { UserAllLocation bool } +func SetAPIKeyAuthenticator(authenticator apikeys.Authenticator) { + apiKeyAuthMu.Lock() + defer apiKeyAuthMu.Unlock() + + apiKeyAuthenticator = authenticator +} + // Auth validates the incoming request against the central SSO access token and // loads the corresponding local user. Optional scopes can be provided to enforce // fine-grained authorization using the SSO access token scopes. @@ -62,10 +83,20 @@ func Auth(userService service.UserService, requiredScopes ...string) fiber.Handl } } if token == "" { + if c.Method() == fiber.MethodGet { + if err := authenticateAPIKey(c); err == nil { + if len(requiredScopes) > 0 { + return fiber.NewError(fiber.StatusForbidden, "Insufficient scope") + } + return c.Next() + } else if err != nil && !errors.Is(err, apikeys.ErrInvalidAPIKey) && !errors.Is(err, apikeys.ErrInactiveKey) { + return err + } + } return fiber.NewError(fiber.StatusUnauthorized, "Please authenticate") } - verification, err := sso.VerifyAccessToken(token) + verification, err := verifyAccessTokenFunc(token) if err != nil { if sso.IsSignatureError(err) { logSignatureError("auth", tokenSource, token, err) @@ -99,7 +130,7 @@ func Auth(userService service.UserService, requiredScopes ...string) fiber.Handl permissions := make(map[string]struct{}) var profile *sso.UserProfile if verification.UserID != 0 { - if p, err := sso.FetchProfile(c.Context(), token, verification); err != nil { + if p, err := fetchProfileFunc(c.Context(), token, verification); err != nil { utils.Log.WithError(err).Warn("auth: failed to fetch sso profile") } else { profile = p @@ -118,6 +149,8 @@ func Auth(userService service.UserService, requiredScopes ...string) fiber.Handl Token: token, Verification: verification, User: user, + PrincipalType: "user", + PrincipalName: user.Name, Roles: roles, Permissions: permissions, UserAreaIDs: nil, @@ -219,6 +252,57 @@ func bearerToken(c *fiber.Ctx) string { return "" } +func authenticateAPIKey(c *fiber.Ctx) error { + rawKey := strings.TrimSpace(c.Get("X-API-Key")) + if rawKey == "" { + return apikeys.ErrInvalidAPIKey + } + + authenticator := currentAPIKeyAuthenticator() + if authenticator == nil { + return apikeys.ErrInvalidAPIKey + } + + principal, err := authenticator.Authenticate(context.Background(), rawKey, c.IP()) + if err != nil { + if errors.Is(err, apikeys.ErrInvalidAPIKey) || errors.Is(err, apikeys.ErrInactiveKey) { + return apikeys.ErrInvalidAPIKey + } + utils.Log.WithError(err).Warn("auth: api key authentication failed") + return fiber.NewError(fiber.StatusInternalServerError, "Failed to authenticate request") + } + + permissions := make(map[string]struct{}, len(principal.Permissions)) + for _, perm := range principal.Permissions { + if canonical := canonicalPermission(perm); canonical != "" { + permissions[canonical] = struct{}{} + } + } + + c.Locals(authContextLocalsKey, &AuthContext{ + Token: "", + Verification: nil, + User: nil, + PrincipalType: "api_key", + PrincipalName: principal.Name, + Roles: nil, + Permissions: permissions, + UserAreaIDs: principal.AreaIDs, + UserLocationIDs: principal.LocationIDs, + UserAllArea: principal.AllArea, + UserAllLocation: principal.AllLocation, + }) + c.Locals(authUserLocalsKey, nil) + return nil +} + +func currentAPIKeyAuthenticator() apikeys.Authenticator { + apiKeyAuthMu.RLock() + defer apiKeyAuthMu.RUnlock() + + return apiKeyAuthenticator +} + func hasAllScopes(have, required []string) bool { if len(required) == 0 { return true diff --git a/internal/middleware/auth_apikey_test.go b/internal/middleware/auth_apikey_test.go new file mode 100644 index 00000000..4ab8c1a6 --- /dev/null +++ b/internal/middleware/auth_apikey_test.go @@ -0,0 +1,239 @@ +package middleware + +import ( + "context" + "errors" + "net/http/httptest" + "testing" + "time" + + "github.com/gofiber/fiber/v2" + "github.com/golang-jwt/jwt/v5" + "gitlab.com/mbugroup/lti-api.git/internal/apikeys" + entity "gitlab.com/mbugroup/lti-api.git/internal/entities" + sso "gitlab.com/mbugroup/lti-api.git/internal/modules/sso/verifier" + userValidation "gitlab.com/mbugroup/lti-api.git/internal/modules/users/validations" +) + +type stubUserService struct { + user *entity.User + err error +} + +func (s *stubUserService) GetAll(_ *fiber.Ctx, _ *userValidation.Query) ([]entity.User, int64, error) { + return nil, 0, nil +} + +func (s *stubUserService) GetOne(_ *fiber.Ctx, _ uint) (*entity.User, error) { + return s.user, s.err +} + +func (s *stubUserService) CreateOne(_ *fiber.Ctx, _ *userValidation.Create) (*entity.User, error) { + return nil, nil +} + +func (s *stubUserService) UpdateOne(_ *fiber.Ctx, _ *userValidation.Update, _ uint) (*entity.User, error) { + return nil, nil +} + +func (s *stubUserService) DeleteOne(_ *fiber.Ctx, _ uint) error { + return nil +} + +func (s *stubUserService) GetBySSOUserID(_ *fiber.Ctx, _ uint) (*entity.User, error) { + return s.user, s.err +} + +type stubAPIKeyAuthenticator struct { + principal *apikeys.Principal + err error +} + +func (s *stubAPIKeyAuthenticator) Authenticate(_ context.Context, _ string, _ string) (*apikeys.Principal, error) { + return s.principal, s.err +} + +func TestAuthAllowsAPIKeyOnGet(t *testing.T) { + SetAPIKeyAuthenticator(&stubAPIKeyAuthenticator{ + principal: &apikeys.Principal{ + Name: "dashboard", + Permissions: []string{"perm.read"}, + LocationIDs: []uint{3, 5}, + }, + }) + defer SetAPIKeyAuthenticator(nil) + + app := fiber.New() + app.Get("/reports", Auth(&stubUserService{}), RequirePermissions("perm.read"), func(c *fiber.Ctx) error { + scope, err := ResolveLocationScope(c, nil) + if err != nil { + return err + } + return c.JSON(fiber.Map{ + "principal": c.Locals(authContextLocalsKey).(*AuthContext).PrincipalType, + "restrict": scope.Restrict, + "ids": scope.IDs, + }) + }) + + req := httptest.NewRequest(fiber.MethodGet, "/reports", nil) + req.Header.Set("X-API-Key", "lti_dev_prefix_secret") + resp, err := app.Test(req) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if resp.StatusCode != fiber.StatusOK { + t.Fatalf("expected 200, got %d", resp.StatusCode) + } +} + +func TestAuthRejectsAPIKeyOnPost(t *testing.T) { + SetAPIKeyAuthenticator(&stubAPIKeyAuthenticator{ + principal: &apikeys.Principal{ + Name: "dashboard", + Permissions: []string{"perm.write"}, + }, + }) + defer SetAPIKeyAuthenticator(nil) + + app := fiber.New() + app.Post("/reports", Auth(&stubUserService{}), RequirePermissions("perm.write"), func(c *fiber.Ctx) error { + return c.SendStatus(fiber.StatusOK) + }) + + req := httptest.NewRequest(fiber.MethodPost, "/reports", nil) + req.Header.Set("X-API-Key", "lti_dev_prefix_secret") + resp, err := app.Test(req) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if resp.StatusCode != fiber.StatusUnauthorized { + t.Fatalf("expected 401, got %d", resp.StatusCode) + } +} + +func TestAuthRejectsInvalidAPIKey(t *testing.T) { + SetAPIKeyAuthenticator(&stubAPIKeyAuthenticator{err: apikeys.ErrInvalidAPIKey}) + defer SetAPIKeyAuthenticator(nil) + + app := fiber.New() + app.Get("/reports", Auth(&stubUserService{}), func(c *fiber.Ctx) error { + return c.SendStatus(fiber.StatusOK) + }) + + req := httptest.NewRequest(fiber.MethodGet, "/reports", nil) + req.Header.Set("X-API-Key", "lti_dev_prefix_secret") + resp, err := app.Test(req) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if resp.StatusCode != fiber.StatusUnauthorized { + t.Fatalf("expected 401, got %d", resp.StatusCode) + } +} + +func TestAuthRejectsInactiveAPIKey(t *testing.T) { + SetAPIKeyAuthenticator(&stubAPIKeyAuthenticator{err: apikeys.ErrInactiveKey}) + defer SetAPIKeyAuthenticator(nil) + + app := fiber.New() + app.Get("/reports", Auth(&stubUserService{}), func(c *fiber.Ctx) error { + return c.SendStatus(fiber.StatusOK) + }) + + req := httptest.NewRequest(fiber.MethodGet, "/reports", nil) + req.Header.Set("X-API-Key", "lti_dev_prefix_secret") + resp, err := app.Test(req) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if resp.StatusCode != fiber.StatusUnauthorized { + t.Fatalf("expected 401, got %d", resp.StatusCode) + } +} + +func TestAuthRejectsMissingPermission(t *testing.T) { + SetAPIKeyAuthenticator(&stubAPIKeyAuthenticator{ + principal: &apikeys.Principal{ + Name: "dashboard", + Permissions: []string{"perm.other"}, + }, + }) + defer SetAPIKeyAuthenticator(nil) + + app := fiber.New() + app.Get("/reports", Auth(&stubUserService{}), RequirePermissions("perm.read"), func(c *fiber.Ctx) error { + return c.SendStatus(fiber.StatusOK) + }) + + req := httptest.NewRequest(fiber.MethodGet, "/reports", nil) + req.Header.Set("X-API-Key", "lti_dev_prefix_secret") + resp, err := app.Test(req) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if resp.StatusCode != fiber.StatusForbidden { + t.Fatalf("expected 403, got %d", resp.StatusCode) + } +} + +func TestAuthAllowsBearerOnGet(t *testing.T) { + previousVerify := verifyAccessTokenFunc + previousProfile := fetchProfileFunc + defer func() { + verifyAccessTokenFunc = previousVerify + fetchProfileFunc = previousProfile + }() + + verifyAccessTokenFunc = func(_ string) (*sso.VerificationResult, error) { + return &sso.VerificationResult{ + UserID: 1, + Claims: &sso.AccessTokenClaims{ + RegisteredClaims: jwt.RegisteredClaims{ + IssuedAt: jwt.NewNumericDate(time.Now().UTC()), + }, + }, + }, nil + } + fetchProfileFunc = func(_ context.Context, _ string, _ *sso.VerificationResult) (*sso.UserProfile, error) { + return &sso.UserProfile{ + Permissions: []sso.Permission{{Name: "perm.read"}}, + LocationIDs: []uint{7}, + }, nil + } + + app := fiber.New() + app.Get("/reports", Auth(&stubUserService{user: &entity.User{Id: 9, Name: "API User"}}), RequirePermissions("perm.read"), func(c *fiber.Ctx) error { + return c.JSON(fiber.Map{"principal": c.Locals(authContextLocalsKey).(*AuthContext).PrincipalType}) + }) + + req := httptest.NewRequest(fiber.MethodGet, "/reports", nil) + req.Header.Set("Authorization", "Bearer test-token") + resp, err := app.Test(req) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if resp.StatusCode != fiber.StatusOK { + t.Fatalf("expected 200, got %d", resp.StatusCode) + } +} + +func TestAuthReturnsServerErrorWhenAPIKeyVerifierFailsUnexpectedly(t *testing.T) { + SetAPIKeyAuthenticator(&stubAPIKeyAuthenticator{err: errors.New("boom")}) + defer SetAPIKeyAuthenticator(nil) + + app := fiber.New() + app.Get("/reports", Auth(&stubUserService{}), func(c *fiber.Ctx) error { + return c.SendStatus(fiber.StatusOK) + }) + + req := httptest.NewRequest(fiber.MethodGet, "/reports", nil) + req.Header.Set("X-API-Key", "lti_dev_prefix_secret") + resp, err := app.Test(req) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if resp.StatusCode != fiber.StatusInternalServerError { + t.Fatalf("expected 500, got %d", resp.StatusCode) + } +} diff --git a/internal/readapi/readapi.go b/internal/readapi/readapi.go new file mode 100644 index 00000000..ba6651b8 --- /dev/null +++ b/internal/readapi/readapi.go @@ -0,0 +1,830 @@ +package readapi + +import ( + "encoding/json" + "fmt" + "net/http" + "sort" + "strings" + + "github.com/gofiber/fiber/v2" + "gitlab.com/mbugroup/lti-api.git/internal/config" + "gopkg.in/yaml.v3" +) + +type Artifacts struct { + OpenAPIJSON []byte + OpenAPIYAML []byte + PostmanCollectionJSON []byte + PostmanEnvironmentJSON []byte +} + +type normalizedRoute struct { + Method string + Path string + Params []string +} + +type securityMode string + +const ( + securityNone securityMode = "none" + securityBearer securityMode = "bearer" + securityAPIOrBearer securityMode = "api_or_bearer" +) + +type parameterMeta struct { + Name string + In string + Description string + Required bool + Example any + PostmanValue string + IncludePostman bool +} + +type routeMeta struct { + Group string + Tag string + Summary string + Description string + Security securityMode + ListStyle bool + QueryParams []parameterMeta + Exclude bool +} + +func RegisterRoutes(router fiber.Router) { + router.Get("/openapi/read.json", serveOpenAPIJSON) + router.Get("/openapi/read.yaml", serveOpenAPIYAML) +} + +func PrimeBuildConfig() { + if strings.TrimSpace(config.S3Bucket) == "" { + config.S3Bucket = "read-api-artifacts" + } + if strings.TrimSpace(config.S3Region) == "" { + config.S3Region = "us-east-1" + } + if strings.TrimSpace(config.S3AccessKey) == "" { + config.S3AccessKey = "local-access-key" + } + if strings.TrimSpace(config.S3SecretKey) == "" { + config.S3SecretKey = "local-secret-key" + } + if strings.TrimSpace(config.S3Endpoint) == "" { + config.S3Endpoint = "http://localhost:9000" + } + if strings.TrimSpace(config.S3PublicBaseURL) == "" { + config.S3PublicBaseURL = "http://localhost:9000/read-api-artifacts" + } +} + +func BuildArtifactsFromApp(app *fiber.App) (Artifacts, error) { + if app == nil { + return Artifacts{}, fmt.Errorf("app is required") + } + + routes := normalizeRoutes(app.GetRoutes(true)) + return buildArtifactsFromNormalized(routes) +} + +func BuildArtifacts(routes []fiber.Route) (Artifacts, error) { + normalized := normalizeRoutes(routes) + return buildArtifactsFromNormalized(normalized) +} + +func buildArtifactsFromNormalized(normalized []normalizedRoute) (Artifacts, error) { + specDoc := buildOpenAPIDocument(normalized) + collectionDoc := buildPostmanCollection(normalized) + environmentDoc := buildPostmanEnvironment(normalized) + + openAPIJSON, err := json.MarshalIndent(specDoc, "", " ") + if err != nil { + return Artifacts{}, err + } + + openAPIYAML, err := yaml.Marshal(specDoc) + if err != nil { + return Artifacts{}, err + } + + postmanCollectionJSON, err := json.MarshalIndent(collectionDoc, "", " ") + if err != nil { + return Artifacts{}, err + } + + postmanEnvironmentJSON, err := json.MarshalIndent(environmentDoc, "", " ") + if err != nil { + return Artifacts{}, err + } + + return Artifacts{ + OpenAPIJSON: openAPIJSON, + OpenAPIYAML: openAPIYAML, + PostmanCollectionJSON: postmanCollectionJSON, + PostmanEnvironmentJSON: postmanEnvironmentJSON, + }, nil +} + +func serveOpenAPIJSON(c *fiber.Ctx) error { + artifacts, err := BuildArtifactsFromApp(c.App()) + if err != nil { + return err + } + return c.Status(fiber.StatusOK).Type("json").Send(artifacts.OpenAPIJSON) +} + +func serveOpenAPIYAML(c *fiber.Ctx) error { + artifacts, err := BuildArtifactsFromApp(c.App()) + if err != nil { + return err + } + c.Set(fiber.HeaderContentType, "application/yaml") + return c.Status(fiber.StatusOK).Send(artifacts.OpenAPIYAML) +} + +func normalizeRoutes(routes []fiber.Route) []normalizedRoute { + seen := make(map[string]struct{}, len(routes)) + normalized := make([]normalizedRoute, 0, len(routes)) + for _, route := range routes { + if route.Method != http.MethodGet { + continue + } + if route.Path == "" || strings.HasPrefix(route.Path, "/api/openapi/") { + continue + } + + key := route.Method + " " + route.Path + if _, ok := seen[key]; ok { + continue + } + seen[key] = struct{}{} + + normalized = append(normalized, normalizedRoute{ + Method: route.Method, + Path: route.Path, + Params: append([]string(nil), route.Params...), + }) + } + + sort.Slice(normalized, func(i, j int) bool { + if normalized[i].Path == normalized[j].Path { + return normalized[i].Method < normalized[j].Method + } + return normalized[i].Path < normalized[j].Path + }) + + return normalized +} + +func buildOpenAPIDocument(routes []normalizedRoute) map[string]any { + paths := make(map[string]any, len(routes)) + for _, route := range routes { + meta := describeRoute(route) + if meta.Exclude { + continue + } + + openAPIPath := toOpenAPIPath(route.Path) + operation := map[string]any{ + "summary": meta.Summary, + "description": meta.Description, + "tags": []string{meta.Tag}, + "responses": map[string]any{ + "200": map[string]any{ + "description": "Successful response", + "content": map[string]any{ + "application/json": map[string]any{ + "schema": successSchema(meta), + }, + }, + }, + "401": map[string]any{ + "description": "Unauthorized", + "content": map[string]any{ + "application/json": map[string]any{ + "schema": map[string]any{"$ref": "#/components/schemas/ErrorEnvelope"}, + }, + }, + }, + "403": map[string]any{ + "description": "Forbidden", + "content": map[string]any{ + "application/json": map[string]any{ + "schema": map[string]any{"$ref": "#/components/schemas/ErrorEnvelope"}, + }, + }, + }, + }, + } + + params := buildParameters(route, meta) + if len(params) > 0 { + operation["parameters"] = params + } + + switch meta.Security { + case securityBearer: + operation["security"] = []map[string][]string{{"BearerAuth": {}}} + case securityAPIOrBearer: + operation["security"] = []map[string][]string{ + {"ApiKeyAuth": {}}, + {"BearerAuth": {}}, + } + } + + paths[openAPIPath] = map[string]any{ + "get": operation, + } + } + + return map[string]any{ + "openapi": "3.1.0", + "info": map[string]any{ + "title": "LTI ERP Read API", + "version": "v1", + "description": "Read-only OpenAPI surface for dashboard integrations and GET endpoint exploration.", + }, + "servers": []map[string]any{ + {"url": "http://localhost:8081"}, + }, + "paths": paths, + "components": map[string]any{ + "securitySchemes": map[string]any{ + "ApiKeyAuth": map[string]any{ + "type": "apiKey", + "in": "header", + "name": "X-API-Key", + }, + "BearerAuth": map[string]any{ + "type": "http", + "scheme": "bearer", + }, + }, + "schemas": map[string]any{ + "SuccessEnvelope": map[string]any{ + "type": "object", + "properties": map[string]any{ + "code": map[string]any{"type": "integer", "example": 200}, + "status": map[string]any{"type": "string", "example": "success"}, + "message": map[string]any{"type": "string", "example": "Request completed successfully"}, + "data": map[string]any{"type": "object", "additionalProperties": true}, + }, + }, + "PaginatedEnvelope": map[string]any{ + "type": "object", + "properties": map[string]any{ + "code": map[string]any{"type": "integer", "example": 200}, + "status": map[string]any{"type": "string", "example": "success"}, + "message": map[string]any{"type": "string", "example": "Request completed successfully"}, + "meta": map[string]any{ + "type": "object", + "properties": map[string]any{ + "page": map[string]any{"type": "integer", "example": 1}, + "limit": map[string]any{"type": "integer", "example": 10}, + "total_pages": map[string]any{"type": "integer", "example": 1}, + "total_results": map[string]any{"type": "integer", "example": 0}, + }, + }, + "data": map[string]any{ + "type": "array", + "items": map[string]any{"type": "object", "additionalProperties": true}, + }, + }, + }, + "ErrorEnvelope": map[string]any{ + "type": "object", + "properties": map[string]any{ + "code": map[string]any{"type": "integer", "example": 401}, + "status": map[string]any{"type": "string", "example": "error"}, + "message": map[string]any{"type": "string", "example": "Please authenticate"}, + "errors": map[string]any{"type": "object", "additionalProperties": true}, + }, + }, + }, + }, + } +} + +func buildParameters(route normalizedRoute, meta routeMeta) []map[string]any { + params := make([]map[string]any, 0, len(route.Params)+len(meta.QueryParams)) + for _, param := range route.Params { + params = append(params, map[string]any{ + "name": param, + "in": "path", + "required": true, + "description": fmt.Sprintf("Path parameter `%s`.", param), + "schema": map[string]any{ + "type": "string", + "example": defaultExampleForVariable(inferPostmanPathVariable(route.Path, param)), + }, + }) + } + for _, query := range meta.QueryParams { + parameter := map[string]any{ + "name": query.Name, + "in": query.In, + "required": query.Required, + "description": query.Description, + "schema": inferSchema(query.Example), + } + if query.Example != nil { + parameter["example"] = query.Example + } + params = append(params, parameter) + } + return params +} + +func successSchema(meta routeMeta) map[string]any { + if meta.ListStyle { + return map[string]any{"$ref": "#/components/schemas/PaginatedEnvelope"} + } + return map[string]any{"$ref": "#/components/schemas/SuccessEnvelope"} +} + +func buildPostmanCollection(routes []normalizedRoute) map[string]any { + type folder struct { + name string + items []any + } + + folders := map[string]map[string]*folder{ + "Public": {}, + "Dashboard API Key": {}, + "Internal/OAuth Reference": {}, + } + + for _, route := range routes { + meta := describeRoute(route) + if meta.Exclude { + continue + } + + tagFolder := folders[meta.Group] + group, ok := tagFolder[meta.Tag] + if !ok { + group = &folder{name: meta.Tag} + tagFolder[meta.Tag] = group + } + group.items = append(group.items, buildPostmanRequest(route, meta)) + } + + rootItems := make([]any, 0, len(folders)) + for _, groupName := range []string{"Public", "Dashboard API Key", "Internal/OAuth Reference"} { + tagFolder := folders[groupName] + tagNames := make([]string, 0, len(tagFolder)) + for tagName := range tagFolder { + tagNames = append(tagNames, tagName) + } + sort.Strings(tagNames) + + groupItems := make([]any, 0, len(tagNames)) + for _, tagName := range tagNames { + groupItems = append(groupItems, map[string]any{ + "name": tagName, + "item": tagFolder[tagName].items, + }) + } + + folder := map[string]any{ + "name": groupName, + "item": groupItems, + } + if events := postmanFolderEvents(groupName); len(events) > 0 { + folder["event"] = events + } + rootItems = append(rootItems, folder) + } + + return map[string]any{ + "info": map[string]any{ + "name": "LTI ERP Read API", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", + }, + "item": rootItems, + } +} + +func postmanFolderEvents(groupName string) []map[string]any { + switch groupName { + case "Dashboard API Key": + return []map[string]any{ + { + "listen": "prerequest", + "script": map[string]any{ + "type": "text/javascript", + "exec": []string{ + "const apiKey = pm.environment.get('api_key');", + "const bearerToken = pm.environment.get('bearer_token');", + "if (apiKey) {", + " pm.request.headers.upsert({ key: 'X-API-Key', value: apiKey });", + " pm.request.headers.remove('Authorization');", + "} else if (bearerToken) {", + " pm.request.headers.upsert({ key: 'Authorization', value: 'Bearer ' + bearerToken });", + " pm.request.headers.remove('X-API-Key');", + "} else {", + " pm.request.headers.remove('Authorization');", + " pm.request.headers.remove('X-API-Key');", + "}", + }, + }, + }, + } + case "Internal/OAuth Reference": + return []map[string]any{ + { + "listen": "prerequest", + "script": map[string]any{ + "type": "text/javascript", + "exec": []string{ + "const bearerToken = pm.environment.get('bearer_token');", + "pm.request.headers.remove('X-API-Key');", + "if (bearerToken) {", + " pm.request.headers.upsert({ key: 'Authorization', value: 'Bearer ' + bearerToken });", + "} else {", + " pm.request.headers.remove('Authorization');", + "}", + }, + }, + }, + } + default: + return nil + } +} + +func buildPostmanRequest(route normalizedRoute, meta routeMeta) map[string]any { + return map[string]any{ + "name": meta.Summary, + "request": map[string]any{ + "method": http.MethodGet, + "header": []map[string]any{ + {"key": "Accept", "value": "application/json"}, + }, + "url": buildPostmanURL(route, meta), + }, + } +} + +func buildPostmanURL(route normalizedRoute, meta routeMeta) string { + path := route.Path + for _, param := range route.Params { + path = strings.ReplaceAll(path, ":"+param, "{{"+inferPostmanPathVariable(route.Path, param)+"}}") + } + + query := make([]string, 0, len(meta.QueryParams)) + for _, param := range meta.QueryParams { + if !param.IncludePostman { + continue + } + query = append(query, fmt.Sprintf("%s=%v", param.Name, param.PostmanValue)) + } + + if len(query) == 0 { + return "{{base_url}}" + path + } + + return "{{base_url}}" + path + "?" + strings.Join(query, "&") +} + +func buildPostmanEnvironment(routes []normalizedRoute) map[string]any { + values := map[string]string{ + "base_url": "http://localhost:8081", + "api_key": "", + "bearer_token": "", + "id": "1", + "bank_id": "1", + "customer_id": "1", + "expense_id": "1", + "location_id": "1", + "project_flock_id": "1", + "project_flock_kandang_id": "1", + "supplier_id": "1", + } + + for _, route := range routes { + meta := describeRoute(route) + if meta.Exclude { + continue + } + + for _, param := range route.Params { + name := inferPostmanPathVariable(route.Path, param) + if _, ok := values[name]; !ok { + values[name] = defaultExampleForVariable(name) + } + } + for _, query := range meta.QueryParams { + if query.IncludePostman && strings.HasPrefix(query.PostmanValue, "{{") && strings.HasSuffix(query.PostmanValue, "}}") { + name := strings.TrimSuffix(strings.TrimPrefix(query.PostmanValue, "{{"), "}}") + if _, ok := values[name]; !ok { + values[name] = defaultExampleForVariable(name) + } + } + } + } + + keys := make([]string, 0, len(values)) + for key := range values { + keys = append(keys, key) + } + sort.Strings(keys) + + items := make([]map[string]any, 0, len(keys)) + for _, key := range keys { + items = append(items, map[string]any{ + "key": key, + "value": values[key], + "enabled": true, + }) + } + + return map[string]any{ + "id": "lti-read-api-local", + "name": "LTI ERP Read API.local", + "values": items, + "_postman_variable_scope": "environment", + "_postman_exported_at": "2026-04-14T00:00:00Z", + "_postman_exported_using": "Codex", + } +} + +func describeRoute(route normalizedRoute) routeMeta { + meta := routeMeta{ + Group: "Dashboard API Key", + Tag: inferTag(route.Path), + Summary: defaultSummary(route.Path), + Description: fmt.Sprintf("Read access to `%s`.", route.Path), + Security: securityAPIOrBearer, + ListStyle: !strings.Contains(route.Path, ":"), + } + + switch { + case route.Path == "/healthz" || route.Path == "/readyz" || route.Path == "/api/constants": + meta.Group = "Public" + meta.Security = securityNone + meta.ListStyle = false + case strings.HasPrefix(route.Path, "/api/sso/"): + meta.Group = "Internal/OAuth Reference" + meta.ListStyle = false + if route.Path == "/api/sso/userinfo" { + meta.Security = securityBearer + } else { + meta.Security = securityNone + } + } + + switch route.Path { + case "/api/dashboards": + meta.QueryParams = []parameterMeta{ + {Name: "page", In: "query", Description: "Page number.", Example: 1}, + {Name: "limit", In: "query", Description: "Page size.", Example: 10}, + {Name: "search", In: "query", Description: "Search keyword.", Example: "farm"}, + {Name: "start_date", In: "query", Description: "Period start date (YYYY-MM-DD).", Example: "2026-01-01"}, + {Name: "end_date", In: "query", Description: "Period end date (YYYY-MM-DD).", Example: "2026-01-31"}, + {Name: "analysis_mode", In: "query", Description: "Dashboard analysis mode.", Example: "OVERVIEW"}, + {Name: "comparison_type", In: "query", Description: "Required when analysis_mode is COMPARISON.", Example: "PREVIOUS_PERIOD"}, + {Name: "metric", In: "query", Description: "Metric to compare.", Example: "egg_mass"}, + {Name: "location_ids", In: "query", Description: "Comma separated location ids.", Example: "1,2"}, + {Name: "flock_ids", In: "query", Description: "Comma separated flock ids.", Example: "1,2"}, + {Name: "kandang_ids", In: "query", Description: "Comma separated kandang ids.", Example: "1,2"}, + {Name: "include", In: "query", Description: "Comma separated dashboard sections to include.", Example: "performance,summary"}, + } + case "/api/closings/:projectFlockId/sapronak", "/api/closings/:projectFlockId/sapronak/summary": + meta.ListStyle = route.Path == "/api/closings/:projectFlockId/sapronak" + meta.QueryParams = []parameterMeta{ + {Name: "type", In: "query", Description: "Required sapronak direction.", Required: true, Example: "incoming", PostmanValue: "incoming", IncludePostman: true}, + {Name: "search", In: "query", Description: "Search keyword.", Example: "pakan"}, + {Name: "kandang_id", In: "query", Description: "Optional kandang id filter.", Example: 1}, + } + case "/api/closings/:projectFlockId/production-data", "/api/closings/:projectFlockId/keuangan": + meta.ListStyle = false + meta.QueryParams = []parameterMeta{ + {Name: "kandang_id", In: "query", Description: "Optional kandang id filter.", Example: 1}, + } + case "/api/closings/:project_flock_id/expedition-hpp": + meta.ListStyle = false + meta.QueryParams = []parameterMeta{ + {Name: "project_flock_kandang_id", In: "query", Description: "Optional project flock kandang id filter.", Example: 1}, + } + case "/api/reports/expense": + meta.QueryParams = []parameterMeta{ + {Name: "page", In: "query", Description: "Page number.", Example: 1}, + {Name: "limit", In: "query", Description: "Page size.", Example: 10}, + {Name: "search", In: "query", Description: "Search keyword.", Example: "operasional"}, + {Name: "category", In: "query", Description: "Expense category filter.", Example: "BOP"}, + {Name: "supplier_id", In: "query", Description: "Supplier id filter.", Example: 1}, + {Name: "location_id", In: "query", Description: "Location id filter.", Example: 1}, + {Name: "area_id", In: "query", Description: "Area id filter.", Example: 1}, + {Name: "realization_date", In: "query", Description: "Realization date filter (YYYY-MM-DD).", Example: "2026-01-15"}, + } + case "/api/reports/marketing": + meta.QueryParams = []parameterMeta{ + {Name: "page", In: "query", Description: "Page number.", Example: 1}, + {Name: "limit", In: "query", Description: "Page size.", Example: 10}, + {Name: "start_date", In: "query", Description: "Period start date (YYYY-MM-DD).", Example: "2026-01-01"}, + {Name: "end_date", In: "query", Description: "Period end date (YYYY-MM-DD).", Example: "2026-01-31"}, + {Name: "customer_id", In: "query", Description: "Customer id filter.", Example: 1}, + {Name: "location_id", In: "query", Description: "Location id filter.", Example: 1}, + } + case "/api/reports/purchase-supplier": + meta.QueryParams = []parameterMeta{ + {Name: "page", In: "query", Description: "Page number.", Example: 1}, + {Name: "limit", In: "query", Description: "Page size.", Example: 10}, + {Name: "start_date", In: "query", Description: "Period start date (YYYY-MM-DD).", Example: "2026-01-01"}, + {Name: "end_date", In: "query", Description: "Period end date (YYYY-MM-DD).", Example: "2026-01-31"}, + {Name: "supplier_id", In: "query", Description: "Comma separated supplier ids.", Example: "1,2"}, + {Name: "area_id", In: "query", Description: "Comma separated area ids.", Example: "1,2"}, + } + case "/api/reports/debt-supplier": + meta.QueryParams = []parameterMeta{ + {Name: "page", In: "query", Description: "Page number.", Example: 1}, + {Name: "limit", In: "query", Description: "Page size.", Example: 10}, + {Name: "start_date", In: "query", Description: "Period start date (YYYY-MM-DD).", Example: "2026-01-01"}, + {Name: "end_date", In: "query", Description: "Period end date (YYYY-MM-DD).", Example: "2026-01-31"}, + {Name: "supplier_ids", In: "query", Description: "Comma separated supplier ids.", Example: "1,2", PostmanValue: "{{supplier_id}}", IncludePostman: true}, + } + case "/api/reports/customer-payment": + meta.QueryParams = []parameterMeta{ + {Name: "page", In: "query", Description: "Page number.", Example: 1}, + {Name: "limit", In: "query", Description: "Page size.", Example: 10}, + {Name: "start_date", In: "query", Description: "Period start date (YYYY-MM-DD).", Example: "2026-01-01"}, + {Name: "end_date", In: "query", Description: "Period end date (YYYY-MM-DD).", Example: "2026-01-31"}, + {Name: "customer_ids", In: "query", Description: "Comma separated customer ids.", Example: "1,2", PostmanValue: "{{customer_id}}", IncludePostman: true}, + } + case "/api/reports/hpp-per-kandang": + meta.QueryParams = []parameterMeta{ + {Name: "page", In: "query", Description: "Page number.", Example: 1}, + {Name: "limit", In: "query", Description: "Page size.", Example: 10}, + {Name: "period", In: "query", Description: "Daily period filter (YYYY-MM).", Example: "2026-01"}, + {Name: "location_id", In: "query", Description: "Location id filter.", Example: 1}, + {Name: "kandang_id", In: "query", Description: "Kandang id filter.", Example: 1}, + } + case "/api/finance/transactions": + meta.QueryParams = []parameterMeta{ + {Name: "page", In: "query", Description: "Page number.", Example: 1}, + {Name: "limit", In: "query", Description: "Page size.", Example: 10}, + {Name: "search", In: "query", Description: "Search keyword.", Example: "invoice"}, + {Name: "bank_ids", In: "query", Description: "Comma separated bank ids.", Example: "1,2", PostmanValue: "{{bank_id}}", IncludePostman: true}, + {Name: "customer_ids", In: "query", Description: "Comma separated customer ids.", Example: "1,2"}, + {Name: "supplier_ids", In: "query", Description: "Comma separated supplier ids.", Example: "1,2"}, + {Name: "transaction_types", In: "query", Description: "Comma separated transaction types.", Example: "payment,initial_balance"}, + {Name: "start_date", In: "query", Description: "Start date (YYYY-MM-DD).", Example: "2026-01-01"}, + {Name: "end_date", In: "query", Description: "End date (YYYY-MM-DD).", Example: "2026-01-31"}, + } + } + + if route.Path == "/healthz" { + meta.Summary = "Health check" + meta.Description = "Simple liveness probe." + } else if route.Path == "/readyz" { + meta.Summary = "Readiness check" + meta.Description = "Readiness probe for database and Redis." + } + + return meta +} + +func inferTag(path string) string { + switch { + case path == "/healthz" || path == "/readyz": + return "System" + case strings.HasPrefix(path, "/api/master-data/"): + return "Master Data" + case strings.HasPrefix(path, "/api/finance/"): + return "Finance" + case strings.HasPrefix(path, "/api/inventory/"): + return "Inventory" + case strings.HasPrefix(path, "/api/production/"): + return "Production" + case strings.HasPrefix(path, "/api/reports/"): + return "Reports" + case strings.HasPrefix(path, "/api/closings/"): + return "Closings" + case strings.HasPrefix(path, "/api/expenses"): + return "Expenses" + case strings.HasPrefix(path, "/api/dashboards"): + return "Dashboards" + case strings.HasPrefix(path, "/api/purchases"): + return "Purchases" + case strings.HasPrefix(path, "/api/marketing"): + return "Marketing" + case strings.HasPrefix(path, "/api/users"): + return "Users" + case strings.HasPrefix(path, "/api/daily-checklists"): + return "Daily Checklists" + case strings.HasPrefix(path, "/api/sso"): + return "SSO" + default: + return "API" + } +} + +func defaultSummary(path string) string { + switch path { + case "/healthz": + return "Health check" + case "/readyz": + return "Readiness check" + } + + trimmed := strings.Trim(path, "/") + if trimmed == "" { + return "List root" + } + parts := strings.Split(trimmed, "/") + for i, part := range parts { + parts[i] = strings.ReplaceAll(part, "-", " ") + } + return "GET " + strings.Join(parts, " / ") +} + +func toOpenAPIPath(path string) string { + segments := strings.Split(path, "/") + for i, segment := range segments { + if strings.HasPrefix(segment, ":") { + segments[i] = "{" + strings.TrimPrefix(segment, ":") + "}" + } + } + return strings.Join(segments, "/") +} + +func inferPostmanPathVariable(path, param string) string { + if param != "id" { + return param + } + + switch { + case strings.HasPrefix(path, "/api/expenses/"): + return "expense_id" + case strings.HasPrefix(path, "/api/finance/payments/"): + return "payment_id" + case strings.HasPrefix(path, "/api/finance/transactions/"): + return "transaction_id" + case strings.HasPrefix(path, "/api/finance/initial-balances/"): + return "initial_balance_id" + case strings.HasPrefix(path, "/api/finance/injections/"): + return "injection_id" + case strings.HasPrefix(path, "/api/purchases/"): + return "purchase_id" + case strings.HasPrefix(path, "/api/inventory/adjustments/"): + return "adjustment_id" + case strings.HasPrefix(path, "/api/inventory/transfers/"): + return "transfer_id" + case strings.HasPrefix(path, "/api/master-data/banks/"): + return "bank_id" + case strings.HasPrefix(path, "/api/master-data/customers/"): + return "customer_id" + case strings.HasPrefix(path, "/api/master-data/suppliers/"): + return "supplier_id" + case strings.HasPrefix(path, "/api/master-data/locations/"): + return "location_id" + case strings.HasPrefix(path, "/api/master-data/areas/"): + return "area_id" + case strings.HasPrefix(path, "/api/master-data/products/"): + return "product_id" + case strings.HasPrefix(path, "/api/master-data/product-categories/"): + return "product_category_id" + case strings.HasPrefix(path, "/api/master-data/nonstocks/"): + return "nonstock_id" + case strings.HasPrefix(path, "/api/master-data/employees/"): + return "employee_id" + case strings.HasPrefix(path, "/api/master-data/flocks/"): + return "flock_id" + case strings.HasPrefix(path, "/api/master-data/warehouses/"): + return "warehouse_id" + case strings.HasPrefix(path, "/api/master-data/uoms/"): + return "uom_id" + case strings.HasPrefix(path, "/api/users/"): + return "user_id" + case strings.HasPrefix(path, "/api/production/recordings/"): + return "recording_id" + case strings.HasPrefix(path, "/api/production/uniformities/"): + return "uniformity_id" + case strings.HasPrefix(path, "/api/production/chickins/"): + return "chickin_id" + default: + return "id" + } +} + +func defaultExampleForVariable(name string) string { + if strings.Contains(name, "date") { + return "2026-01-01" + } + if strings.Contains(name, "token") || strings.Contains(name, "key") { + return "" + } + return "1" +} + +func inferSchema(example any) map[string]any { + switch example.(type) { + case int, int32, int64, uint, uint32, uint64, float64: + return map[string]any{"type": "integer"} + default: + return map[string]any{"type": "string"} + } +} diff --git a/internal/readapi/readapi_test.go b/internal/readapi/readapi_test.go new file mode 100644 index 00000000..fa740428 --- /dev/null +++ b/internal/readapi/readapi_test.go @@ -0,0 +1,97 @@ +package readapi + +import ( + "encoding/json" + "os" + "path/filepath" + "reflect" + "runtime" + "testing" + + "gitlab.com/mbugroup/lti-api.git/internal/cache" + "gitlab.com/mbugroup/lti-api.git/internal/config" + "gitlab.com/mbugroup/lti-api.git/internal/route" + + "github.com/gofiber/fiber/v2" + "github.com/redis/go-redis/v9" + "gopkg.in/yaml.v3" +) + +func TestGeneratedArtifactsAreCurrent(t *testing.T) { + PrimeBuildConfig() + cache.SetRedis(redis.NewClient(&redis.Options{Addr: "127.0.0.1:6379"})) + app := fiber.New(config.FiberConfig()) + app.Get("/healthz", func(c *fiber.Ctx) error { + return c.JSON(fiber.Map{"status": "ok"}) + }) + app.Get("/readyz", func(c *fiber.Ctx) error { + return c.JSON(fiber.Map{"status": "ok"}) + }) + route.Routes(app, nil) + + artifacts, err := BuildArtifactsFromApp(app) + if err != nil { + t.Fatalf("build artifacts: %v", err) + } + + root := repoRoot(t) + assertJSONMatchesFile(t, artifacts.OpenAPIJSON, filepath.Join(root, "docs", "openapi", "read-api.json")) + assertYAMLMatchesFile(t, artifacts.OpenAPIYAML, filepath.Join(root, "docs", "openapi", "read-api.yaml")) + assertJSONMatchesFile(t, artifacts.PostmanCollectionJSON, filepath.Join(root, "docs", "postman", "read-api.collection.json")) + assertJSONMatchesFile(t, artifacts.PostmanEnvironmentJSON, filepath.Join(root, "docs", "postman", "read-api.environment.json")) +} + +func assertJSONMatchesFile(t *testing.T, got []byte, path string) { + t.Helper() + + want, err := os.ReadFile(path) + if err != nil { + t.Fatalf("read %s: %v", path, err) + } + + var gotValue any + if err := json.Unmarshal(got, &gotValue); err != nil { + t.Fatalf("unmarshal generated json: %v", err) + } + var wantValue any + if err := json.Unmarshal(want, &wantValue); err != nil { + t.Fatalf("unmarshal fixture json: %v", err) + } + + if !reflect.DeepEqual(gotValue, wantValue) { + t.Fatalf("json artifact mismatch for %s", path) + } +} + +func assertYAMLMatchesFile(t *testing.T, got []byte, path string) { + t.Helper() + + want, err := os.ReadFile(path) + if err != nil { + t.Fatalf("read %s: %v", path, err) + } + + var gotValue any + if err := yaml.Unmarshal(got, &gotValue); err != nil { + t.Fatalf("unmarshal generated yaml: %v", err) + } + var wantValue any + if err := yaml.Unmarshal(want, &wantValue); err != nil { + t.Fatalf("unmarshal fixture yaml: %v", err) + } + + if !reflect.DeepEqual(gotValue, wantValue) { + t.Fatalf("yaml artifact mismatch for %s", path) + } +} + +func repoRoot(t *testing.T) string { + t.Helper() + + _, filename, _, ok := runtime.Caller(0) + if !ok { + t.Fatal("runtime.Caller failed") + } + + return filepath.Clean(filepath.Join(filepath.Dir(filename), "..", "..")) +}