feat(admin): manage server updates and local configuration
ci / api / api (push) Successful in 52s
ci / contract / contract (сгенерированный код совпадает с openapi.yaml) (push) Successful in 1m9s
ci / mobile / mobile (Metro-бандл Android) (push) Successful in 1m35s
release / release (версия и заметки) (push) Successful in 5s
release / android (APK в релиз) (push) Successful in 4m6s
ci / db / db (push) Successful in 1m15s
ci / docker / docker (образы собираются — их сервер собирает сам при обновлении) (push) Successful in 1m19s
ci / ts / ts (packages + web) (push) Successful in 2m34s
release / desktop (Linux и Windows в релиз) (push) Successful in 14m27s
release / prune (старые файлы и прогоны) (push) Successful in 6s

This commit is contained in:
mrleo1nid committed 2026-10-22 23:01:36 +03:00
1 parent e33abc948c
commit a29a8b18ad
49 files changed
+3604 -353

No files matched your search

+14 -3
View File
@@ -78,6 +78,7 @@ type CreateUserInput struct {
Username string
DisplayName string
Role Role
ManagesUpdates bool
ReviewsUploads bool
}
@@ -89,7 +90,7 @@ func (s *Service) CreateUser(ctx context.Context, p Principal, in CreateUserInpu
if !CanAssignRole(p.Role, in.Role) || !CanManageUser(actorOf(p), Actor{Role: in.Role}, UserCreate) {
return User{}, "", forbidden()
}
if in.ReviewsUploads && p.Role != RoleAdmin {
if (in.ReviewsUploads || in.ManagesUpdates) && p.Role != RoleAdmin {
return User{}, "", forbidden()
}
username := NormalizeUsername(in.Username)
@@ -114,7 +115,7 @@ func (s *Service) CreateUser(ctx context.Context, p Principal, in CreateUserInpu
}
u := User{
ID: s.ids.New(), Username: username, DisplayName: name, PasswordHash: hash, Role: in.Role,
ReviewsUploads: in.ReviewsUploads, Locale: locale,
ReviewsUploads: in.ReviewsUploads, ManagesUpdates: in.ManagesUpdates, Locale: locale,
MustChangePassword: true, CreatedAt: s.clock.Now(),
}
err = s.store.WithTx(ctx, func(tx Store) error {
@@ -123,7 +124,7 @@ func (s *Service) CreateUser(ctx context.Context, p Principal, in CreateUserInpu
}
return tx.InsertAudit(ctx, s.ids.New(), AuditEntry{
ActorID: &p.UserID, Action: "user.create", TargetType: "user", TargetID: &u.ID,
Payload: map[string]any{"username": username, "role": string(in.Role), "reviews_uploads": in.ReviewsUploads},
Payload: map[string]any{"username": username, "role": string(in.Role), "reviews_uploads": in.ReviewsUploads, "manages_updates": in.ManagesUpdates},
})
})
if errors.Is(err, ErrUsernameTaken) {
@@ -154,6 +155,7 @@ type UserPatch struct {
DisplayName *string
Role *Role
IsBlocked *bool
ManagesUpdates *bool
ReviewsUploads *bool
}
@@ -218,6 +220,15 @@ func (s *Service) UpdateUser(ctx context.Context, p Principal, id uuid.UUID, pat
upd.ReviewsUploads = patch.ReviewsUploads
audit("user.reviews_uploads", map[string]any{"reviews_uploads": *patch.ReviewsUploads})
}
if patch.ManagesUpdates != nil {
if p.Role != RoleAdmin {
return forbidden()
}
if *patch.ManagesUpdates != target.ManagesUpdates {
upd.ManagesUpdates = patch.ManagesUpdates
audit("user.manages_updates", map[string]any{"manages_updates": *patch.ManagesUpdates})
}
}
blockChange := patch.IsBlocked != nil && *patch.IsBlocked != target.IsBlocked
if blockChange {
if !CanManageUser(actor, tgt, UserBlock) {