init push

This commit is contained in:
2026-07-16 22:16:45 +07:00
commit 8b068bdb10
1021 changed files with 332816 additions and 0 deletions

423
internal/app/api/seed.go Normal file
View File

@@ -0,0 +1,423 @@
package api
import (
"strings"
"time"
"gorm.io/gorm"
"gorm.io/gorm/clause"
"wucher/internal/domain/auth"
"wucher/internal/domain/base"
)
// SeedRoles inserts default roles if they don't exist.
func SeedRoles(db *gorm.DB) error {
roles := []auth.Role{
{Code: "admin", Name: "admin", Description: "Full access to all resources."},
{Code: auth.RoleCodeStaff, Name: auth.RoleCodeStaff, Description: "Operational access with limited admin rights."},
{Code: "user", Name: "user", Description: "Regular end-user access."},
{Code: auth.RoleCodePilot, Name: "pilot", Description: "HEMS pilot contact role."},
{Code: auth.RoleCodeDoctor, Name: "doctor", Description: "HEMS doctor contact role."},
{Code: auth.RoleCodeAirRescuer, Name: "air_rescuer", Description: "HEMS air rescuer contact role."},
{Code: auth.RoleCodeTechnician, Name: "technician", Description: "HEMS technician contact role."},
{Code: auth.RoleCodeFlightAssistant, Name: "flight_assistant", Description: "HEMS flight assistant contact role."},
}
return db.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "name"}},
DoUpdates: clause.AssignmentColumns([]string{"code", "description"}),
}).Create(&roles).Error
}
// DefaultPermissions returns the canonical catalog of permissions seeded into the system.
func DefaultPermissions() []auth.Permission {
return []auth.Permission{
{Name: "Create User", Key: "user.create", Description: "Create users"},
{Name: "Read User", Key: "user.read", Description: "Read users"},
{Name: "Update User", Key: "user.update", Description: "Update users"},
{Name: "Delete User", Key: "user.delete", Description: "Delete users"},
{Name: "Create Role", Key: "role.create", Description: "Create roles"},
{Name: "Read Role", Key: "role.read", Description: "Read roles"},
{Name: "Update Role", Key: "role.update", Description: "Update roles"},
{Name: "Delete Role", Key: "role.delete", Description: "Delete roles"},
{Name: "Read Permission", Key: "role.permission.read", Description: "Read permissions"},
{Name: "Assign Permission", Key: "role.permission.assign", Description: "Assign permissions to role"},
{Name: "Remove Permission", Key: "role.permission.remove", Description: "Remove permissions from role"},
{Name: "Update Permission", Key: "role.permission.update", Description: "Update permission configuration"},
{Name: "Read Filesystem", Key: "filesystem.read", Description: "Read filesystem structure"},
{Name: "Read File Manager", Key: "file_manager.read", Description: "Read file manager"},
{Name: "Create File Manager", Key: "file_manager.create", Description: "Create in file manager"},
{Name: "Update File Manager", Key: "file_manager.update", Description: "Update in file manager"},
{Name: "Delete File Manager", Key: "file_manager.delete", Description: "Delete in file manager"},
{Name: "Read Folders", Key: "file_manager.folder.read", Description: "Read folders"},
{Name: "Create Folders", Key: "file_manager.folder.create", Description: "Create folders"},
{Name: "Update Folders", Key: "file_manager.folder.update", Description: "Update folders"},
{Name: "Delete Folders", Key: "file_manager.folder.delete", Description: "Delete folders"},
{Name: "Read Files", Key: "file_manager.file.read", Description: "Read files"},
{Name: "Create Files", Key: "file_manager.file.create", Description: "Create files"},
{Name: "Update Files", Key: "file_manager.file.update", Description: "Update files"},
{Name: "Delete Files", Key: "file_manager.file.delete", Description: "Delete files"},
{Name: "Create Helicopter", Key: "helicopter.create", Description: "Create helicopter"},
{Name: "Generate Helicopter Report Number", Key: "helicopter.report.generate", Description: "Generate next helicopter report number"},
{Name: "Read Helicopter", Key: "helicopter.read", Description: "Read helicopter"},
{Name: "Update Helicopter", Key: "helicopter.update", Description: "Update helicopter"},
{Name: "Delete Helicopter", Key: "helicopter.delete", Description: "Delete helicopter"},
{Name: "Create Helicopter Usage", Key: "helicopter_usage.create", Description: "Create helicopter usage"},
{Name: "Read Helicopter Usage", Key: "helicopter_usage.read", Description: "Read helicopter usage"},
{Name: "Update Helicopter Usage", Key: "helicopter_usage.update", Description: "Update helicopter usage"},
{Name: "Delete Helicopter Usage", Key: "helicopter_usage.delete", Description: "Delete helicopter usage"},
{Name: "Create Flight Inspection", Key: "flight_inspection.create", Description: "Create flight inspection"},
{Name: "Read Flight Inspection", Key: "flight_inspection.read", Description: "Read flight inspection"},
{Name: "Update Flight Inspection", Key: "flight_inspection.update", Description: "Update flight inspection"},
{Name: "Create After Flight Inspection", Key: "after_flight.create", Description: "Create after flight inspection"},
{Name: "Read After Flight Inspection", Key: "after_flight.read", Description: "Read after flight inspection"},
{Name: "Update After Flight Inspection", Key: "after_flight.update", Description: "Update after flight inspection"},
{Name: "Delete After Flight Inspection", Key: "after_flight.delete", Description: "Delete after flight inspection"},
{Name: "Create Flight", Key: "flight.create", Description: "Create flight"},
{Name: "Read Flight", Key: "flight.read", Description: "Read flight"},
{Name: "Update Flight", Key: "flight.update", Description: "Update flight"},
{Name: "Complete Flight Report", Key: "flight.complete", Description: "Complete (finalize) flight report"},
{Name: "Delete Flight", Key: "flight.delete", Description: "Delete flight"},
{Name: "Create Reserve AC", Key: "reserve_ac.create", Description: "Create reserve AC"},
{Name: "Read Reserve AC", Key: "reserve_ac.read", Description: "Read reserve AC"},
{Name: "Update Reserve AC", Key: "reserve_ac.update", Description: "Update reserve AC"},
{Name: "Delete Reserve AC", Key: "reserve_ac.delete", Description: "Delete reserve AC"},
{Name: "Create Facility", Key: "facility.create", Description: "Create facility"},
{Name: "Read Facility", Key: "facility.read", Description: "Read facility"},
{Name: "Update Facility", Key: "facility.update", Description: "Update facility"},
{Name: "Delete Facility", Key: "facility.delete", Description: "Delete facility"},
{Name: "Create Base", Key: "base.create", Description: "Create base"},
{Name: "Read Base", Key: "base.read", Description: "Read base"},
{Name: "Update Base", Key: "base.update", Description: "Update base"},
{Name: "Delete Base", Key: "base.delete", Description: "Delete base"},
{Name: "Create HEMS Base", Key: "hems_base.create", Description: "Create HEMS base"},
{Name: "Read HEMS Base", Key: "hems_base.read", Description: "Read HEMS base"},
{Name: "Update HEMS Base", Key: "hems_base.update", Description: "Update HEMS base"},
{Name: "Delete HEMS Base", Key: "hems_base.delete", Description: "Delete HEMS base"},
{Name: "Create Medicine", Key: "medicine.create", Description: "Create medicine"},
{Name: "Read Medicine", Key: "medicine.read", Description: "Read medicine"},
{Name: "Update Medicine", Key: "medicine.update", Description: "Update medicine"},
{Name: "Delete Medicine", Key: "medicine.delete", Description: "Delete medicine"},
{Name: "Create Vocation", Key: "vocation.create", Description: "Create vocation"},
{Name: "Read Vocation", Key: "vocation.read", Description: "Read vocation"},
{Name: "Update Vocation", Key: "vocation.update", Description: "Update vocation"},
{Name: "Delete Vocation", Key: "vocation.delete", Description: "Delete vocation"},
{Name: "Create OPC", Key: "opc.create", Description: "Create opc"},
{Name: "Read OPC", Key: "opc.read", Description: "Read opc"},
{Name: "Update OPC", Key: "opc.update", Description: "Update opc"},
{Name: "Delete OPC", Key: "opc.delete", Description: "Delete opc"},
{Name: "Create Forces Present", Key: "forces_present.create", Description: "Create forces present"},
{Name: "Read Forces Present", Key: "forces_present.read", Description: "Read forces present"},
{Name: "Update Forces Present", Key: "forces_present.update", Description: "Update forces present"},
{Name: "Delete Forces Present", Key: "forces_present.delete", Description: "Delete forces present"},
{Name: "Create No ICAO Code", Key: "no_icao_code.create", Description: "Create no icao code"},
{Name: "Read No ICAO Code", Key: "no_icao_code.read", Description: "Read no icao code"},
{Name: "Update No ICAO Code", Key: "no_icao_code.update", Description: "Update no icao code"},
{Name: "Delete No ICAO Code", Key: "no_icao_code.delete", Description: "Delete no icao code"},
{Name: "Create Health Insurance Companies", Key: "health_insurance_companies.create", Description: "Create health insurance companies"},
{Name: "Read Health Insurance Companies", Key: "health_insurance_companies.read", Description: "Read health insurance companies"},
{Name: "Update Health Insurance Companies", Key: "health_insurance_companies.update", Description: "Update health insurance companies"},
{Name: "Delete Health Insurance Companies", Key: "health_insurance_companies.delete", Description: "Delete health insurance companies"},
{Name: "Create Federal State", Key: "federal_state.create", Description: "Create federal state"},
{Name: "Read Federal State", Key: "federal_state.read", Description: "Read federal state"},
{Name: "Update Federal State", Key: "federal_state.update", Description: "Update federal state"},
{Name: "Delete Federal State", Key: "federal_state.delete", Description: "Delete federal state"},
{Name: "Create ICAO", Key: "icao.create", Description: "Create ICAO"},
{Name: "Read ICAO", Key: "icao.read", Description: "Read ICAO"},
{Name: "Update ICAO", Key: "icao.update", Description: "Update ICAO"},
{Name: "Delete ICAO", Key: "icao.delete", Description: "Delete ICAO"},
{Name: "Create Country", Key: "country.create", Description: "Create country"},
{Name: "Read Country", Key: "country.read", Description: "Read country"},
{Name: "Update Country", Key: "country.update", Description: "Update country"},
{Name: "Delete Country", Key: "country.delete", Description: "Delete country"},
{Name: "Create Master Settings", Key: "master_settings.create", Description: "Create master settings"},
{Name: "Read Master Settings", Key: "master_settings.read", Description: "Read master settings"},
{Name: "Update Master Settings", Key: "master_settings.update", Description: "Update master settings"},
{Name: "Delete Master Settings", Key: "master_settings.delete", Description: "Delete master settings"},
{Name: "Create Duty Roster", Key: "duty_roster.create", Description: "Create duty roster"},
{Name: "Read Duty Roster", Key: "duty_roster.read", Description: "Read duty roster"},
{Name: "Update Duty Roster", Key: "duty_roster.update", Description: "Update duty roster"},
{Name: "Delete Duty Roster", Key: "duty_roster.delete", Description: "Delete duty roster"},
{Name: "Create Air Rescuer Checklist", Key: "air_rescuer_checklist.create", Description: "Create air rescuer checklist"},
{Name: "Read Air Rescuer Checklist", Key: "air_rescuer_checklist.read", Description: "Read air rescuer checklist"},
{Name: "Update Air Rescuer Checklist", Key: "air_rescuer_checklist.update", Description: "Update air rescuer checklist"},
{Name: "Delete Air Rescuer Checklist", Key: "air_rescuer_checklist.delete", Description: "Delete air rescuer checklist"},
{Name: "Create Flight Prep Check", Key: "flight_prep_check.create", Description: "Create flight prep check"},
{Name: "Read Flight Prep Check", Key: "flight_prep_check.read", Description: "Read flight prep check"},
{Name: "Update Flight Prep Check", Key: "flight_prep_check.update", Description: "Update flight prep check"},
{Name: "Read Audit Log", Key: "audit.read", Description: "Read audit logs"},
{Name: "Create Insurance Patient Data", Key: "insurance_patient_data.create", Description: "Create insurance patient data"},
{Name: "Read Insurance Patient Data", Key: "insurance_patient_data.read", Description: "Read insurance patient data"},
{Name: "Update Insurance Patient Data", Key: "insurance_patient_data.update", Description: "Update insurance patient data"},
{Name: "Delete Insurance Patient Data", Key: "insurance_patient_data.delete", Description: "Delete insurance patient data"},
{Name: "Create Patient Data", Key: "patient_data.create", Description: "Create patient data"},
{Name: "Read Patient Data", Key: "patient_data.read", Description: "Read patient data"},
{Name: "Update Patient Data", Key: "patient_data.update", Description: "Update patient data"},
{Name: "Delete Patient Data", Key: "patient_data.delete", Description: "Delete patient data"},
{Name: "Create Operation", Key: "operation.create", Description: "Create HEMS operational data"},
{Name: "Read Operation", Key: "operation.read", Description: "Read HEMS operational data"},
{Name: "Update Operation", Key: "operation.update", Description: "Update HEMS operational data"},
{Name: "Delete Operation", Key: "operation.delete", Description: "Delete HEMS operational data"},
{Name: "Create Operation Category", Key: "operation_category.create", Description: "Create HEMS operation category"},
{Name: "Read Operation Category", Key: "operation_category.read", Description: "Read HEMS operation category"},
{Name: "Update Operation Category", Key: "operation_category.update", Description: "Update HEMS operation category"},
{Name: "Delete Operation Category", Key: "operation_category.delete", Description: "Delete HEMS operation category"},
{Name: "Create HEMS Operation", Key: "hems_operation.create", Description: "Create HEMS operation"},
{Name: "Read HEMS Operation", Key: "hems_operation.read", Description: "Read HEMS operation"},
{Name: "Update HEMS Operation", Key: "hems_operation.update", Description: "Update HEMS operation"},
{Name: "Delete HEMS Operation", Key: "hems_operation.delete", Description: "Delete HEMS operation"},
{Name: "Create DUL", Key: "dul.create", Description: "Create DUL"},
{Name: "Read DUL", Key: "dul.read", Description: "Read DUL"},
{Name: "Update DUL", Key: "dul.update", Description: "Update DUL"},
{Name: "Delete DUL", Key: "dul.delete", Description: "Delete DUL"},
{Name: "Create Complaint", Key: "complaint.create", Description: "Create complaint"},
{Name: "Read Complaint", Key: "complaint.read", Description: "Read complaint"},
{Name: "Update Complaint", Key: "complaint.update", Description: "Update complaint"},
{Name: "Delete Complaint", Key: "complaint.delete", Description: "Delete complaint"},
{Name: "Decide Complaint NSR", Key: "complaint.nsr", Description: "Set or cancel complaint NSR (non-safety release)"},
{Name: "Create EASA Release", Key: "easa_release.create", Description: "Create EASA maintenance release"},
{Name: "Read EASA Release", Key: "easa_release.read", Description: "Read EASA maintenance release"},
{Name: "Update EASA Release", Key: "easa_release.update", Description: "Update EASA maintenance release"},
{Name: "Delete EASA Release", Key: "easa_release.delete", Description: "Delete EASA maintenance release"},
{Name: "Sign EASA Release", Key: "easa_release.sign", Description: "Sign EASA maintenance release (return to service)"},
{Name: "Read Action Sign-off", Key: "action_signoff.read", Description: "Read corrective-action sign-off"},
{Name: "Sign Action Sign-off", Key: "action_signoff.sign", Description: "Toggle (sign/unsign) corrective-action sign-off, per complaint or fleet-wide"},
{Name: "Create MCF", Key: "mcf.create", Description: "Create maintenance check flight"},
{Name: "Read MCF", Key: "mcf.read", Description: "Read maintenance check flight"},
{Name: "Complete MCF", Key: "mcf.complete", Description: "Complete (release) a helicopter's open maintenance check flight"},
}
}
// SeedPermissions inserts default permissions and keeps existing rows in sync.
func SeedPermissions(db *gorm.DB) error {
perms := DefaultPermissions()
for i := range perms {
perms[i].Module = auth.ModuleForKey(perms[i].Key)
perms[i].RequiresPIN = auth.DefaultPermissionRequiresPIN(perms[i].Key)
}
return db.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "key"}},
DoUpdates: clause.AssignmentColumns([]string{"name", "description", "module"}),
}).Create(&perms).Error
}
// SeedRolePermissions inserts default role-permission mappings and ensures admin has all permissions.
func SeedRolePermissions(db *gorm.DB) error {
type rolePerm struct {
RoleName string
PermKey string
}
mappings := []rolePerm{
// staff: read-only for users/roles/permissions
{RoleName: "staff", PermKey: "user.read"},
{RoleName: "staff", PermKey: "role.read"},
{RoleName: "staff", PermKey: "role.permission.read"},
{RoleName: "staff", PermKey: "helicopter.read"},
{RoleName: "staff", PermKey: "flight_inspection.read"},
{RoleName: "staff", PermKey: "flight.read"},
{RoleName: "staff", PermKey: "reserve_ac.read"},
{RoleName: "staff", PermKey: "facility.read"},
{RoleName: "staff", PermKey: "base.read"},
{RoleName: "staff", PermKey: "hems_base.read"},
{RoleName: "staff", PermKey: "medicine.read"},
{RoleName: "staff", PermKey: "vocation.read"},
{RoleName: "staff", PermKey: "opc.read"},
{RoleName: "staff", PermKey: "forces_present.read"},
{RoleName: "staff", PermKey: "no_icao_code.read"},
{RoleName: "staff", PermKey: "health_insurance_companies.read"},
{RoleName: "staff", PermKey: "federal_state.read"},
{RoleName: "staff", PermKey: "icao.read"},
{RoleName: "staff", PermKey: "country.read"},
{RoleName: "staff", PermKey: "master_settings.read"},
{RoleName: "staff", PermKey: "file_manager.read"},
{RoleName: "staff", PermKey: "duty_roster.read"},
{RoleName: "staff", PermKey: "air_rescuer_checklist.read"},
{RoleName: "staff", PermKey: "flight_prep_check.create"},
{RoleName: "staff", PermKey: "flight_prep_check.read"},
{RoleName: "staff", PermKey: "flight_prep_check.update"},
{RoleName: "staff", PermKey: "helicopter_usage.read"},
{RoleName: "staff", PermKey: "after_flight.read"},
{RoleName: "staff", PermKey: "audit.read"},
{RoleName: "staff", PermKey: "insurance_patient_data.read"},
{RoleName: "staff", PermKey: "patient_data.read"},
{RoleName: "staff", PermKey: "operation.read"},
{RoleName: "staff", PermKey: "operation_category.read"},
{RoleName: "staff", PermKey: "hems_operation.read"},
{RoleName: "pilot", PermKey: "flight.read"},
{RoleName: "pilot", PermKey: "duty_roster.read"},
}
if err := assignAllPermissionsToRole(db, "admin"); err != nil {
return err
}
for _, m := range mappings {
if err := assignPermissionToRole(db, m.RoleName, m.PermKey); err != nil {
return err
}
}
return nil
}
func assignAllPermissionsToRole(db *gorm.DB, roleName string) error {
var role auth.Role
if err := db.Where("name = ?", roleName).First(&role).Error; err != nil {
return err
}
var perms []auth.Permission
if err := db.Find(&perms).Error; err != nil {
return err
}
for _, perm := range perms {
rp := auth.RolePermission{RoleID: role.ID, PermissionID: perm.ID}
if err := db.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "role_id"}, {Name: "permission_id"}},
DoNothing: true,
}).Create(&rp).Error; err != nil {
return err
}
}
return nil
}
func assignPermissionToRole(db *gorm.DB, roleName, permKey string) error {
var role auth.Role
if err := db.Where("name = ?", roleName).First(&role).Error; err != nil {
return err
}
var perm auth.Permission
if err := db.Where("`key` = ?", permKey).First(&perm).Error; err != nil {
return err
}
rp := auth.RolePermission{RoleID: role.ID, PermissionID: perm.ID}
return db.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "role_id"}, {Name: "permission_id"}},
DoNothing: true,
}).Create(&rp).Error
}
// SeedBaseCategories inserts default base categories if they don't exist.
func SeedBaseCategories(db *gorm.DB) error {
categories := []base.BaseCategory{
{Key: base.CategoryKeyRegular, Name: "Regular Base"},
{Key: base.CategoryKeyHEMS, Name: "HEMS Base"},
}
return db.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "key"}},
DoUpdates: clause.AssignmentColumns([]string{"name"}),
}).Create(&categories).Error
}
// SeedRosterUsers inserts sample users for duty roster assignment in development.
// This seed is idempotent by email and keeps role assignment synced via user_roles.
func SeedRosterUsers(db *gorm.DB) error {
type rosterSeed struct {
Email string
FirstName string
LastName string
MobilePhone string
RoleCode string
}
seeds := []rosterSeed{
{Email: "pilot.main@wucher.local", FirstName: "Pilot", LastName: "Main", MobilePhone: "+620001", RoleCode: auth.RoleCodePilot},
{Email: "pilot.additional@wucher.local", FirstName: "Pilot", LastName: "Additional", MobilePhone: "+620002", RoleCode: auth.RoleCodePilot},
{Email: "doctor.main@wucher.local", FirstName: "Doctor", LastName: "Main", MobilePhone: "+620003", RoleCode: auth.RoleCodeDoctor},
{Email: "rescuer.main@wucher.local", FirstName: "Rescuer", LastName: "Main", MobilePhone: "+620004", RoleCode: auth.RoleCodeAirRescuer},
{Email: "helper.main@wucher.local", FirstName: "Helper", LastName: "Main", MobilePhone: "+620005", RoleCode: auth.RoleCodeFlightAssistant},
}
return db.Transaction(func(tx *gorm.DB) error {
roleKeys := make([]string, 0, len(seeds))
seenRole := make(map[string]struct{}, len(seeds))
for i := range seeds {
k := strings.ToLower(strings.TrimSpace(seeds[i].RoleCode))
if k == "" {
continue
}
if _, ok := seenRole[k]; ok {
continue
}
seenRole[k] = struct{}{}
roleKeys = append(roleKeys, k)
}
var roles []auth.Role
if err := tx.Where("LOWER(code) IN ? OR LOWER(name) IN ?", roleKeys, roleKeys).Find(&roles).Error; err != nil {
return err
}
roleIDByCode := make(map[string][]byte, len(roles))
for i := range roles {
code := strings.ToLower(strings.TrimSpace(roles[i].Code))
if code == "" {
code = strings.ToLower(strings.TrimSpace(roles[i].Name))
}
if code == "" {
continue
}
roleIDByCode[code] = append([]byte(nil), roles[i].ID...)
}
now := time.Now().UTC()
for i := range seeds {
roleCode := strings.ToLower(strings.TrimSpace(seeds[i].RoleCode))
roleID := roleIDByCode[roleCode]
if len(roleID) != 16 {
continue
}
row := auth.User{
Email: strings.ToLower(strings.TrimSpace(seeds[i].Email)),
Username: nil,
FirstName: strings.TrimSpace(seeds[i].FirstName),
LastName: strings.TrimSpace(seeds[i].LastName),
MobilePhone: strings.TrimSpace(seeds[i].MobilePhone),
Timezone: "UTC",
RoleID: roleID,
IsActive: true,
EmailVerifiedAt: &now,
}
if err := tx.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "email"}},
DoUpdates: clause.Assignments(map[string]any{
"first_name": row.FirstName,
"last_name": row.LastName,
"mobile_phone": row.MobilePhone,
"timezone": row.Timezone,
"role_id": row.RoleID,
"is_active": row.IsActive,
"email_verified_at": row.EmailVerifiedAt,
"updated_at": now,
}),
}).Create(&row).Error; err != nil {
return err
}
var created auth.User
if err := tx.Where("email = ?", row.Email).Take(&created).Error; err != nil {
return err
}
link := auth.UserRole{
UserID: created.ID,
RoleID: roleID,
}
if err := tx.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "user_id"}, {Name: "role_id"}},
DoNothing: true,
}).Create(&link).Error; err != nil {
return err
}
}
return nil
})
}