Reorganize repo: backend/, deploy/, docs/ layout + AGENTS.md
Restructure the monorepo into clear top-level directories: - backend/: Go module root (cmd, internal, pkg, configs, migrations, docs/swagger, scripts, tests, go.mod, Makefile, .air.toml) - deploy/: Docker (Dockerfile, docker-compose*), quickstart, fluentd - docs/: project documentation + reports/ (moved from repo root) - AGENTS.md: new AI coding-agent guide at repo root Update all references to the new layout: - Dockerfile: COPY backend/go.mod, COPY backend/ (context = repo root) - docker-compose files: context ../.., dockerfile deploy/docker/Dockerfile, env_file ../../.env, volume mounts ../../backend:/app - deploy/quickstart/compose.yaml: dockerfile deploy/docker/Dockerfile - CI: working-directory: backend for go commands, file deploy/docker/Dockerfile, coverage path backend/coverage.out, health_check backend/scripts/ - backend/Makefile: docker target uses -f ../deploy/docker/Dockerfile ../ - README: architecture tree, quickstart, config paths updated Move root stray scripts (rename_models.*, run_m11_tests.sh, verify_build.sh, gorm_bool_main.go) to backend/scripts/legacy/. All moves via git mv to preserve history. Build, vet, SQLite tests, and docker compose config verified.
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
package middleware
|
||||
|
||||
// Reference: P2E §2.7 — RoleCheck middleware
|
||||
// Simple middleware that verifies the user has the specified role in the current account.
|
||||
// Used for endpoints that require a specific role regardless of permission dimensions.
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/gochat/gochat/internal/auth"
|
||||
"github.com/gochat/gochat/pkg/response"
|
||||
)
|
||||
|
||||
// RoleCheck creates a middleware that verifies the current user has the specified role.
|
||||
// This is a simple role gate — for more granular permission checks, use PolicyMiddleware.
|
||||
//
|
||||
// Usage:
|
||||
// router.POST("/accounts/:id/users", RoleCheck("administrator"), inviteUser)
|
||||
// router.GET("/reports", RoleCheck("administrator"), viewReports)
|
||||
//
|
||||
// Valid role values: "agent", "administrator", "custom_role"
|
||||
func RoleCheck(role string) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
pc, exists := c.Get("policy_context")
|
||||
if !exists {
|
||||
response.AbortWithStatusError(c, http.StatusForbidden, response.ErrForbidden,
|
||||
"Policy context not found — ensure AccountScope middleware is applied first")
|
||||
return
|
||||
}
|
||||
|
||||
policyCtx, ok := pc.(*auth.PolicyContext)
|
||||
if !ok {
|
||||
response.AbortWithStatusError(c, http.StatusForbidden, response.ErrForbidden,
|
||||
"Invalid policy context type")
|
||||
return
|
||||
}
|
||||
|
||||
switch role {
|
||||
case "administrator":
|
||||
if !policyCtx.IsAdministrator() {
|
||||
response.AbortWithStatusError(c, http.StatusForbidden, response.ErrForbidden,
|
||||
"Administrator role required")
|
||||
return
|
||||
}
|
||||
case "agent":
|
||||
if !policyCtx.IsAgent() {
|
||||
response.AbortWithStatusError(c, http.StatusForbidden, response.ErrForbidden,
|
||||
"Agent role required")
|
||||
return
|
||||
}
|
||||
case "custom_role":
|
||||
if !policyCtx.IsCustomRole() {
|
||||
response.AbortWithStatusError(c, http.StatusForbidden, response.ErrForbidden,
|
||||
"Custom role required")
|
||||
return
|
||||
}
|
||||
default:
|
||||
// Check if the user's role matches exactly
|
||||
if policyCtx.Role != role {
|
||||
response.AbortWithStatusError(c, http.StatusForbidden, response.ErrForbidden,
|
||||
"Role '"+role+"' required, but user has role '"+policyCtx.Role+"'")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
// RoleCheckAny creates a middleware that verifies the current user has ANY of the specified roles.
|
||||
// Useful for endpoints accessible to multiple roles.
|
||||
//
|
||||
// Usage:
|
||||
// router.GET("/dashboard", RoleCheckAny("administrator", "custom_role"), viewDashboard)
|
||||
func RoleCheckAny(roles ...string) gin.HandlerFunc {
|
||||
roleSet := make(map[string]bool, len(roles))
|
||||
for _, r := range roles {
|
||||
roleSet[r] = true
|
||||
}
|
||||
|
||||
return func(c *gin.Context) {
|
||||
pc, exists := c.Get("policy_context")
|
||||
if !exists {
|
||||
response.AbortWithStatusError(c, http.StatusForbidden, response.ErrForbidden,
|
||||
"Policy context not found — ensure AccountScope middleware is applied first")
|
||||
return
|
||||
}
|
||||
|
||||
policyCtx, ok := pc.(*auth.PolicyContext)
|
||||
if !ok {
|
||||
response.AbortWithStatusError(c, http.StatusForbidden, response.ErrForbidden,
|
||||
"Invalid policy context type")
|
||||
return
|
||||
}
|
||||
|
||||
if !roleSet[policyCtx.Role] {
|
||||
response.AbortWithStatusError(c, http.StatusForbidden, response.ErrForbidden,
|
||||
"One of the following roles required: "+formatRoles(roles))
|
||||
return
|
||||
}
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func formatRoles(roles []string) string {
|
||||
result := ""
|
||||
for i, r := range roles {
|
||||
if i > 0 {
|
||||
result += ", "
|
||||
}
|
||||
result += r
|
||||
}
|
||||
return result
|
||||
}
|
||||
Reference in New Issue
Block a user