mirror of
https://github.com/kamask-dev/pchelpmaster.git
synced 2026-11-11 05:49:38 +00:00
feat(users): configure users collection and validate email format
Migration sets access rules, password min 8, name max 100, password-only auth, and a case-insensitive unique email index. A hook rejects non-ASCII and malformed emails on every save. API tests cover registration, login, and isolation between two users and a guest. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5.5
parent
eb4c1c6604
commit
115b5fb036
+77
-75
@@ -8,21 +8,23 @@
|
||||
|
||||
## users (auth-коллекция, уже существует в PocketBase)
|
||||
|
||||
| Поле | Тип | Ограничения |
|
||||
| --- | --- | --- |
|
||||
| `email` | email | обязательно, уникально |
|
||||
| `password` | password | минимум 8 символов |
|
||||
| `name` | text | необязательно, до 100 символов |
|
||||
| `verified` | bool | системное; гость не может выставить `true` |
|
||||
| Поле | Тип | Ограничения |
|
||||
| ---------- | -------- | ------------------------------------------------------------------- |
|
||||
| `email` | email | обязательно, уникально без учёта регистра (индекс `COLLATE NOCASE`) |
|
||||
| `password` | password | минимум 8 символов |
|
||||
| `name` | text | необязательно, до 100 символов |
|
||||
| `verified` | bool | системное; гость не может выставить `true` |
|
||||
|
||||
Формат email дополнительно проверяет хук `pb_hooks/users_email.pb.js` при любом сохранении записи: только печатные символы ASCII без пробелов, минимум один символ до `@`, один после `@`, точка и минимум два символа после неё. Поэтому кириллические адреса отклоняются (400, ошибка в поле `email`), а индекс `COLLATE NOCASE` покрывает все допустимые адреса.
|
||||
|
||||
Способ входа — только email + пароль (OAuth2, OTP, MFA выключены).
|
||||
|
||||
| Правило | Значение | Смысл |
|
||||
| --- | --- | --- |
|
||||
| list / view | `id = @request.auth.id` | пользователь видит только себя |
|
||||
| create | `""` (всем) | открытая регистрация |
|
||||
| update | `id = @request.auth.id` | меняет только себя |
|
||||
| delete | `null` (только суперпользователь) | удаление аккаунта в MVP не делаем |
|
||||
| Правило | Значение | Смысл |
|
||||
| ----------- | --------------------------------- | --------------------------------- |
|
||||
| list / view | `id = @request.auth.id` | пользователь видит только себя |
|
||||
| create | `""` (всем) | открытая регистрация |
|
||||
| update | `id = @request.auth.id` | меняет только себя |
|
||||
| delete | `null` (только суперпользователь) | удаление аккаунта в MVP не делаем |
|
||||
|
||||
Поле `avatar`, созданное PocketBase по умолчанию, можно оставить, в MVP оно не используется.
|
||||
|
||||
@@ -30,11 +32,11 @@
|
||||
|
||||
Метки заменяют и категории, и источники заявок: «Установка Windows», «YouDo», «Профи.ру», «Сарафан», «Гарантия» — это всё метки. Набор у каждого пользователя свой.
|
||||
|
||||
| Поле | Тип | Ограничения |
|
||||
| --- | --- | --- |
|
||||
| `owner` | relation → users | обязательно, одна, `cascadeDelete: true` |
|
||||
| `name` | text | обязательно, 1–50 символов |
|
||||
| `color` | select | одно из: `gray`, `red`, `orange`, `yellow`, `green`, `teal`, `blue`, `purple`, `pink`; обязательно |
|
||||
| Поле | Тип | Ограничения |
|
||||
| ------- | ---------------- | -------------------------------------------------------------------------------------------------- |
|
||||
| `owner` | relation → users | обязательно, одна, `cascadeDelete: true` |
|
||||
| `name` | text | обязательно, 1–50 символов |
|
||||
| `color` | select | одно из: `gray`, `red`, `orange`, `yellow`, `green`, `teal`, `blue`, `purple`, `pink`; обязательно |
|
||||
|
||||
Индекс: уникальный `(owner, name COLLATE NOCASE)` — у одного пользователя нет двух меток с одинаковым названием без учёта регистра; у разных пользователей названия могут совпадать.
|
||||
|
||||
@@ -42,16 +44,16 @@
|
||||
|
||||
## clients — клиенты
|
||||
|
||||
| Поле | Тип | Ограничения |
|
||||
| --- | --- | --- |
|
||||
| `owner` | relation → users | обязательно, `cascadeDelete: true` |
|
||||
| `name` | text | обязательно, 1–200 символов (как клиента зовут или как он подписан на площадке) |
|
||||
| `phones` | json | массив нормализованных номеров (`["79991234567"]`), до 5 штук, можно пустой |
|
||||
| `email` | email | необязательно |
|
||||
| `contacts` | text | свободный текст до 2000 символов: мессенджеры и прочие контакты |
|
||||
| `external_urls` | json | необязательно; массив ссылок на профили клиента на любых площадках (YouDo, Профи.ру, Avito и др.), до 5 штук, каждая — валидный `http(s)` URL |
|
||||
| `address` | text | необязательно, до 500 символов |
|
||||
| `note` | text | внутренняя заметка, до 5000 символов |
|
||||
| Поле | Тип | Ограничения |
|
||||
| --------------- | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `owner` | relation → users | обязательно, `cascadeDelete: true` |
|
||||
| `name` | text | обязательно, 1–200 символов (как клиента зовут или как он подписан на площадке) |
|
||||
| `phones` | json | массив нормализованных номеров (`["79991234567"]`), до 5 штук, можно пустой |
|
||||
| `email` | email | необязательно |
|
||||
| `contacts` | text | свободный текст до 2000 символов: мессенджеры и прочие контакты |
|
||||
| `external_urls` | json | необязательно; массив ссылок на профили клиента на любых площадках (YouDo, Профи.ру, Avito и др.), до 5 штук, каждая — валидный `http(s)` URL |
|
||||
| `address` | text | необязательно, до 500 символов |
|
||||
| `note` | text | внутренняя заметка, до 5000 символов |
|
||||
|
||||
Нормализация телефона (делает фронтенд, функция в `clients/domain`): оставить только цифры; `8XXXXXXXXXX` → `7XXXXXXXXXX`; 10 цифр, начинающиеся на `9`, → добавить `7`. Остальные номера хранятся как есть (только цифры).
|
||||
|
||||
@@ -59,33 +61,33 @@
|
||||
|
||||
## orders — заявки
|
||||
|
||||
| Поле | Тип | Ограничения |
|
||||
| --- | --- | --- |
|
||||
| `owner` | relation → users | обязательно, `cascadeDelete: true` |
|
||||
| `client` | relation → clients | обязательно, одна, без каскада |
|
||||
| `title` | text | обязательно, 1–200 символов |
|
||||
| `description` | text | описание проблемы со слов клиента, до 10000 |
|
||||
| `status` | select | обязательно: `new`, `scheduled`, `in_progress`, `done`, `failed` |
|
||||
| `scheduled_at` | date | дата и время визита, необязательно |
|
||||
| `address` | text | адрес выезда, до 500 (по умолчанию в форме подставляется адрес клиента) |
|
||||
| `price` | number | целые рубли, ≥ 0, необязательно |
|
||||
| `result` | text | что сделано, до 10000; **виден по публичной ссылке** |
|
||||
| `note` | text | примечания мастера, до 10000; **видны по публичной ссылке** |
|
||||
| `review` | text | отзыв клиента (например, с площадки), до 5000; **виден по публичной ссылке** |
|
||||
| `external_url` | url | необязательно; ссылка на страницу заказа на любой площадке (YouDo, Профи.ру, Avito и др.) |
|
||||
| `tags` | relation → tags | несколько, до 20, необязательно |
|
||||
| `attachments` | file | несколько, до 20 файлов, до 50 МБ каждый, **protected**; изображения (`image/jpeg`, `image/png`, `image/webp`, `image/heic`) и аудио (`audio/mpeg`, `audio/mp4`, `audio/x-m4a`, `audio/aac`, `audio/ogg`, `audio/wav`, `audio/webm`, `audio/amr`); миниатюры `200x200` |
|
||||
| `share_token` | text | необязательно; если задан — 43 символа `[A-Za-z0-9_-]`; уникален среди непустых |
|
||||
| Поле | Тип | Ограничения |
|
||||
| -------------- | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `owner` | relation → users | обязательно, `cascadeDelete: true` |
|
||||
| `client` | relation → clients | обязательно, одна, без каскада |
|
||||
| `title` | text | обязательно, 1–200 символов |
|
||||
| `description` | text | описание проблемы со слов клиента, до 10000 |
|
||||
| `status` | select | обязательно: `new`, `scheduled`, `in_progress`, `done`, `failed` |
|
||||
| `scheduled_at` | date | дата и время визита, необязательно |
|
||||
| `address` | text | адрес выезда, до 500 (по умолчанию в форме подставляется адрес клиента) |
|
||||
| `price` | number | целые рубли, ≥ 0, необязательно |
|
||||
| `result` | text | что сделано, до 10000; **виден по публичной ссылке** |
|
||||
| `note` | text | примечания мастера, до 10000; **видны по публичной ссылке** |
|
||||
| `review` | text | отзыв клиента (например, с площадки), до 5000; **виден по публичной ссылке** |
|
||||
| `external_url` | url | необязательно; ссылка на страницу заказа на любой площадке (YouDo, Профи.ру, Avito и др.) |
|
||||
| `tags` | relation → tags | несколько, до 20, необязательно |
|
||||
| `attachments` | file | несколько, до 20 файлов, до 50 МБ каждый, **protected**; изображения (`image/jpeg`, `image/png`, `image/webp`, `image/heic`) и аудио (`audio/mpeg`, `audio/mp4`, `audio/x-m4a`, `audio/aac`, `audio/ogg`, `audio/wav`, `audio/webm`, `audio/amr`); миниатюры `200x200` |
|
||||
| `share_token` | text | необязательно; если задан — 43 символа `[A-Za-z0-9_-]`; уникален среди непустых |
|
||||
|
||||
Статусы и подписи в UI:
|
||||
|
||||
| Значение | Подпись |
|
||||
| --- | --- |
|
||||
| `new` | Новая |
|
||||
| `scheduled` | Запланирована |
|
||||
| `in_progress` | В работе |
|
||||
| `done` | Выполнена |
|
||||
| `failed` | Не выполнена |
|
||||
| Значение | Подпись |
|
||||
| ------------- | ------------- |
|
||||
| `new` | Новая |
|
||||
| `scheduled` | Запланирована |
|
||||
| `in_progress` | В работе |
|
||||
| `done` | Выполнена |
|
||||
| `failed` | Не выполнена |
|
||||
|
||||
Переходы между статусами не ограничены: мастер может выставить любой.
|
||||
|
||||
@@ -93,28 +95,28 @@
|
||||
|
||||
### Соответствие полям YouDo (для справки)
|
||||
|
||||
| YouDo | Pchelpcrm |
|
||||
| --- | --- |
|
||||
| title, description | `title`, `description` |
|
||||
| price | `price` |
|
||||
| address, privateInfo | `address` |
|
||||
| customerName | `clients.name` |
|
||||
| customerUrl | `clients.external_urls` |
|
||||
| taskUrl | `external_url` |
|
||||
| category | метка |
|
||||
| review | `review` |
|
||||
| taskDate | `created` / `scheduled_at` |
|
||||
| YouDo | Pchelpcrm |
|
||||
| -------------------- | -------------------------- |
|
||||
| title, description | `title`, `description` |
|
||||
| price | `price` |
|
||||
| address, privateInfo | `address` |
|
||||
| customerName | `clients.name` |
|
||||
| customerUrl | `clients.external_urls` |
|
||||
| taskUrl | `external_url` |
|
||||
| category | метка |
|
||||
| review | `review` |
|
||||
| taskDate | `created` / `scheduled_at` |
|
||||
|
||||
## Правила доступа для tags, clients, orders
|
||||
|
||||
Общий шаблон (для `orders` добавляются проверки связей):
|
||||
|
||||
| Правило | Значение |
|
||||
| --- | --- |
|
||||
| list / view | `owner = @request.auth.id` |
|
||||
| create | `@request.auth.id != "" && owner = @request.auth.id` |
|
||||
| update | `owner = @request.auth.id && (@request.body.owner:isset = false \|\| @request.body.owner = @request.auth.id)` |
|
||||
| delete | `owner = @request.auth.id` |
|
||||
| Правило | Значение |
|
||||
| ----------- | ------------------------------------------------------------------------------------------------------------- |
|
||||
| list / view | `owner = @request.auth.id` |
|
||||
| create | `@request.auth.id != "" && owner = @request.auth.id` |
|
||||
| update | `owner = @request.auth.id && (@request.body.owner:isset = false \|\| @request.body.owner = @request.auth.id)` |
|
||||
| delete | `owner = @request.auth.id` |
|
||||
|
||||
Дополнительно для `orders` (create и update):
|
||||
|
||||
@@ -127,13 +129,13 @@
|
||||
|
||||
API-тесты проверяют именно это поведение. Если фактический код в PocketBase 0.40 окажется другим, тесты фиксируют фактический, а главное требование неизменно: **чужие данные никогда не попадают в ответ и не меняются**.
|
||||
|
||||
| Операция | Правило не выполнено | Правило `null` (не суперпользователь) |
|
||||
| --- | --- | --- |
|
||||
| list | 200, чужие записи отфильтрованы | 403 |
|
||||
| view | 404 | 403 |
|
||||
| create | 400 | 403 |
|
||||
| update | 404 | 403 |
|
||||
| delete | 404 | 403 |
|
||||
| Операция | Правило не выполнено | Правило `null` (не суперпользователь) |
|
||||
| -------- | ------------------------------- | ------------------------------------- |
|
||||
| list | 200, чужие записи отфильтрованы | 403 |
|
||||
| view | 404 | 403 |
|
||||
| create | 400 | 403 |
|
||||
| update | 404 | 403 |
|
||||
| delete | 404 | 403 |
|
||||
|
||||
## Публичный маршрут
|
||||
|
||||
|
||||
Reference in New Issue
Block a user