diff --git a/api/openapi.yaml b/api/openapi.yaml
index 00a3df3..868f573 100644
--- a/api/openapi.yaml
+++ b/api/openapi.yaml
@@ -2176,7 +2176,7 @@ components:
type: boolean
Me:
type: object
- required: [id, username, display_name, role, locale, ui_prefs, must_change_password, must_change_credentials, created_at]
+ required: [id, username, display_name, role, reviews_uploads, locale, ui_prefs, must_change_password, must_change_credentials, created_at]
properties:
id:
type: string
@@ -2188,6 +2188,9 @@ components:
nullable: true
role:
$ref: "#/components/schemas/Role"
+ reviews_uploads:
+ type: boolean
+ description: Может рассматривать очередь на добавление. Не следует из роли и может быть у нескольких людей сразу
locale:
type: string
ui_prefs:
@@ -2228,7 +2231,7 @@ components:
type: string
AdminUser:
type: object
- required: [id, username, display_name, role, locale, is_blocked, must_change_password, must_change_credentials, created_at, last_login_at]
+ required: [id, username, display_name, role, reviews_uploads, locale, is_blocked, must_change_password, must_change_credentials, created_at, last_login_at]
properties:
id:
type: string
@@ -2240,6 +2243,9 @@ components:
nullable: true
role:
$ref: "#/components/schemas/Role"
+ reviews_uploads:
+ type: boolean
+ description: Может рассматривать очередь на добавление. Не следует из роли и может быть у нескольких людей сразу
locale:
type: string
is_blocked:
@@ -2277,6 +2283,9 @@ components:
maxLength: 64
role:
$ref: "#/components/schemas/Role"
+ reviews_uploads:
+ type: boolean
+ description: Сразу выдать аудит загрузок. Может задать только админ
CreatedUser:
type: object
required: [user, temporary_password]
@@ -2297,6 +2306,9 @@ components:
$ref: "#/components/schemas/Role"
is_blocked:
type: boolean
+ reviews_uploads:
+ type: boolean
+ description: Выдать или забрать аудит загрузок. Может менять только админ, в том числе у себя
TemporaryPassword:
type: object
required: [temporary_password]
diff --git a/apps/web/src/app/router.tsx b/apps/web/src/app/router.tsx
index aba05b3..38be27b 100644
--- a/apps/web/src/app/router.tsx
+++ b/apps/web/src/app/router.tsx
@@ -226,7 +226,9 @@ const myUploadsRoute = createRoute({ getParentRoute: () => appRoute, path: '/upl
const uploadQueueRoute = createRoute({
getParentRoute: () => appRoute,
path: '/admin/uploads',
- beforeLoad: adminOnly,
+ beforeLoad: ({ context }) => {
+ if (!context.user.reviews_uploads) throw redirect({ to: '/' });
+ },
component: UploadQueuePage,
});
diff --git a/apps/web/src/pages/admin/UsersPage.tsx b/apps/web/src/pages/admin/UsersPage.tsx
index 4092c75..a8f128b 100644
--- a/apps/web/src/pages/admin/UsersPage.tsx
+++ b/apps/web/src/pages/admin/UsersPage.tsx
@@ -132,6 +132,11 @@ export function UsersPage() {
label: t('users.make_role', { role: t(`common:roles.${r}`) }),
onSelect: () => patch.mutate({ id: u.id, body: { role: r } }),
});
+ if (me.role === 'admin')
+ items.push({
+ label: t(u.reviews_uploads ? 'users.revoke_review' : 'users.grant_review'),
+ onSelect: () => patch.mutate({ id: u.id, body: { reviews_uploads: !u.reviews_uploads } }),
+ });
if (can('delete'))
items.push({ label: t('users.delete'), danger: true, onSelect: () => setConfirm({ kind: 'delete', user: u }) });
return items;
@@ -206,6 +211,7 @@ export function UsersPage() {
{t(`common:roles.${u.role}`)}
+ {u.reviews_uploads && {t('users.review_uploads')}}
|
{t(`users.status_${st}`)}
@@ -285,12 +291,18 @@ function CreateUserDialog({
const [username, setUsername] = useState('');
const [name, setName] = useState('');
const [role, setRole] = useState('user');
+ const [reviews, setReviews] = useState(false);
const create = useMutation({
mutationFn: async () =>
unwrap(
await session.client.POST('/api/v1/admin/users', {
- body: { username: username.trim(), display_name: name.trim() || undefined, role },
+ body: {
+ username: username.trim(),
+ display_name: name.trim() || undefined,
+ role,
+ reviews_uploads: actorRole === 'admin' && reviews ? true : undefined,
+ },
}),
),
onSuccess: (r) => onCreated(r.user.username, r.temporary_password),
@@ -342,6 +354,13 @@ function CreateUserDialog({
)}
+ {actorRole === 'admin' && (
+
+ )}
+ {actorRole === 'admin' && {t('create.review_uploads_hint')} }
{t('create.password_note')}
{create.error && !usernameError && !nameError ? errorText(create.error) : null}
diff --git a/apps/web/src/test/fakeApi.ts b/apps/web/src/test/fakeApi.ts
index b9339e7..276ca51 100644
--- a/apps/web/src/test/fakeApi.ts
+++ b/apps/web/src/test/fakeApi.ts
@@ -56,6 +56,7 @@ export function makeUser(username: string, role: Role, extra: Partial
is_blocked: false,
must_change_password: false,
must_change_credentials: false,
+ reviews_uploads: false,
created_at: '2026-01-01T00:00:00Z',
last_login_at: null,
password: `${username}-password`,
@@ -80,6 +81,7 @@ export function fakeApi(initial: FakeUser[]) {
username: u.username,
display_name: u.display_name,
role: u.role,
+ reviews_uploads: u.reviews_uploads,
locale: u.locale,
ui_prefs: prefs.get(u.id) ?? {},
must_change_password: u.must_change_password,
@@ -179,6 +181,7 @@ export function fakeApi(initial: FakeUser[]) {
display_name: (body?.display_name as string) ?? null,
must_change_password: true,
password: 'Temp-Pass-123',
+ reviews_uploads: body?.reviews_uploads === true && user.role === 'admin',
});
users.set(u.id, u);
return json({ user: admin(u), temporary_password: u.password }, 201);
@@ -235,6 +238,20 @@ export function fakeApi(initial: FakeUser[]) {
}
if (req.method === 'POST' && /^\/api\/v1\/admin\/users\/[^/]+\/reset-recommendations$/.test(path))
return new Response(null, { status: 204 });
+ const userPath = /^\/api\/v1\/admin\/users\/([^/]+)$/.exec(path);
+ if (userPath && req.method === 'PATCH') {
+ if (!user || user.role === 'user') return problem(403, 'auth.forbidden');
+ const target = users.get(userPath[1]!);
+ if (!target) return problem(404, 'common.not_found');
+ if (typeof body?.reviews_uploads === 'boolean') {
+ if (user.role !== 'admin') return problem(403, 'auth.forbidden');
+ target.reviews_uploads = body.reviews_uploads;
+ }
+ if (typeof body?.role === 'string' && user.role === 'admin') target.role = body.role as Role;
+ if (typeof body?.is_blocked === 'boolean') target.is_blocked = body.is_blocked;
+ users.set(target.id, target);
+ return json(admin(target));
+ }
const reactionPath = /^\/api\/v1\/me\/reactions\/tracks\/([^/]+)$/.exec(path);
if (reactionPath) {
if (req.method === 'PUT') reactions.set(reactionPath[1]!, body?.reaction as 'like' | 'dislike');
diff --git a/apps/web/src/widgets/AppShell.tsx b/apps/web/src/widgets/AppShell.tsx
index fcf884b..88fbc83 100644
--- a/apps/web/src/widgets/AppShell.tsx
+++ b/apps/web/src/widgets/AppShell.tsx
@@ -153,7 +153,8 @@ function Shell() {
const searchAt = user.ui_prefs.search_position ?? 'center';
const search = ;
const counts = useCollectionCounts();
- const pending = usePendingUploads(user.role === 'admin');
+ const reviews = user.reviews_uploads;
+ const pending = usePendingUploads(reviews);
const unseen = useUnseenUploads(staff);
return (
@@ -207,8 +208,17 @@ function Shell() {
count={counts.dislikes}
/>
+ {!staff && reviews && (
+
+ )}
{staff && (
-
+
{user.role === 'admin' && (
@@ -216,15 +226,17 @@ function Shell() {
+ {reviews && (
+
+ )}
{user.role === 'admin' && (
<>
-
>
diff --git a/docs/admin-guide.md b/docs/admin-guide.md
index d8298f3..2502ad3 100644
--- a/docs/admin-guide.md
+++ b/docs/admin-guide.md
@@ -20,7 +20,13 @@
|---|---|
| Пользователь | Слушать, искать, собирать коллекцию, «Моя волна» |
| Модератор | Плюс: заводить пользователей и сбрасывать им пароли, смотреть статус библиотеки, предлагать треки |
-| Администратор | Всё: хранилища, сканы, очередь на добавление, настройки сервера, роли, аудит |
+| Администратор | Всё: хранилища, сканы, настройки сервера, роли, аудит |
+
+**Аудит загрузок** — отдельное право, не роль: рассматривать очередь на добавление (одобрять, отклонять, править
+метаданные). Выдаёт и забирает только администратор — при создании пользователя или в меню строки
+(«Дать / Забрать аудит загрузок»), в том числе себе. Право может быть у нескольких людей сразу и у любой роли;
+у администратора его может не быть. Первый (bootstrap) администратор и админы, существовавшие до появления права,
+получают его автоматически.
Полная матрица прав — [auth-and-roles.md](auth-and-roles.md#матрица-прав).
@@ -57,8 +63,8 @@
## 4. Очередь на добавление
-Модераторы и администраторы предлагают треки («Предложить треки»), администратор решает, что попадёт
-в библиотеку. Подробно — [uploads.md](uploads.md).
+Модераторы и администраторы предлагают треки («Предложить треки»), что попадёт в библиотеку, решают люди
+с правом **аудита загрузок**. Подробно — [uploads.md](uploads.md).
- **Куда пишутся файлы:** у хранилища включите «Принимать загрузки» и задайте **шаблон раскладки** — «Подобрать»
предложит шаблон по уже лежащим файлам, «Проверить» убедится, что из пути, построенного шаблоном, сканер
diff --git a/docs/auth-and-roles.md b/docs/auth-and-roles.md
index f1c11f2..4a2c7d3 100644
--- a/docs/auth-and-roles.md
+++ b/docs/auth-and-roles.md
@@ -24,9 +24,13 @@
| Хранилища, сканирование, переиндексация | — | — | ✅ |
| Статус библиотеки (только просмотр) | — | ✅ | ✅ |
| Предлагать треки (загрузка в очередь), видеть свои предложения | — | ✅ | ✅ |
-| Рассматривать очередь, одобрять / отклонять, править метаданные | — | — | ✅ |
| Лимит очереди, целевое хранилище, шаблон раскладки | — | — | ✅ |
| Статус рекомендательной системы, аудит | — | — | ✅ |
+| Выдать / забрать аудит загрузок | — | — | ✅ |
+
+**Аудит загрузок** (рассматривать очередь, одобрять / отклонять, править метаданные) — не строка матрицы, а
+отдельный флаг пользователя `reviews_uploads`, не зависящий от роли: его может иметь кто угодно, сразу несколько
+человек, а у админа его может не быть. Проверка — `Principal.Allowed(PermReviewUploads)`.
Реализация: в Go — политики в `internal/auth/policy.go` вида `CanManageUser(actor, target, action) bool`,
проверяются в сервисном слое (не только в middleware), покрываются табличными тестами по этой матрице.
diff --git a/docs/uploads.md b/docs/uploads.md
index 5f0bf2d..4715284 100644
--- a/docs/uploads.md
+++ b/docs/uploads.md
@@ -1,7 +1,7 @@
# Предложение треков (загрузка с модерацией)
Модераторы (и админы) предлагают новые треки. Файлы попадают в **очередь на добавление** с общим лимитом размера,
-дубли отбрасываются сразу, админ одобряет или отклоняет, одобренные файлы раскладываются в хранилище
+дубли отбрасываются сразу, аудитор загрузок одобряет или отклоняет, одобренные файлы раскладываются в хранилище
**по той же структуре папок, что принята в этом хранилище**.
## 1. Роли
@@ -10,10 +10,13 @@
|---|:-:|:-:|:-:|
| Загрузить треки в очередь | — | ✅ | ✅ |
| Видеть свои предложения и их статус, отозвать до рассмотрения | — | ✅ | ✅ |
-| Видеть всю очередь | — | — | ✅ |
-| Править метаданные, слушать, одобрять / отклонять | — | — | ✅ |
| Настраивать лимит очереди, целевое хранилище и шаблон раскладки | — | — | ✅ |
+Видеть всю очередь, слушать, править метаданные, одобрять и отклонять может только тот, у кого есть право
+**аудита загрузок** (`users.reviews_uploads`, в API — `reviews_uploads` в `Me` / `AdminUser`). Это не роль: право
+выдаёт админ, оно может быть у нескольких людей сразу, у модератора или даже у обычного пользователя, а у админа
+его может не быть. Без него админ по-прежнему настраивает лимиты и хранилища, но очередь не видит.
+
Загрузки админа проходят ту же проверку на дубли; в очередь они тоже попадают, но админ может одобрить их сразу.
## 2. Жизненный цикл загрузки
@@ -142,7 +145,7 @@
- Если файл по целевому пути уже существует — добавляется суффикс ` (2)`, а в задаче — предупреждение.
- Путь проверяется на выход за корень хранилища (`..`, абсолютные пути) — ошибка.
-## 8. Рассмотрение (админ)
+## 8. Рассмотрение (аудит загрузок)
Экран «Очередь на добавление»:
- Группировка по пачке загрузки / по альбому; фильтры: статус, автор, `possible_duplicate`.
@@ -173,7 +176,7 @@
## 10. Уведомления
- Автор видит статусы в «Мои предложения» (раздел админки модератора), бейдж с числом решений с последнего визита.
-- Админ видит бейдж «N на рассмотрении» в сайдбаре.
+- Аудитор загрузок видит бейдж «N на рассмотрении» в сайдбаре.
## Реализация (`services/api/internal/uploads`)
diff --git a/packages/api-client/src/generated/schema.ts b/packages/api-client/src/generated/schema.ts
index c770c8a..ea11439 100644
--- a/packages/api-client/src/generated/schema.ts
+++ b/packages/api-client/src/generated/schema.ts
@@ -1648,6 +1648,8 @@ export interface components {
username: string;
display_name: string | null;
role: components["schemas"]["Role"];
+ /** @description Может рассматривать очередь на добавление. Не следует из роли и может быть у нескольких людей сразу */
+ reviews_uploads: boolean;
locale: string;
ui_prefs: components["schemas"]["UiPrefs"];
must_change_password: boolean;
@@ -1675,6 +1677,8 @@ export interface components {
username: string;
display_name: string | null;
role: components["schemas"]["Role"];
+ /** @description Может рассматривать очередь на добавление. Не следует из роли и может быть у нескольких людей сразу */
+ reviews_uploads: boolean;
locale: string;
is_blocked: boolean;
must_change_password: boolean;
@@ -1692,6 +1696,8 @@ export interface components {
username: string;
display_name?: string;
role: components["schemas"]["Role"];
+ /** @description Сразу выдать аудит загрузок. Может задать только админ */
+ reviews_uploads?: boolean;
};
CreatedUser: {
user: components["schemas"]["AdminUser"];
@@ -1703,6 +1709,8 @@ export interface components {
display_name?: string;
role?: components["schemas"]["Role"];
is_blocked?: boolean;
+ /** @description Выдать или забрать аудит загрузок. Может менять только админ, в том числе у себя */
+ reviews_uploads?: boolean;
};
TemporaryPassword: {
temporary_password: string;
diff --git a/packages/core/src/auth.test.ts b/packages/core/src/auth.test.ts
index c921301..3130c44 100644
--- a/packages/core/src/auth.test.ts
+++ b/packages/core/src/auth.test.ts
@@ -11,6 +11,7 @@ const user = {
ui_prefs: {},
must_change_password: false,
must_change_credentials: false,
+ reviews_uploads: false,
created_at: '2026-01-01T00:00:00Z',
};
diff --git a/packages/i18n/locales/en/admin.json b/packages/i18n/locales/en/admin.json
index 6dccff5..c70956a 100644
--- a/packages/i18n/locales/en/admin.json
+++ b/packages/i18n/locales/en/admin.json
@@ -1,7 +1,7 @@
{
"users": {
"title": "Users",
- "subtitle": "Create users and moderators, manage roles, reset passwords, block accounts",
+ "subtitle": "Create users and moderators, manage roles, upload review, reset passwords, block accounts",
"create": "Create user",
"search": "Name or username",
"all": "All",
@@ -20,6 +20,9 @@
"block": "Block",
"unblock": "Unblock",
"make_role": "Make {{role}}",
+ "review_uploads": "Upload review",
+ "grant_review": "Grant upload review",
+ "revoke_review": "Revoke upload review",
"delete": "Delete",
"delete_confirm": "Delete {{username}}? Their history and likes will be lost.",
"block_confirm": "Block {{username}}? All their sessions will end.",
@@ -36,6 +39,8 @@
"username_hint": "Latin letters, digits, \".\", \"_\", \"-\"",
"display_name": "Name",
"role": "Role",
+ "review_uploads": "Upload review",
+ "review_uploads_hint": "They can review the upload queue. This is not a role: several people can have it, and an admin might not.",
"password_note": "The server will generate a password and show it once.",
"submit": "Create"
},
diff --git a/packages/i18n/locales/ru/admin.json b/packages/i18n/locales/ru/admin.json
index 41f431f..50b88f6 100644
--- a/packages/i18n/locales/ru/admin.json
+++ b/packages/i18n/locales/ru/admin.json
@@ -1,7 +1,7 @@
{
"users": {
"title": "Пользователи",
- "subtitle": "Создание пользователей и модераторов, роли, сброс пароля, блокировка",
+ "subtitle": "Создание пользователей и модераторов, роли, аудит загрузок, сброс пароля, блокировка",
"create": "Создать пользователя",
"search": "Имя или логин",
"all": "Все",
@@ -20,6 +20,9 @@
"block": "Заблокировать",
"unblock": "Разблокировать",
"make_role": "Сделать: {{role}}",
+ "review_uploads": "Аудит загрузок",
+ "grant_review": "Дать аудит загрузок",
+ "revoke_review": "Забрать аудит загрузок",
"delete": "Удалить",
"delete_confirm": "Удалить {{username}}? Его история и лайки пропадут.",
"block_confirm": "Заблокировать {{username}}? Все его сессии завершатся.",
@@ -36,6 +39,8 @@
"username_hint": "Латинские буквы, цифры, «.», «_», «-»",
"display_name": "Имя",
"role": "Роль",
+ "review_uploads": "Аудит загрузок",
+ "review_uploads_hint": "Сможет рассматривать очередь. Это не роль: можно выдать нескольким людям, у админа её может не быть.",
"password_note": "Пароль сгенерирует сервер и покажет один раз.",
"submit": "Создать"
},
diff --git a/services/api/internal/api/gen/api.gen.go b/services/api/internal/api/gen/api.gen.go
index dec407d..9753cf0 100644
--- a/services/api/internal/api/gen/api.gen.go
+++ b/services/api/internal/api/gen/api.gen.go
@@ -241,8 +241,11 @@ type AdminUser struct {
Locale string `json:"locale"`
MustChangeCredentials bool `json:"must_change_credentials"`
MustChangePassword bool `json:"must_change_password"`
- Role Role `json:"role"`
- Username string `json:"username"`
+
+ // ReviewsUploads Может рассматривать очередь на добавление. Не следует из роли и может быть у нескольких людей сразу
+ ReviewsUploads bool `json:"reviews_uploads"`
+ Role Role `json:"role"`
+ Username string `json:"username"`
}
// AdminUserPage defines model for AdminUserPage.
@@ -388,8 +391,11 @@ type ClientType string
// CreateUserRequest defines model for CreateUserRequest.
type CreateUserRequest struct {
DisplayName *string `json:"display_name,omitempty"`
- Role Role `json:"role"`
- Username string `json:"username"`
+
+ // ReviewsUploads Сразу выдать аудит загрузок. Может задать только админ
+ ReviewsUploads *bool `json:"reviews_uploads,omitempty"`
+ Role Role `json:"role"`
+ Username string `json:"username"`
}
// CreatedUser defines model for CreatedUser.
@@ -493,9 +499,12 @@ type Me struct {
Locale string `json:"locale"`
MustChangeCredentials bool `json:"must_change_credentials"`
MustChangePassword bool `json:"must_change_password"`
- Role Role `json:"role"`
- UiPrefs UiPrefs `json:"ui_prefs"`
- Username string `json:"username"`
+
+ // ReviewsUploads Может рассматривать очередь на добавление. Не следует из роли и может быть у нескольких людей сразу
+ ReviewsUploads bool `json:"reviews_uploads"`
+ Role Role `json:"role"`
+ UiPrefs UiPrefs `json:"ui_prefs"`
+ Username string `json:"username"`
}
// Onboarding defines model for Onboarding.
@@ -962,7 +971,10 @@ type UpdateUserRequest struct {
// DisplayName Пустая строка — сбросить имя
DisplayName *string `json:"display_name,omitempty"`
IsBlocked *bool `json:"is_blocked,omitempty"`
- Role *Role `json:"role,omitempty"`
+
+ // ReviewsUploads Выдать или забрать аудит загрузок. Может менять только админ, в том числе у себя
+ ReviewsUploads *bool `json:"reviews_uploads,omitempty"`
+ Role *Role `json:"role,omitempty"`
}
// UploadBatchCreated defines model for UploadBatchCreated.
@@ -9789,265 +9801,268 @@ func (sh *strictHandler) GetReadyz(w http.ResponseWriter, r *http.Request) {
// Base64 encoded, gzipped, json marshaled Swagger object
var swaggerSpec = []string{
- "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",
+ "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",
+ "AQA=",
}
// GetSwagger returns the content of the embedded swagger specification file
diff --git a/services/api/internal/auth/admin.go b/services/api/internal/auth/admin.go
index 248fda2..bea588d 100644
--- a/services/api/internal/auth/admin.go
+++ b/services/api/internal/auth/admin.go
@@ -75,9 +75,10 @@ func (s *Service) ListUsers(ctx context.Context, p Principal, query string, role
// CreateUserInput — форма создания пользователя.
type CreateUserInput struct {
- Username string
- DisplayName string
- Role Role
+ Username string
+ DisplayName string
+ Role Role
+ ReviewsUploads bool
}
// CreateUser создаёт пользователя с временным паролем, который возвращается один раз.
@@ -88,6 +89,9 @@ 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 {
+ return User{}, "", forbidden()
+ }
username := NormalizeUsername(in.Username)
if err := ValidateUsername("username", username); err != nil {
return User{}, "", err
@@ -109,7 +113,8 @@ func (s *Service) CreateUser(ctx context.Context, p Principal, in CreateUserInpu
locale = s.cfg.SupportedLocales[0]
}
u := User{
- ID: s.ids.New(), Username: username, DisplayName: name, PasswordHash: hash, Role: in.Role, Locale: locale,
+ ID: s.ids.New(), Username: username, DisplayName: name, PasswordHash: hash, Role: in.Role,
+ ReviewsUploads: in.ReviewsUploads, Locale: locale,
MustChangePassword: true, CreatedAt: s.clock.Now(),
}
err = s.store.WithTx(ctx, func(tx Store) error {
@@ -118,7 +123,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)},
+ Payload: map[string]any{"username": username, "role": string(in.Role), "reviews_uploads": in.ReviewsUploads},
})
})
if errors.Is(err, ErrUsernameTaken) {
@@ -145,9 +150,10 @@ func displayName(s string) (*string, error) {
// UserPatch — изменения пользователя из админки. nil — не менять.
type UserPatch struct {
- DisplayName *string
- Role *Role
- IsBlocked *bool
+ DisplayName *string
+ Role *Role
+ IsBlocked *bool
+ ReviewsUploads *bool
}
// UpdateUser меняет имя, роль и блокировку с проверкой прав по каждому изменению.
@@ -191,6 +197,13 @@ func (s *Service) UpdateUser(ctx context.Context, p Principal, id uuid.UUID, pat
upd.Role, upd.RevokeTokens = patch.Role, true
audit("user.role_change", map[string]any{"from": string(target.Role), "to": string(*patch.Role)})
}
+ if patch.ReviewsUploads != nil && *patch.ReviewsUploads != target.ReviewsUploads {
+ if p.Role != RoleAdmin {
+ return forbidden()
+ }
+ upd.ReviewsUploads = patch.ReviewsUploads
+ audit("user.reviews_uploads", map[string]any{"reviews_uploads": *patch.ReviewsUploads})
+ }
blockChange := patch.IsBlocked != nil && *patch.IsBlocked != target.IsBlocked
if blockChange {
if !CanManageUser(actor, tgt, UserBlock) {
diff --git a/services/api/internal/auth/admin_handler.go b/services/api/internal/auth/admin_handler.go
index 407f153..782d442 100644
--- a/services/api/internal/auth/admin_handler.go
+++ b/services/api/internal/auth/admin_handler.go
@@ -44,6 +44,9 @@ func (h *Handler) CreateUser(ctx context.Context, req gen.CreateUserRequestObjec
return nil, err
}
in := CreateUserInput{Username: req.Body.Username, Role: Role(req.Body.Role)}
+ if req.Body.ReviewsUploads != nil {
+ in.ReviewsUploads = *req.Body.ReviewsUploads
+ }
if req.Body.DisplayName != nil {
in.DisplayName = *req.Body.DisplayName
}
@@ -60,7 +63,7 @@ func (h *Handler) UpdateUser(ctx context.Context, req gen.UpdateUserRequestObjec
if err != nil {
return nil, err
}
- patch := UserPatch{DisplayName: req.Body.DisplayName, IsBlocked: req.Body.IsBlocked}
+ patch := UserPatch{DisplayName: req.Body.DisplayName, IsBlocked: req.Body.IsBlocked, ReviewsUploads: req.Body.ReviewsUploads}
if req.Body.Role != nil {
r := Role(*req.Body.Role)
patch.Role = &r
@@ -147,6 +150,7 @@ func AdminUserDTO(u User) gen.AdminUser {
Username: u.Username,
DisplayName: u.DisplayName,
Role: gen.Role(u.Role),
+ ReviewsUploads: u.ReviewsUploads,
Locale: u.Locale,
IsBlocked: u.IsBlocked,
MustChangePassword: u.MustChangePassword,
diff --git a/services/api/internal/auth/admin_test.go b/services/api/internal/auth/admin_test.go
index 123c2eb..fe6a3e6 100644
--- a/services/api/internal/auth/admin_test.go
+++ b/services/api/internal/auth/admin_test.go
@@ -395,3 +395,28 @@ func TestAudit(t *testing.T) {
mod := e.signIn("mod", "moderator-pass-1")
assert.Equal(t, auth.CodeForbidden, e.do(http.MethodGet, "/api/v1/admin/audit", nil, bearer(mod)).code())
}
+
+// Аудит загрузок выдаёт только админ, и он не следует из роли.
+func TestReviewUploadsGrant(t *testing.T) {
+ e := newEnv(t)
+ e.addUser("root", "root-password-1", auth.RoleAdmin)
+ admin := e.signIn("root", "root-password-1")
+ target := e.addUser("mod", "mod-password-1", auth.RoleModerator)
+ mod := e.signIn("mod", "mod-password-1")
+ path := "/api/v1/admin/users/" + target.ID.String()
+ grant := map[string]any{"reviews_uploads": true}
+
+ me := e.do(http.MethodGet, "/api/v1/me", nil, bearer(admin))
+ assert.Equal(t, false, me.body["reviews_uploads"])
+
+ assert.Equal(t, auth.CodeForbidden, e.do(http.MethodPatch, path, grant, bearer(mod)).code())
+ assert.Equal(t, auth.CodeForbidden, e.do(http.MethodPost, "/api/v1/admin/users",
+ map[string]any{"username": "rev", "role": "user", "reviews_uploads": true}, bearer(mod)).code())
+
+ r := e.do(http.MethodPatch, path, grant, bearer(admin))
+ require.Equal(t, http.StatusOK, r.Code, r.Body.String())
+ assert.Equal(t, true, r.body["reviews_uploads"])
+ assert.Equal(t, "moderator", r.body["role"])
+ me = e.do(http.MethodGet, "/api/v1/me", nil, bearer(mod))
+ assert.Equal(t, true, me.body["reviews_uploads"])
+}
diff --git a/services/api/internal/auth/authtest/memstore.go b/services/api/internal/auth/authtest/memstore.go
index f01d0d1..bdb17e9 100644
--- a/services/api/internal/auth/authtest/memstore.go
+++ b/services/api/internal/auth/authtest/memstore.go
@@ -303,6 +303,9 @@ func (s *MemStore) UpdateUser(ctx context.Context, id uuid.UUID, upd auth.UserUp
if upd.IsBlocked != nil {
u.IsBlocked = *upd.IsBlocked
}
+ if upd.ReviewsUploads != nil {
+ u.ReviewsUploads = *upd.ReviewsUploads
+ }
if upd.RevokeTokens {
u.TokenVersion++
}
diff --git a/services/api/internal/auth/handler.go b/services/api/internal/auth/handler.go
index 80a5fa4..425e1f4 100644
--- a/services/api/internal/auth/handler.go
+++ b/services/api/internal/auth/handler.go
@@ -231,6 +231,7 @@ func MeDTO(u User) gen.Me {
Username: u.Username,
DisplayName: u.DisplayName,
Role: gen.Role(u.Role),
+ ReviewsUploads: u.ReviewsUploads,
Locale: u.Locale,
UiPrefs: uiPrefsDTO(u.UIPrefs),
MustChangePassword: u.MustChangePassword,
diff --git a/services/api/internal/auth/model.go b/services/api/internal/auth/model.go
index c0d663c..5709da5 100644
--- a/services/api/internal/auth/model.go
+++ b/services/api/internal/auth/model.go
@@ -54,6 +54,7 @@ type User struct {
DisplayName *string
PasswordHash string
Role Role
+ ReviewsUploads bool // аудит очереди на добавление; не следует из роли
Locale string
UIPrefs UIPrefs
MustChangePassword bool
@@ -94,6 +95,7 @@ type Session struct {
type Principal struct {
UserID uuid.UUID
Role Role
+ ReviewsUploads bool
SessionID uuid.UUID
MustChangePassword bool
MustChangeCredentials bool
diff --git a/services/api/internal/auth/pgstore.go b/services/api/internal/auth/pgstore.go
index 7f79565..68c7312 100644
--- a/services/api/internal/auth/pgstore.go
+++ b/services/api/internal/auth/pgstore.go
@@ -39,7 +39,8 @@ func (s *PgStore) WithTx(ctx context.Context, fn func(Store) error) error {
}
const userColumns = `id, username, display_name, password_hash, role, locale, ui_prefs, must_change_password,
- must_change_credentials, is_blocked, token_version, failed_login_count, locked_until, created_at, last_login_at`
+ must_change_credentials, is_blocked, token_version, failed_login_count, locked_until, created_at, last_login_at,
+ reviews_uploads`
func scanUser(row pgx.Row) (User, error) {
var (
@@ -49,7 +50,7 @@ func scanUser(row pgx.Row) (User, error) {
)
err := row.Scan(&u.ID, &u.Username, &u.DisplayName, &u.PasswordHash, &role, &u.Locale, &prefs,
&u.MustChangePassword, &u.MustChangeCredentials, &u.IsBlocked, &u.TokenVersion, &u.FailedLoginCount,
- &u.LockedUntil, &u.CreatedAt, &u.LastLoginAt)
+ &u.LockedUntil, &u.CreatedAt, &u.LastLoginAt, &u.ReviewsUploads)
if errors.Is(err, pgx.ErrNoRows) {
return User{}, ErrNotFound
}
@@ -81,10 +82,10 @@ func (s *PgStore) CreateUser(ctx context.Context, u User, createdBy *uuid.UUID)
}
_, err = s.q.Exec(ctx, `
INSERT INTO users (id, username, display_name, password_hash, role, locale, ui_prefs,
- must_change_password, must_change_credentials, created_by, created_at, updated_at)
- VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $11)`,
+ must_change_password, must_change_credentials, created_by, created_at, updated_at, reviews_uploads)
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $11, $12)`,
u.ID, u.Username, u.DisplayName, u.PasswordHash, string(u.Role), u.Locale, prefs,
- u.MustChangePassword, u.MustChangeCredentials, createdBy, u.CreatedAt)
+ u.MustChangePassword, u.MustChangeCredentials, createdBy, u.CreatedAt, u.ReviewsUploads)
if isUniqueViolation(err) {
return ErrUsernameTaken
}
@@ -344,10 +345,11 @@ func (s *PgStore) UpdateUser(ctx context.Context, id uuid.UUID, upd UserUpdate)
display_name = CASE WHEN $2 THEN $3 ELSE display_name END,
role = COALESCE($4::user_role, role),
is_blocked = COALESCE($5, is_blocked),
+ reviews_uploads = COALESCE($7, reviews_uploads),
token_version = token_version + CASE WHEN $6 THEN 1 ELSE 0 END,
updated_at = now()
WHERE id = $1
- RETURNING `+userColumns, id, setName, name, role, upd.IsBlocked, upd.RevokeTokens))
+ RETURNING `+userColumns, id, setName, name, role, upd.IsBlocked, upd.RevokeTokens, upd.ReviewsUploads))
}
// ResetPassword реализует Store.
diff --git a/services/api/internal/auth/policy.go b/services/api/internal/auth/policy.go
index 3b0a545..a368c7f 100644
--- a/services/api/internal/auth/policy.go
+++ b/services/api/internal/auth/policy.go
@@ -14,7 +14,7 @@ const (
PermManageLibrary // хранилища, сканирование, переиндексация
PermViewLibraryStatus // статус библиотеки (просмотр)
PermSuggestTracks // предлагать треки, видеть свои предложения
- PermReviewUploads // очередь, одобрение, правка метаданных, лимиты
+ PermReviewUploads // очередь, одобрение, правка метаданных; даёт флаг, не роль
PermSystemStatus // статус рекомендательной системы, аудит
)
@@ -24,11 +24,11 @@ var permissions = map[Permission][]Role{
PermManageLibrary: {RoleAdmin},
PermViewLibraryStatus: {RoleModerator, RoleAdmin},
PermSuggestTracks: {RoleModerator, RoleAdmin},
- PermReviewUploads: {RoleAdmin},
PermSystemStatus: {RoleAdmin},
}
// Can сообщает, есть ли у роли глобальное право.
+// Аудит загрузок сюда не входит: его проверяет Principal.Allowed.
func Can(r Role, p Permission) bool {
for _, allowed := range permissions[p] {
if allowed == r {
@@ -38,6 +38,14 @@ func Can(r Role, p Permission) bool {
return false
}
+// Allowed — право конкретного пользователя. Аудит загрузок не вытекает из роли admin.
+func (p Principal) Allowed(perm Permission) bool {
+ if perm == PermReviewUploads {
+ return p.ReviewsUploads
+ }
+ return Can(p.Role, perm)
+}
+
// UserAction — действие над учётной записью другого пользователя.
type UserAction int
diff --git a/services/api/internal/auth/policy_test.go b/services/api/internal/auth/policy_test.go
index 17c2be3..a2f0a34 100644
--- a/services/api/internal/auth/policy_test.go
+++ b/services/api/internal/auth/policy_test.go
@@ -20,7 +20,6 @@ func TestCan(t *testing.T) {
{PermManageLibrary, "manage library", [3]bool{false, false, true}},
{PermViewLibraryStatus, "library status", [3]bool{false, true, true}},
{PermSuggestTracks, "suggest tracks", [3]bool{false, true, true}},
- {PermReviewUploads, "review uploads", [3]bool{false, false, true}},
{PermSystemStatus, "system status", [3]bool{false, false, true}},
}
roles := [3]Role{RoleUser, RoleModerator, RoleAdmin}
@@ -72,6 +71,14 @@ func TestCanManageUserSelf(t *testing.T) {
assert.True(t, CanManageUser(admin, admin, UserResetPassword))
}
+func TestReviewUploadsIsSeparateFromRole(t *testing.T) {
+ assert.False(t, Principal{Role: RoleAdmin}.Allowed(PermReviewUploads))
+ assert.True(t, Principal{Role: RoleModerator, ReviewsUploads: true}.Allowed(PermReviewUploads))
+ assert.True(t, Principal{Role: RoleUser, ReviewsUploads: true}.Allowed(PermReviewUploads))
+ assert.False(t, Principal{Role: RoleUser}.Allowed(PermReviewUploads))
+ assert.True(t, Principal{Role: RoleUser, ReviewsUploads: true}.Allowed(PermSuggestTracks) == false)
+}
+
func TestCanAssignRole(t *testing.T) {
cases := []struct {
actor, role Role
diff --git a/services/api/internal/auth/service.go b/services/api/internal/auth/service.go
index b3955d0..bfaf84d 100644
--- a/services/api/internal/auth/service.go
+++ b/services/api/internal/auth/service.go
@@ -368,6 +368,7 @@ func (s *Service) Authenticate(ctx context.Context, access string) (Principal, e
return Principal{
UserID: u.ID,
Role: u.Role,
+ ReviewsUploads: u.ReviewsUploads,
SessionID: claims.SessionID,
MustChangePassword: u.MustChangePassword,
MustChangeCredentials: u.MustChangeCredentials,
@@ -621,7 +622,7 @@ func (s *Service) Bootstrap(ctx context.Context) error {
}
u := User{
ID: s.ids.New(), Username: username, PasswordHash: hash, Role: RoleAdmin, Locale: "ru",
- MustChangeCredentials: true, CreatedAt: s.clock.Now(),
+ ReviewsUploads: true, MustChangeCredentials: true, CreatedAt: s.clock.Now(),
}
err = s.store.WithTx(ctx, func(tx Store) error {
if err := tx.CreateUser(ctx, u, nil); err != nil {
diff --git a/services/api/internal/auth/store.go b/services/api/internal/auth/store.go
index 2671127..5590268 100644
--- a/services/api/internal/auth/store.go
+++ b/services/api/internal/auth/store.go
@@ -31,10 +31,11 @@ type UserFilter struct {
// UserUpdate — изменения из админки. nil — не менять.
type UserUpdate struct {
- DisplayName **string
- Role *Role
- IsBlocked *bool
- RevokeTokens bool
+ DisplayName **string
+ Role *Role
+ IsBlocked *bool
+ ReviewsUploads *bool
+ RevokeTokens bool
}
// Store — доступ к users, refresh_tokens, audit_log и app_settings.
diff --git a/services/api/internal/uploads/handler.go b/services/api/internal/uploads/handler.go
index 36d293d..fd79d56 100644
--- a/services/api/internal/uploads/handler.go
+++ b/services/api/internal/uploads/handler.go
@@ -53,7 +53,7 @@ func (h *Handler) fileDTO(ctx context.Context, f File) gen.UploadFile {
out.TusUrl = &u
}
if h.signer != nil && f.StagingPath != "" && f.Status.KeepsStaging() {
- if p, ok := auth.PrincipalFrom(ctx); ok && auth.Can(p.Role, auth.PermReviewUploads) {
+ if p, ok := auth.PrincipalFrom(ctx); ok && p.ReviewsUploads {
u := h.streamURL(f.ID)
out.StreamUrl = &u
}
diff --git a/services/api/internal/uploads/service.go b/services/api/internal/uploads/service.go
index 9aa42d2..a0cdd9d 100644
--- a/services/api/internal/uploads/service.go
+++ b/services/api/internal/uploads/service.go
@@ -67,7 +67,7 @@ func principal(ctx context.Context, perm auth.Permission) (auth.Principal, error
if !ok {
return auth.Principal{}, apperr.Unauthorized(auth.CodeUnauthorized, "not authenticated")
}
- if !auth.Can(p.Role, perm) {
+ if !p.Allowed(perm) {
return auth.Principal{}, apperr.Forbidden(auth.CodeForbidden, "not allowed")
}
return p, nil
@@ -425,9 +425,9 @@ func (s *Service) Retry(ctx context.Context, id uuid.UUID) (File, error) {
return s.store.File(ctx, id)
}
-// GetSettings — настройки очереди (админ).
+// GetSettings — лимиты очереди (админ, не аудитор загрузок).
func (s *Service) GetSettings(ctx context.Context) (Settings, error) {
- if _, err := principal(ctx, auth.PermReviewUploads); err != nil {
+ if _, err := principal(ctx, auth.PermManageLibrary); err != nil {
return Settings{}, err
}
return s.store.Settings(ctx)
@@ -446,7 +446,7 @@ type SettingsInput struct {
// UpdateSettings меняет лимиты; уменьшение ниже занятого ничего не удаляет (docs/uploads.md#3).
func (s *Service) UpdateSettings(ctx context.Context, in SettingsInput) (Settings, error) {
- p, err := principal(ctx, auth.PermReviewUploads)
+ p, err := principal(ctx, auth.PermManageLibrary)
if err != nil {
return Settings{}, err
}
diff --git a/services/api/migrations/00012_upload_review.sql b/services/api/migrations/00012_upload_review.sql
new file mode 100644
index 0000000..bce09a4
--- /dev/null
+++ b/services/api/migrations/00012_upload_review.sql
@@ -0,0 +1,8 @@
+-- +goose Up
+-- Аудит загрузок — отдельное право, не роль. Уже существующие админы его сохраняют,
+-- чтобы очередь не осталась без того, кто её рассматривает. Новым админам его выдают явно.
+ALTER TABLE users ADD COLUMN reviews_uploads boolean NOT NULL DEFAULT false;
+UPDATE users SET reviews_uploads = true WHERE role = 'admin';
+
+-- +goose Down
+ALTER TABLE users DROP COLUMN reviews_uploads;
|