chore: checkpoint 第一版
ci / verify (push) Canceled after 0s

This commit is contained in:
Codex Local
2026-09-06 17:04:11 +08:00
parent a8cdec98d1
commit 0c2151ace9
659 changed files with 185855 additions and 554 deletions
+162
View File
@@ -0,0 +1,162 @@
package deviceactivation
import (
"context"
"errors"
"fmt"
"strings"
"time"
"github.com/jackc/pgx/v5"
)
type State string
const (
Factory State = "FACTORY"
Claimable State = "CLAIMABLE"
Bound State = "BOUND"
Provisioning State = "PROVISIONING"
Active State = "ACTIVE"
Lost State = "LOST"
Revoked State = "REVOKED"
)
var (
ErrInvalidState = errors.New("invalid device activation state")
ErrTransitionRejected = errors.New("device activation transition rejected")
ErrRevisionConflict = errors.New("device activation revision conflict")
ErrDeviceNotFound = errors.New("device activation target not found")
)
type Input struct {
DeviceID string
TenantID string
Target State
Reason string
Source string
ActorType string
ActorPublicID *string
RequestID string
ExpectedRevision *int64
}
type Result struct {
From State
To State
Revision int64
DeviceVersion int64
UpdatedAt time.Time
Changed bool
}
func Parse(value string) (State, error) {
state := State(strings.ToUpper(strings.TrimSpace(value)))
switch state {
case Factory, Claimable, Bound, Provisioning, Active, Lost, Revoked:
return state, nil
default:
return "", ErrInvalidState
}
}
func AllowsDeviceAuthentication(state string) bool {
parsed, err := Parse(state)
if err != nil {
// Unit-test fakes and rolling deployments may omit the new field. Database
// rows are never empty after migration 50.
return strings.TrimSpace(state) == ""
}
return parsed == Claimable || parsed == Bound || parsed == Provisioning || parsed == Active
}
func CanTransition(from, to State) bool {
if from == to {
return true
}
if from == Revoked {
return false
}
if to == Revoked {
return true
}
switch from {
case Factory:
return to == Claimable
case Claimable:
return to == Bound
case Bound:
return to == Claimable || to == Provisioning
case Provisioning:
return to == Bound || to == Active
case Active:
return to == Claimable || to == Provisioning || to == Lost
case Lost:
return to == Claimable || to == Active
default:
return false
}
}
func Apply(ctx context.Context, tx pgx.Tx, input Input) (Result, error) {
if tx == nil || strings.TrimSpace(input.DeviceID) == "" || strings.TrimSpace(input.TenantID) == "" ||
strings.TrimSpace(input.Reason) == "" || strings.TrimSpace(input.Source) == "" || strings.TrimSpace(input.ActorType) == "" {
return Result{}, ErrInvalidState
}
target, err := Parse(string(input.Target))
if err != nil {
return Result{}, err
}
var currentText string
var revision, deviceVersion int64
var updatedAt time.Time
err = tx.QueryRow(ctx, `
SELECT activation_state,activation_revision,version,activation_updated_at
FROM device WHERE id=$1::uuid AND tenant_id=$2::uuid FOR UPDATE`, input.DeviceID, input.TenantID).
Scan(&currentText, &revision, &deviceVersion, &updatedAt)
if errors.Is(err, pgx.ErrNoRows) {
return Result{}, ErrDeviceNotFound
}
if err != nil {
return Result{}, fmt.Errorf("lock device activation: %w", err)
}
current, err := Parse(currentText)
if err != nil {
return Result{}, fmt.Errorf("parse stored device activation state: %w", err)
}
if input.ExpectedRevision != nil && revision != *input.ExpectedRevision {
return Result{}, ErrRevisionConflict
}
if !CanTransition(current, target) {
return Result{}, ErrTransitionRejected
}
if current == target {
return Result{From: current, To: target, Revision: revision, DeviceVersion: deviceVersion, UpdatedAt: updatedAt}, nil
}
err = tx.QueryRow(ctx, `
UPDATE device SET activation_state=$3,activation_revision=activation_revision+1,
activation_reason=$4,activation_updated_at=now(),version=version+1,updated_at=now()
WHERE id=$1::uuid AND tenant_id=$2::uuid
RETURNING activation_revision,version,activation_updated_at`, input.DeviceID, input.TenantID, target, strings.TrimSpace(input.Reason)).
Scan(&revision, &deviceVersion, &updatedAt)
if err != nil {
return Result{}, fmt.Errorf("update device activation: %w", err)
}
_, err = tx.Exec(ctx, `
INSERT INTO device_activation_transition(
tenant_id,device_id,from_state,to_state,reason,source,actor_type,actor_public_id,request_id,activation_revision
) VALUES($1::uuid,$2::uuid,$3,$4,$5,$6,$7,NULLIF($8,'')::uuid,NULLIF($9,''),$10)`,
input.TenantID, input.DeviceID, current, target, strings.TrimSpace(input.Reason), strings.TrimSpace(input.Source),
strings.TrimSpace(input.ActorType), optionalString(input.ActorPublicID), strings.TrimSpace(input.RequestID), revision)
if err != nil {
return Result{}, fmt.Errorf("record device activation transition: %w", err)
}
return Result{From: current, To: target, Revision: revision, DeviceVersion: deviceVersion, UpdatedAt: updatedAt, Changed: true}, nil
}
func optionalString(value *string) string {
if value == nil {
return ""
}
return strings.TrimSpace(*value)
}