mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-20 21:41:55 +00:00
578 lines
15 KiB
Go
578 lines
15 KiB
Go
package middleware
|
|
|
|
import (
|
|
"errors"
|
|
|
|
"github.com/gofiber/fiber/v2"
|
|
"gorm.io/gorm"
|
|
|
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
|
)
|
|
|
|
type ScopeFilter struct {
|
|
IDs []uint
|
|
Restrict bool
|
|
}
|
|
|
|
type roleScope struct {
|
|
allArea bool
|
|
allLocation bool
|
|
areaIDs []uint
|
|
locationIDs []uint
|
|
hasAnyScopes bool
|
|
}
|
|
|
|
func ResolveAreaScope(c *fiber.Ctx, db *gorm.DB) (ScopeFilter, error) {
|
|
scope, err := collectRoleScope(c)
|
|
if err != nil || !scope.hasAnyScopes {
|
|
return ScopeFilter{}, err
|
|
}
|
|
|
|
if scope.allArea || scope.allLocation {
|
|
return ScopeFilter{}, nil
|
|
}
|
|
|
|
allowed := uniqueUint(scope.areaIDs)
|
|
if len(scope.locationIDs) > 0 {
|
|
derived, err := areaIDsByLocationIDs(db, scope.locationIDs)
|
|
if err != nil {
|
|
return ScopeFilter{}, err
|
|
}
|
|
allowed = uniqueUint(append(allowed, derived...))
|
|
}
|
|
|
|
if len(allowed) == 0 {
|
|
return ScopeFilter{Restrict: true}, nil
|
|
}
|
|
return ScopeFilter{IDs: allowed, Restrict: true}, nil
|
|
}
|
|
|
|
func ResolveLocationScope(c *fiber.Ctx, db *gorm.DB) (ScopeFilter, error) {
|
|
scope, err := collectRoleScope(c)
|
|
if err != nil || !scope.hasAnyScopes {
|
|
return ScopeFilter{}, err
|
|
}
|
|
|
|
if scope.allLocation || scope.allArea {
|
|
return ScopeFilter{}, nil
|
|
}
|
|
|
|
areaIDs := uniqueUint(scope.areaIDs)
|
|
locationIDs := uniqueUint(scope.locationIDs)
|
|
|
|
switch {
|
|
case len(locationIDs) > 0 && len(areaIDs) > 0:
|
|
filtered, err := filterLocationIDsByAreaIDs(db, locationIDs, areaIDs)
|
|
if err != nil {
|
|
return ScopeFilter{}, err
|
|
}
|
|
locationIDs = filtered
|
|
case len(locationIDs) == 0 && len(areaIDs) > 0:
|
|
derived, err := locationIDsByAreaIDs(db, areaIDs)
|
|
if err != nil {
|
|
return ScopeFilter{}, err
|
|
}
|
|
locationIDs = derived
|
|
}
|
|
|
|
locationIDs = uniqueUint(locationIDs)
|
|
if len(locationIDs) == 0 {
|
|
return ScopeFilter{Restrict: true}, nil
|
|
}
|
|
return ScopeFilter{IDs: locationIDs, Restrict: true}, nil
|
|
}
|
|
|
|
func collectRoleScope(c *fiber.Ctx) (roleScope, error) {
|
|
ctx, ok := AuthDetails(c)
|
|
if !ok || ctx == nil {
|
|
return roleScope{}, nil
|
|
}
|
|
|
|
userAreaIDs := uniqueUint(ctx.UserAreaIDs)
|
|
userLocationIDs := uniqueUint(ctx.UserLocationIDs)
|
|
userScope := roleScope{
|
|
allArea: ctx.UserAllArea,
|
|
allLocation: ctx.UserAllLocation,
|
|
areaIDs: userAreaIDs,
|
|
locationIDs: userLocationIDs,
|
|
hasAnyScopes: ctx.UserAllArea || ctx.UserAllLocation || len(userAreaIDs) > 0 || len(userLocationIDs) > 0,
|
|
}
|
|
if userScope.hasAnyScopes {
|
|
return userScope, nil
|
|
}
|
|
|
|
return roleScope{}, nil
|
|
}
|
|
|
|
func areaIDsByLocationIDs(db *gorm.DB, locationIDs []uint) ([]uint, error) {
|
|
if db == nil {
|
|
return nil, errors.New("database not configured")
|
|
}
|
|
if len(locationIDs) == 0 {
|
|
return nil, nil
|
|
}
|
|
var areaIDs []uint
|
|
if err := db.Model(&entity.Location{}).
|
|
Where("deleted_at IS NULL").
|
|
Where("id IN ?", locationIDs).
|
|
Distinct("area_id").
|
|
Pluck("area_id", &areaIDs).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
return areaIDs, nil
|
|
}
|
|
|
|
func locationIDsByAreaIDs(db *gorm.DB, areaIDs []uint) ([]uint, error) {
|
|
if db == nil {
|
|
return nil, errors.New("database not configured")
|
|
}
|
|
if len(areaIDs) == 0 {
|
|
return nil, nil
|
|
}
|
|
var locationIDs []uint
|
|
if err := db.Model(&entity.Location{}).
|
|
Where("deleted_at IS NULL").
|
|
Where("area_id IN ?", areaIDs).
|
|
Distinct("id").
|
|
Pluck("id", &locationIDs).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
return locationIDs, nil
|
|
}
|
|
|
|
func filterLocationIDsByAreaIDs(db *gorm.DB, locationIDs, areaIDs []uint) ([]uint, error) {
|
|
if db == nil {
|
|
return nil, errors.New("database not configured")
|
|
}
|
|
if len(locationIDs) == 0 || len(areaIDs) == 0 {
|
|
return nil, nil
|
|
}
|
|
var filtered []uint
|
|
if err := db.Model(&entity.Location{}).
|
|
Where("deleted_at IS NULL").
|
|
Where("id IN ?", locationIDs).
|
|
Where("area_id IN ?", areaIDs).
|
|
Distinct("id").
|
|
Pluck("id", &filtered).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
return filtered, nil
|
|
}
|
|
|
|
func uniqueUint(ids []uint) []uint {
|
|
if len(ids) == 0 {
|
|
return nil
|
|
}
|
|
seen := make(map[uint]struct{}, len(ids))
|
|
result := make([]uint, 0, len(ids))
|
|
for _, id := range ids {
|
|
if id == 0 {
|
|
continue
|
|
}
|
|
if _, ok := seen[id]; ok {
|
|
continue
|
|
}
|
|
seen[id] = struct{}{}
|
|
result = append(result, id)
|
|
}
|
|
return result
|
|
}
|
|
|
|
|
|
|
|
func ApplyScopeFilter(db *gorm.DB, scope ScopeFilter, column string) *gorm.DB {
|
|
if db == nil || !scope.Restrict {
|
|
return db
|
|
}
|
|
if len(scope.IDs) == 0 {
|
|
return db.Where("1 = 0")
|
|
}
|
|
return db.Where(column+" IN ?", scope.IDs)
|
|
}
|
|
|
|
func EnsureWarehouseAccess(c *fiber.Ctx, db *gorm.DB, warehouseID uint) error {
|
|
if warehouseID == 0 {
|
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid warehouse id")
|
|
}
|
|
if db == nil {
|
|
return fiber.NewError(fiber.StatusInternalServerError, "Database not configured")
|
|
}
|
|
|
|
scope, err := ResolveLocationScope(c, db)
|
|
if err != nil || !scope.Restrict {
|
|
return err
|
|
}
|
|
if len(scope.IDs) == 0 {
|
|
return fiber.NewError(fiber.StatusNotFound, "Warehouse not found")
|
|
}
|
|
|
|
var count int64
|
|
if err := ApplyScopeFilter(
|
|
db.WithContext(c.Context()).
|
|
Model(&entity.Warehouse{}).
|
|
Where("id = ?", warehouseID),
|
|
scope,
|
|
"warehouses.location_id",
|
|
).Count(&count).Error; err != nil {
|
|
return err
|
|
}
|
|
if count == 0 {
|
|
return fiber.NewError(fiber.StatusNotFound, "Warehouse not found")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func EnsureAreaAccess(c *fiber.Ctx, db *gorm.DB, areaID uint) error {
|
|
if areaID == 0 {
|
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid area id")
|
|
}
|
|
if db == nil {
|
|
return fiber.NewError(fiber.StatusInternalServerError, "Database not configured")
|
|
}
|
|
|
|
scope, err := ResolveAreaScope(c, db)
|
|
if err != nil || !scope.Restrict {
|
|
return err
|
|
}
|
|
if len(scope.IDs) == 0 {
|
|
return fiber.NewError(fiber.StatusNotFound, "Area not found")
|
|
}
|
|
|
|
var count int64
|
|
if err := ApplyScopeFilter(
|
|
db.WithContext(c.Context()).
|
|
Model(&entity.Area{}).
|
|
Where("id = ?", areaID),
|
|
scope,
|
|
"areas.id",
|
|
).Count(&count).Error; err != nil {
|
|
return err
|
|
}
|
|
if count == 0 {
|
|
return fiber.NewError(fiber.StatusNotFound, "Area not found")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func EnsureLocationAccess(c *fiber.Ctx, db *gorm.DB, locationID uint) error {
|
|
if locationID == 0 {
|
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid location id")
|
|
}
|
|
if db == nil {
|
|
return fiber.NewError(fiber.StatusInternalServerError, "Database not configured")
|
|
}
|
|
|
|
scope, err := ResolveLocationScope(c, db)
|
|
if err != nil || !scope.Restrict {
|
|
return err
|
|
}
|
|
if len(scope.IDs) == 0 {
|
|
return fiber.NewError(fiber.StatusNotFound, "Location not found")
|
|
}
|
|
|
|
var count int64
|
|
if err := ApplyScopeFilter(
|
|
db.WithContext(c.Context()).
|
|
Model(&entity.Location{}).
|
|
Where("id = ?", locationID),
|
|
scope,
|
|
"locations.id",
|
|
).Count(&count).Error; err != nil {
|
|
return err
|
|
}
|
|
if count == 0 {
|
|
return fiber.NewError(fiber.StatusNotFound, "Location not found")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func EnsureKandangAccess(c *fiber.Ctx, db *gorm.DB, kandangID uint) error {
|
|
if kandangID == 0 {
|
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid kandang id")
|
|
}
|
|
if db == nil {
|
|
return fiber.NewError(fiber.StatusInternalServerError, "Database not configured")
|
|
}
|
|
|
|
scope, err := ResolveLocationScope(c, db)
|
|
if err != nil || !scope.Restrict {
|
|
return err
|
|
}
|
|
if len(scope.IDs) == 0 {
|
|
return fiber.NewError(fiber.StatusNotFound, "Kandang not found")
|
|
}
|
|
|
|
var count int64
|
|
if err := ApplyScopeFilter(
|
|
db.WithContext(c.Context()).
|
|
Model(&entity.Kandang{}).
|
|
Where("id = ?", kandangID),
|
|
scope,
|
|
"kandangs.location_id",
|
|
).Count(&count).Error; err != nil {
|
|
return err
|
|
}
|
|
if count == 0 {
|
|
return fiber.NewError(fiber.StatusNotFound, "Kandang not found")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func EnsureProductWarehouseAccess(c *fiber.Ctx, db *gorm.DB, productWarehouseID uint) error {
|
|
if productWarehouseID == 0 {
|
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid product warehouse id")
|
|
}
|
|
if db == nil {
|
|
return fiber.NewError(fiber.StatusInternalServerError, "Database not configured")
|
|
}
|
|
|
|
scope, err := ResolveLocationScope(c, db)
|
|
if err != nil || !scope.Restrict {
|
|
return err
|
|
}
|
|
if len(scope.IDs) == 0 {
|
|
return fiber.NewError(fiber.StatusNotFound, "Product warehouse not found")
|
|
}
|
|
|
|
var count int64
|
|
q := db.WithContext(c.Context()).
|
|
Table("product_warehouses pw").
|
|
Joins("JOIN warehouses w ON w.id = pw.warehouse_id").
|
|
Where("pw.id = ?", productWarehouseID)
|
|
q = ApplyScopeFilter(q, scope, "w.location_id")
|
|
if err := q.Count(&count).Error; err != nil {
|
|
return err
|
|
}
|
|
if count == 0 {
|
|
return fiber.NewError(fiber.StatusNotFound, "Product warehouse not found")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func EnsureStockLogAccess(c *fiber.Ctx, db *gorm.DB, stockLogID uint) error {
|
|
if stockLogID == 0 {
|
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid stock log id")
|
|
}
|
|
if db == nil {
|
|
return fiber.NewError(fiber.StatusInternalServerError, "Database not configured")
|
|
}
|
|
|
|
scope, err := ResolveLocationScope(c, db)
|
|
if err != nil || !scope.Restrict {
|
|
return err
|
|
}
|
|
if len(scope.IDs) == 0 {
|
|
return fiber.NewError(fiber.StatusNotFound, "Stock log not found")
|
|
}
|
|
|
|
var count int64
|
|
q := db.WithContext(c.Context()).
|
|
Table("stock_logs sl").
|
|
Joins("JOIN product_warehouses pw ON pw.id = sl.product_warehouse_id").
|
|
Joins("JOIN warehouses w ON w.id = pw.warehouse_id").
|
|
Where("sl.id = ?", stockLogID)
|
|
q = ApplyScopeFilter(q, scope, "w.location_id")
|
|
if err := q.Count(&count).Error; err != nil {
|
|
return err
|
|
}
|
|
if count == 0 {
|
|
return fiber.NewError(fiber.StatusNotFound, "Stock log not found")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func EnsureMarketingAccess(c *fiber.Ctx, db *gorm.DB, marketingID uint) error {
|
|
if marketingID == 0 {
|
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid marketing id")
|
|
}
|
|
if db == nil {
|
|
return fiber.NewError(fiber.StatusInternalServerError, "Database not configured")
|
|
}
|
|
|
|
scope, err := ResolveLocationScope(c, db)
|
|
if err != nil || !scope.Restrict {
|
|
return err
|
|
}
|
|
if len(scope.IDs) == 0 {
|
|
return fiber.NewError(fiber.StatusNotFound, "Marketing not found")
|
|
}
|
|
|
|
var count int64
|
|
q := db.WithContext(c.Context()).
|
|
Table("marketings m").
|
|
Joins("JOIN marketing_products mp ON mp.marketing_id = m.id").
|
|
Joins("JOIN product_warehouses pw ON pw.id = mp.product_warehouse_id").
|
|
Joins("JOIN warehouses w ON w.id = pw.warehouse_id").
|
|
Where("m.id = ?", marketingID)
|
|
q = ApplyScopeFilter(q, scope, "w.location_id")
|
|
if err := q.Count(&count).Error; err != nil {
|
|
return err
|
|
}
|
|
if count == 0 {
|
|
return fiber.NewError(fiber.StatusNotFound, "Marketing not found")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func EnsureRecordingAccess(c *fiber.Ctx, db *gorm.DB, recordingID uint) error {
|
|
if recordingID == 0 {
|
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid recording id")
|
|
}
|
|
if db == nil {
|
|
return fiber.NewError(fiber.StatusInternalServerError, "Database not configured")
|
|
}
|
|
|
|
scope, err := ResolveLocationScope(c, db)
|
|
if err != nil || !scope.Restrict {
|
|
return err
|
|
}
|
|
if len(scope.IDs) == 0 {
|
|
return fiber.NewError(fiber.StatusNotFound, "Recording not found")
|
|
}
|
|
|
|
var count int64
|
|
q := db.WithContext(c.Context()).
|
|
Table("recordings r").
|
|
Joins("JOIN project_flock_kandangs pfk ON pfk.id = r.project_flock_kandangs_id").
|
|
Joins("JOIN project_flocks pf ON pf.id = pfk.project_flock_id").
|
|
Where("r.id = ?", recordingID)
|
|
q = ApplyScopeFilter(q, scope, "pf.location_id")
|
|
if err := q.Count(&count).Error; err != nil {
|
|
return err
|
|
}
|
|
if count == 0 {
|
|
return fiber.NewError(fiber.StatusNotFound, "Recording not found")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func EnsureUniformityAccess(c *fiber.Ctx, db *gorm.DB, uniformityID uint) error {
|
|
if uniformityID == 0 {
|
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid uniformity id")
|
|
}
|
|
if db == nil {
|
|
return fiber.NewError(fiber.StatusInternalServerError, "Database not configured")
|
|
}
|
|
|
|
scope, err := ResolveLocationScope(c, db)
|
|
if err != nil || !scope.Restrict {
|
|
return err
|
|
}
|
|
if len(scope.IDs) == 0 {
|
|
return fiber.NewError(fiber.StatusNotFound, "Uniformity not found")
|
|
}
|
|
|
|
var count int64
|
|
q := db.WithContext(c.Context()).
|
|
Table("project_flock_kandang_uniformities u").
|
|
Joins("JOIN project_flock_kandangs pfk ON pfk.id = u.project_flock_kandang_id").
|
|
Joins("JOIN project_flocks pf ON pf.id = pfk.project_flock_id").
|
|
Where("u.id = ?", uniformityID)
|
|
q = ApplyScopeFilter(q, scope, "pf.location_id")
|
|
if err := q.Count(&count).Error; err != nil {
|
|
return err
|
|
}
|
|
if count == 0 {
|
|
return fiber.NewError(fiber.StatusNotFound, "Uniformity not found")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func EnsureLayingTransferAccess(c *fiber.Ctx, db *gorm.DB, transferID uint) error {
|
|
if transferID == 0 {
|
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid transfer id")
|
|
}
|
|
if db == nil {
|
|
return fiber.NewError(fiber.StatusInternalServerError, "Database not configured")
|
|
}
|
|
|
|
scope, err := ResolveLocationScope(c, db)
|
|
if err != nil || !scope.Restrict {
|
|
return err
|
|
}
|
|
if len(scope.IDs) == 0 {
|
|
return fiber.NewError(fiber.StatusNotFound, "Transfer not found")
|
|
}
|
|
|
|
var count int64
|
|
q := db.WithContext(c.Context()).
|
|
Table("laying_transfers lt").
|
|
Joins("JOIN project_flocks pf_from ON pf_from.id = lt.from_project_flock_id").
|
|
Joins("JOIN project_flocks pf_to ON pf_to.id = lt.to_project_flock_id").
|
|
Where("lt.id = ?", transferID).
|
|
Where("(pf_from.location_id IN ? OR pf_to.location_id IN ?)", scope.IDs, scope.IDs)
|
|
if err := q.Count(&count).Error; err != nil {
|
|
return err
|
|
}
|
|
if count == 0 {
|
|
return fiber.NewError(fiber.StatusNotFound, "Transfer not found")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func EnsureProjectFlockAccess(c *fiber.Ctx, db *gorm.DB, projectFlockID uint) error {
|
|
if projectFlockID == 0 {
|
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid project flock id")
|
|
}
|
|
if db == nil {
|
|
return fiber.NewError(fiber.StatusInternalServerError, "Database not configured")
|
|
}
|
|
|
|
scope, err := ResolveLocationScope(c, db)
|
|
if err != nil || !scope.Restrict {
|
|
return err
|
|
}
|
|
if len(scope.IDs) == 0 {
|
|
return fiber.NewError(fiber.StatusNotFound, "Project Flock not found")
|
|
}
|
|
|
|
var count int64
|
|
if err := ApplyScopeFilter(
|
|
db.WithContext(c.Context()).
|
|
Model(&entity.ProjectFlock{}).
|
|
Where("id = ?", projectFlockID),
|
|
scope,
|
|
"project_flocks.location_id",
|
|
).Count(&count).Error; err != nil {
|
|
return err
|
|
}
|
|
if count == 0 {
|
|
return fiber.NewError(fiber.StatusNotFound, "Project Flock not found")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func EnsureProjectFlockKandangAccess(c *fiber.Ctx, db *gorm.DB, projectFlockID, projectFlockKandangID uint) error {
|
|
if projectFlockKandangID == 0 {
|
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid project flock kandang id")
|
|
}
|
|
if db == nil {
|
|
return fiber.NewError(fiber.StatusInternalServerError, "Database not configured")
|
|
}
|
|
|
|
scope, err := ResolveLocationScope(c, db)
|
|
if err != nil || !scope.Restrict {
|
|
return err
|
|
}
|
|
if len(scope.IDs) == 0 {
|
|
return fiber.NewError(fiber.StatusNotFound, "Project Flock Kandang not found")
|
|
}
|
|
|
|
var count int64
|
|
q := db.WithContext(c.Context()).
|
|
Table("project_flock_kandangs").
|
|
Joins("JOIN project_flocks ON project_flocks.id = project_flock_kandangs.project_flock_id").
|
|
Where("project_flock_kandangs.id = ?", projectFlockKandangID)
|
|
if projectFlockID > 0 {
|
|
q = q.Where("project_flock_kandangs.project_flock_id = ?", projectFlockID)
|
|
}
|
|
q = ApplyScopeFilter(q, scope, "project_flocks.location_id")
|
|
if err := q.Count(&count).Error; err != nil {
|
|
return err
|
|
}
|
|
if count == 0 {
|
|
return fiber.NewError(fiber.StatusNotFound, "Project Flock Kandang not found")
|
|
}
|
|
return nil
|
|
}
|