refactor: rename safe and audit backend resources
This commit is contained in:
@@ -0,0 +1,97 @@
|
|||||||
|
# Task 2 Report: Safe and Audit Backend Rename
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
Task 2 is complete for the backend. The model exports, ORM table names, resource
|
||||||
|
contracts, protected routes, approval workflow, dashboard query, and focused SQL
|
||||||
|
tests now use the `safe_*` and `audit_*` names exclusively. No frontend
|
||||||
|
resources, routes, or pages were changed.
|
||||||
|
|
||||||
|
## TDD Evidence
|
||||||
|
|
||||||
|
The existing Task 1 route and resource-contract tests provided the initial RED
|
||||||
|
baseline. Before production changes, the backend focused suite failed because
|
||||||
|
the `safe_*` and `audit_*` contracts/routes were missing while the legacy
|
||||||
|
`saf_*` and `aud_*` routes remained registered.
|
||||||
|
|
||||||
|
The backend behavior tests were then updated first for the renamed model API,
|
||||||
|
tables, request paths, and disposal relation. Running the focused suite again
|
||||||
|
failed as expected with `models.SafeRule` undefined, in addition to the route
|
||||||
|
contract failures. This confirmed the tests required the production rename.
|
||||||
|
|
||||||
|
After the minimal production implementation, the focused suite turned GREEN:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
$env:GIN_MODE='release'
|
||||||
|
go test ./internal/logic/platform ./internal/routers -run 'Test.*(Safe|Audit)' -v
|
||||||
|
```
|
||||||
|
|
||||||
|
Result: exit code 0. All selected safe/audit logic and router tests passed.
|
||||||
|
|
||||||
|
## Implementation
|
||||||
|
|
||||||
|
- Renamed the seven model files and exports to:
|
||||||
|
- `models.SafeRule`
|
||||||
|
- `models.SafeEvent`
|
||||||
|
- `models.SafeEventDisposal`
|
||||||
|
- `models.SafeInspection`
|
||||||
|
- `models.AuditApproval`
|
||||||
|
- `models.AuditExportLog`
|
||||||
|
- `models.AuditOperationLog`
|
||||||
|
- Updated model comments while preserving the Chinese model and field
|
||||||
|
descriptions.
|
||||||
|
- Updated migration registrations and `TableName()` values to `safe_rule`,
|
||||||
|
`safe_event`, `safe_event_disposal`, `safe_inspection`, `audit_approval`,
|
||||||
|
`audit_export_log`, and `audit_operation_log`.
|
||||||
|
- Renamed the disposal relation field, GORM column, JSON field, queries, and SQL
|
||||||
|
expectations to `safe_event_identity`.
|
||||||
|
- Renamed the safety and audit resource catalogue entries and protected routes,
|
||||||
|
including the append-only safe-event disposal endpoints and the audit approval
|
||||||
|
action.
|
||||||
|
- Updated the audit workflow to persist to the renamed audit tables and record
|
||||||
|
`audit_approval` as its object type.
|
||||||
|
- Updated dashboard and resource behavior tests for the renamed tables and
|
||||||
|
models.
|
||||||
|
|
||||||
|
No legacy aliases were retained. No table/data migration was added.
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
From `backend/api`:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
go test ./...
|
||||||
|
```
|
||||||
|
|
||||||
|
Result: exit code 0; all Go packages passed.
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
go build ./cmd/main
|
||||||
|
```
|
||||||
|
|
||||||
|
Result: exit code 0.
|
||||||
|
|
||||||
|
Production-only searches found no legacy model exports, `saf_`/`aud_` tokens, or
|
||||||
|
legacy model filenames under `backend/api`. Negative assertions in backend tests
|
||||||
|
intentionally retain the old public paths so regressions cannot reintroduce
|
||||||
|
aliases.
|
||||||
|
|
||||||
|
From `frontend/platform_admin`:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
node --test scripts/audit-check.test.mjs
|
||||||
|
```
|
||||||
|
|
||||||
|
Result: expected exit code 1, with 6 passing and 1 failing test. The remaining
|
||||||
|
failure is the Task 1 frontend rename contract assigned to Task 3. No frontend
|
||||||
|
file was modified by this task.
|
||||||
|
|
||||||
|
`git diff --check` completed without whitespace errors.
|
||||||
|
|
||||||
|
## Concerns
|
||||||
|
|
||||||
|
- Deploying this backend before Task 3 would leave the current frontend calling
|
||||||
|
the removed legacy resource paths. The coordinated frontend rename must ship
|
||||||
|
with the backend contract change.
|
||||||
|
- Historical `saf_*` and `aud_*` tables/data are intentionally not migrated or
|
||||||
|
aliased, per the task constraint.
|
||||||
@@ -48,7 +48,7 @@ func ApproveAudit(ctx *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
values := approvalValues(request.Status, request.Opinion, claims.Identity)
|
values := approvalValues(request.Status, request.Opinion, claims.Identity)
|
||||||
var approval models.AudApproval
|
var approval models.AuditApproval
|
||||||
err = impl.DBService.Transaction(func(transaction *gorm.DB) error {
|
err = impl.DBService.Transaction(func(transaction *gorm.DB) error {
|
||||||
if err := transaction.Where("identity = ?", ctx.Param("identity")).First(&approval).Error; err != nil {
|
if err := transaction.Where("identity = ?", ctx.Param("identity")).First(&approval).Error; err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -63,7 +63,7 @@ func ApproveAudit(ctx *gin.Context) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if result := transaction.Model(&models.AudApproval{}).Where("identity = ? AND status = ?", approval.Identity, "pending").Updates(values); result.Error != nil {
|
if result := transaction.Model(&models.AuditApproval{}).Where("identity = ? AND status = ?", approval.Identity, "pending").Updates(values); result.Error != nil {
|
||||||
return result.Error
|
return result.Error
|
||||||
} else if result.RowsAffected == 0 {
|
} else if result.RowsAffected == 0 {
|
||||||
return errApprovalNotProcessable
|
return errApprovalNotProcessable
|
||||||
@@ -72,11 +72,11 @@ func ApproveAudit(ctx *gin.Context) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return transaction.Create(&models.AudOperationLog{
|
return transaction.Create(&models.AuditOperationLog{
|
||||||
Entity: newEntity("enabled"),
|
Entity: newEntity("enabled"),
|
||||||
OperatorIdentity: claims.Identity,
|
OperatorIdentity: claims.Identity,
|
||||||
Action: "approve",
|
Action: "approve",
|
||||||
ObjectType: "aud_approval",
|
ObjectType: "audit_approval",
|
||||||
ObjectIdentity: approval.Identity,
|
ObjectIdentity: approval.Identity,
|
||||||
BeforeData: string(before),
|
BeforeData: string(before),
|
||||||
AfterData: string(after),
|
AfterData: string(after),
|
||||||
|
|||||||
@@ -32,19 +32,19 @@ func TestApproveAuditOnlyUpdatesApprovalFieldsAndAppendsOperationAudit(t *testin
|
|||||||
_, mock := setupPlatformRoleDatabase(t)
|
_, mock := setupPlatformRoleDatabase(t)
|
||||||
now := time.Now().UTC()
|
now := time.Now().UTC()
|
||||||
mock.ExpectBegin()
|
mock.ExpectBegin()
|
||||||
mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "aud_approval" WHERE identity = $1 ORDER BY "aud_approval"."id" LIMIT $2`)).
|
mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "audit_approval" WHERE identity = $1 ORDER BY "audit_approval"."id" LIMIT $2`)).
|
||||||
WithArgs("approval-a", 1).
|
WithArgs("approval-a", 1).
|
||||||
WillReturnRows(sqlmock.NewRows([]string{"id", "identity", "created_at", "updated_at", "status", "version", "business_type", "business_identity", "applicant_identity", "opinion", "handler_identity", "handled_at"}).
|
WillReturnRows(sqlmock.NewRows([]string{"id", "identity", "created_at", "updated_at", "status", "version", "business_type", "business_identity", "applicant_identity", "opinion", "handler_identity", "handled_at"}).
|
||||||
AddRow(uint64(1), "approval-a", now, now, "pending", 1, "refund", "payment-a", "applicant-a", "", "", nil))
|
AddRow(uint64(1), "approval-a", now, now, "pending", 1, "refund", "payment-a", "applicant-a", "", "", nil))
|
||||||
mock.ExpectExec(regexp.QuoteMeta(`UPDATE "aud_approval" SET "handled_at"=$1,"handler_identity"=$2,"opinion"=$3,"status"=$4,"updated_at"=$5 WHERE identity = $6 AND status = $7`)).
|
mock.ExpectExec(regexp.QuoteMeta(`UPDATE "audit_approval" SET "handled_at"=$1,"handler_identity"=$2,"opinion"=$3,"status"=$4,"updated_at"=$5 WHERE identity = $6 AND status = $7`)).
|
||||||
WithArgs(sqlmock.AnyArg(), "operator-a", "accepted", "approved", sqlmock.AnyArg(), "approval-a", "pending").
|
WithArgs(sqlmock.AnyArg(), "operator-a", "accepted", "approved", sqlmock.AnyArg(), "approval-a", "pending").
|
||||||
WillReturnResult(sqlmock.NewResult(0, 1))
|
WillReturnResult(sqlmock.NewResult(0, 1))
|
||||||
mock.ExpectQuery(regexp.QuoteMeta(`INSERT INTO "aud_operation_log" ("identity","created_at","updated_at","status","version","operator_identity","action","object_type","object_identity","before_data","after_data") VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11) RETURNING "id"`)).
|
mock.ExpectQuery(regexp.QuoteMeta(`INSERT INTO "audit_operation_log" ("identity","created_at","updated_at","status","version","operator_identity","action","object_type","object_identity","before_data","after_data") VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11) RETURNING "id"`)).
|
||||||
WithArgs(sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), "enabled", 1, "operator-a", "approve", "aud_approval", "approval-a", jsonContaining(`"status":"pending"`), jsonContaining(`"handler_identity":"operator-a"`)).
|
WithArgs(sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), "enabled", 1, "operator-a", "approve", "audit_approval", "approval-a", jsonContaining(`"status":"pending"`), jsonContaining(`"handler_identity":"operator-a"`)).
|
||||||
WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(uint64(2)))
|
WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(uint64(2)))
|
||||||
mock.ExpectCommit()
|
mock.ExpectCommit()
|
||||||
|
|
||||||
ctx, recorder := updateContext(http.MethodPost, "/audit/aud_approval/approval-a/approve", "approval-a", []byte(`{"status":"approved","opinion":"accepted","business_identity":"payment-b"}`))
|
ctx, recorder := updateContext(http.MethodPost, "/audit/audit_approval/approval-a/approve", "approval-a", []byte(`{"status":"approved","opinion":"accepted","business_identity":"payment-b"}`))
|
||||||
ctx.Set("Auth", &types.JwtClaims{Identity: "operator-a"})
|
ctx.Set("Auth", &types.JwtClaims{Identity: "operator-a"})
|
||||||
ApproveAudit(ctx)
|
ApproveAudit(ctx)
|
||||||
|
|
||||||
@@ -59,7 +59,7 @@ func TestApproveAuditRejectsStatusesOutsideApprovedAndRejected(t *testing.T) {
|
|||||||
for _, decision := range []string{"pending", "archived"} {
|
for _, decision := range []string{"pending", "archived"} {
|
||||||
t.Run(decision, func(t *testing.T) {
|
t.Run(decision, func(t *testing.T) {
|
||||||
_, mock := setupPlatformRoleDatabase(t)
|
_, mock := setupPlatformRoleDatabase(t)
|
||||||
ctx, recorder := updateContext(http.MethodPost, "/audit/aud_approval/approval-a/approve", "approval-a", []byte(`{"status":"`+decision+`"}`))
|
ctx, recorder := updateContext(http.MethodPost, "/audit/audit_approval/approval-a/approve", "approval-a", []byte(`{"status":"`+decision+`"}`))
|
||||||
ctx.Set("Auth", &types.JwtClaims{Identity: "operator-a"})
|
ctx.Set("Auth", &types.JwtClaims{Identity: "operator-a"})
|
||||||
|
|
||||||
ApproveAudit(ctx)
|
ApproveAudit(ctx)
|
||||||
@@ -73,12 +73,12 @@ func TestApproveAuditRejectsStatusesOutsideApprovedAndRejected(t *testing.T) {
|
|||||||
func TestApproveAuditRejectsAlreadyHandledApproval(t *testing.T) {
|
func TestApproveAuditRejectsAlreadyHandledApproval(t *testing.T) {
|
||||||
_, mock := setupPlatformRoleDatabase(t)
|
_, mock := setupPlatformRoleDatabase(t)
|
||||||
mock.ExpectBegin()
|
mock.ExpectBegin()
|
||||||
mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "aud_approval" WHERE identity = $1 ORDER BY "aud_approval"."id" LIMIT $2`)).
|
mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "audit_approval" WHERE identity = $1 ORDER BY "audit_approval"."id" LIMIT $2`)).
|
||||||
WithArgs("approval-a", 1).
|
WithArgs("approval-a", 1).
|
||||||
WillReturnRows(approvalRows("approval-a", "approved", "applicant-a"))
|
WillReturnRows(approvalRows("approval-a", "approved", "applicant-a"))
|
||||||
mock.ExpectRollback()
|
mock.ExpectRollback()
|
||||||
|
|
||||||
ctx, recorder := updateContext(http.MethodPost, "/audit/aud_approval/approval-a/approve", "approval-a", []byte(`{"status":"approved"}`))
|
ctx, recorder := updateContext(http.MethodPost, "/audit/audit_approval/approval-a/approve", "approval-a", []byte(`{"status":"approved"}`))
|
||||||
ctx.Set("Auth", &types.JwtClaims{Identity: "operator-a"})
|
ctx.Set("Auth", &types.JwtClaims{Identity: "operator-a"})
|
||||||
ApproveAudit(ctx)
|
ApproveAudit(ctx)
|
||||||
|
|
||||||
@@ -89,12 +89,12 @@ func TestApproveAuditRejectsAlreadyHandledApproval(t *testing.T) {
|
|||||||
func TestApproveAuditRejectsTheApplicant(t *testing.T) {
|
func TestApproveAuditRejectsTheApplicant(t *testing.T) {
|
||||||
_, mock := setupPlatformRoleDatabase(t)
|
_, mock := setupPlatformRoleDatabase(t)
|
||||||
mock.ExpectBegin()
|
mock.ExpectBegin()
|
||||||
mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "aud_approval" WHERE identity = $1 ORDER BY "aud_approval"."id" LIMIT $2`)).
|
mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "audit_approval" WHERE identity = $1 ORDER BY "audit_approval"."id" LIMIT $2`)).
|
||||||
WithArgs("approval-a", 1).
|
WithArgs("approval-a", 1).
|
||||||
WillReturnRows(approvalRows("approval-a", "pending", "operator-a"))
|
WillReturnRows(approvalRows("approval-a", "pending", "operator-a"))
|
||||||
mock.ExpectRollback()
|
mock.ExpectRollback()
|
||||||
|
|
||||||
ctx, recorder := updateContext(http.MethodPost, "/audit/aud_approval/approval-a/approve", "approval-a", []byte(`{"status":"approved"}`))
|
ctx, recorder := updateContext(http.MethodPost, "/audit/audit_approval/approval-a/approve", "approval-a", []byte(`{"status":"approved"}`))
|
||||||
ctx.Set("Auth", &types.JwtClaims{Identity: "operator-a"})
|
ctx.Set("Auth", &types.JwtClaims{Identity: "operator-a"})
|
||||||
ApproveAudit(ctx)
|
ApproveAudit(ctx)
|
||||||
|
|
||||||
@@ -105,15 +105,15 @@ func TestApproveAuditRejectsTheApplicant(t *testing.T) {
|
|||||||
func TestApproveAuditRejectsAConcurrentSecondDecision(t *testing.T) {
|
func TestApproveAuditRejectsAConcurrentSecondDecision(t *testing.T) {
|
||||||
_, mock := setupPlatformRoleDatabase(t)
|
_, mock := setupPlatformRoleDatabase(t)
|
||||||
mock.ExpectBegin()
|
mock.ExpectBegin()
|
||||||
mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "aud_approval" WHERE identity = $1 ORDER BY "aud_approval"."id" LIMIT $2`)).
|
mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "audit_approval" WHERE identity = $1 ORDER BY "audit_approval"."id" LIMIT $2`)).
|
||||||
WithArgs("approval-a", 1).
|
WithArgs("approval-a", 1).
|
||||||
WillReturnRows(approvalRows("approval-a", "pending", "applicant-a"))
|
WillReturnRows(approvalRows("approval-a", "pending", "applicant-a"))
|
||||||
mock.ExpectExec(regexp.QuoteMeta(`UPDATE "aud_approval" SET "handled_at"=$1,"handler_identity"=$2,"opinion"=$3,"status"=$4,"updated_at"=$5 WHERE identity = $6 AND status = $7`)).
|
mock.ExpectExec(regexp.QuoteMeta(`UPDATE "audit_approval" SET "handled_at"=$1,"handler_identity"=$2,"opinion"=$3,"status"=$4,"updated_at"=$5 WHERE identity = $6 AND status = $7`)).
|
||||||
WithArgs(sqlmock.AnyArg(), "operator-a", "", "approved", sqlmock.AnyArg(), "approval-a", "pending").
|
WithArgs(sqlmock.AnyArg(), "operator-a", "", "approved", sqlmock.AnyArg(), "approval-a", "pending").
|
||||||
WillReturnResult(sqlmock.NewResult(0, 0))
|
WillReturnResult(sqlmock.NewResult(0, 0))
|
||||||
mock.ExpectRollback()
|
mock.ExpectRollback()
|
||||||
|
|
||||||
ctx, recorder := updateContext(http.MethodPost, "/audit/aud_approval/approval-a/approve", "approval-a", []byte(`{"status":"approved"}`))
|
ctx, recorder := updateContext(http.MethodPost, "/audit/audit_approval/approval-a/approve", "approval-a", []byte(`{"status":"approved"}`))
|
||||||
ctx.Set("Auth", &types.JwtClaims{Identity: "operator-a"})
|
ctx.Set("Auth", &types.JwtClaims{Identity: "operator-a"})
|
||||||
ApproveAudit(ctx)
|
ApproveAudit(ctx)
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ func TestDashboardOverviewReturnsZeroValuesForAnEmptyDatabase(t *testing.T) {
|
|||||||
`SELECT count\(\*\) FROM "delivery_basic" WHERE status = \$1`,
|
`SELECT count\(\*\) FROM "delivery_basic" WHERE status = \$1`,
|
||||||
`SELECT count\(\*\) FROM "staff_account" WHERE work_status = \$1`,
|
`SELECT count\(\*\) FROM "staff_account" WHERE work_status = \$1`,
|
||||||
`SELECT count\(\*\) FROM "user_account" WHERE status = \$1`,
|
`SELECT count\(\*\) FROM "user_account" WHERE status = \$1`,
|
||||||
`SELECT count\(\*\) FROM "saf_event" WHERE status = \$1`,
|
`SELECT count\(\*\) FROM "safe_event" WHERE status = \$1`,
|
||||||
} {
|
} {
|
||||||
mock.ExpectQuery(regexp.MustCompile(query).String()).WithArgs(sqlmock.AnyArg()).WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(0))
|
mock.ExpectQuery(regexp.MustCompile(query).String()).WithArgs(sqlmock.AnyArg()).WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(0))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ func ExpectedResources() []ResourceContract {
|
|||||||
resourceContract("staff", "staff_account", Writable, "list"), resourceContract("staff", "staff_credential", Writable, "list"),
|
resourceContract("staff", "staff_account", Writable, "list"), resourceContract("staff", "staff_credential", Writable, "list"),
|
||||||
resourceContract("user", "user_account", Writable, "list"), resourceContract("user", "user_address", Writable, "list"), resourceContract("user", "user_service_relation", Writable, "list"),
|
resourceContract("user", "user_account", Writable, "list"), resourceContract("user", "user_address", Writable, "list"), resourceContract("user", "user_service_relation", Writable, "list"),
|
||||||
resourceContract("device", "dev_smart_cylinder_valve", Writable, "list"), resourceContract("device", "dev_device_binding", Writable, "list"), resourceContract("device", "dev_telemetry", ReadOnly, "list"),
|
resourceContract("device", "dev_smart_cylinder_valve", Writable, "list"), resourceContract("device", "dev_device_binding", Writable, "list"), resourceContract("device", "dev_telemetry", ReadOnly, "list"),
|
||||||
resourceContract("safety", "saf_rule", Writable, "list"), resourceContract("safety", "saf_event", Writable, "list"), resourceContract("safety", "saf_inspection", Writable, "list"), resourceContract("safety", "saf_event_disposal", AppendOnly, "list"),
|
resourceContract("safety", "safe_rule", Writable, "list"), resourceContract("safety", "safe_event", Writable, "list"), resourceContract("safety", "safe_inspection", Writable, "list"), resourceContract("safety", "safe_event_disposal", AppendOnly, "list"),
|
||||||
resourceContract("ec", "ec_category", Writable, "list"), resourceContract("ec", "ec_product", Writable, "list"), resourceContract("ec", "ec_product_attribute", Writable, "list"), resourceContract("ec", "ec_product_image", Writable, "list"), resourceContract("ec", "ec_cart", Writable, "list"), resourceContract("ec", "ec_order", Writable, "list"), resourceContract("ec", "ec_order_item", Writable, "list"), resourceContract("ec", "ec_review", Writable, "list"),
|
resourceContract("ec", "ec_category", Writable, "list"), resourceContract("ec", "ec_product", Writable, "list"), resourceContract("ec", "ec_product_attribute", Writable, "list"), resourceContract("ec", "ec_product_image", Writable, "list"), resourceContract("ec", "ec_cart", Writable, "list"), resourceContract("ec", "ec_order", Writable, "list"), resourceContract("ec", "ec_order_item", Writable, "list"), resourceContract("ec", "ec_review", Writable, "list"),
|
||||||
resourceContract("delivery", "delivery_task", Writable, "list"), resourceContract("delivery", "delivery_track", Writable, "list"), resourceContract("delivery", "delivery_track_point", ReadOnly, "list"),
|
resourceContract("delivery", "delivery_task", Writable, "list"), resourceContract("delivery", "delivery_track", Writable, "list"), resourceContract("delivery", "delivery_track_point", ReadOnly, "list"),
|
||||||
resourceContract("finance", "fin_payment", Writable, "list"), resourceContract("finance", "fin_settlement", Writable, "list"), resourceContract("finance", "fin_reconciliation", Writable, "list"),
|
resourceContract("finance", "fin_payment", Writable, "list"), resourceContract("finance", "fin_settlement", Writable, "list"), resourceContract("finance", "fin_reconciliation", Writable, "list"),
|
||||||
@@ -83,7 +83,7 @@ func ExpectedResources() []ResourceContract {
|
|||||||
resourceContract("platform", "platfrom_account", Writable, "list"), resourceContract("platform", "platform_role", Writable, "list"), resourceContract("platform", "platform_menu", Writable, "tree"),
|
resourceContract("platform", "platfrom_account", Writable, "list"), resourceContract("platform", "platform_role", Writable, "list"), resourceContract("platform", "platform_menu", Writable, "tree"),
|
||||||
resourceContract("wallet", "wallet", ReadOnly, "list"), resourceContract("wallet", "wallet_ledger", ReadOnly, "list"), resourceContract("wallet", "wallet_recharge", ReadOnly, "list"), resourceContract("wallet", "wallet_withdrawal", ReadOnly, "list"),
|
resourceContract("wallet", "wallet", ReadOnly, "list"), resourceContract("wallet", "wallet_ledger", ReadOnly, "list"), resourceContract("wallet", "wallet_recharge", ReadOnly, "list"), resourceContract("wallet", "wallet_withdrawal", ReadOnly, "list"),
|
||||||
resourceContract("report", "report", ReadOnly, "list"), resourceContract("report", "report_item", ReadOnly, "list"), resourceContract("report", "report_metric_snapshot", ReadOnly, "list"),
|
resourceContract("report", "report", ReadOnly, "list"), resourceContract("report", "report_item", ReadOnly, "list"), resourceContract("report", "report_metric_snapshot", ReadOnly, "list"),
|
||||||
resourceContract("audit", "aud_operation_log", ReadOnly, "list"), resourceContract("audit", "aud_export_log", ReadOnly, "list"), resourceContract("audit", "aud_approval", ReadOnly, "list"),
|
resourceContract("audit", "audit_operation_log", ReadOnly, "list"), resourceContract("audit", "audit_export_log", ReadOnly, "list"), resourceContract("audit", "audit_approval", ReadOnly, "list"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,8 +103,8 @@ func resourcePath(domain, name string) string {
|
|||||||
return "/user/address"
|
return "/user/address"
|
||||||
case "user_service_relation":
|
case "user_service_relation":
|
||||||
return "/user/service_relation"
|
return "/user/service_relation"
|
||||||
case "saf_event_disposal":
|
case "safe_event_disposal":
|
||||||
return "/safety/saf_event/:identity/disposals"
|
return "/safety/safe_event/:identity/disposals"
|
||||||
default:
|
default:
|
||||||
return "/" + domain + "/" + name
|
return "/" + domain + "/" + name
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -250,8 +250,8 @@ func TestPrepareResourceValuesResolvesRequiredIdentityRelationsAndRejectsInvalid
|
|||||||
|
|
||||||
func TestPrepareResourceValuesNormalizesStringJSONBFields(t *testing.T) {
|
func TestPrepareResourceValuesNormalizesStringJSONBFields(t *testing.T) {
|
||||||
t.Run("safety rule", func(t *testing.T) {
|
t.Run("safety rule", func(t *testing.T) {
|
||||||
ctx, _ := updateContext(http.MethodPost, "/safety/saf_rule", "", []byte(`{"rule_code":"pressure-limit","threshold":{"max":10},"action":"close-valve","gray_scope":["north"]}`))
|
ctx, _ := updateContext(http.MethodPost, "/safety/safe_rule", "", []byte(`{"rule_code":"pressure-limit","threshold":{"max":10},"action":"close-valve","gray_scope":["north"]}`))
|
||||||
values, err := prepareResourceValues(ctx, &models.SafRule{}, []string{"rule_code", "threshold", "action", "gray_scope"}, nil)
|
values, err := prepareResourceValues(ctx, &models.SafeRule{}, []string{"rule_code", "threshold", "action", "gray_scope"}, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -284,8 +284,8 @@ func TestPrepareResourceValuesNormalizesStringJSONBFields(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("invalid json string", func(t *testing.T) {
|
t.Run("invalid json string", func(t *testing.T) {
|
||||||
ctx, _ := updateContext(http.MethodPost, "/safety/saf_rule", "", []byte(`{"rule_code":"pressure-limit","threshold":"{invalid}","action":"close-valve"}`))
|
ctx, _ := updateContext(http.MethodPost, "/safety/safe_rule", "", []byte(`{"rule_code":"pressure-limit","threshold":"{invalid}","action":"close-valve"}`))
|
||||||
if _, err := prepareResourceValues(ctx, &models.SafRule{}, []string{"rule_code", "threshold", "action"}, nil); err == nil {
|
if _, err := prepareResourceValues(ctx, &models.SafeRule{}, []string{"rule_code", "threshold", "action"}, nil); err == nil {
|
||||||
t.Fatal("invalid JSON string was accepted for a string/jsonb field")
|
t.Fatal("invalid JSON string was accepted for a string/jsonb field")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -341,7 +341,7 @@ func TestKeywordColumnsUseSafeTextAllowlist(t *testing.T) {
|
|||||||
model any
|
model any
|
||||||
want []string
|
want []string
|
||||||
}{
|
}{
|
||||||
{"safety rule excludes jsonb", &models.SafRule{}, []string{"rule_code", "action"}},
|
{"safety rule excludes jsonb", &models.SafeRule{}, []string{"rule_code", "action"}},
|
||||||
{"gas basic excludes sensitive fields", &models.GasBasic{}, []string{"code", "name"}},
|
{"gas basic excludes sensitive fields", &models.GasBasic{}, []string{"code", "name"}},
|
||||||
{"user address has no searchable safe text", &models.UserAddress{}, []string{}},
|
{"user address has no searchable safe text", &models.UserAddress{}, []string{}},
|
||||||
}
|
}
|
||||||
@@ -648,19 +648,19 @@ func TestNonRootCannotAssignPlatformAccountRole(t *testing.T) {
|
|||||||
|
|
||||||
func TestListSafetyEventDisposalsReturnsOnlyTheRequestedEventHistory(t *testing.T) {
|
func TestListSafetyEventDisposalsReturnsOnlyTheRequestedEventHistory(t *testing.T) {
|
||||||
_, mock := setupPlatformRoleDatabase(t)
|
_, mock := setupPlatformRoleDatabase(t)
|
||||||
mock.ExpectQuery(regexp.QuoteMeta(`SELECT count(*) FROM "saf_event_disposal" WHERE saf_event_identity = $1`)).
|
mock.ExpectQuery(regexp.QuoteMeta(`SELECT count(*) FROM "safe_event_disposal" WHERE safe_event_identity = $1`)).
|
||||||
WithArgs("event-a").
|
WithArgs("event-a").
|
||||||
WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(1))
|
WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(1))
|
||||||
mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "saf_event_disposal" WHERE saf_event_identity = $1 ORDER BY created_at asc LIMIT $2`)).
|
mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "safe_event_disposal" WHERE safe_event_identity = $1 ORDER BY created_at asc LIMIT $2`)).
|
||||||
WithArgs("event-a", 20).
|
WithArgs("event-a", 20).
|
||||||
WillReturnRows(sqlmock.NewRows([]string{"id", "identity", "created_at", "updated_at", "status", "version", "saf_event_identity", "action", "reason", "operator_identity"}).
|
WillReturnRows(sqlmock.NewRows([]string{"id", "identity", "created_at", "updated_at", "status", "version", "safe_event_identity", "action", "reason", "operator_identity"}).
|
||||||
AddRow(uint64(9), "disposal-a", nil, nil, "enabled", 1, "event-a", "close", "resolved", "operator-a"))
|
AddRow(uint64(9), "disposal-a", nil, nil, "enabled", 1, "event-a", "close", "resolved", "operator-a"))
|
||||||
|
|
||||||
ctx, recorder := updateContext(http.MethodGet, "/safety/saf_event/event-a/disposals", "event-a", nil)
|
ctx, recorder := updateContext(http.MethodGet, "/safety/safe_event/event-a/disposals", "event-a", nil)
|
||||||
ListSafetyEventDisposals(ctx)
|
ListSafetyEventDisposals(ctx)
|
||||||
|
|
||||||
assertResponseCode(t, recorder, 0)
|
assertResponseCode(t, recorder, 0)
|
||||||
if !strings.Contains(recorder.Body.String(), `"saf_event_identity":"event-a"`) || strings.Contains(recorder.Body.String(), `"id":`) {
|
if !strings.Contains(recorder.Body.String(), `"safe_event_identity":"event-a"`) || strings.Contains(recorder.Body.String(), `"id":`) {
|
||||||
t.Fatalf("disposal history did not keep the event identity-only shape: %s", recorder.Body.String())
|
t.Fatalf("disposal history did not keep the event identity-only shape: %s", recorder.Body.String())
|
||||||
}
|
}
|
||||||
assertMockExpectations(t, mock)
|
assertMockExpectations(t, mock)
|
||||||
@@ -815,19 +815,19 @@ func TestDisposeSafetyEventUpdatesEventAndAppendsOperatorActionTransactionally(t
|
|||||||
_, mock := setupPlatformRoleDatabase(t)
|
_, mock := setupPlatformRoleDatabase(t)
|
||||||
now := time.Now().UTC()
|
now := time.Now().UTC()
|
||||||
mock.ExpectBegin()
|
mock.ExpectBegin()
|
||||||
mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "saf_event" WHERE identity = $1 ORDER BY "saf_event"."id" LIMIT $2`)).
|
mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "safe_event" WHERE identity = $1 ORDER BY "safe_event"."id" LIMIT $2`)).
|
||||||
WithArgs("event-a", 1).
|
WithArgs("event-a", 1).
|
||||||
WillReturnRows(sqlmock.NewRows([]string{"id", "identity", "created_at", "updated_at", "status", "version", "event_code", "level", "title", "smart_cylinder_valve_identity", "sla_at"}).
|
WillReturnRows(sqlmock.NewRows([]string{"id", "identity", "created_at", "updated_at", "status", "version", "event_code", "level", "title", "smart_cylinder_valve_identity", "sla_at"}).
|
||||||
AddRow(uint64(3), "event-a", now, now, "open", 1, "E-1", 2, "alarm", "valve-a", nil))
|
AddRow(uint64(3), "event-a", now, now, "open", 1, "E-1", 2, "alarm", "valve-a", nil))
|
||||||
mock.ExpectExec(regexp.QuoteMeta(`UPDATE "saf_event" SET "status"=$1,"updated_at"=$2 WHERE identity = $3`)).
|
mock.ExpectExec(regexp.QuoteMeta(`UPDATE "safe_event" SET "status"=$1,"updated_at"=$2 WHERE identity = $3`)).
|
||||||
WithArgs("disposed", sqlmock.AnyArg(), "event-a").
|
WithArgs("disposed", sqlmock.AnyArg(), "event-a").
|
||||||
WillReturnResult(sqlmock.NewResult(0, 1))
|
WillReturnResult(sqlmock.NewResult(0, 1))
|
||||||
mock.ExpectQuery(regexp.QuoteMeta(`INSERT INTO "saf_event_disposal" ("identity","created_at","updated_at","status","version","saf_event_identity","action","reason","operator_identity") VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9) RETURNING "id"`)).
|
mock.ExpectQuery(regexp.QuoteMeta(`INSERT INTO "safe_event_disposal" ("identity","created_at","updated_at","status","version","safe_event_identity","action","reason","operator_identity") VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9) RETURNING "id"`)).
|
||||||
WithArgs(sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), "enabled", 1, "event-a", "close", "resolved", "operator-a").
|
WithArgs(sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), "enabled", 1, "event-a", "close", "resolved", "operator-a").
|
||||||
WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(uint64(1)))
|
WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(uint64(1)))
|
||||||
mock.ExpectCommit()
|
mock.ExpectCommit()
|
||||||
|
|
||||||
ctx, recorder := updateContext(http.MethodPost, "/safety/saf_event/event-a/disposals", "event-a", []byte(`{"action":"close","reason":"resolved"}`))
|
ctx, recorder := updateContext(http.MethodPost, "/safety/safe_event/event-a/disposals", "event-a", []byte(`{"action":"close","reason":"resolved"}`))
|
||||||
ctx.Set("Auth", &types.JwtClaims{Identity: "operator-a"})
|
ctx.Set("Auth", &types.JwtClaims{Identity: "operator-a"})
|
||||||
DisposeSafetyEvent(ctx)
|
DisposeSafetyEvent(ctx)
|
||||||
|
|
||||||
|
|||||||
@@ -565,8 +565,8 @@ func stripInternalIDs(value any) any {
|
|||||||
// action record. Disposal records deliberately have no update or delete route.
|
// action record. Disposal records deliberately have no update or delete route.
|
||||||
func ListSafetyEventDisposals(ctx *gin.Context) {
|
func ListSafetyEventDisposals(ctx *gin.Context) {
|
||||||
page, size := pageSize(ctx)
|
page, size := pageSize(ctx)
|
||||||
var list []models.SafEventDisposal
|
var list []models.SafeEventDisposal
|
||||||
query := impl.DBService.Model(&models.SafEventDisposal{}).Where("saf_event_identity = ?", ctx.Param("identity"))
|
query := impl.DBService.Model(&models.SafeEventDisposal{}).Where("safe_event_identity = ?", ctx.Param("identity"))
|
||||||
var total int64
|
var total int64
|
||||||
if err := query.Count(&total).Error; err != nil {
|
if err := query.Count(&total).Error; err != nil {
|
||||||
infra.Response.Error(ctx, err)
|
infra.Response.Error(ctx, err)
|
||||||
@@ -597,23 +597,23 @@ func DisposeSafetyEvent(ctx *gin.Context) {
|
|||||||
if request.Status == "" {
|
if request.Status == "" {
|
||||||
request.Status = "disposed"
|
request.Status = "disposed"
|
||||||
}
|
}
|
||||||
var disposal models.SafEventDisposal
|
var disposal models.SafeEventDisposal
|
||||||
err = impl.DBService.Transaction(func(transaction *gorm.DB) error {
|
err = impl.DBService.Transaction(func(transaction *gorm.DB) error {
|
||||||
var event models.SafEvent
|
var event models.SafeEvent
|
||||||
if err := transaction.Where("identity = ?", ctx.Param("identity")).First(&event).Error; err != nil {
|
if err := transaction.Where("identity = ?", ctx.Param("identity")).First(&event).Error; err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if result := transaction.Model(&models.SafEvent{}).Where("identity = ?", event.Identity).Update("status", request.Status); result.Error != nil {
|
if result := transaction.Model(&models.SafeEvent{}).Where("identity = ?", event.Identity).Update("status", request.Status); result.Error != nil {
|
||||||
return result.Error
|
return result.Error
|
||||||
} else if result.RowsAffected == 0 {
|
} else if result.RowsAffected == 0 {
|
||||||
return gorm.ErrRecordNotFound
|
return gorm.ErrRecordNotFound
|
||||||
}
|
}
|
||||||
disposal = models.SafEventDisposal{
|
disposal = models.SafeEventDisposal{
|
||||||
Entity: newEntity("enabled"),
|
Entity: newEntity("enabled"),
|
||||||
SafEventIdentity: event.Identity,
|
SafeEventIdentity: event.Identity,
|
||||||
Action: request.Action,
|
Action: request.Action,
|
||||||
Reason: request.Reason,
|
Reason: request.Reason,
|
||||||
OperatorIdentity: claims.Identity,
|
OperatorIdentity: claims.Identity,
|
||||||
}
|
}
|
||||||
return transaction.Create(&disposal).Error
|
return transaction.Create(&disposal).Error
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ import (
|
|||||||
"git.apinb.com/bsm-sdk/core/database"
|
"git.apinb.com/bsm-sdk/core/database"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AudApproval 对应 aud_approval,保存审批流与复核意见。
|
// AuditApproval 对应 audit_approval,保存审批流与复核意见。
|
||||||
type AudApproval struct {
|
type AuditApproval struct {
|
||||||
Entity // 公共实体字段
|
Entity // 公共实体字段
|
||||||
BusinessType string `gorm:"column:business_type;type:varchar(64);not null" json:"business_type"` // business_type 业务字段
|
BusinessType string `gorm:"column:business_type;type:varchar(64);not null" json:"business_type"` // business_type 业务字段
|
||||||
BusinessIdentity string `gorm:"column:business_identity;type:varchar(36);not null;index" json:"business_identity"` // business_identity 业务字段
|
BusinessIdentity string `gorm:"column:business_identity;type:varchar(36);not null;index" json:"business_identity"` // business_identity 业务字段
|
||||||
@@ -17,5 +17,5 @@ type AudApproval struct {
|
|||||||
HandledAt *time.Time `gorm:"column:handled_at;type:timestamptz" json:"handled_at"` // handled_at 业务字段
|
HandledAt *time.Time `gorm:"column:handled_at;type:timestamptz" json:"handled_at"` // handled_at 业务字段
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() { database.AppendMigrate(&AudApproval{}) }
|
func init() { database.AppendMigrate(&AuditApproval{}) }
|
||||||
func (table *AudApproval) TableName() string { return "aud_approval" }
|
func (table *AuditApproval) TableName() string { return "audit_approval" }
|
||||||
@@ -5,8 +5,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AudExportLog 对应 aud_export_log,保存敏感导出审计。
|
// AuditExportLog 对应 audit_export_log,保存敏感导出审计。
|
||||||
type AudExportLog struct {
|
type AuditExportLog struct {
|
||||||
Entity // 公共实体字段
|
Entity // 公共实体字段
|
||||||
ApplicantIdentity string `gorm:"column:applicant_identity;type:varchar(36);not null;index" json:"applicant_identity"` // applicant_identity 业务字段
|
ApplicantIdentity string `gorm:"column:applicant_identity;type:varchar(36);not null;index" json:"applicant_identity"` // applicant_identity 业务字段
|
||||||
Purpose string `gorm:"column:purpose;type:varchar(255);not null" json:"purpose"` // purpose 业务字段
|
Purpose string `gorm:"column:purpose;type:varchar(255);not null" json:"purpose"` // purpose 业务字段
|
||||||
@@ -15,5 +15,5 @@ type AudExportLog struct {
|
|||||||
FileURI string `gorm:"column:file_uri;type:varchar(512);not null;default:''" json:"file_uri"` // file_uri 业务字段
|
FileURI string `gorm:"column:file_uri;type:varchar(512);not null;default:''" json:"file_uri"` // file_uri 业务字段
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() { database.AppendMigrate(&AudExportLog{}) }
|
func init() { database.AppendMigrate(&AuditExportLog{}) }
|
||||||
func (table *AudExportLog) TableName() string { return "aud_export_log" }
|
func (table *AuditExportLog) TableName() string { return "audit_export_log" }
|
||||||
@@ -2,8 +2,8 @@ package models
|
|||||||
|
|
||||||
import "git.apinb.com/bsm-sdk/core/database"
|
import "git.apinb.com/bsm-sdk/core/database"
|
||||||
|
|
||||||
// AudOperationLog 对应 aud_operation_log,保存不可变操作审计。
|
// AuditOperationLog 对应 audit_operation_log,保存不可变操作审计。
|
||||||
type AudOperationLog struct {
|
type AuditOperationLog struct {
|
||||||
Entity // 公共实体字段
|
Entity // 公共实体字段
|
||||||
OperatorIdentity string `gorm:"column:operator_identity;type:varchar(36);not null;index" json:"operator_identity"` // operator_identity 业务字段
|
OperatorIdentity string `gorm:"column:operator_identity;type:varchar(36);not null;index" json:"operator_identity"` // operator_identity 业务字段
|
||||||
Action string `gorm:"column:action;type:varchar(64);not null" json:"action"` // action 业务字段
|
Action string `gorm:"column:action;type:varchar(64);not null" json:"action"` // action 业务字段
|
||||||
@@ -13,5 +13,5 @@ type AudOperationLog struct {
|
|||||||
AfterData string `gorm:"column:after_data;type:jsonb;not null;default:'{}'" json:"after_data"` // after_data 业务字段
|
AfterData string `gorm:"column:after_data;type:jsonb;not null;default:'{}'" json:"after_data"` // after_data 业务字段
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() { database.AppendMigrate(&AudOperationLog{}) }
|
func init() { database.AppendMigrate(&AuditOperationLog{}) }
|
||||||
func (table *AudOperationLog) TableName() string { return "aud_operation_log" }
|
func (table *AuditOperationLog) TableName() string { return "audit_operation_log" }
|
||||||
@@ -26,7 +26,7 @@ func GetDashboardOverview() (DashboardOverview, error) {
|
|||||||
if err := impl.DBService.Model(&UserAccount{}).Where("status = ?", "enabled").Count(&overview.UserCount).Error; err != nil {
|
if err := impl.DBService.Model(&UserAccount{}).Where("status = ?", "enabled").Count(&overview.UserCount).Error; err != nil {
|
||||||
return DashboardOverview{}, err
|
return DashboardOverview{}, err
|
||||||
}
|
}
|
||||||
if err := impl.DBService.Model(&SafEvent{}).Where("status = ?", "pending").Count(&overview.PendingSafetyCount).Error; err != nil {
|
if err := impl.DBService.Model(&SafeEvent{}).Where("status = ?", "pending").Count(&overview.PendingSafetyCount).Error; err != nil {
|
||||||
return DashboardOverview{}, err
|
return DashboardOverview{}, err
|
||||||
}
|
}
|
||||||
return overview, nil
|
return overview, nil
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
package models
|
|
||||||
|
|
||||||
import "git.apinb.com/bsm-sdk/core/database"
|
|
||||||
|
|
||||||
// SafEventDisposal 对应 saf_event_disposal,保存安全处置记录。
|
|
||||||
type SafEventDisposal struct {
|
|
||||||
Entity // 公共实体字段
|
|
||||||
SafEventIdentity string `gorm:"column:saf_event_identity;type:varchar(36);not null;index" json:"saf_event_identity"` // saf_event_identity 业务字段
|
|
||||||
Action string `gorm:"column:action;type:varchar(64);not null" json:"action"` // action 业务字段
|
|
||||||
Reason string `gorm:"column:reason;type:text;not null;default:''" json:"reason"` // reason 业务字段
|
|
||||||
OperatorIdentity string `gorm:"column:operator_identity;type:varchar(36);not null;default:''" json:"operator_identity"` // operator_identity 业务字段
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() { database.AppendMigrate(&SafEventDisposal{}) }
|
|
||||||
func (table *SafEventDisposal) TableName() string { return "saf_event_disposal" }
|
|
||||||
@@ -5,8 +5,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SafEvent 对应 saf_event,保存安全事件统一入口。
|
// SafeEvent 对应 safe_event,保存安全事件统一入口。
|
||||||
type SafEvent struct {
|
type SafeEvent struct {
|
||||||
Entity // 公共实体字段
|
Entity // 公共实体字段
|
||||||
EventCode string `gorm:"column:event_code;type:varchar(64);not null;uniqueIndex" json:"event_code"` // event_code 业务字段
|
EventCode string `gorm:"column:event_code;type:varchar(64);not null;uniqueIndex" json:"event_code"` // event_code 业务字段
|
||||||
Level int `gorm:"column:level;not null;default:3" json:"level"` // level 业务字段
|
Level int `gorm:"column:level;not null;default:3" json:"level"` // level 业务字段
|
||||||
@@ -15,5 +15,5 @@ type SafEvent struct {
|
|||||||
SLAAt *time.Time `gorm:"column:sla_at;type:timestamptz" json:"sla_at"` // sla_at 业务字段
|
SLAAt *time.Time `gorm:"column:sla_at;type:timestamptz" json:"sla_at"` // sla_at 业务字段
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() { database.AppendMigrate(&SafEvent{}) }
|
func init() { database.AppendMigrate(&SafeEvent{}) }
|
||||||
func (table *SafEvent) TableName() string { return "saf_event" }
|
func (table *SafeEvent) TableName() string { return "safe_event" }
|
||||||
15
backend/api/internal/models/safe_event_disposal.go
Normal file
15
backend/api/internal/models/safe_event_disposal.go
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import "git.apinb.com/bsm-sdk/core/database"
|
||||||
|
|
||||||
|
// SafeEventDisposal 对应 safe_event_disposal,保存安全处置记录。
|
||||||
|
type SafeEventDisposal struct {
|
||||||
|
Entity // 公共实体字段
|
||||||
|
SafeEventIdentity string `gorm:"column:safe_event_identity;type:varchar(36);not null;index" json:"safe_event_identity"` // safe_event_identity 业务字段
|
||||||
|
Action string `gorm:"column:action;type:varchar(64);not null" json:"action"` // action 业务字段
|
||||||
|
Reason string `gorm:"column:reason;type:text;not null;default:''" json:"reason"` // reason 业务字段
|
||||||
|
OperatorIdentity string `gorm:"column:operator_identity;type:varchar(36);not null;default:''" json:"operator_identity"` // operator_identity 业务字段
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() { database.AppendMigrate(&SafeEventDisposal{}) }
|
||||||
|
func (table *SafeEventDisposal) TableName() string { return "safe_event_disposal" }
|
||||||
@@ -2,8 +2,8 @@ package models
|
|||||||
|
|
||||||
import "git.apinb.com/bsm-sdk/core/database"
|
import "git.apinb.com/bsm-sdk/core/database"
|
||||||
|
|
||||||
// SafInspection 对应 saf_inspection,保存安检与复检记录。
|
// SafeInspection 对应 safe_inspection,保存安检与复检记录。
|
||||||
type SafInspection struct {
|
type SafeInspection struct {
|
||||||
Entity // 公共实体字段
|
Entity // 公共实体字段
|
||||||
UserAccountID uint64 `gorm:"column:user_account_id;not null;index" json:"user_account_id"` // user_account_id 业务字段
|
UserAccountID uint64 `gorm:"column:user_account_id;not null;index" json:"user_account_id"` // user_account_id 业务字段
|
||||||
StaffAccountID uint64 `gorm:"column:staff_account_id;not null;index" json:"staff_account_id"` // staff_account_id 业务字段
|
StaffAccountID uint64 `gorm:"column:staff_account_id;not null;index" json:"staff_account_id"` // staff_account_id 业务字段
|
||||||
@@ -11,5 +11,5 @@ type SafInspection struct {
|
|||||||
EvidenceURI string `gorm:"column:evidence_uri;type:varchar(512);not null;default:''" json:"evidence_uri"` // evidence_uri 业务字段
|
EvidenceURI string `gorm:"column:evidence_uri;type:varchar(512);not null;default:''" json:"evidence_uri"` // evidence_uri 业务字段
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() { database.AppendMigrate(&SafInspection{}) }
|
func init() { database.AppendMigrate(&SafeInspection{}) }
|
||||||
func (table *SafInspection) TableName() string { return "saf_inspection" }
|
func (table *SafeInspection) TableName() string { return "safe_inspection" }
|
||||||
@@ -2,8 +2,8 @@ package models
|
|||||||
|
|
||||||
import "git.apinb.com/bsm-sdk/core/database"
|
import "git.apinb.com/bsm-sdk/core/database"
|
||||||
|
|
||||||
// SafRule 对应 saf_rule,保存安全规则。
|
// SafeRule 对应 safe_rule,保存安全规则。
|
||||||
type SafRule struct {
|
type SafeRule struct {
|
||||||
Entity // 公共实体字段
|
Entity // 公共实体字段
|
||||||
RuleCode string `gorm:"column:rule_code;type:varchar(64);not null;uniqueIndex" json:"rule_code"` // rule_code 业务字段
|
RuleCode string `gorm:"column:rule_code;type:varchar(64);not null;uniqueIndex" json:"rule_code"` // rule_code 业务字段
|
||||||
VersionNo int `gorm:"column:version_no;not null;default:1" json:"version_no"` // version_no 业务字段
|
VersionNo int `gorm:"column:version_no;not null;default:1" json:"version_no"` // version_no 业务字段
|
||||||
@@ -12,5 +12,5 @@ type SafRule struct {
|
|||||||
GrayScope string `gorm:"column:gray_scope;type:jsonb;not null;default:'{}'" json:"gray_scope"` // gray_scope 业务字段
|
GrayScope string `gorm:"column:gray_scope;type:jsonb;not null;default:'{}'" json:"gray_scope"` // gray_scope 业务字段
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() { database.AppendMigrate(&SafRule{}) }
|
func init() { database.AppendMigrate(&SafeRule{}) }
|
||||||
func (table *SafRule) TableName() string { return "saf_rule" }
|
func (table *SafeRule) TableName() string { return "safe_rule" }
|
||||||
@@ -62,11 +62,11 @@ func registerDeviceRoute(group *gin.RouterGroup) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func registerSafetyRoute(group *gin.RouterGroup) {
|
func registerSafetyRoute(group *gin.RouterGroup) {
|
||||||
registerRestrictedWritableResource(group, "/safety/saf_rule", &models.SafRule{}, []string{"rule_code", "version_no", "threshold", "action", "gray_scope"})
|
registerRestrictedWritableResource(group, "/safety/safe_rule", &models.SafeRule{}, []string{"rule_code", "version_no", "threshold", "action", "gray_scope"})
|
||||||
registerRestrictedWritableResource(group, "/safety/saf_event", &models.SafEvent{}, []string{"event_code", "level", "title", "smart_cylinder_valve_identity", "sla_at"})
|
registerRestrictedWritableResource(group, "/safety/safe_event", &models.SafeEvent{}, []string{"event_code", "level", "title", "smart_cylinder_valve_identity", "sla_at"})
|
||||||
registerRestrictedWritableResource(group, "/safety/saf_inspection", &models.SafInspection{}, []string{"result", "evidence_uri"}, requiredRelation("user_account_identity", "user_account_id", &models.UserAccount{}), requiredRelation("staff_account_identity", "staff_account_id", &models.StaffAccount{}))
|
registerRestrictedWritableResource(group, "/safety/safe_inspection", &models.SafeInspection{}, []string{"result", "evidence_uri"}, requiredRelation("user_account_identity", "user_account_id", &models.UserAccount{}), requiredRelation("staff_account_identity", "staff_account_id", &models.StaffAccount{}))
|
||||||
group.GET("/safety/saf_event/:identity/disposals", platform.ListSafetyEventDisposals)
|
group.GET("/safety/safe_event/:identity/disposals", platform.ListSafetyEventDisposals)
|
||||||
group.POST("/safety/saf_event/:identity/disposals", platform.DisposeSafetyEvent)
|
group.POST("/safety/safe_event/:identity/disposals", platform.DisposeSafetyEvent)
|
||||||
}
|
}
|
||||||
|
|
||||||
func registerCommerceRoute(group *gin.RouterGroup) {
|
func registerCommerceRoute(group *gin.RouterGroup) {
|
||||||
@@ -138,10 +138,10 @@ func registerContentRoute(group *gin.RouterGroup) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func registerAuditRoute(group *gin.RouterGroup) {
|
func registerAuditRoute(group *gin.RouterGroup) {
|
||||||
registerReadOnlyResource(group, "/audit/aud_operation_log", &models.AudOperationLog{})
|
registerReadOnlyResource(group, "/audit/audit_operation_log", &models.AuditOperationLog{})
|
||||||
registerReadOnlyResource(group, "/audit/aud_export_log", &models.AudExportLog{})
|
registerReadOnlyResource(group, "/audit/audit_export_log", &models.AuditExportLog{})
|
||||||
registerReadOnlyResource(group, "/audit/aud_approval", &models.AudApproval{})
|
registerReadOnlyResource(group, "/audit/audit_approval", &models.AuditApproval{})
|
||||||
group.POST("/audit/aud_approval/:identity/approve", platform.ApproveAudit)
|
group.POST("/audit/audit_approval/:identity/approve", platform.ApproveAudit)
|
||||||
}
|
}
|
||||||
|
|
||||||
func registerWritableResource(group *gin.RouterGroup, path string, list, create, get, update gin.HandlerFunc, model any) {
|
func registerWritableResource(group *gin.RouterGroup, path string, list, create, get, update gin.HandlerFunc, model any) {
|
||||||
|
|||||||
Reference in New Issue
Block a user