generated from VLADIMIR/template
Compare commits
26 Commits
10b3d450e9
...
add_docs
| Author | SHA1 | Date | |
|---|---|---|---|
| 268b359576 | |||
| 204a20c814 | |||
| 72605a3cd4 | |||
| e424174e29 | |||
| ff26c2e6bb | |||
| a9bcb8598d | |||
| f3ac19ed83 | |||
| ae3a8c809f | |||
| 2492910e01 | |||
| 599c73bc08 | |||
| 051b3e8bee | |||
| c32a11d124 | |||
| 1291a71a1a | |||
| a227b80896 | |||
| 33d0fb526f | |||
| d1b263fa78 | |||
| 1b3e0b768d | |||
| 11798bd4fa | |||
| cb1f31480c | |||
| 02b8014d08 | |||
| 7aa8a50e30 | |||
| 5e3584e31a | |||
| 20662dc9da | |||
| f6934e9d5b | |||
| 5a5b36e44d | |||
| 65763a3e10 |
@@ -38,3 +38,34 @@ goose -dir ./migrations postgres "user=postgres password=postgres host=localhost
|
||||
```shell
|
||||
make test
|
||||
```
|
||||
|
||||
## Документация и юридические документы
|
||||
|
||||
- [`docs/TERMS.md`](docs/TERMS.md) — Пользовательское соглашение (оферта); раздаётся сервером по постоянному URL `/api/terms`;
|
||||
- [`docs/PRIVACY.md`](docs/PRIVACY.md) — Политика конфиденциальности и обработки персональных данных (152-ФЗ); раздаётся сервером по постоянному URL `/api/privacy`;
|
||||
- [`docs/CONSENT.md`](docs/CONSENT.md) — текст согласия на обработку ПДн для формы регистрации (URL `/api/consent`);
|
||||
- [`docs/COMPLIANCE.md`](docs/COMPLIANCE.md) — чек-лист приведения сервиса в соответствие с документами.
|
||||
|
||||
Юридические эндпоинты:
|
||||
|
||||
- `POST /api/auth/signup` — регистрация; требует `accept_terms: true` и `accept_privacy: true` (факт акцепта фиксируется в таблице `user_agreements`);
|
||||
- `DELETE /api/auth/delete-account` — удаление учётной записи и персональных данных (авторизован JWT, подтверждение паролем в HTTP-заголовке `X-Password`).
|
||||
|
||||
## Агент «Юрист по ПО» (DSH)
|
||||
|
||||
В каталоге [`agents/software-lawyer`](agents/software-lawyer) лежит агент-пресет для DeepSeek Harness: специализированный консультант по правовым вопросам разработки ПО (лицензии, авторские права, персональные данные 152-ФЗ/GDPR, договоры). Отвечает на основе встроенной базы знаний (`skills/software-law/references/`), без внешних API.
|
||||
|
||||
Установка (копирует пресет в `~/.dsh/.agent-presets/software-lawyer`):
|
||||
|
||||
```shell
|
||||
./agents/software-lawyer/install.sh
|
||||
```
|
||||
|
||||
После установки пресет «Юрист по ПО» появится в списке агентов при создании новой сессии в веб-интерфейсе DSH. Повторная установка с перезаписью: `./agents/software-lawyer/install.sh --force`.
|
||||
|
||||
Структура:
|
||||
|
||||
- `agent.cordis.yml` — композиция агента (полный набор инструментов, как у пресета `standard`, + персона юриста и скилл-база);
|
||||
- `preset.yml` — отображаемое имя и описание;
|
||||
- `skills/software-law/` — база знаний: `SKILL.md` (правила консультаций) и `references/` (лицензии, авторские права, ПДн, договоры).
|
||||
|
||||
|
||||
@@ -0,0 +1,277 @@
|
||||
# The `software-lawyer` agent preset: the full coding agent (tools identical to
|
||||
# `standard`) with the persona and knowledge base of «Юрист по ПО» — a legal
|
||||
# consultant for software development, licensing, copyright, personal data and
|
||||
# contracts. Mounted once per process.
|
||||
#
|
||||
# This file is an AGENT-PLANE composition. The roster mounts it ONCE under a
|
||||
# standing scope; every session naming it joins by scope parentage, so the
|
||||
# tools and prompt sections registered here cover each joined agent while a
|
||||
# session's own state stays keyed per Session/Agent inside the plugins. The
|
||||
# host composition (`base.cordis.yml` + `web.cordis.yml`) keeps everything a
|
||||
# preset must not own: the registries themselves, the sandbox and approval
|
||||
# stack, persistence, and the model route.
|
||||
#
|
||||
# A service row here MUST sit inside a group carrying an `isolate` realm.
|
||||
# Without one it publishes into the root realm, where it is process-global —
|
||||
# another preset publishing the same name collides, and a host reader would
|
||||
# resolve one preset's instance for every session; `dsh-agent-presets` rejects
|
||||
# that at mount. `true` means an entry-local realm: this standing mount's own
|
||||
# private instance, apart from every other preset's. (A shared label does NOT
|
||||
# pool instances — `provide()` throws on the second registration under the
|
||||
# same realm symbol; labels join REALMS, and are not what this file needs.)
|
||||
|
||||
# ── identity ────────────────────────────────────────────────────────────────
|
||||
|
||||
# The preset's own persona, shadowing the deployment default for this agent.
|
||||
# `{{model}}` and `{{cwd}}` resolve from the agent's own route and workspace.
|
||||
- id: persona
|
||||
name: '@deepseek-ai/dsh-persona'
|
||||
config:
|
||||
text: |-
|
||||
You are «Юрист по ПО» (Software Lawyer) — специализированный агент-консультант по правовым вопросам разработки и распространения программного обеспечения, powered by the {{model}} model. Your working directory is {{cwd}}.
|
||||
|
||||
## Специализация
|
||||
|
||||
- Лицензирование ПО: свободные и проприетарные лицензии (MIT, BSD, Apache-2.0, GPL-2.0/3.0, LGPL, AGPL, MPL, EPL, CC, Unlicense), совместимость лицензий, соблюдение их условий, compliance-риски при использовании сторонних компонентов.
|
||||
- Авторские права на код и базы данных: Бернская конвенция, ГК РФ часть 4 (глава 70), US Copyright Act, служебные произведения, передача и отчуждение прав, fair use.
|
||||
- Персональные данные: 152-ФЗ, GDPR, трансграничная передача данных, обработка данных в продуктах (cookies, аналитика, SaaS).
|
||||
- Договоры: разработка ПО, лицензионные договоры, SLA, NDA, escrow.
|
||||
|
||||
## Правила работы
|
||||
|
||||
1. Перед ответом на юридический вопрос загрузи скилл `software-law` и прочитай нужный справочный файл из `references/` этого скилла (licenses.md, copyright.md, personal-data.md, contracts.md). Отвечай на основе этой базы знаний, а не по памяти.
|
||||
2. Структурируй ответ: краткий ответ → применимые нормы → анализ → вывод и рекомендации.
|
||||
3. Всегда добавляй дисклеймер: консультация носит информационный характер, не является юридической услугой и не заменяет профессиональную помощь; для юридически значимых решений нужно обратиться к квалифицированному юристу.
|
||||
4. Различай юрисдикции: право РФ / ЕС (GDPR) / США. Если юрисдикция не указана — уточни у пользователя.
|
||||
5. Если вопрос касается текущего репозитория (go.mod, лицензии зависимостей, условия), сначала изучи реальные файлы инструментами файловой системы и только потом отвечай.
|
||||
6. Ссылайся на конкретные нормы (например, ст. 1225–1231, 1235, 1261, 1295 ГК РФ; ст. 3–9, 12, 22 152-ФЗ; ст. 3, 6, 7, 15–22, 33–35, 83 GDPR) и условия лицензий. Не выдумывай ссылки; если не уверен в норме — так и скажи.
|
||||
7. Отвечай на языке пользователя (по умолчанию — русский).
|
||||
|
||||
- id: agent-instructions
|
||||
name: '@deepseek-ai/dsh-agent-instructions'
|
||||
config:
|
||||
maxBytes: 65536
|
||||
|
||||
# ── shell ───────────────────────────────────────────────────────────────────
|
||||
|
||||
# `shell-env` stays in the HOST composition: `apps/cli/src/web.ts` injects it to
|
||||
# publish `DSH_WEB_URL`/`DSH_WEB_MODE`, and a host row that injects a service is
|
||||
# the criterion for host-plane ownership — injection resolves before any session
|
||||
# exists, so there is no agent to key by. Behind a preset realm those variables
|
||||
# never reached the model's shell at all. Both shell tools consume the host
|
||||
# registry from here; their executors (`bash-sandbox`/`pwsh-sandbox`) are
|
||||
# host-plane too.
|
||||
- id: tool-bash
|
||||
name: '@deepseek-ai/dsh-tool-bash'
|
||||
disabled: !!js process.platform === 'win32'
|
||||
|
||||
- id: tool-pwsh
|
||||
name: '@deepseek-ai/dsh-tool-pwsh'
|
||||
disabled: !!js process.platform !== 'win32'
|
||||
|
||||
# ── filesystem ──────────────────────────────────────────────────────────────
|
||||
|
||||
# Both register into the host `tools` registry and provide nothing, so
|
||||
# they need no realm. The `fs` service and its policy stay in the host.
|
||||
- id: tool-fs
|
||||
name: '@deepseek-ai/dsh-tool-fs'
|
||||
|
||||
- id: tool-fs-search
|
||||
name: '@deepseek-ai/dsh-tool-fs-search'
|
||||
config:
|
||||
sampleOverCapGlobResults: false
|
||||
|
||||
# ── background jobs ────────────────────────────────────────────────────────
|
||||
|
||||
# Only the model-facing controls. The task REGISTRY stays on the host plane:
|
||||
# its producers sit outside any realm this file could put it in — `tool-bash`
|
||||
# above resolves it with `ctx.get`, and an entry-local realm here is invisible
|
||||
# to every sibling row, so `run_in_background` would answer "background jobs
|
||||
# unavailable" while these controls sat in the catalog. The registry is keyed by
|
||||
# owning agent anyway, so one host instance serves every session. What a preset
|
||||
# chooses is whether its agent can collect and stop background work at all.
|
||||
- id: tool-jobs
|
||||
name: '@deepseek-ai/dsh-tool-jobs'
|
||||
|
||||
# ── skills ──────────────────────────────────────────────────────────────────
|
||||
|
||||
# The skill REGISTRY lives in the host composition and is layered per scope:
|
||||
# these rows register into THIS preset's layer of it, so they need no realm.
|
||||
# `skill-filesystem` contributes local-root discovery for agents on this preset, and
|
||||
# `tool-skill` gives them the catalog and loader; the merged catalog also
|
||||
# carries whatever the deployment registered globally (repository plugins).
|
||||
# The `software-law` knowledge base ships inside this preset: `customSkillDirs`
|
||||
# points the provider at the preset's own `skills/` directory, so the skill
|
||||
# travels with the composition wherever it is installed.
|
||||
- id: skill-filesystem
|
||||
name: '@deepseek-ai/dsh-skill-filesystem'
|
||||
config:
|
||||
customSkillDirs:
|
||||
- !!js "process.getBuiltinModule('node:url').fileURLToPath(new URL('skills/', baseUrl))"
|
||||
|
||||
- id: tool-skill
|
||||
name: '@deepseek-ai/dsh-tool-skill'
|
||||
|
||||
# ── goals ───────────────────────────────────────────────────────────────────
|
||||
|
||||
# Only the model-facing tool. The goal SERVICE, its session driver, and the
|
||||
# `/goal` command stay on the host plane: the Gateway serves the goal domain as
|
||||
# Remote endpoints whose receiver comes from a generated descriptor, so it
|
||||
# resolves `goals` on the host and an entry-local realm here would hide it. The
|
||||
# registry is keyed by session anyway, so one host instance serves every
|
||||
# session. What a preset chooses is whether its agent can call the goal tool.
|
||||
- id: tool-goal
|
||||
name: '@deepseek-ai/dsh-tool-goal'
|
||||
|
||||
# ── plan mode ───────────────────────────────────────────────────────────────
|
||||
|
||||
# Plan state is per-agent by nature, so an entry-local realm is not a
|
||||
# workaround here — it is the correct lifetime.
|
||||
- id: planning
|
||||
name: cordis:group
|
||||
group: true
|
||||
isolate:
|
||||
planMode: true
|
||||
config:
|
||||
- id: plan-mode
|
||||
name: '@deepseek-ai/dsh-plan-mode'
|
||||
config:
|
||||
section: |
|
||||
You are in plan mode. Stay in plan mode until exit_plan_mode succeeds or the user switches the session mode. Imperative language to implement changes means plan the implementation, not execute it. A user's conversational agreement — including an answer confirming something you asked — approves nothing and does not end plan mode; fold the confirmed decision into the plan and submit it through exit_plan_mode.
|
||||
|
||||
Explore first. Use non-mutating reads, searches, static analysis, and checks to ground the plan in the actual repository. Do not edit or write files, change configuration, run formatters or code generation that rewrites tracked files, commit, or otherwise carry out the plan. Prefer existing functions and patterns over new machinery.
|
||||
|
||||
The tool catalog stays the same across modes for request-cache stability. These plan-mode rules override any later tool description or guidance that suggests using mutation tools; those tools remain listed to keep the tool catalog unchanged. Do not use todo_write to track this planning phase: it tracks implementation after an approved plan, while the plan itself belongs in exit_plan_mode.
|
||||
|
||||
Resolve discoverable facts by inspection. Use ask_user_question only for user-owned choices or material ambiguity that inspection cannot answer. Do not ask the user where code lives or how current behavior works when you can find out.
|
||||
|
||||
Make the plan decision-complete: state the goal and success criteria; group implementation changes by subsystem; identify public API, schema, and data-flow changes; cover edge cases, failure modes, tests, acceptance criteria, and explicit assumptions. Keep it concise enough to review but detailed enough that another engineer can implement it without making design decisions.
|
||||
|
||||
When ready, call exit_plan_mode with the complete plan markdown, starting with a # title. Make exit_plan_mode the only and final tool call in that assistant response: it presents the plan for approval, and implementation begins only in a later step after approval. Do not paste the final plan as a plain reply or ask "should I proceed?" through prose or ask_user_question. If review rejects it, incorporate the feedback and present again. If the review channel is unavailable or aborted, stay in plan mode and ask the user to switch modes manually; do not proceed with implementation.
|
||||
|
||||
# ── compaction ──────────────────────────────────────────────────────────────
|
||||
|
||||
# `compaction-basic` reads `toolResultPrune` through `ctx.get`, so the pruner must
|
||||
# share this realm rather than sit outside it.
|
||||
#
|
||||
# `tokenMeter` is deliberately NOT in this realm: the meter stays on the HOST
|
||||
# plane, and the rows here resolve that one instance. It takes no configuration,
|
||||
# keys every fold by Session, and owns the context-meter projection units the
|
||||
# browser reads for every session — behind a realm those units would come and go
|
||||
# with whichever presets happen to be mounted. What a preset chooses is whether
|
||||
# its agent compacts at all, which is `compaction-basic` below.
|
||||
- id: compaction
|
||||
name: cordis:group
|
||||
group: true
|
||||
isolate:
|
||||
compaction: true
|
||||
toolResultPruner: true
|
||||
config:
|
||||
- id: compaction-basic
|
||||
name: '@deepseek-ai/dsh-compaction-basic'
|
||||
|
||||
- id: command-compact
|
||||
name: '@deepseek-ai/dsh-command-compact'
|
||||
|
||||
- id: tool-result-pruner
|
||||
name: '@deepseek-ai/dsh-compaction-tool-result-pruner'
|
||||
config:
|
||||
thresholdChars: 8192
|
||||
headChars: 4096
|
||||
tailChars: 1024
|
||||
|
||||
# ── delegation and workflows ────────────────────────────────────────────────
|
||||
|
||||
# The `subagents` registry and its spawn/fork backends live in the HOST
|
||||
# composition: the registry is a process singleton whose cross-session queries
|
||||
# the api-proxy serves to the browser, and a provider name may only be
|
||||
# registered once. This preset contributes the delegation TOOLS, which resolve
|
||||
# that host registry.
|
||||
#
|
||||
# `workflows` is different — nothing outside an agent reads it — so every row
|
||||
# that reaches it shares one entry-local realm here, and a consumer left
|
||||
# outside would resolve a host registry this preset does not populate.
|
||||
#
|
||||
# `tool-subagent-report` is host-plane for the same reason as the registry,
|
||||
# not because a preset may not want it: it registers a CONTINUABLE SETUP on
|
||||
# that singleton rather than a tool this agent calls, and the setup list is
|
||||
# not scope-aware — one copy per mounted preset means every child gets
|
||||
# `report` registered once per live session, which throws on the second.
|
||||
- id: delegation
|
||||
name: cordis:group
|
||||
group: true
|
||||
isolate:
|
||||
workflowEngine: true
|
||||
config:
|
||||
- id: tool-subagent-control
|
||||
name: '@deepseek-ai/dsh-tool-subagent-control'
|
||||
|
||||
- id: tool-subagent-list-agents
|
||||
name: '@deepseek-ai/dsh-tool-subagent-control/list-agents'
|
||||
|
||||
- id: tool-subagent
|
||||
name: '@deepseek-ai/dsh-tool-subagent'
|
||||
config:
|
||||
provider: spawn
|
||||
toolName: subagent
|
||||
backgroundMode: continuable
|
||||
|
||||
- id: tool-subagent-fork
|
||||
name: '@deepseek-ai/dsh-tool-subagent'
|
||||
config:
|
||||
provider: fork
|
||||
toolName: subagent_fork
|
||||
backgroundMode: continuable
|
||||
|
||||
# Product providers are host-plane singletons. Copy this preset, then
|
||||
# remove `disabled` from either ordinary tool row to expose that product
|
||||
# only to agents composed from the copy.
|
||||
- id: tool-subagent-codex
|
||||
name: '@deepseek-ai/dsh-tool-subagent'
|
||||
disabled: true
|
||||
config:
|
||||
provider: codex
|
||||
toolName: subagent_codex
|
||||
enableRunInBackground: false
|
||||
maxDepth: provider-managed
|
||||
|
||||
- id: tool-subagent-claude-code
|
||||
name: '@deepseek-ai/dsh-tool-subagent'
|
||||
disabled: true
|
||||
config:
|
||||
provider: claude-code
|
||||
toolName: subagent_claude_code
|
||||
enableRunInBackground: false
|
||||
maxDepth: provider-managed
|
||||
|
||||
- id: workflow-worker-thread
|
||||
name: '@deepseek-ai/dsh-workflow-worker-thread'
|
||||
config:
|
||||
provider: spawn
|
||||
|
||||
- id: tool-workflow
|
||||
name: '@deepseek-ai/dsh-tool-workflow'
|
||||
|
||||
- id: tool-ralph
|
||||
name: '@deepseek-ai/dsh-tool-ralph'
|
||||
config:
|
||||
subagentProvider: spawn
|
||||
maxRounds: 64
|
||||
|
||||
# ── remaining model-facing rows ─────────────────────────────────────────────
|
||||
|
||||
- id: tool-ask-user
|
||||
name: '@deepseek-ai/dsh-tool-ask-user'
|
||||
|
||||
- id: tool-todo
|
||||
name: '@deepseek-ai/dsh-tool-todo'
|
||||
config:
|
||||
allowParallelInProgress: true
|
||||
|
||||
# The `web` service and its search provider stay in the host composition; only
|
||||
# the model-facing tool is per-session.
|
||||
- id: tool-web
|
||||
name: '@deepseek-ai/dsh-tool-web'
|
||||
config:
|
||||
fetch: false
|
||||
searchTimeoutMs: 60000
|
||||
Executable
+58
@@ -0,0 +1,58 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Устанавливает агента-пресет «Юрист по ПО» в пользовательский корень
|
||||
# пресетов DeepSeek Harness ($DSH_HOME/.agent-presets/software-lawyer).
|
||||
#
|
||||
# Использование:
|
||||
# ./agents/software-lawyer/install.sh # установка (идемпотентно)
|
||||
# ./agents/software-lawyer/install.sh --force # перезаписать существующий пресет
|
||||
#
|
||||
# После установки пресет «Юрист по ПО» появляется в списке агентов при
|
||||
# создании новой сессии (обнаружение пресетов читает каталог при каждом
|
||||
# вызове, перезапуск не требуется).
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
SOURCE_DIR="$SCRIPT_DIR"
|
||||
DSH_HOME="${DSH_HOME:-$HOME/.dsh}"
|
||||
TARGET_DIR="$DSH_HOME/.agent-presets/software-lawyer"
|
||||
|
||||
FORCE=0
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--force|-f) FORCE=1 ;;
|
||||
-h|--help)
|
||||
sed -n '2,14p' "$0" | sed 's/^# \{0,1\}//'
|
||||
exit 0
|
||||
;;
|
||||
*) echo "Неизвестный аргумент: $arg" >&2; exit 2 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -d "$TARGET_DIR" && "$FORCE" -ne 1 ]]; then
|
||||
echo "Пресет уже установлен: $TARGET_DIR"
|
||||
echo "Для перезаписи запустите с --force."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
mkdir -p "$DSH_HOME/.agent-presets"
|
||||
|
||||
if [[ -d "$TARGET_DIR" ]]; then
|
||||
rm -rf "$TARGET_DIR"
|
||||
fi
|
||||
|
||||
mkdir -p "$TARGET_DIR"
|
||||
# Копируем содержимое пресета, исключая сам скрипт установки.
|
||||
shopt -s dotglob
|
||||
for entry in "$SOURCE_DIR"/*; do
|
||||
[[ "$(basename "$entry")" == "install.sh" ]] && continue
|
||||
cp -R "$entry" "$TARGET_DIR"/
|
||||
done
|
||||
shopt -u dotglob
|
||||
# Пресет-каталоги в DSH создаются с правами владельца (0o700/0o600); копия
|
||||
# наследует umask — приведём к аналогичному виду.
|
||||
chmod -R u+rwX,go-rwx "$TARGET_DIR"
|
||||
|
||||
echo "Установлено: $TARGET_DIR"
|
||||
echo "Пресет «Юрист по ПО» доступен при создании новой сессии."
|
||||
@@ -0,0 +1,2 @@
|
||||
name: Юрист по ПО
|
||||
description: Агент-консультант по правовым вопросам разработки ПО — лицензии (MIT, GPL, Apache...), авторские права, персональные данные (152-ФЗ/GDPR), договоры. Отвечает на основе встроенной базы знаний, без внешних API.
|
||||
@@ -0,0 +1,38 @@
|
||||
---
|
||||
name: software-law
|
||||
description: База знаний агента «Юрист по ПО» — правила проведения юридических консультаций по разработке ПО и справочные материалы (лицензии, авторские права, персональные данные, договоры). Загружай перед ответом на любой юридический вопрос.
|
||||
---
|
||||
|
||||
# База знаний «Юрист по ПО»
|
||||
|
||||
Это встроенная база знаний агента «Юрист по ПО» (движок: знания + правила, без внешних API). Справочные материалы лежат в `references/` этого скилла. Перед ответом на юридический вопрос прочитай соответствующий файл (или несколько) и строй ответ на его содержании.
|
||||
|
||||
## Справочные файлы
|
||||
|
||||
| Тема вопроса | Файл |
|
||||
|---|---|
|
||||
| Лицензии ПО, совместимость, open source, compliance | `references/licenses.md` |
|
||||
| Авторские права на код, служебные произведения, передача прав | `references/copyright.md` |
|
||||
| Персональные данные: 152-ФЗ, GDPR, cookies, трансграничная передача | `references/personal-data.md` |
|
||||
| Договоры разработки, лицензионные договоры, SLA, NDA, escrow | `references/contracts.md` |
|
||||
|
||||
## Как проводить консультацию
|
||||
|
||||
1. **Определи вопрос и юрисдикцию.** Если не указана (РФ / ЕС / США / иная) — уточни. Многие ответы радикально различаются по юрисдикциям.
|
||||
2. **Прочитай нужный справочный файл.** При комплексном вопросе — несколько файлов. Не отвечай «по памяти»: база знаний — источник истины.
|
||||
3. **Если вопрос касается конкретного проекта** (go.mod, зависимости, README, условия в репозитории) — сначала изучи реальные файлы инструментами файловой системы.
|
||||
4. **Структура ответа:**
|
||||
- краткий прямой ответ (1–3 предложения);
|
||||
- применимые нормы и условия (со ссылками на статьи/пункты);
|
||||
- анализ применительно к ситуации пользователя;
|
||||
- вывод и практические рекомендации (что сделать, что проверить, к кому обратиться).
|
||||
5. **Всегда добавляй дисклеймер:** консультация информационная, не является юридической услугой и не заменяет профессиональную помощь; для юридически значимых решений — к квалифицированному юристу.
|
||||
6. **Не выдумывай нормы.** Ссылайся только на то, что есть в справочных файлах или в проверенных источниках. Если нормы нет в базе — скажи, что вопрос требует уточнения у юриста.
|
||||
7. **Различай уровни уверенности.** Если норма спорная или зависит от обстоятельств — покажи оба прочтения и что влияет на вывод.
|
||||
|
||||
## Границы компетенции
|
||||
|
||||
- Агент не подписывает документы, не даёт заключений «для суда», не представляет интересы в органах.
|
||||
- Шаблоны договоров/согласий — только информационный материал, требующий адаптации юристом.
|
||||
- Законы и практика меняются: при значимом решении проверяй актуальность норм (например, web_search).
|
||||
- Налоги, уголовная ответственность, регистрация юрлиц — вне специализации; перенаправь к профильному специалисту.
|
||||
@@ -0,0 +1,65 @@
|
||||
# Договоры в разработке ПО
|
||||
|
||||
Справочник агента «Юрист по ПО»: договоры разработки, лицензионные договоры, SLA, NDA, escrow.
|
||||
|
||||
## Договор разработки ПО (подряд/услуги)
|
||||
|
||||
- **Квалификация**: разработка «под ключ» с результатом — договор подряда (гл. 37 ГК РФ) или смешанный; поддержка и сопровождение — возмездное оказание услуг (гл. 39 ГК РФ); для IT-проектов часто смешанный договор.
|
||||
- **Существенные условия**:
|
||||
- предмет: что разрабатывается (ТЗ/спецификация/приложение — обязательное приложение), объём, функциональные требования;
|
||||
- сроки: этапы, промежуточные и финальный сроки, порядок сдачи-приёмки;
|
||||
- цена и порядок оплаты (аванс/вехи/финальный платёж);
|
||||
- права на результаты (см. ниже) и на исходный код;
|
||||
- гарантии и ответственность сторон.
|
||||
- **Приёмка**: акт сдачи-приёмки, порядок тестирования, срок на замечания, последствия несоответствия (устранение за счёт исполнителя).
|
||||
- **Права на результат**: по умолчанию по договору подряда — у подрядчика (автора), заказчик получает право использования для целей договора (ст. 1296 ГК РФ — по договору подряда/заказа на создание программы: исключительное право принадлежит подрядчику, если договором не установлено иное; заказчик вправе использовать для целей договора). → **Договор должен явно передавать исключительное право или лицензию заказчику** (ст. 1234/1235 ГК РФ).
|
||||
- **Гарантии качества**: гарантийный срок, скрытые недостатки, техническая поддержка после сдачи.
|
||||
- **Ответственность**: неустойка за просрочку (ст. 330 ГК РФ), ограничение ответственности (cap), исключение косвенных убытков.
|
||||
|
||||
## Лицензионный договор (ст. 1235 ГК РФ)
|
||||
|
||||
- **Существенные условия**: предмет (произведение/ПО), **способы использования** (обязательно! иначе право не считается предоставленным — ст. 1235 п. 2).
|
||||
- **Территория**: по умолчанию — вся территория РФ; можно указать иную.
|
||||
- **Срок**: до 5 лет, если не указан; можно бессрочно/до 5 лет.
|
||||
- **Вознаграждение**: форма (паушальное/роялти/смешанное); для возмездного договора цена существенна (ст. 1235 п. 5 — при отсутствии условия о вознаграждении договор считается незаключённым).
|
||||
- **Виды** (ст. 1236): простая (неисключительная) лицензия — правообладатель может лицензировать другим; исключительная — лицензиат единственный.
|
||||
- **Сублицензия** (ст. 1238): только с письменного согласия лицензиара, если не указано иное.
|
||||
- **Открытая лицензия** (ст. 1286.1) — упрощённый порядок акцепта, см. licenses.md.
|
||||
- **End User License Agreement (EULA)**: договор присоединения (ст. 428 ГК РФ); акцепт при установке/использовании (ст. 1286 п. 3 ГК РФ — допускается заключение путём совершения конклюдентных действий).
|
||||
|
||||
## SLA (Service Level Agreement)
|
||||
|
||||
- Часть договора услуг/поддержки: **метрики** (uptime %, время ответа, время восстановления RTO), **периоды измерения**, **порядок расчёта**, **штрафы/компенсации** (service credits), исключения из SLA (форс-мажор, плановые окна, действия клиента).
|
||||
- Соотношение с гарантиями: SLA — операционные обязательства, не замена гарантий качества.
|
||||
- Типичные метрики: доступность (99.9%), MTTR, MTBF, время реакции по уровням приоритета инцидентов.
|
||||
|
||||
## NDA (соглашение о конфиденциальности)
|
||||
|
||||
- **Предмет**: какая информация конфиденциальна (перечень/критерии), исключения (публичная информация, известная ранее, полученная от третьих лиц, разработанная самостоятельно).
|
||||
- **Обязательства**: неразглашение, ограничение использования, защита (разумные меры), возврат/уничтожение информации.
|
||||
- **Срок**: период действия + срок конфиденциальности после прекращения (обычно 2–5 лет).
|
||||
- **Одностороннее/двустороннее**: зависит от того, кто раскрывает информацию.
|
||||
- В РФ: ст. 1465–1470 ГК РФ (коммерческая тайна), 98-ФЗ «О коммерческой тайне».
|
||||
|
||||
## Escrow (депонирование исходного кода)
|
||||
|
||||
- Третья сторона (эскроу-агент) хранит исходный код и документацию; заказчик получает доступ при наступлении условий (банкротство/прекращение поддержки вендора, неисполнение обязательств).
|
||||
- Применимо для критичных систем при закрытом коде вендора.
|
||||
- Условия: состав депозита, порядок обновления, условия и процедура раскрытия, ответственность эскроу-агента.
|
||||
|
||||
## Международные договоры
|
||||
|
||||
- Выбор права и суда/арбитража (важно для кросс-граничных контрактов).
|
||||
- Инкотермс — для поставок (не для ПО как услуги).
|
||||
- Экспортный контроль (в т.ч. криптография — экспортные ограничения США (EAR), ЕС) — при распространении ПО за рубеж.
|
||||
- Налоговые аспекты (роялти, постоянное представительство) — вне специализации, направить к профильному специалисту.
|
||||
|
||||
## Чек-лист договора разработки
|
||||
|
||||
1. Предмет + ТЗ/спецификация как приложение.
|
||||
2. Этапы, сроки, порядок приёмки.
|
||||
3. Права на код и результаты — явная передача заказчику.
|
||||
4. Гарантии, SLA (если поддержка), ответственность и cap.
|
||||
5. Конфиденциальность (NDA).
|
||||
6. Порядок расторжения и передача при прекращении (передача кода, данных).
|
||||
7. Право и юрисдикция.
|
||||
@@ -0,0 +1,57 @@
|
||||
# Авторские права на программное обеспечение
|
||||
|
||||
Справочник агента «Юрист по ПО»: охрана кода, служебные произведения, передача прав, ответственность.
|
||||
|
||||
## Основы
|
||||
|
||||
- **Программа для ЭВМ** охраняется авторским правом как литературное произведение (ст. 1261 ГК РФ). Охраняется код (исходный и объектный), а также подготовительные материалы.
|
||||
- **База данных** — составное произведение (ст. 1260 ГК РФ); также охраняется право изготовителя базы данных (ст. 1333–1336 ГК РФ).
|
||||
- **Авторское право возникает автоматически** с момента создания произведения — без регистрации, без депонирования (ст. 1257, 1259 ГК РФ). Регистрация программ для ЭМВ возможна в Роспатенте (ст. 1262 ГК РФ) — добровольная, даёт презумпцию авторства и удобна в спорах.
|
||||
- **Международная охрана**: Бернская конвенция об охране литературных и художественных произведений (РФ — участник) — охрана без формальностей во всех странах-участницах.
|
||||
- **США**: US Copyright Act (17 U.S.C.), регистрация в U.S. Copyright Office — условие для statutory damages; fair use (§ 107).
|
||||
|
||||
## Объекты и права
|
||||
|
||||
- **Личные неимущественные права автора** (ст. 1255, 1265 ГК РФ): право авторства, право на имя, неприкосновенность произведения, обнародование. Непередаваемы.
|
||||
- **Исключительное право** (ст. 1270 ГК РФ): использование произведения любым способом — воспроизведение, распространение, публичный показ, переработка (модификация кода!), доведение до всеобщего сведения (интернет) и др.
|
||||
- **Срок действия**: вся жизнь автора + 70 лет (ст. 1281 ГК РФ); в США — 70 лет после смерти автора (для работ по найму — 95 лет от публикации / 120 лет от создания).
|
||||
|
||||
## Служебные произведения (ст. 1295 ГК РФ)
|
||||
|
||||
- Произведение, созданное **в пределах трудовых обязанностей** работника — исключительное право принадлежит **работодателю**, если договором не предусмотрено иное.
|
||||
- Автор (работник) вправе получить вознаграждение за использование (если не установлено иное).
|
||||
- Работодатель вправе обнародовать произведение и использовать его; автор не вправе препятствовать.
|
||||
- **Важно**: если код написан работником вне трудовых обязанностей (в свободное время, без задания) — по умолчанию права у работника. Границы спорны; решается по факту (должностная инструкция, характер работы).
|
||||
|
||||
## Произведения по заказу и по госконтракту
|
||||
|
||||
- **По договору авторского заказа** (ст. 1288–1290 ГК РФ): права на произведение принадлежат автору, если договором не предусмотрена передача заказчику; договор должен прямо предусматривать отчуждение исключительного права или лицензию.
|
||||
- **По государственному/муниципальному контракту** (ст. 1298 ГК РФ): исключительное право принадлежит исполнителю, если контрактом не установлено иное (обычно — РФ/субъекту, если прямо указано).
|
||||
|
||||
## Передача и распоряжение правами
|
||||
|
||||
- **Договор об отчуждении исключительного права** (ст. 1234 ГК РФ): переход права целиком; существенные условия — предмет (произведение) и цена (если безвозмездно — прямо указать).
|
||||
- **Лицензионный договор** (ст. 1235 ГК РФ): предоставление права использования в пределах (способы, территория, срок). Существенные условия: предмет, способы использования. Срок — до 5 лет, иначе 5 лет; территория — РФ, иначе вся территория РФ. Если способ использования не указан — право не считается предоставленным.
|
||||
- **Открытая лицензия** (ст. 1286.1 ГК РФ) — см. licenses.md.
|
||||
- **Простая/исключительная лицензия** (ст. 1236 ГК РФ): неисключительная (можно лицензировать другим) vs исключительная (лицензиат — единственный).
|
||||
- Форма: лицензионный договор — письменная, кроме открытых лицензий и лицензий на ПО для ЭВМ (ст. 1286 ГК РФ — допускается заключение путём акцепта условий, например при установке). Договор отчуждения исключительного права — письменный (ст. 1234).
|
||||
|
||||
## Свободное использование и fair use
|
||||
|
||||
- **РФ** (ст. 1273–1280 ГК РФ): узкий перечень — личное использование, цитирование, использование для целей обучения/исследования (в установленных пределах) и др. Для ПО свободное использование практически не работает.
|
||||
- **США** (fair use, §107 Copyright Act): 4 фактора — цель/характер использования, характер произведения, объём использованного, влияние на рынок. Применяется в США, в РФ аналога нет.
|
||||
- **Реверс-инжиниринг**: в РФ ограниченно (ст. 1280 ГК РФ — право на декомпиляцию для взаимодействия программ, при определённых условиях); в ЕС — Directive 2009/24/EC (интероперабельность); в США — ограниченные исключения.
|
||||
|
||||
## Ответственность за нарушение (РФ)
|
||||
|
||||
- Гражданско-правовая (ст. 1252, 1301 ГК РФ): требование о прекращении нарушения, изъятие контрафакта, **компенсация** — от 10 000 до 5 000 000 ₽ либо двукратная стоимость права использования или контрафактных экземпляров.
|
||||
- Административная (ст. 7.12 КоАП РФ): штрафы + конфискация.
|
||||
- Уголовная (ст. 146 УК РФ): крупный/особо крупный размер — штраф до 1 млн ₽, обязательные/принудительные работы, лишение свободы до 6 лет.
|
||||
- Плагиат (ст. 146 УК РФ ч. 1) — присвоение авторства.
|
||||
|
||||
## Практические вопросы
|
||||
|
||||
- **Кто автор кода в команде?** Все, кто вносил творческий вклад; распределение прав — договором/соглашением.
|
||||
- **Стартапы**: закреплять права на код за компанией договорами с разработчиками (трудовой договор + положение о служебных произведениях, договоры подряда/заказа с исполнителями, соглашения об отчуждении прав от фрилансеров).
|
||||
- **Вклад в open source**: контрибьютор сохраняет права на свой вклад, лицензируя его под лицензией проекта (по умолчанию — лицензия репозитория); CLA (Contributor License Agreement) — договор, перераспределяющий/лицензирующий права в пользу проекта.
|
||||
- **AI-сгенерированный код**: правовой статус не устоялся; в ряде юрисдикций (США, ЕС) результаты генерации без творческого вклада человека могут не охраняться; РФ — доктринально спорно, практика формируется. Рекомендовать: фиксировать в договорах права на AI-результаты, проверять условия инструментов генерации.
|
||||
@@ -0,0 +1,98 @@
|
||||
# Лицензии программного обеспечения
|
||||
|
||||
Справочник агента «Юрист по ПО». Лицензия — правовой инструмент, определяющий, как можно использовать, изменять и распространять ПО. В РФ лицензии на ПО оформляются как лицензионные договоры, в т.ч. открытые лицензии (ст. 1286.1 ГК РФ).
|
||||
|
||||
## Базовые понятия
|
||||
|
||||
- **Проприетарная лицензия** — права ограничены: обычно запрет копирования, модификации, реверс-инжиниринга, субаренды; часто оплата за использование.
|
||||
- **Open source (OSI)** — ПО с лицензией, удовлетворяющей 10 критериям OSI (свободное распространение, доступность исходников, право на модификацию и производные и т.д.).
|
||||
- **Свободное ПО (FSF)** — 4 свободы: запускать для любых целей, изучать и изменять, распространять копии, распространять улучшенные версии.
|
||||
- **SPDX-идентификатор** — стандартный короткий код лицензии (например `MIT`, `Apache-2.0`, `GPL-3.0-only`), используется в метаданных зависимостей и отчётах.
|
||||
|
||||
## Пермиссивные лицензии (permissive)
|
||||
|
||||
Разрешают почти всё (использование, модификацию, закрытие исходников, включение в проприетарный продукт) при соблюдении условий атрибуции.
|
||||
|
||||
| Лицензия | SPDX | Ключевые условия | Особенности |
|
||||
|---|---|---|---|
|
||||
| MIT | `MIT` | Сохранение уведомления об авторстве и текста лицензии в копиях/производных | Самая простая и распространённая; совместима практически со всем |
|
||||
| BSD-2-Clause | `BSD-2-Clause` | Атрибуция, запрет использования имени автора для рекламы | |
|
||||
| BSD-3-Clause | `BSD-3-Clause` | Как BSD-2 + запрет использования имени автора в рекламе без разрешения | |
|
||||
| ISC | `ISC` | Атрибуция | Похожа на MIT, короче |
|
||||
| Apache-2.0 | `Apache-2.0` | Атрибуция + уведомление об изменениях (NOTICE), явная патентная лицензия | Патентная «граната»: грант патентов на вклад; совместима с GPL-3.0 |
|
||||
| Unlicense / CC0 | `Unlicense` / `CC0-1.0` | Отказ от прав (public domain по возможности) | CC0 — не для ПО в РФ напрямую (нет института public domain); Unlicense — текст-отказ |
|
||||
|
||||
**Вывод для проприетарного продукта:** пермиссивные лицензии безопасны — можно включить код в закрытый продукт, выполнив условия атрибуции (обычно копия текста лицензии + уведомление об авторстве в составе дистрибутива).
|
||||
|
||||
## Копилефт-лицензии (copyleft)
|
||||
|
||||
Требуют, чтобы производные работы распространялись под той же лицензией (с открытием исходников).
|
||||
|
||||
| Лицензия | SPDX | Обязательство | Особенности |
|
||||
|---|---|---|---|
|
||||
| GPL-2.0 | `GPL-2.0-only` | Производные работы — под GPL-2.0 с открытием исходников | Классический «сильный» копилефт; несовместима с Apache-2.0 |
|
||||
| GPL-3.0 | `GPL-3.0-only` | Как GPL-2.0 + явная патентная лицензия, защита от TiVo-зации (антиблокировка) | Совместима с Apache-2.0; несовместима с GPL-2.0 (можно под «GPL-2.0-or-later» объединять) |
|
||||
| LGPL-2.1 / LGPL-3.0 | `LGPL-2.1-only` / `LGPL-3.0-only` | Копилефт для самой библиотеки; использование через динамическую компоновку не «заражает» вызывающий код | Слабее GPL: проприетарное приложение может использовать LGPL-библиотеку динамически |
|
||||
| AGPL-3.0 | `AGPL-3.0-only` | Как GPL-3.0 + обязательство открытия исходников при предоставлении ПО через сеть (SaaS) | Критична для серверных продуктов и веб-сервисов |
|
||||
| MPL-2.0 | `MPL-2.0` | Копилефт на уровне файлов: изменённые файлы — под MPL, остальное можно под другой лицензией | «Слабый» копилефт; файловый уровень; совместима с GPL |
|
||||
| EPL-2.0 | `EPL-2.0` | Копилефт на уровне модуля; патентный грант | Используется Eclipse Foundation |
|
||||
|
||||
**Ключевые риски:**
|
||||
- Включение GPL/AGPL-кода в проприетарный продукт обычно **недопустимо** без открытия исходников продукта под GPL.
|
||||
- **AGPL** опасна для SaaS: использование AGPL-кода в серверной части веб-сервиса обязывает открыть исходники сервиса (при передаче через сеть).
|
||||
- **LGPL + статическая компоновка / копирование кода** может «заразить» продукт; динамическая компоновка (shared library) обычно безопасна.
|
||||
- **Копирование кода** (copy-paste) из copyleft-проекта = создание производной работы со всеми последствиями.
|
||||
|
||||
## Лицензии Creative Commons (для контента, не для ПО)
|
||||
|
||||
- `CC0-1.0` — отказ от прав (public domain).
|
||||
- `CC-BY-4.0` — атрибуция.
|
||||
- `CC-BY-SA-4.0` — атрибуция + share-alike (производные под той же лицензией).
|
||||
- `CC-BY-NC-*` — запрет коммерческого использования.
|
||||
- `CC-BY-ND-*` — запрет производных работ.
|
||||
- **Не рекомендуется для ПО** (нет чётких правил для кода, нет патентных положений); для кода лучше CC0 (отказ) или MIT.
|
||||
|
||||
## Совместимость лицензий (кратко)
|
||||
|
||||
| Комбинация | Совместимо? |
|
||||
|---|---|
|
||||
| MIT/BSD/ISC + любая лицензия | Да |
|
||||
| Apache-2.0 + GPL-3.0 | Да (Apache-2.0 совместима с GPL-3.0) |
|
||||
| Apache-2.0 + GPL-2.0 | Нет |
|
||||
| GPL-2.0 + GPL-3.0 | Нет (если не «or-later») |
|
||||
| GPL + LGPL | Да (LGPL-код можно включать в GPL-проект) |
|
||||
| MPL-2.0 + GPL | Да |
|
||||
| GPL + проприетарный продукт | Только если продукт открывается под GPL |
|
||||
| AGPL + проприетарный SaaS | Нет (по умолчанию) |
|
||||
|
||||
## Dual licensing (двойное лицензирование)
|
||||
|
||||
Правообладатель предлагает выбор: например, open source-лицензия (GPL/AGPL) ИЛИ коммерческая лицензия. Пользователь выбирает одну. Частая модель для библиотек (пример: MySQL, Qt, MongoDB).
|
||||
|
||||
## Проверка лицензий зависимостей (compliance)
|
||||
|
||||
- **SPDX-метаданные** в go.mod / package.json / Cargo.toml и т.д.
|
||||
- Инструменты: `go-licenses`, `license-checker` (npm), `cargo-license`, FOSSA, ScanCode Toolkit, GitHub Dependency Insights.
|
||||
- В отчёте фиксировать: компонент, версия, SPDX-идентификатор, текст лицензии, совместимость с моделью распространения продукта.
|
||||
- Атрибуция: собрать THIRD_PARTY_NOTICES / NOTICE с текстами лицензий и уведомлениями об авторстве.
|
||||
|
||||
## Открытая лицензия в РФ (ст. 1286.1 ГК РФ)
|
||||
|
||||
- Открытая лицензия — лицензионный договор, заключаемый в упрощённом порядке (акцепт условий, например началом использования).
|
||||
- Действует на всей территории мира, на срок действия исключительного права, если не указано иное.
|
||||
- Лицензиар вправе отказаться от договора в одностороннем порядке, если лицензиат предоставляет третьим лицам права на ПО (например, сублицензии) в нарушение условий.
|
||||
- Для ПО открытые лицензии (MIT, GPL и т.д.) признаются в РФ лицензионными договорами.
|
||||
|
||||
## Ответственность и риски
|
||||
|
||||
- Нарушение условий лицензии = нарушение исключительного права (ст. 1252, 1301 ГК РФ): компенсация от 10 000 до 5 000 000 ₽ или двукратная стоимость права использования / контрафактных экземпляров; также возможны изъятие контрафакта, судебные запреты.
|
||||
- Для иностранных правообладателей — Бернская конвенция обеспечивает охрану в РФ.
|
||||
- Ответственность за отсутствие лицензионной чистоты (license hygiene) ложится на распространителя продукта.
|
||||
|
||||
## Чек-лист для ответа про лицензии
|
||||
|
||||
1. Какой компонент и какая лицензия (SPDX)?
|
||||
2. Как используется: включение кода, компоновка (динамическая/статическая), сервис (SaaS)?
|
||||
3. Как распространяется продукт: закрытый, open source, под какой лицензией?
|
||||
4. Есть ли патентные положения (Apache-2.0, GPL-3.0) и патентные риски?
|
||||
5. Выполнены ли условия атрибуции и доставлен ли текст лицензии?
|
||||
@@ -0,0 +1,58 @@
|
||||
# Персональные данные в ПО: 152-ФЗ и GDPR
|
||||
|
||||
Справочник агента «Юрист по ПО»: обработка персональных данных (ПДн) в продуктах, сервисах и приложениях.
|
||||
|
||||
## Основные понятия (152-ФЗ)
|
||||
|
||||
- **Персональные данные (ПДн)** (ст. 3): любая информация, относящаяся к прямо или косвенно определённому или определяемому физическому лицу (субъекту ПДн).
|
||||
- **Оператор**: государственный орган, юрлицо или физлицо, организующее/осуществляющее обработку ПДн.
|
||||
- **Обработка**: любое действие с ПДн — сбор, запись, систематизация, накопление, хранение, уточнение, извлечение, использование, передача, обезличивание, блокирование, удаление, уничтожение.
|
||||
- **Специальные категории** (ст. 10): раса, национальность, политические взгляды, религия, здоровье, интимная жизнь. Обработка — только в исключительных случаях.
|
||||
- **Биометрические ПДн** (ст. 11): обработка — только с согласия в письменной форме (кроме гос. систем).
|
||||
|
||||
## Принципы и основания (152-ФЗ)
|
||||
|
||||
- **Принципы** (ст. 5): законность и справедливость; соответствие целям; минимизация; точность; достаточность; ограничение сроков хранения.
|
||||
- **Основания обработки** (ст. 6): согласие субъекта; исполнение договора; исполнение закона; защита жизни/здоровья; исполнение полномочий госорганов; законные интересы оператора (с учётом прав субъекта).
|
||||
- **Согласие** (ст. 9): конкретное, информированное, сознательное; может быть в любой форме, позволяющей подтвердить факт получения; отзыв — в любое время; при отзыве оператор обязан прекратить обработку и уничтожить ПДн, если нет иных оснований.
|
||||
|
||||
## Обязанности оператора (152-ФЗ)
|
||||
|
||||
- **Уведомление Роскомнадзора** о намерении обрабатывать ПДн (ст. 22) — до начала обработки, кроме случаев из ч. 2 ст. 22 (трудовые отношения, договоры с субъектом, одноразовый пропуск, без использования средств автоматизации и т.д.).
|
||||
- **Локальные акты** (ст. 18.1): политика обработки ПДн, положение/инструкции, назначение ответственного, оценка вреда, меры безопасности.
|
||||
- **Меры безопасности** (ст. 19): правовые, организационные и технические меры (шифрование, разграничение доступа, антивирус, защита каналов).
|
||||
- **Права субъекта** (ст. 14): доступ к своим ПДн, уточнение, блокирование, уничтожение, обжалование действий оператора.
|
||||
- **Трансграничная передача** (ст. 12): на территорию стран из перечня Роскомнадзора — свободно; в иные страны — при определённых условиях (согласие, договор, международные договоры); с 01.09.2025 ужесточение: уведомление/разрешение Роскомнадзора на трансграничную передачу (для стран вне перечня) с обязанностью оператора подтвердить соблюдение требований. *Норма меняется — проверяй актуальную редакцию.*
|
||||
- **Утечки** (ст. 21, ст. 23): обязанность уведомить Роскомнадзор (и, с 2023, в определённых случаях — о фактах утечек) и субъектов; оборотные штрафы за утечки (ст. 13.11 КоАП РФ с 2024–2025): для юрлиц до 3% выручки, но не менее 20 млн ₽ и не более 500 млн ₽ (за повторную утечку).
|
||||
- **Уничтожение ПДн** (ст. 21): по достижении целей, при отзыве согласия, по требованию субъекта.
|
||||
|
||||
## GDPR (ЕС)
|
||||
|
||||
- **Сфера действия** (ст. 3): обработка в ЕС; или предложение товаров/услуг субъектам в ЕС; или мониторинг поведения субъектов в ЕС. → Для продуктов, работающих с пользователями из ЕС, GDPR применим независимо от места нахождения компании.
|
||||
- **Роли**: контролёр (определяет цели/средства), обработчик (по поручению). SaaS-провайдеры обычно обработчики; требуются **DPA** (Data Processing Agreement).
|
||||
- **Правовые основания** (ст. 6): согласие, договор, правовое обязательство, жизненно важные интересы, публичный интерес, **законный интерес** (legitimate interest — требует балансового теста LIA).
|
||||
- **Согласие** (ст. 7): свободное, конкретное, информированное, однозначное; отзыв так же прост, как предоставление; для детей — согласие родителей (ст. 8).
|
||||
- **Права субъектов** (ст. 15–22): доступ, исправление, удаление (right to be forgotten), ограничение, переносимость (portability), возражение, отказ от автоматизированных решений (profiling).
|
||||
- **Обязанности**: записи обработки (ст. 30), уведомление об утечках в течение 72 часов (ст. 33), уведомление субъектов (ст. 34), **DPIA** — оценка влияния на защиту данных для высокорисковых обработок (ст. 35), **DPO** — ответственный за защиту данных для определённых операторов (ст. 37), privacy by design/by default (ст. 25).
|
||||
- **Трансграничная передача** (ст. 44–49): решения об адекватности (adequacy), SCC (стандартные договорные оговорки), BCR, исключения.
|
||||
- **Штрафы** (ст. 83): до 20 млн евро или 4% мирового оборота (большее из двух) за тяжкие нарушения; до 10 млн евро / 2% — за остальные.
|
||||
|
||||
## Применение к продуктам и сервисам
|
||||
|
||||
- **Cookies и трекинг**: согласие на необязательные cookies (ePrivacy / Директива 2009/136; в РФ — требования к cookie-баннерам формируются практикой Роскомнадзора и законом о «cookie» в стадии регулирования — уточнять актуальное).
|
||||
- **Аналитика** (Google Analytics, Яндекс.Метрика): обработка ПДн; нужны основания и (для ЕС) проверка легальности передачи (решения судов ЕС по Schrems II — SCC + доп. меры).
|
||||
- **Мобильные приложения**: политика конфиденциальности, разрешения, согласия, возрастные ограничения (COPPA в США, GDPR ст. 8, 152-ФЗ + закон о защите детей от вредной информации).
|
||||
- **SaaS/B2B**: DPA с клиентами, журналирование, локализация данных, порядок исполнения запросов субъектов.
|
||||
- **Локализация баз данных (РФ)**: операторы обязаны записывать/систематизировать ПДн граждан РФ в базах на территории РФ (ст. 18 ч. 5 152-ФЗ) — при сборе через интернет.
|
||||
|
||||
## Чек-лист для продукта
|
||||
|
||||
1. Какие ПДн собираются и зачем (цели)?
|
||||
2. Есть ли правовое основание (согласие/договор/законный интерес)?
|
||||
3. Составлена ли политика конфиденциальности и получено ли согласие (форма)?
|
||||
4. Уведомлён ли Роскомнадзор (если требуется)?
|
||||
5. Соблюдена ли локализация баз данных (РФ)?
|
||||
6. Что происходит при запросе субъекта (доступ/удаление)?
|
||||
7. Урегулирована ли передача данных за границу (ЕС — SCC/adequacy; РФ — перечень РКН)?
|
||||
8. Есть ли DPA с подрядчиками/хостингом?
|
||||
9. Как реагируем на утечки (72 часа GDPR, уведомление РКН)?
|
||||
+203
-5
@@ -115,6 +115,47 @@ service EveningDetectiveServer {
|
||||
};
|
||||
}
|
||||
|
||||
rpc DeleteAccount(DeleteAccountReq) returns (DeleteAccountRsp) {
|
||||
option (google.api.http) = {
|
||||
delete: "/api/auth/delete-account"
|
||||
};
|
||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||
tags : "Регистрация и вход";
|
||||
summary : "Удалить учётную запись и персональные данные";
|
||||
description: "Удаляет учётную запись и персональные данные текущего пользователя (ст. 21 152-ФЗ). Требует заголовок X-Password с паролем учётной записи (подтверждение владельца; пароль не передаётся в URL).";
|
||||
};
|
||||
}
|
||||
|
||||
rpc GetTerms(GetTermsReq) returns (GetTermsRsp) {
|
||||
option (google.api.http) = {
|
||||
get: "/api/terms"
|
||||
};
|
||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||
tags : "Документы";
|
||||
summary: "Получить текст Пользовательского соглашения (оферты)";
|
||||
};
|
||||
}
|
||||
|
||||
rpc GetPrivacy(GetPrivacyReq) returns (GetPrivacyRsp) {
|
||||
option (google.api.http) = {
|
||||
get: "/api/privacy"
|
||||
};
|
||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||
tags : "Документы";
|
||||
summary: "Получить текст Политики конфиденциальности";
|
||||
};
|
||||
}
|
||||
|
||||
rpc GetConsent(GetConsentReq) returns (GetConsentRsp) {
|
||||
option (google.api.http) = {
|
||||
get: "/api/consent"
|
||||
};
|
||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||
tags : "Документы";
|
||||
summary: "Получить текст Согласия на обработку персональных данных";
|
||||
};
|
||||
}
|
||||
|
||||
rpc GetUsers(GetUsersReq) returns (GetUsersRsp) {
|
||||
option (google.api.http) = {
|
||||
get: "/api/users"
|
||||
@@ -366,6 +407,61 @@ service EveningDetectiveServer {
|
||||
};
|
||||
}
|
||||
|
||||
rpc StartGame(StartGameReq) returns (StartGameRsp) {
|
||||
option (google.api.http) = {
|
||||
put : "/api/games/{id}/start"
|
||||
body: "*"
|
||||
};
|
||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||
tags : "Игры";
|
||||
summary: "Начать игру";
|
||||
};
|
||||
}
|
||||
|
||||
rpc PauseGame(PauseGameReq) returns (PauseGameRsp) {
|
||||
option (google.api.http) = {
|
||||
put : "/api/games/{id}/pause"
|
||||
body: "*"
|
||||
};
|
||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||
tags : "Игры";
|
||||
summary: "Остановить игру";
|
||||
};
|
||||
}
|
||||
|
||||
rpc PlayGame(PlayGameReq) returns (PlayGameRsp) {
|
||||
option (google.api.http) = {
|
||||
put : "/api/games/{id}/play"
|
||||
body: "*"
|
||||
};
|
||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||
tags : "Игры";
|
||||
summary: "Продолжить игру";
|
||||
};
|
||||
}
|
||||
|
||||
rpc FinishGame(FinishGameReq) returns (FinishGameRsp) {
|
||||
option (google.api.http) = {
|
||||
put : "/api/games/{id}/finish"
|
||||
body: "*"
|
||||
};
|
||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||
tags : "Игры";
|
||||
summary: "Закончить игру";
|
||||
};
|
||||
}
|
||||
|
||||
rpc ResetGame(ResetGameReq) returns (ResetGameRsp) {
|
||||
option (google.api.http) = {
|
||||
put : "/api/games/{id}/reset"
|
||||
body: "*"
|
||||
};
|
||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||
tags : "Игры";
|
||||
summary: "Сбросить игру";
|
||||
};
|
||||
}
|
||||
|
||||
rpc DeleteGame(DeleteGameReq) returns (DeleteGameRsp) {
|
||||
option (google.api.http) = {
|
||||
delete: "/api/games/{id}"
|
||||
@@ -419,9 +515,9 @@ service EveningDetectiveServer {
|
||||
};
|
||||
}
|
||||
|
||||
rpc GetTeamActions(GetTeamActionsReq) returns (GetTeamActionsRsp) {
|
||||
rpc GetTeamStory(GetTeamStoryReq) returns (GetTeamStoryRsp) {
|
||||
option (google.api.http) = {
|
||||
get: "/api/teams/{id}/actions"
|
||||
get: "/api/teams/{id}/story"
|
||||
};
|
||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||
security: {
|
||||
@@ -445,6 +541,16 @@ service EveningDetectiveServer {
|
||||
summary: "Сделать ход";
|
||||
};
|
||||
}
|
||||
|
||||
rpc DeleteLastTeamAction(DeleteLastTeamActionReq) returns (DeleteLastTeamActionRsp) {
|
||||
option (google.api.http) = {
|
||||
delete: "/api/teams/{id}/last-actions"
|
||||
};
|
||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||
tags : "Ходы";
|
||||
summary: "Удалить последний ход";
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
message PingReq {}
|
||||
@@ -462,6 +568,8 @@ message EchoRsp {
|
||||
message SignupReq {
|
||||
string username = 1;
|
||||
string email = 2;
|
||||
bool accept_terms = 3;
|
||||
bool accept_privacy = 4;
|
||||
}
|
||||
|
||||
message SignupRsp {
|
||||
@@ -497,6 +605,39 @@ message RefreshRsp {
|
||||
string refreshToken = 3;
|
||||
}
|
||||
|
||||
// Пароль подтверждает, что удаляет аккаунт его владелец, а не обладатель
|
||||
// украденного access-токена (ст. 21 152-ФЗ). Передаётся HTTP-заголовком
|
||||
// X-Password (пробрасывается матчером в cmd/evening_detective_server/main.go),
|
||||
// чтобы креденшел не попадал в URL/query-параметры и логи.
|
||||
message DeleteAccountReq {}
|
||||
|
||||
message DeleteAccountRsp {
|
||||
string error = 1;
|
||||
}
|
||||
|
||||
// Документы раздаются по постоянным URL, на которые ссылается сервис при
|
||||
// фиксации акцептов соглашений (user_agreements) и форма регистрации.
|
||||
message GetTermsReq {}
|
||||
|
||||
message GetTermsRsp {
|
||||
string error = 1;
|
||||
string text = 2;
|
||||
}
|
||||
|
||||
message GetPrivacyReq {}
|
||||
|
||||
message GetPrivacyRsp {
|
||||
string error = 1;
|
||||
string text = 2;
|
||||
}
|
||||
|
||||
message GetConsentReq {}
|
||||
|
||||
message GetConsentRsp {
|
||||
string error = 1;
|
||||
string text = 2;
|
||||
}
|
||||
|
||||
message GetUsersReq {}
|
||||
|
||||
message GetUsersRsp {
|
||||
@@ -733,11 +874,17 @@ message Game {
|
||||
string status = 5;
|
||||
Scenario scenario = 6;
|
||||
repeated Team teams = 7;
|
||||
optional google.protobuf.Timestamp started_at = 8;
|
||||
optional google.protobuf.Timestamp ended_at = 9;
|
||||
}
|
||||
|
||||
message Team {
|
||||
int32 id = 1;
|
||||
string name = 2;
|
||||
string status = 3;
|
||||
string password = 4;
|
||||
int32 actions_count = 5;
|
||||
repeated Application applications = 6;
|
||||
}
|
||||
|
||||
message GetGameReq {
|
||||
@@ -761,6 +908,46 @@ message UpdateGameRsp {
|
||||
string error = 1;
|
||||
}
|
||||
|
||||
message StartGameReq {
|
||||
int32 id = 1;
|
||||
}
|
||||
|
||||
message StartGameRsp {
|
||||
string error = 1;
|
||||
}
|
||||
|
||||
message PauseGameReq {
|
||||
int32 id = 1;
|
||||
}
|
||||
|
||||
message PauseGameRsp {
|
||||
string error = 1;
|
||||
}
|
||||
|
||||
message PlayGameReq {
|
||||
int32 id = 1;
|
||||
}
|
||||
|
||||
message PlayGameRsp {
|
||||
string error = 1;
|
||||
}
|
||||
|
||||
message FinishGameReq {
|
||||
int32 id = 1;
|
||||
}
|
||||
|
||||
message FinishGameRsp {
|
||||
string error = 1;
|
||||
}
|
||||
|
||||
message ResetGameReq {
|
||||
int32 id = 1;
|
||||
}
|
||||
|
||||
message ResetGameRsp {
|
||||
string error = 1;
|
||||
}
|
||||
|
||||
message DeleteGameReq {
|
||||
int32 id = 1;
|
||||
}
|
||||
@@ -776,6 +963,8 @@ message AddTeamReq {
|
||||
|
||||
message AddTeamRsp {
|
||||
string error = 1;
|
||||
int32 id = 2;
|
||||
string password = 3;
|
||||
}
|
||||
|
||||
message UpdateTeamReq {
|
||||
@@ -804,14 +993,15 @@ message GiveTeamApplicationsRsp {
|
||||
string error = 1;
|
||||
}
|
||||
|
||||
message GetTeamActionsReq {
|
||||
message GetTeamStoryReq {
|
||||
int64 id = 1;
|
||||
string password = 2;
|
||||
}
|
||||
|
||||
message GetTeamActionsRsp {
|
||||
message GetTeamStoryRsp {
|
||||
string error = 1;
|
||||
repeated Place places = 2;
|
||||
Story story = 2;
|
||||
Game game = 3;
|
||||
}
|
||||
|
||||
message AddTeamActionReq {
|
||||
@@ -823,3 +1013,11 @@ message AddTeamActionReq {
|
||||
message AddTeamActionRsp {
|
||||
string error = 1;
|
||||
}
|
||||
|
||||
message DeleteLastTeamActionReq {
|
||||
int64 id = 1;
|
||||
}
|
||||
|
||||
message DeleteLastTeamActionRsp {
|
||||
string error = 1;
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -4,10 +4,14 @@ import (
|
||||
"context"
|
||||
_ "embed"
|
||||
"evening_detective_server/internal/app"
|
||||
"evening_detective_server/internal/modules/cleaner"
|
||||
"evening_detective_server/internal/modules/email_sender"
|
||||
"evening_detective_server/internal/modules/file_storage"
|
||||
"evening_detective_server/internal/modules/password_generator"
|
||||
"evening_detective_server/internal/modules/processor_jwt"
|
||||
"evening_detective_server/internal/modules/storytelling"
|
||||
"evening_detective_server/internal/repos/actions_repo"
|
||||
"evening_detective_server/internal/repos/applications_repo"
|
||||
"evening_detective_server/internal/repos/games_repo"
|
||||
"evening_detective_server/internal/repos/refresh_tokens_repo"
|
||||
"evening_detective_server/internal/repos/scenarios_repo"
|
||||
@@ -23,6 +27,7 @@ import (
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
@@ -75,17 +80,28 @@ func main() {
|
||||
}
|
||||
fileService := file_service.NewFileService(fileStorage)
|
||||
scenariosRepo := scenarios_repo.NewScenariosRepo(dbpool)
|
||||
cleaner := cleaner.NewCleaner()
|
||||
scenarioService := scenarios_service.NewScenarioService(
|
||||
scenariosRepo,
|
||||
cleaner,
|
||||
os.Getenv("FILE_PREFIX_DOMAIN"),
|
||||
)
|
||||
gameRepo := games_repo.NewGamesRepo(dbpool)
|
||||
teamRepo := teams_repo.NewTeamsRepo(dbpool)
|
||||
storyteller := storytelling.NewStory(cleaner)
|
||||
actionsRepo := actions_repo.NewActionsRepo(dbpool)
|
||||
applicationsRepo := applications_repo.NewApplicationsRepo(dbpool)
|
||||
gameService := game_service.NewGameService(
|
||||
gameRepo,
|
||||
teamRepo,
|
||||
scenariosRepo,
|
||||
scenarioService,
|
||||
os.Getenv("FILE_PREFIX_DOMAIN"),
|
||||
passwordGenerator,
|
||||
storyteller,
|
||||
actionsRepo,
|
||||
applicationsRepo,
|
||||
cleaner,
|
||||
)
|
||||
|
||||
// Create a listener on TCP port
|
||||
@@ -108,8 +124,11 @@ func main() {
|
||||
"/crabs.evening_detective_server.EveningDetectiveServer/DownloadFile": true,
|
||||
"/crabs.evening_detective_server.EveningDetectiveServer/GetScenario": true,
|
||||
"/crabs.evening_detective_server.EveningDetectiveServer/GetScenariosCatalog": true,
|
||||
"/crabs.evening_detective_server.EveningDetectiveServer/GetTeamActions": true,
|
||||
"/crabs.evening_detective_server.EveningDetectiveServer/GetTeamStory": true,
|
||||
"/crabs.evening_detective_server.EveningDetectiveServer/AddTeamAction": true,
|
||||
"/crabs.evening_detective_server.EveningDetectiveServer/GetTerms": true,
|
||||
"/crabs.evening_detective_server.EveningDetectiveServer/GetPrivacy": true,
|
||||
"/crabs.evening_detective_server.EveningDetectiveServer/GetConsent": true,
|
||||
},
|
||||
processorJWT,
|
||||
),
|
||||
@@ -143,7 +162,23 @@ func main() {
|
||||
log.Fatalln("Failed to dial server:", err)
|
||||
}
|
||||
|
||||
gwmux := runtime.NewServeMux()
|
||||
// X-Real-IP пробрасывается от доверенного обратного прокси (если фронт-
|
||||
// контур развёрнут перед gateway, см. docs/COMPLIANCE.md п. 6.6) и
|
||||
// используется в clientInfoFromContext для фиксации IP клиента при
|
||||
// акцепте соглашений (ст. 9 152-ФЗ). X-Password — подтверждение паролем
|
||||
// при удалении аккаунта (не в query, чтобы не светить креденшел в URL
|
||||
// и логах).
|
||||
gwmux := runtime.NewServeMux(
|
||||
runtime.WithIncomingHeaderMatcher(func(key string) (string, bool) {
|
||||
switch {
|
||||
case strings.EqualFold(key, "X-Real-IP"):
|
||||
return "x-real-ip", true
|
||||
case strings.EqualFold(key, "X-Password"):
|
||||
return "x-password", true
|
||||
}
|
||||
return runtime.DefaultHeaderMatcher(key)
|
||||
}),
|
||||
)
|
||||
// Register Greeter
|
||||
err = proto.RegisterEveningDetectiveServerHandler(context.Background(), gwmux, conn)
|
||||
if err != nil {
|
||||
|
||||
@@ -16,6 +16,30 @@
|
||||
"application/json"
|
||||
],
|
||||
"paths": {
|
||||
"/api/auth/delete-account": {
|
||||
"delete": {
|
||||
"summary": "Удалить учётную запись и персональные данные",
|
||||
"description": "Удаляет учётную запись и персональные данные текущего пользователя (ст. 21 152-ФЗ). Требует заголовок X-Password с паролем учётной записи (подтверждение владельца; пароль не передаётся в URL).",
|
||||
"operationId": "EveningDetectiveServer_DeleteAccount",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/evening_detective_serverDeleteAccountRsp"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "An unexpected error response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/rpcStatus"
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"Регистрация и вход"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/auth/login": {
|
||||
"post": {
|
||||
"summary": "Войти и получить токена доступа",
|
||||
@@ -191,6 +215,29 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/consent": {
|
||||
"get": {
|
||||
"summary": "Получить текст Согласия на обработку персональных данных",
|
||||
"operationId": "EveningDetectiveServer_GetConsent",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/evening_detective_serverGetConsentRsp"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "An unexpected error response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/rpcStatus"
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"Документы"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/files/upload": {
|
||||
"post": {
|
||||
"summary": "Загрузить файл",
|
||||
@@ -409,6 +456,206 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/games/{id}/finish": {
|
||||
"put": {
|
||||
"summary": "Закончить игру",
|
||||
"operationId": "EveningDetectiveServer_FinishGame",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/evening_detective_serverFinishGameRsp"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "An unexpected error response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/rpcStatus"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
{
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/EveningDetectiveServerFinishGameBody"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"Игры"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/games/{id}/pause": {
|
||||
"put": {
|
||||
"summary": "Остановить игру",
|
||||
"operationId": "EveningDetectiveServer_PauseGame",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/evening_detective_serverPauseGameRsp"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "An unexpected error response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/rpcStatus"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
{
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/EveningDetectiveServerPauseGameBody"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"Игры"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/games/{id}/play": {
|
||||
"put": {
|
||||
"summary": "Продолжить игру",
|
||||
"operationId": "EveningDetectiveServer_PlayGame",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/evening_detective_serverPlayGameRsp"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "An unexpected error response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/rpcStatus"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
{
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/EveningDetectiveServerPlayGameBody"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"Игры"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/games/{id}/reset": {
|
||||
"put": {
|
||||
"summary": "Сбросить игру",
|
||||
"operationId": "EveningDetectiveServer_ResetGame",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/evening_detective_serverResetGameRsp"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "An unexpected error response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/rpcStatus"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
{
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/EveningDetectiveServerResetGameBody"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"Игры"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/games/{id}/start": {
|
||||
"put": {
|
||||
"summary": "Начать игру",
|
||||
"operationId": "EveningDetectiveServer_StartGame",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/evening_detective_serverStartGameRsp"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "An unexpected error response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/rpcStatus"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
{
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/EveningDetectiveServerStartGameBody"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"Игры"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/my-scenarios": {
|
||||
"get": {
|
||||
"summary": "Получить свои сценарии",
|
||||
@@ -432,6 +679,29 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/privacy": {
|
||||
"get": {
|
||||
"summary": "Получить текст Политики конфиденциальности",
|
||||
"operationId": "EveningDetectiveServer_GetPrivacy",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/evening_detective_serverGetPrivacyRsp"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "An unexpected error response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/rpcStatus"
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"Документы"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/scenario": {
|
||||
"post": {
|
||||
"summary": "Создать сценарий",
|
||||
@@ -903,47 +1173,6 @@
|
||||
}
|
||||
},
|
||||
"/api/teams/{id}/actions": {
|
||||
"get": {
|
||||
"summary": "Получить ходы",
|
||||
"operationId": "EveningDetectiveServer_GetTeamActions",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/evening_detective_serverGetTeamActionsRsp"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "An unexpected error response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/rpcStatus"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"type": "string",
|
||||
"format": "int64"
|
||||
},
|
||||
{
|
||||
"name": "password",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"Ходы"
|
||||
],
|
||||
"security": [
|
||||
{
|
||||
"": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"post": {
|
||||
"summary": "Сделать ход",
|
||||
"operationId": "EveningDetectiveServer_AddTeamAction",
|
||||
@@ -1028,6 +1257,104 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/teams/{id}/last-actions": {
|
||||
"delete": {
|
||||
"summary": "Удалить последний ход",
|
||||
"operationId": "EveningDetectiveServer_DeleteLastTeamAction",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/evening_detective_serverDeleteLastTeamActionRsp"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "An unexpected error response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/rpcStatus"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"type": "string",
|
||||
"format": "int64"
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"Ходы"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/teams/{id}/story": {
|
||||
"get": {
|
||||
"summary": "Получить ходы",
|
||||
"operationId": "EveningDetectiveServer_GetTeamStory",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/evening_detective_serverGetTeamStoryRsp"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "An unexpected error response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/rpcStatus"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"type": "string",
|
||||
"format": "int64"
|
||||
},
|
||||
{
|
||||
"name": "password",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"Ходы"
|
||||
],
|
||||
"security": [
|
||||
{
|
||||
"": []
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/terms": {
|
||||
"get": {
|
||||
"summary": "Получить текст Пользовательского соглашения (оферты)",
|
||||
"operationId": "EveningDetectiveServer_GetTerms",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/evening_detective_serverGetTermsRsp"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "An unexpected error response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/rpcStatus"
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"Документы"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/test/echo": {
|
||||
"post": {
|
||||
"summary": "Проверить обработку данных",
|
||||
@@ -1313,6 +1640,9 @@
|
||||
"EveningDetectiveServerDraftScenarioBody": {
|
||||
"type": "object"
|
||||
},
|
||||
"EveningDetectiveServerFinishGameBody": {
|
||||
"type": "object"
|
||||
},
|
||||
"EveningDetectiveServerGiveTeamApplicationsBody": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -1321,9 +1651,21 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"EveningDetectiveServerPauseGameBody": {
|
||||
"type": "object"
|
||||
},
|
||||
"EveningDetectiveServerPlayGameBody": {
|
||||
"type": "object"
|
||||
},
|
||||
"EveningDetectiveServerPublicScenarioBody": {
|
||||
"type": "object"
|
||||
},
|
||||
"EveningDetectiveServerResetGameBody": {
|
||||
"type": "object"
|
||||
},
|
||||
"EveningDetectiveServerStartGameBody": {
|
||||
"type": "object"
|
||||
},
|
||||
"EveningDetectiveServerUpdateGameBody": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -1480,6 +1822,13 @@
|
||||
"properties": {
|
||||
"error": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"password": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1502,6 +1851,14 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"evening_detective_serverDeleteAccountRsp": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"error": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"evening_detective_serverDeleteGameRsp": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -1510,6 +1867,14 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"evening_detective_serverDeleteLastTeamActionRsp": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"error": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"evening_detective_serverDeleteScenarioPlaceRsp": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -1576,6 +1941,14 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"evening_detective_serverFinishGameRsp": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"error": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"evening_detective_serverGame": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -1605,6 +1978,25 @@
|
||||
"type": "object",
|
||||
"$ref": "#/definitions/evening_detective_serverTeam"
|
||||
}
|
||||
},
|
||||
"startedAt": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"endedAt": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
}
|
||||
}
|
||||
},
|
||||
"evening_detective_serverGetConsentRsp": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"error": {
|
||||
"type": "string"
|
||||
},
|
||||
"text": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1674,6 +2066,17 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"evening_detective_serverGetPrivacyRsp": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"error": {
|
||||
"type": "string"
|
||||
},
|
||||
"text": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"evening_detective_serverGetScenarioRsp": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -1700,19 +2103,29 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"evening_detective_serverGetTeamActionsRsp": {
|
||||
"evening_detective_serverGetTeamStoryRsp": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"error": {
|
||||
"type": "string"
|
||||
},
|
||||
"places": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"$ref": "#/definitions/evening_detective_serverPlace"
|
||||
"story": {
|
||||
"$ref": "#/definitions/evening_detective_serverStory"
|
||||
},
|
||||
"game": {
|
||||
"$ref": "#/definitions/evening_detective_serverGame"
|
||||
}
|
||||
}
|
||||
},
|
||||
"evening_detective_serverGetTermsRsp": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"error": {
|
||||
"type": "string"
|
||||
},
|
||||
"text": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"evening_detective_serverGetUserByIdRsp": {
|
||||
@@ -1782,6 +2195,14 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"evening_detective_serverPauseGameRsp": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"error": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"evening_detective_serverPingRsp": {
|
||||
"type": "object"
|
||||
},
|
||||
@@ -1826,6 +2247,14 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"evening_detective_serverPlayGameRsp": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"error": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"evening_detective_serverPublicScenarioRsp": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -1872,6 +2301,14 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"evening_detective_serverResetGameRsp": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"error": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"evening_detective_serverScenario": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -1919,6 +2356,12 @@
|
||||
},
|
||||
"email": {
|
||||
"type": "string"
|
||||
},
|
||||
"acceptTerms": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"acceptPrivacy": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1930,6 +2373,14 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"evening_detective_serverStartGameRsp": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"error": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"evening_detective_serverStory": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -1951,6 +2402,23 @@
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
"password": {
|
||||
"type": "string"
|
||||
},
|
||||
"actionsCount": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"applications": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"$ref": "#/definitions/evening_detective_serverApplication"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
# ЧЕК-ЛИСТ COMPLIANCE: ПРИВЕДЕНИЕ СЕРВИСА В СООТВЕТСТВИЕ С ДОКУМЕНТАМИ
|
||||
|
||||
Сервис «Вечерний детектив» · Обновлено: 18.08.2026
|
||||
|
||||
Сопутствующие документы: `docs/TERMS.md` (Пользовательское соглашение), `docs/PRIVACY.md` (Политика конфиденциальности), `docs/CONSENT.md` (Согласие на обработку ПДн).
|
||||
|
||||
Легенда: ✅ — реализовано, 🟡 — частично / требует решения владельца, ⬜ — осталось сделать.
|
||||
|
||||
---
|
||||
|
||||
## 1. Юридические документы
|
||||
|
||||
| № | Задача | Статус | Примечание |
|
||||
|---|---|---|---|
|
||||
| 1.1 | Пользовательское соглашение (оферта) | ✅ | `docs/TERMS.md` — заполнить плейсхолдеры, утвердить у юриста |
|
||||
| 1.2 | Политика конфиденциальности (152-ФЗ) | ✅ | `docs/PRIVACY.md` — заполнить плейсхолдеры, утвердить у юриста |
|
||||
| 1.3 | Текст согласия на обработку ПДн | ✅ | `docs/CONSENT.md` — встроить в форму регистрации |
|
||||
| 1.4 | Публикация документов на сайте по постоянным URL | ✅ | RPC `GetTerms`/`GetPrivacy`/`GetConsent` (`api/main.proto`, пути `/api/terms`, `/api/privacy`, `/api/consent`); текст встраивается из `docs/embed.go`; подставить реальные домены в тексты документов |
|
||||
|
||||
## 2. Акцепт соглашений при регистрации (ст. 428 ГК РФ, ст. 9 152-ФЗ)
|
||||
|
||||
| № | Задача | Статус | Где |
|
||||
|---|---|---|---|
|
||||
| 2.1 | Поля `accept_terms` / `accept_privacy` в запросе регистрации | ✅ | `api/main.proto` (`SignupReq`), перегенерирован `proto/` |
|
||||
| 2.2 | Серверная проверка: регистрация без акцепта отклоняется (`ErrTermsNotAccepted`) | ✅ | `internal/services/users_service/service.go` → `AddUser`; `internal/app/server.go` → `Signup` |
|
||||
| 2.3 | Таблица `user_agreements` (user_id, тип, версия, дата, IP, user-agent) | ✅ | `migrations/20260818231943_create_user_agreements_table.sql` |
|
||||
| 2.4 | Фиксация акцепта при регистрации (terms + privacy), атомарно с созданием пользователя | ✅ | `users_repo.AddUserWithAgreements` (одна транзакция: пользователь не существует без записей о согласиях) |
|
||||
| 2.5 | Версии документов как константы (`termsVersion`, `privacyVersion`) | ✅ | `users_service/service.go` — при новой редакции документов поднять версию |
|
||||
| 2.6 | Чекбоксы в форме регистрации (не предустановленные!) + блокировка отправки | ⬜ | Фронтенд (репозиторий фронта) |
|
||||
| 2.7 | Возраст 18+ (текст + проверка) | 🟡 | В документах и коде — запрет; проверить, что форма запрашивает подтверждение возраста |
|
||||
| 2.8 | Фиксация IP и user-agent клиента при акцепте (ст. 9 ч. 3 152-ФЗ) | ✅ | `clientInfoFromContext`: `X-Real-IP` от доверенного прокси (матчер в `cmd/evening_detective_server/main.go`), иначе последний элемент `X-Forwarded-For`; UA — `grpcgateway-user-agent` |
|
||||
|
||||
## 3. Удаление аккаунта и данных (ст. 14, 21 152-ФЗ)
|
||||
|
||||
| № | Задача | Статус | Где |
|
||||
|---|---|---|---|
|
||||
| 3.1 | Эндпоинт `DELETE /api/auth/delete-account` (авторизован JWT) | ✅ | `api/main.proto` (`DeleteAccount`), `internal/app/server.go` |
|
||||
| 3.2 | Удаление аккаунта: ПДн удаляются (refresh-токены, учётная запись); записи об акцептах СОХРАНЯЮТСЯ обезличенными (факт/версия/время; `user_id` отвязывается через FK `ON DELETE SET NULL`, `ip`/`user_agent` обнуляются — бремя доказывания согласия на операторе, ст. 9 ч. 4 152-ФЗ) | ✅ | `internal/repos/users_repo/repo.go` → `DeleteUser` (транзакция); миграции `20260818231943_create_user_agreements_table.sql`, `20260818232725_keep_content_on_user_delete.sql` |
|
||||
| 3.2а | Подтверждение паролем при удалении аккаунта (защита от удаления чужим обладателем украденного access-токена) | ✅ | HTTP-заголовок `X-Password` (матчер в `cmd/evening_detective_server/main.go`) → `users_service.DeleteAccount` (bcrypt-сверка); пароль не попадает в URL/query |
|
||||
| 3.3 | Судьба контента при удалении аккаунта | ✅ | Выбран вариант «отчуждение исключительного права на опубликованные Сценарии»: контент сохраняется, автор не вправе требовать удаления (п. 7.2, 7.6, 7.8 TERMS; ст. 1234, 1269 ГК РФ) |
|
||||
| 3.4 | NULL-safe чтение сценариев с обезличенным автором | ✅ | `scenarios_repo` (scan через указатели), `mapUser` возвращает nil → `author: null` в API (клиент показывает «Автор удалён») |
|
||||
| 3.5 | Удаление файлов из S3 при удалении аккаунта | ⬜ | Сейчас удаляются только записи в БД; файлы пользователя в S3 (`file_service`) остаются — добавить вызов `fileStorage.Delete` для файлов автора |
|
||||
| 3.6 | UI-кнопка «Удалить аккаунт» + подтверждение | ⬜ | Фронтенд |
|
||||
| 3.7 | Отзыв согласия без удаления аккаунта (ст. 9 ч. 2, 152-ФЗ) | ⬜ | Опционально: отдельный эндпоинт «отозвать согласие» → прекращение обработки, блокировка аккаунта |
|
||||
| 3.8 | Инвалидация выданных access-токенов после удаления аккаунта | 🟡 | JWT без revoke-листа: refresh-токен удаляется сразу, access-токен действует до истечения TTL (3 ч, `processor_jwt`); запросы с устаревшим токеном после удаления получат ошибку FK/«Пользователь не найден». Защита: короткий TTL; при необходимости — версия токена/чёрный список |
|
||||
|
||||
**Примечание по акцептам (п. 3.2):** решение владельца — хранить записи об акцептах обезличенными после удаления аккаунта (факт/версия/время без `user_id`/`ip`/`user_agent`): ст. 9 ч. 4 152-ФЗ возлагает на оператора бремя доказывания получения согласия. Альтернатива, если позже решат уничтожать и их (ст. 21 152-ФЗ): вернуть FK `ON DELETE CASCADE` в миграции `20260818231943` и `DELETE FROM user_agreements WHERE user_id = $1` в `DeleteUser`.
|
||||
|
||||
## 3а. Отчуждение исключительных прав на сценарии (ст. 1234, 1269 ГК РФ)
|
||||
|
||||
| № | Задача | Статус | Где |
|
||||
|---|---|---|---|
|
||||
| 3а.1 | Пункт TERMS об отчуждении исключительного права при публикации (безвозмездно, в полном объёме) | ✅ | `docs/TERMS.md` п. 7.2–7.4 |
|
||||
| 3а.2 | Запрет автору удалять / снимать с публикации опубликованный сценарий (`ErrScenarioPublished`); ПРАВКА КОНТЕНТА опубликованного сценария РАЗРЕШЕНА владельцу (решение владельца продукта) | ✅ | `internal/services/scenarios_service/service.go`; админ — исключение; guard в SQL от гонки «чтение → запись» для status/delete |
|
||||
| 3а.3 | Проверка владельца сценария при всех изменениях (public/draft/delete/update/места) (`ErrScenarioNotOwner`) | ✅ | `internal/services/scenarios_service/service.go` → `getScenarioForChange` |
|
||||
| 3а.4 | Право на отзыв ограничено моментом обнародования (ст. 1269 ГК РФ) | ✅ | `docs/TERMS.md` п. 7.6 |
|
||||
| 3а.5 | Отдельный чекбокс «передаю исключительные права» при первой публикации + серверная проверка | ⬜ | Фронтенд + бэкенд (сейчас публикация доступна владельцу без доп. подтверждения; добавить `confirm_transfer` в `PublicScenarioReq` при необходимости) |
|
||||
| 3а.6 | Проверка владельца при редактировании сценария (UpdateScenario, AddScenarioPlace и др.) | ✅ | `getScenarioForChange` в `internal/services/scenarios_service/service.go`; правка опубликованного разрешена (решение владельца, см. 3а.2) |
|
||||
| 3а.7 | Оценка рисков ЗоЗПП (ст. 16) для безвозмездного отчуждения в договоре присоединения | 🟡 | См. «Риски» ниже; при запуске для широкой аудитории — консультация юриста и судебная практика |
|
||||
|
||||
**Риски отчуждения прав в договоре присоединения:**
|
||||
|
||||
- **ЗоЗПП, ст. 16**: безвозмездное отчуждение исключительного права на пользовательский контент в оферте для потребителя может быть оспорено как условие, ущемляющее права потребителя. Снижение риска: отдельное явное подтверждение при публикации, возможность отозвать Сценарий до публикации (черновики не отчуждаются), прозрачное выделение условия.
|
||||
- **Личные неимущественные права** (ст. 1265 ГК РФ) не отчуждаются: автор сохраняет право авторства и может требовать его признания — отчуждается только имущественное (исключительное) право.
|
||||
- **Право на отзыв** (ст. 1269 ГК РФ) действует только до обнародования — после публикации требования автора об удалении не подлежат удовлетворению (кроме случаев, предусмотренных законом, напр. признание сценария нарушающим права третьих лиц).
|
||||
- **Коммерческие организации**: п. 3.1 ст. 1234 ГК РФ запрещает безвозмездное отчуждение исключительного права между коммерческими организациями — не применяется, т.к. пользователи — физические лица.
|
||||
|
||||
## 4. Регистрация в Роскомнадзоре (ст. 22 152-ФЗ)
|
||||
|
||||
| № | Задача | Статус | Примечание |
|
||||
|---|---|---|---|
|
||||
| 4.1 | Оценить необходимость уведомления РКН о начале обработки ПДн | 🟡 | Интернет-сервис с автоматизированной обработкой обычно требует уведомления; исключения ч. 2 ст. 22 вряд ли применимы. Подать до запуска в прод |
|
||||
| 4.2 | Подать уведомление (портал РКН, форма «оператор ПДн») | ⬜ | Внешняя процедура; оператор — физлицо |
|
||||
| 4.3 | Получить и хранить номер уведомления / решение | ⬜ | Приложить к документации |
|
||||
|
||||
## 5. Локализация баз данных (ст. 18 ч. 5 152-ФЗ)
|
||||
|
||||
| № | Задача | Статус | Где |
|
||||
|---|---|---|---|
|
||||
| 5.1 | PostgreSQL в РФ | ✅ | `docker-compose-db.yml` / прод-конфигурация — проверить, что БД в РФ |
|
||||
| 5.2 | S3 (RustFS) — файлы пользователей | 🟡 | Проверить, где физически находится `S3_HOST` в проде |
|
||||
| 5.3 | SMTP-логи переписки | 🟡 | Проверить, не сохраняются ли письма/логи за пределами РФ |
|
||||
|
||||
## 6. Безопасность (ст. 19 152-ФЗ)
|
||||
|
||||
| № | Задача | Статус | Где |
|
||||
|---|---|---|---|
|
||||
| 6.1 | Хеширование паролей (bcrypt) | ✅ | `users_service/service.go` |
|
||||
| 6.2 | JWT access (3 ч) + refresh (7 дн) с ротацией | ✅ | `processor_jwt` |
|
||||
| 6.3 | HTTPS на проде | 🟡 | Через обратный прокси (не в репозитории) |
|
||||
| 6.4 | Назначить ответственного за обработку ПДн (ст. 22.1) | ⬜ | Организационный шаг (для физлица-оператора — сам владелец) |
|
||||
| 6.5 | Внутренний документ: сроки хранения логов, порядок реагирования на утечки (ст. 21 ч. 3.1 — уведомление РКН за 24 ч) | ⬜ | Организационный шаг |
|
||||
| 6.6 | Gateway только за доверенным обратным прокси, перезаписывающим `X-Real-IP` (иначе клиент спуфит IP при акцепте; значения валидируются `netip.ParseAddr`) | 🟡 | Требование к прод-развёртыванию: nginx/Caddy перед `:8090`; настройка не в репозитории |
|
||||
|
||||
## 7. Права субъекта (ст. 14, 17, 20 152-ФЗ)
|
||||
|
||||
| № | Задача | Статус | Где |
|
||||
|---|---|---|---|
|
||||
| 7.1 | Эндпоинт «получить свои данные» (GDPR-стиль / ст. 14) | 🟡 | Частично есть `GET /api/users/me`; полная выгрузка ПДн (все таблицы) — добавить |
|
||||
| 7.2 | Эндпоинт «изменить email/ник» | ⬜ | Сейчас нет; без него нельзя исполнить «уточнение ПДн» |
|
||||
| 7.3 | Эндпоинт «удалить аккаунт» | ✅ | П. 3.1 |
|
||||
| 7.4 | Процедура ответа на запросы (10 раб. дней, ст. 20) | ⬜ | Организационный шаг + адрес `[email]` из документов |
|
||||
|
||||
## 8. Прочее
|
||||
|
||||
| № | Задача | Статус | Примечание |
|
||||
|---|---|---|---|
|
||||
| 8.1 | Обновление swagger (`cmd/evening_detective_server/main.swagger.json`) | ✅ | Перегенерирован `make generate` |
|
||||
| 8.2 | Прогон миграции на реальной БД (`goose up`) | ⬜ | Выполнить на staging перед продом |
|
||||
| 8.3 | Интеграционный тест: signup без акцепта → ошибка; delete-account → данные удалены | ⬜ | `go test` (сейчас тестов нет) |
|
||||
| 8.4 | Обновить README: новые эндпоинты, документы | ⬜ | — |
|
||||
| 8.5 | GDPR (если появятся пользователи из ЕС) | ⬜ | Политика + DPA + механизмы прав субъекта — отдельный этап |
|
||||
|
||||
---
|
||||
|
||||
## Порядок действий перед запуском в прод
|
||||
|
||||
1. Заполнить плейсхолдеры в `docs/TERMS.md`, `docs/PRIVACY.md`, `docs/CONSENT.md` и утвердить у юриста.
|
||||
2. Вставить в тексты документов реальные домены (`/api/terms`, `/api/privacy`, `/api/consent`); вставить чекбоксы акцепта, подтверждение отчуждения прав при публикации сценария и кнопку «Удалить аккаунт» во фронтенд.
|
||||
3. Подать уведомление в Роскомнадзор (п. 4.2) до начала обработки.
|
||||
4. Проверить локализацию: БД, S3, SMTP — в РФ.
|
||||
5. Прогнать `goose up` на staging; добавить интеграционный тест (п. 8.3) — включая сценарий: публикация → удаление запрещено; удаление аккаунта → сценарий остаётся с `author: null`.
|
||||
6. Решить задачу удаления файлов из S3 (п. 3.5) и «изменить email/ник» (п. 7.2) — по остаточному принципу после запуска.
|
||||
@@ -0,0 +1,54 @@
|
||||
# СОГЛАСИЕ НА ОБРАБОТКУ ПЕРСОНАЛЬНЫХ ДАННЫХ
|
||||
|
||||
**Сервис «Вечерний детектив»** · **Редакция от «__» ________ 20__ года**
|
||||
|
||||
*Настоящий документ является шаблоном и носит информационный характер. Перед использованием текст должен быть адаптирован и утверждён квалифицированным юристом.*
|
||||
|
||||
---
|
||||
|
||||
## Текст согласия (для регистрационной формы)
|
||||
|
||||
Я, свободно, своей волей и в своём интересе, являясь дееспособным лицом, достигшим возраста 18 лет, даю своё согласие **[ФИО Оператора]** (далее — **Оператор**), зарегистрированному по адресу: **[адрес]**, на обработку моих персональных данных (далее — **ПДн**) в соответствии с Федеральным законом от 27.07.2006 № 152-ФЗ «О персональных данных» (далее — **152-ФЗ**), а именно на совершение действий, предусмотренных п. 3 ч. 1 ст. 3 152-ФЗ, со следующими ПДн:
|
||||
|
||||
1. адрес электронной почты;
|
||||
2. имя пользователя (никнейм / псевдоним);
|
||||
3. сведения об учётной записи (включая роли и права);
|
||||
4. данные об участии в играх, командах, поданных заявках;
|
||||
5. создаваемые мной материалы (сценарии, тексты, иной контент);
|
||||
6. загружаемые мной файлы;
|
||||
7. технические данные (IP-адрес, сведения об устройстве и браузере, файлы cookie);
|
||||
8. иные данные, добровольно предоставляемые мной при использовании Сервиса.
|
||||
|
||||
**Цели обработки** ПДн: идентификация и аутентификация при использовании Сервиса «Вечерний детектив»; предоставление доступа к Сервису и его функциям; связь со мной (направление пароля, уведомлений); обеспечение безопасности Сервиса; улучшение качества Сервиса; исполнение требований законодательства РФ.
|
||||
|
||||
**Перечень действий с ПДн:** сбор, запись, систематизация, накопление, хранение, уточнение (обновление, изменение), извлечение, использование, обезличивание, блокирование, удаление, уничтожение, — как с использованием средств автоматизации, так и без их использования.
|
||||
|
||||
Настоящее согласие действует со дня его предоставления до дня его отзыва. Согласие может быть отозвано мной в любое время путём направления Оператору письменного требования (в т.ч. в форме электронного документа) на адрес: **[email]**. При отзыве согласия Оператор обязан прекратить обработку ПДн и уничтожить их в срок, не превышающий 30 (тридцати) календарных дней с даты получения отзыва, если иное не предусмотрено законодательством РФ.
|
||||
|
||||
Я ознакомлен(а) с Политикой конфиденциальности и обработки персональных данных, размещённой по адресу: **[адрес политики]**, и Пользовательским соглашением, размещённым по адресу: **[адрес соглашения]**, и согласен(на) с их условиями.
|
||||
|
||||
«___» ________ 20___ года
|
||||
|
||||
Подпись: ____________________ / ____________________
|
||||
|
||||
---
|
||||
|
||||
## Техническая реализация в форме регистрации
|
||||
|
||||
Для юридической значимости согласие должно быть получено до начала обработки и подтверждаемо:
|
||||
|
||||
1. **Чекбоксы с явным согласием** (не предустановленные):
|
||||
- [ ] Я принимаю условия [Пользовательского соглашения]
|
||||
- [ ] Я даю согласие на обработку персональных данных в соответствии с [Политикой конфиденциальности]
|
||||
2. **Блокировка отправки** формы при неотмеченных чекбоксах (клиентская и серверная проверка).
|
||||
3. **Серверная проверка**: `POST /api/auth/signup` отклоняет запрос (`accept_terms=false` или `accept_privacy=false`) с ошибкой — уже реализовано в бэкенде (см. `docs/COMPLIANCE.md`).
|
||||
4. **Фиксация факта акцепта**: сервер сохраняет запись в таблице `user_agreements` (тип документа, версия, дата, IP, user-agent) — уже реализовано.
|
||||
5. **Возраст 18+**: подтверждение возраста включается в текст согласия и регистрационную форму.
|
||||
6. **Отдельное подтверждение при публикации Сценария** (рекомендуется): при первом переводе сценария в статус «опубликован» — чекбокс «Я передаю Оператору исключительные права на этот сценарий безвозмездно в полном объёме» с указанием, что после публикации отозвать сценарий нельзя (ст. 1269 ГК РФ). Серверная проверка: `PUT /api/scenarios/{id}/public` отклоняет запрос без этого подтверждения (см. раздел «Риски» в `docs/COMPLIANCE.md`).
|
||||
|
||||
## Примечания
|
||||
|
||||
- **Отдельное согласие vs соглашение**: согласие на обработку ПДн (ст. 9 152-ФЗ) и акцепт пользовательского соглашения (ст. 428, 1286 ГК РФ) — разные юридические факты, поэтому в форме два отдельных чекбокса.
|
||||
- Для несовершеннолетних (до 18 лет) использование Сервиса запрещено (п. 4.1 Пользовательского соглашения); согласие родителей не предусмотрено, т.к. доступ запрещён полностью.
|
||||
- При введении платных услуг и изменении целей обработки согласие должно быть переактуализировано.
|
||||
- **Удаление аккаунта и контент**: отзыв согласия / удаление Учётной записи влечёт удаление персональных данных, но не прекращает отчуждение исключительного права на опубликованные Сценарии: они остаются в Сервисе, атрибуция обезличивается (п. 7.8 Пользовательского соглашения, ст. 1234 ГК РФ).
|
||||
+185
@@ -0,0 +1,185 @@
|
||||
# ПОЛИТИКА КОНФИДЕНЦИАЛЬНОСТИ И ОБРАБОТКИ ПЕРСОНАЛЬНЫХ ДАННЫХ
|
||||
|
||||
Сервис «Вечерний детектив»
|
||||
|
||||
**Редакция от «__» ________ 20__ года**
|
||||
|
||||
*Настоящий документ является шаблоном и носит информационный характер. Перед публикацией документ должен быть адаптирован и утверждён квалифицированным юристом.*
|
||||
|
||||
---
|
||||
|
||||
## 1. Общие положения
|
||||
|
||||
1.1. Настоящая Политика конфиденциальности и обработки персональных данных (далее — **Политика**) определяет порядок и условия обработки персональных данных (далее — **ПДн**) Пользователей Сервиса «Вечерний детектив» (далее — **Сервис**), размещённого в сети Интернет по адресу: **[адрес сайта]**.
|
||||
|
||||
1.2. Оператором ПДн (ст. 3 Федерального закона от 27.07.2006 № 152-ФЗ «О персональных данных», далее — **152-ФЗ**) является **[ФИО Оператора]** (далее — **Оператор**). Реквизиты Оператора указаны в разделе 14 Политики.
|
||||
|
||||
1.3. Политика разработана во исполнение требований ст. 18.1 152-ФЗ и действует в отношении всех ПДн, которые Оператор может получить о Пользователе при использовании Сервиса.
|
||||
|
||||
1.4. Политика является неотъемлемой частью Пользовательского соглашения (далее — **Соглашение**), размещённого по адресу: **[адрес соглашения]**. Термины, используемые в Политике, применяются в значениях, определённых Соглашением, если иное не установлено Политикой или законодательством РФ.
|
||||
|
||||
1.5. Используя Сервис (регистрируясь, авторизуясь, осуществляя доступ к Сервису), Пользователь выражает согласие с условиями Политики и согласие на обработку своих ПДн в порядке и на условиях, изложенных в Политике.
|
||||
|
||||
1.6. Если Пользователь не согласен с условиями Политики, он обязан прекратить использование Сервиса.
|
||||
|
||||
## 2. Состав обрабатываемых ПДн
|
||||
|
||||
2.1. Оператор обрабатывает следующие ПДн Пользователей:
|
||||
|
||||
2.1.1. **Данные, предоставляемые Пользователем при регистрации и использовании Сервиса:**
|
||||
- адрес электронной почты;
|
||||
- имя пользователя (никнейм / псевдоним);
|
||||
- пароль (в захешированном виде, недоступном для прочтения);
|
||||
- иные данные, которые Пользователь добровольно указывает при использовании Сервиса.
|
||||
|
||||
2.1.2. **Данные, создаваемые в процессе использования Сервиса:**
|
||||
- сведения об Учётной записи и её настройках;
|
||||
- сведения о ролях и правах Пользователя в Сервисе;
|
||||
- данные об участии в Играх, о Командах, о поданных заявках;
|
||||
- создаваемые Пользователем материалы (Сценарии, тексты, иной Пользовательский контент);
|
||||
- загружаемые Пользователем файлы (изображения и иные материалы), в т.ч. при подаче заявок;
|
||||
- история действий Пользователя в Сервисе (в т.ч. сведения о входе в Учётную запись).
|
||||
|
||||
2.1.3. **Технические данные, собираемые автоматически:**
|
||||
- IP-адрес устройства;
|
||||
- данные об устройстве и браузере (тип, версия, язык, разрешение экрана);
|
||||
- файлы cookie и аналогичные технологии;
|
||||
- сведения о времени и длительности сеансов;
|
||||
- иные технические данные, передаваемые устройством Пользователя при обращении к Сервису.
|
||||
|
||||
2.1.4. Оператор не обрабатывает специальные категории ПДн (ст. 10 152-ФЗ: раса, национальность, политические взгляды, религиозные убеждения, состояние здоровья, интимная жизнь) и биометрические ПДн (ст. 11 152-ФЗ), за исключением случаев, прямо предусмотренных законодательством РФ.
|
||||
|
||||
2.1.5. Загружая файлы и размещая Пользовательский контент, Пользователь самостоятельно несёт ответственность за то, чтобы такие материалы не содержали ПДн третьих лиц без их согласия, а также иных сведений, обработка которых не допускается.
|
||||
|
||||
## 3. Цели обработки ПДн
|
||||
|
||||
3.1. Оператор обрабатывает ПДн Пользователей исключительно в следующих целях:
|
||||
|
||||
- идентификация и аутентификация Пользователя при регистрации и входе в Сервис;
|
||||
- предоставление доступа к Сервису и его функциям;
|
||||
- обеспечение функционирования Сервиса (в т.ч. создание Игр, Команд, Сценариев, обработка заявок);
|
||||
- связь с Пользователем: направление пароля при регистрации, уведомлений о восстановлении пароля, иных служебных сообщений, связанных с использованием Сервиса;
|
||||
- обеспечение безопасности Сервиса, защита от несанкционированного доступа, мошенничества и злоупотреблений;
|
||||
- улучшение качества Сервиса, анализ использования его функций;
|
||||
- исполнение требований законодательства РФ.
|
||||
|
||||
3.2. Оператор не использует ПДн для целей, несовместимых с целями, указанными в п. 3.1 Политики.
|
||||
|
||||
## 4. Правовые основания обработки ПДн
|
||||
|
||||
4.1. Обработка ПДн Пользователя осуществляется на следующих основаниях (ст. 6 152-ФЗ):
|
||||
|
||||
- **согласие Пользователя** на обработку ПДн, выраженное при регистрации и/или использовании Сервиса (акцепт Политики и Соглашения);
|
||||
- **исполнение Соглашения**, стороной которого является Пользователь (в т.ч. предоставление доступа к Сервису, исполнение обязательств Оператора);
|
||||
- **исполнение требований законодательства РФ**, возложенных на Оператора;
|
||||
- **защита прав и законных интересов** Оператора и третьих лиц (в т.ч. предотвращение злоупотреблений и нарушений).
|
||||
|
||||
4.2. Согласие Пользователя на обработку ПДн является конкретным, информированным и сознательным (ст. 9 152-ФЗ). Согласие может быть отозвано Пользователем в любое время в порядке, предусмотренном разделом 12 Политики.
|
||||
|
||||
## 5. Порядок и условия обработки ПДн
|
||||
|
||||
5.1. Обработка ПДн осуществляется с использованием средств автоматизации и без использования средств автоматизации, в т.ч. путём сбора, записи, систематизации, накопления, хранения, уточнения (обновления, изменения), извлечения, использования, передачи, обезличивания, блокирования, удаления, уничтожения (ст. 3 152-ФЗ).
|
||||
|
||||
5.2. Обработка ПДн осуществляется с соблюдением принципов, установленных ст. 5 152-ФЗ: законности и справедливости, соответствия целям обработки, минимизации, точности и достаточности, ограничения сроков хранения.
|
||||
|
||||
5.3. Оператор не раскрывает ПДн Пользователей третьим лицам и не распространяет их без согласия Пользователя, за исключением случаев, предусмотренных законодательством РФ.
|
||||
|
||||
5.4. Для обеспечения функционирования Сервиса Оператор может привлекать третьих лиц (технических подрядчиков): поставщиков хостинга и вычислительных мощностей, почтового сервиса для отправки сообщений, сервиса облачного хранения файлов, сервисов аналитики. Передача ПДн таким лицам осуществляется в объёме, необходимом для оказания соответствующих услуг, на основании договоров, содержащих обязательства о конфиденциальности и обеспечении безопасности ПДн (ст. 6, 19 152-ФЗ).
|
||||
|
||||
5.5. ПДн Пользователей не передаются третьим лицам в целях маркетинга и рекламы без отдельного согласия Пользователя.
|
||||
|
||||
## 6. Файлы cookie и аналитика
|
||||
|
||||
6.1. Сервис может использовать файлы cookie и аналогичные технологии (localStorage, sessionStorage, идентификаторы устройств) для:
|
||||
|
||||
- обеспечения корректной работы Сервиса (технически необходимые cookie);
|
||||
- запоминания настроек и предпочтений Пользователя;
|
||||
- сбора статистики использования Сервиса (аналитические cookie).
|
||||
|
||||
6.2. Технически необходимые cookie не требуют согласия Пользователя. Использование аналитических и иных необязательных cookie осуществляется с согласия Пользователя, выраженного через настройки браузера и/или элементы интерфейса Сервиса.
|
||||
|
||||
6.3. Пользователь может в любой момент отключить или ограничить использование cookie в настройках браузера. Отключение cookie может повлиять на доступность отдельных функций Сервиса.
|
||||
|
||||
6.4. Данные аналитики могут обрабатываться в обезличенном виде и использоваться для улучшения Сервиса.
|
||||
|
||||
## 7. Трансграничная передача ПДн
|
||||
|
||||
7.1. На дату вступления Политики в силу трансграничная передача ПДн (ст. 12 152-ФЗ) Оператором не осуществляется.
|
||||
|
||||
7.2. В случае необходимости трансграничной передачи ПДн Оператор обеспечит соблюдение требований ст. 12 152-ФЗ, включая (при необходимости) уведомление или получение разрешения уполномоченного органа (Роскомнадзора) в соответствии с действующим законодательством РФ, и соответствующим образом актуализирует настоящую Политику.
|
||||
|
||||
## 8. Локализация баз данных
|
||||
|
||||
8.1. Оператор обеспечивает запись, систематизацию, накопление, хранение, уточнение, извлечение ПДн граждан Российской Федерации с использованием баз данных, находящихся на территории Российской Федерации (ст. 18 ч. 5 152-ФЗ).
|
||||
|
||||
## 9. Меры по защите ПДн
|
||||
|
||||
9.1. Оператор принимает правовые, организационные и технические меры, необходимые и достаточные для обеспечения безопасности ПДн (ст. 19 152-ФЗ), включая:
|
||||
|
||||
- назначение лица, ответственного за организацию обработки ПДн (ст. 22.1 152-ФЗ);
|
||||
- применение правовых, организационных и технических мер защиты информации (шифрование паролей, ограничение и разграничение доступа к ПДн, защита каналов передачи данных, резервное копирование);
|
||||
- внутренний контроль за соответствием обработки ПДн требованиям законодательства;
|
||||
- оценку вреда, который может быть причинён субъектам ПДн в случае нарушения требований 152-ФЗ;
|
||||
- ознакомление лиц, допущенных к обработке ПДн, с требованиями законодательства и Политики.
|
||||
|
||||
9.2. Пароли Пользователей хранятся в захешированном виде и недоступны для чтения никому, включая работников и подрядчиков Оператора.
|
||||
|
||||
9.3. При выявлении фактов неправомерной или случайной передачи (предоставления, распространения, доступа) ПДн, повлёкшей нарушение прав субъектов ПДн, Оператор в срок, не превышающий 24 часов, уведомляет уполномоченный орган по защите прав субъектов персональных данных (Роскомнадзор) о произошедшем инциденте, а также в установленных законодательством случаях уведомляет субъектов ПДн (ст. 21 ч. 3.1 152-ФЗ).
|
||||
|
||||
## 10. Сроки обработки и хранения ПДн
|
||||
|
||||
10.1. ПДн обрабатываются в течение срока, необходимого для достижения целей обработки, либо в течение срока действия Соглашения, если иное не предусмотрено законодательством РФ.
|
||||
|
||||
10.2. ПДн уничтожаются либо обезличиваются по достижении целей обработки, при отзыве Пользователем согласия на обработку ПДн, при удалении Учётной записи, при прекращении Соглашения, а также по требованию Пользователя (ст. 21 152-ФЗ), за исключением случаев, когда законодательством РФ предусмотрено обязательное хранение таких данных в течение определённого срока.
|
||||
|
||||
10.3. При удалении Учётной записи ПДн Пользователя удаляются в срок не более **[30]** календарных дней с момента получения соответствующего требования, если иное не предусмотрено законодательством. Публичный контент Пользователя (опубликованные Сценарии и т.п.) при этом сохраняется в Сервисе: исключительное право на него отчуждено Оператору (п. 7.2 Пользовательского соглашения, ст. 1234 ГК РФ), а связь контента с персональными данными Пользователя прекращается (обезличивание, ст. 3 152-ФЗ).
|
||||
|
||||
## 11. Защита прав несовершеннолетних
|
||||
|
||||
11.1. Сервис предназначен для лиц, достигших 18 лет (п. 4.1 Соглашения). Регистрация и использование Сервиса лицами, не достигшими 18 лет, запрещены.
|
||||
|
||||
11.2. Оператор не собирает сознательно ПДн лиц, не достигших 18 лет. При обнаружении факта обработки ПДн несовершеннолетнего Оператор незамедлительно прекращает такую обработку и удаляет соответствующие данные.
|
||||
|
||||
## 12. Права субъекта ПДн и порядок их реализации
|
||||
|
||||
12.1. Пользователь (субъект ПДн) вправе (ст. 14, 17 152-ФЗ):
|
||||
|
||||
- получать информацию, касающуюся обработки его ПДн (сведения, предусмотренные ст. 14 152-ФЗ);
|
||||
- требовать уточнения, блокирования или уничтожения своих ПДн, если они являются неполными, устаревшими, неточными, незаконно полученными или не являются необходимыми для заявленной цели обработки (ст. 14, 21 152-ФЗ);
|
||||
- отозвать согласие на обработку ПДн в любое время;
|
||||
- обжаловать действия (бездействие) Оператора в уполномоченном органе по защите прав субъектов персональных данных (Роскомнадзор) и/или в судебном порядке (ст. 17 152-ФЗ).
|
||||
|
||||
12.2. Для реализации прав, предусмотренных п. 12.1 Политики, Пользователь направляет Оператору запрос в письменной форме (в т.ч. в форме электронного документа) на адрес: **[email]**.
|
||||
|
||||
12.3. Запрос должен содержать (ст. 14 ч. 3, 152-ФЗ):
|
||||
|
||||
- номер основного документа, удостоверяющего личность Пользователя (или его представителя), сведения о дате выдачи и выдавшем органе;
|
||||
- сведения, подтверждающие участие Пользователя в отношениях с Оператором (например, адрес электронной почты, использованный при регистрации);
|
||||
- подпись Пользователя (или его представителя).
|
||||
|
||||
12.4. Оператор рассматривает запрос и направляет ответ в срок, не превышающий **10 рабочих дней** с даты получения запроса (ст. 20 ч. 1 152-ФЗ). В случае отказа в удовлетворении запроса Оператор направляет мотивированный ответ.
|
||||
|
||||
12.5. Отзыв согласия на обработку ПДн (ст. 9 ч. 2, 152-ФЗ) влечёт прекращение обработки и уничтожение ПДн в срок, не превышающий **30 календарных дней** с даты получения отзыва (ст. 21 ч. 5, 152-ФЗ). Отзыв согласия может сделать невозможным дальнейшее использование Сервиса.
|
||||
|
||||
## 13. Заключительные положения
|
||||
|
||||
13.1. Оператор вправе в одностороннем порядке изменять условия Политики. Новая редакция Политики вступает в силу с момента её опубликования в Сервисе, если иной срок не указан при опубликовании. Продолжение использования Сервиса после вступления в силу новой редакции Политики означает согласие Пользователя с изменениями.
|
||||
|
||||
13.2. В случае внесения существенных изменений в Политику, затрагивающих права Пользователей, Оператор уведомляет Пользователей способами, доступными в Сервисе (в т.ч. путём направления уведомления на адрес электронной почты).
|
||||
|
||||
13.3. Контроль за соблюдением Политики осуществляет лицо, ответственное за организацию обработки ПДн (п. 9.1 Политики).
|
||||
|
||||
13.4. По всем вопросам, связанным с обработкой ПДн и реализацией прав субъектов ПДн, Пользователь может обратиться к Оператору по адресу: **[email]**.
|
||||
|
||||
13.5. Настоящая Политика регулируется и толкуется в соответствии с законодательством Российской Федерации.
|
||||
|
||||
## 14. Реквизиты Оператора
|
||||
|
||||
**Оператор Сервиса «Вечерний детектив»:**
|
||||
|
||||
- ФИО: **[ФИО Оператора]**
|
||||
- Адрес: **[адрес]**
|
||||
- Адрес электронной почты: **[email]**
|
||||
- Иные реквизиты: **[ИНН (при наличии), иные сведения]**
|
||||
|
||||
**Адрес для обращений по вопросам обработки персональных данных: [email].**
|
||||
+245
@@ -0,0 +1,245 @@
|
||||
# ПОЛЬЗОВАТЕЛЬСКОЕ СОГЛАШЕНИЕ (ОФЕРТА)
|
||||
|
||||
Сервис «Вечерний детектив»
|
||||
|
||||
**Редакция от «__» ________ 20__ года**
|
||||
|
||||
*Настоящий документ является шаблоном и носит информационный характер. Перед публикацией документ должен быть адаптирован и утверждён квалифицированным юристом.*
|
||||
|
||||
---
|
||||
|
||||
## 1. Общие положения
|
||||
|
||||
1.1. Настоящее Пользовательское соглашение (далее — **Соглашение**) является публичной офертой (ст. 435, 437 Гражданского кодекса РФ, далее — ГК РФ) и определяет условия использования Сервиса «Вечерний детектив» (далее — **Сервис**), размещённого в сети Интернет по адресу: **[адрес сайта]**.
|
||||
|
||||
1.2. Оператор Сервиса и правообладатель — **[ФИО Оператора]** (далее — **Оператор**). Реквизиты Оператора указаны в разделе 14 Соглашения.
|
||||
|
||||
1.3. Использование Сервиса регулируется:
|
||||
|
||||
- настоящим Соглашением;
|
||||
- Политикой конфиденциальности и обработки персональных данных (далее — **Политика конфиденциальности**), размещённой по адресу: **[адрес политики]**;
|
||||
- законодательством Российской Федерации.
|
||||
|
||||
1.4. Соглашение является договором присоединения (ст. 428 ГК РФ). Акцептом Соглашения признаётся совершение Пользователем любого из следующих действий (ст. 438 ГК РФ, ст. 1286 ГК РФ):
|
||||
|
||||
- регистрация учётной записи в Сервисе;
|
||||
- начало использования Сервиса (в т.ч. вход в Сервис, создание игры, подача заявки на участие, загрузка материалов);
|
||||
- иное действие, свидетельствующее о принятии условий Соглашения.
|
||||
|
||||
1.5. С момента акцепта Соглашение считается заключённым между Пользователем и Оператором в письменной форме (путём обмена документами в электронном виде) на условиях, изложенных в Соглашении, и действует до прекращения в порядке, предусмотренном Соглашением.
|
||||
|
||||
1.6. Оператор вправе в одностороннем порядке изменять условия Соглашения. Новая редакция Соглашения вступает в силу с момента её опубликования в Сервисе, если иной срок не указан при опубликовании. Продолжение использования Сервиса после вступления в силу новой редакции означает согласие Пользователя с изменениями.
|
||||
|
||||
1.7. Если Пользователь не согласен с условиями Соглашения или его изменениями, он обязан прекратить использование Сервиса.
|
||||
|
||||
1.8. Оператор вправе в любое время без уведомления Пользователя вносить изменения в функциональность Сервиса, добавлять или удалять отдельные функции, изменять их содержание.
|
||||
|
||||
## 2. Термины
|
||||
|
||||
2.1. В целях настоящего Соглашения применяются следующие термины:
|
||||
|
||||
- **Пользователь** — дееспособное физическое лицо, достигшее возраста 18 лет, акцептовавшее Соглашение и использующее Сервис;
|
||||
- **Учётная запись (аккаунт)** — совокупность данных Пользователя, создаваемая при регистрации в Сервисе, позволяющая идентифицировать Пользователя;
|
||||
- **Игра** — организованное Оператором или Пользователем мероприятие (детективная игра) в рамках Сервиса;
|
||||
- **Сценарий** — текстовая и/или мультимедийная основа Игры, создаваемая Оператором или Пользователем;
|
||||
- **Команда** — объединение Пользователей для участия в Игре;
|
||||
- **Пользовательский контент** — материалы (тексты, изображения, аудио, видео, сценарии, иные объекты), созданные Пользователем и размещённые им в Сервисе;
|
||||
- **Личный кабинет** — персонализированный раздел Сервиса, доступный Пользователю после авторизации.
|
||||
|
||||
2.2. Иные термины, не определённые в Соглашении, используются в значениях, установленных законодательством Российской Федерации и Политикой конфиденциальности.
|
||||
|
||||
## 3. Предмет Соглашения
|
||||
|
||||
3.1. Оператор предоставляет Пользователю доступ к Сервису на условиях простой (неисключительной) лицензии (ст. 1235, 1236 ГК РФ), а Пользователь обязуется использовать Сервис способами, предусмотренными Соглашением.
|
||||
|
||||
3.2. Сервис предоставляет Пользователю возможность:
|
||||
|
||||
- регистрироваться и использовать Учётную запись;
|
||||
- создавать Команды и Игры, подавать заявки на участие в Играх;
|
||||
- создавать, загружать и использовать Сценарии и иной Пользовательский контент;
|
||||
- взаимодействовать с другими Пользователями в рамках Сервиса;
|
||||
- использовать иные функции Сервиса по мере их появления.
|
||||
|
||||
3.3. Способы использования Сервиса (ст. 1235 п. 2 ГК РФ): воспроизведение программного обеспечения Сервиса в объёме, необходимом для доступа к Сервису через сеть Интернет, и использование функций Сервиса в соответствии с его назначением. Пользователю не предоставляются права на изменение, распространение, модификацию программного обеспечения Сервиса.
|
||||
|
||||
## 4. Условия регистрации и Учётная запись
|
||||
|
||||
4.1. Регистрация в Сервисе возможна только для физических лиц, достигших возраста 18 лет. Использование Сервиса лицами, не достигшими 18 лет, запрещено. Регистрируясь, Пользователь подтверждает, что достиг 18 лет и имеет право заключать Соглашение.
|
||||
|
||||
4.2. При регистрации Пользователь предоставляет достоверные данные: адрес электронной почты и иные сведения, запрашиваемые Сервисом. Пользователь несёт ответственность за достоверность предоставленных данных.
|
||||
|
||||
4.3. Пароль для доступа к Учётной записи направляется Пользователю на адрес электронной почты, указанный при регистрации. Пользователь обязан:
|
||||
|
||||
- обеспечить сохранность пароля и не передавать его третьим лицам;
|
||||
- незамедлительно уведомить Оператора о любом несанкционированном доступе к Учётной записи;
|
||||
- принимать разумные меры для защиты Учётной записи от несанкционированного доступа.
|
||||
|
||||
4.4. Пользователь несёт ответственность за все действия, совершённые с использованием его Учётной записи, если он не докажет, что действия совершены без его участия вследствие обстоятельств непреодолимой силы или вины Оператора.
|
||||
|
||||
4.5. Оператор вправе заблокировать или удалить Учётную запись Пользователя (полностью или частично), а также ограничить доступ к Сервису, в случае нарушения Пользователем условий Соглашения, требований законодательства, а также по иным основаниям, предусмотренным Соглашением. Оператор вправе удалить Учётную запись, не использовавшуюся Пользователем в течение **[12]** месяцев.
|
||||
|
||||
4.6. Учётная запись не может быть передана или отчуждена третьим лицам, за исключением случаев, предусмотренных законом или прямо разрешённых Оператором.
|
||||
|
||||
## 5. Права и обязанности Пользователя
|
||||
|
||||
5.1. Пользователь вправе:
|
||||
|
||||
- использовать Сервис в соответствии с Соглашением и его назначением;
|
||||
- создавать Пользовательский контент и размещать его в Сервисе;
|
||||
- обращаться к Оператору по вопросам функционирования Сервиса;
|
||||
- реализовывать иные права, предусмотренные Соглашением и законодательством.
|
||||
|
||||
5.2. Пользователь обязуется:
|
||||
|
||||
- соблюдать условия Соглашения, Политики конфиденциальности и требования законодательства РФ;
|
||||
- не использовать Сервис для целей, противоречащих законодательству;
|
||||
- не совершать действий, направленных на нарушение функционирования Сервиса, его программного обеспечения, баз данных;
|
||||
- не пытаться получить несанкционированный доступ к Сервису, его системам и данным других Пользователей;
|
||||
- не использовать автоматизированные средства (боты, скрипты, парсеры) для взаимодействия с Сервисом без предварительного письменного разрешения Оператора;
|
||||
- не размещать в Сервисе материалы, перечисленные в разделе 6 Соглашения;
|
||||
- не воспроизводить, не копировать, не изменять, не декомпилировать, не дизассемблировать программное обеспечение Сервиса, за исключением случаев, прямо разрешённых законом (ст. 1280 ГК РФ);
|
||||
- не использовать Сервис для обхода или нарушения прав третьих лиц, включая права на объекты интеллектуальной собственности.
|
||||
|
||||
## 6. Запрещённый контент и правила поведения
|
||||
|
||||
6.1. В Сервисе запрещено размещение Пользовательского контента, который:
|
||||
|
||||
- нарушает законодательство РФ, в т.ч. авторские и смежные права, права на товарные знаки, иные права третьих лиц;
|
||||
- содержит порнографические материалы, а также материалы сексуального характера с участием несовершеннолетних;
|
||||
- содержит призывы к насилию, экстремизму, терроризму, разжигание национальной, расовой, религиозной или иной розни;
|
||||
- содержит клевету, оскорбления, угрозы, преследование или иным образом нарушает права других лиц;
|
||||
- содержит пропаганду наркотических средств, алкоголя, азартных игр (в запрещённых законом формах);
|
||||
- содержит вредоносные программы, вирусы, ссылки на них;
|
||||
- нарушает права несовершеннолетних;
|
||||
- иным образом противоречит целям Сервиса и принципам добросовестности.
|
||||
|
||||
6.2. Оператор вправе, но не обязан, осуществлять модерацию Пользовательского контента. Оператор вправе удалить любой Пользовательский контент, нарушающий условия Соглашения или законодательство, без предварительного уведомления Пользователя, а также заблокировать или удалить Учётную запись нарушителя.
|
||||
|
||||
6.3. Пользователь самостоятельно несёт ответственность за размещаемый им Пользовательский контент, включая его законность, достоверность и соответствие правам третьих лиц.
|
||||
|
||||
## 7. Пользовательский контент и права на него
|
||||
|
||||
7.1. Личные неимущественные права на Пользовательский контент (право авторства, право на имя) сохраняются за автором и не могут быть отчуждены (ст. 1228, 1265 ГК РФ).
|
||||
|
||||
7.2. Переводя Сценарий в статус «опубликован» (публикуя Сценарий в Сервисе), Пользователь безвозмездно отчуждает Оператору принадлежащее ему исключительное право на такой Сценарий в полном объёме (ст. 1234 ГК РФ). Настоящий договор об отчуждении исключительного права является **безвозмездным**. Исключительное право переходит к Оператору в момент перевода Сценария в статус «опубликован» (ст. 1234 п. 4 ГК РФ), на весь срок действия исключительного права, на территории всего мира. Ограничение п. 3.1 ст. 1234 ГК РФ не применяется, поскольку Пользователь не является коммерческой организацией.
|
||||
|
||||
7.3. Пользователь соглашается с использованием опубликованных Сценариев Оператором без указания имени автора (анонимно) либо под псевдонимом (никнеймом), указанным Пользователем при регистрации (ст. 1265 п. 1 ГК РФ), в т.ч. после удаления Учётной записи Пользователя.
|
||||
|
||||
7.4. Сценарии, не переведённые в статус «опубликован» (черновики), не отчуждаются: Пользователь сохраняет исключительное право на такие Сценарии и вправе свободно их редактировать, изменять и удалять.
|
||||
|
||||
7.5. Пользователь гарантирует, что:
|
||||
|
||||
- он обладает достаточными правами на размещаемый Пользовательский контент (является автором и/или правообладателем либо имеет разрешение правообладателя);
|
||||
- размещение контента не нарушает прав третьих лиц и законодательства РФ;
|
||||
- контент не содержит персональных данных третьих лиц без их согласия;
|
||||
- на момент публикации Сценария он вправе отчуждать исключительное право на него Оператору.
|
||||
|
||||
7.6. Право на отзыв (ст. 1269 ГК РФ) действует только **до фактического обнародования** Сценария. После перевода Сценария в статус «опубликован» Пользователь не вправе отозвать Сценарий, требовать прекращения его использования, удаления или снятия с публикации. Оператор вправе использовать опубликованный Сценарий любыми способами, включая воспроизведение, распространение, переработку, включение в составные и сложные объекты, доведение до всеобщего сведения, предоставление доступа третьим лицам, — без дополнительного согласия Пользователя.
|
||||
|
||||
7.7. При получении претензий третьих лиц в отношении Пользовательского контента Оператор вправе временно ограничить доступ к такому контенту до урегулирования спора и уведомить об этом Пользователя.
|
||||
|
||||
7.8. Удаление Учётной записи Пользователя не прекращает отчуждение исключительного права на опубликованные Сценарии (ст. 1234 п. 1 ГК РФ). При удалении Учётной записи Оператор удаляет персональные данные Пользователя, при этом опубликованные Сценарии сохраняются в Сервисе, а атрибуция автора обезличивается (ст. 3 152-ФЗ, обезличивание) в порядке, предусмотренном п. 7.3 Соглашения.
|
||||
|
||||
## 8. Платные услуги
|
||||
|
||||
8.1. На дату вступления Соглашения в силу Сервис предоставляется Пользователям бесплатно. Отдельные функции Сервиса могут стать платными в будущем.
|
||||
|
||||
8.2. Оператор вправе вводить платные услуги (функции) Сервиса, а также изменять стоимость платных услуг, уведомив Пользователей не менее чем за **[14]** дней путём опубликования соответствующей информации в Сервисе и/или направления уведомления на адрес электронной почты Пользователя.
|
||||
|
||||
8.3. При введении платных услуг условия их предоставления, включая порядок оплаты, сроки, порядок возврата денежных средств, определяются отдельными документами (правилами), размещаемыми в Сервисе. Использование платных услуг означает согласие Пользователя с соответствующими правилами.
|
||||
|
||||
8.4. Если Сервис будет предоставлять платные услуги, отношения между Оператором и Пользователем в части таких услуг регулируются, в т.ч., Законом РФ от 07.02.1992 № 2300-1 «О защите прав потребителей» (далее — Закон о защите прав потребителей) в части, применимой к отношениям сторон.
|
||||
|
||||
## 9. Персональные данные
|
||||
|
||||
9.1. При использовании Сервиса Оператор обрабатывает персональные данные Пользователя (ст. 3 Федерального закона от 27.07.2006 № 152-ФЗ «О персональных данных», далее — 152-ФЗ): адрес электронной почты, имя (псевдоним), данные Учётной записи, сведения о действиях Пользователя в Сервисе, технические данные (IP-адрес, файлы cookie, данные об устройстве), иные данные, указанные в Политике конфиденциальности.
|
||||
|
||||
9.2. Обработка персональных данных осуществляется на основании (ст. 6 152-ФЗ):
|
||||
|
||||
- согласия Пользователя, выраженного при регистрации и/или использовании Сервиса (акцепт Политики конфиденциальности);
|
||||
- необходимости исполнения Соглашения, стороной которого является Пользователь;
|
||||
- иных законных оснований.
|
||||
|
||||
9.3. Цели обработки персональных данных: идентификация Пользователя, предоставление доступа к Сервису, обеспечение функционирования Сервиса, связь с Пользователем (в т.ч. направление пароля и уведомлений), улучшение качества Сервиса, исполнение требований законодательства.
|
||||
|
||||
9.4. Оператор обрабатывает персональные данные в соответствии с принципами законности, справедливости, минимизации и ограничения сроков хранения (ст. 5 152-ФЗ), принимает правовые, организационные и технические меры защиты (ст. 19 152-ФЗ).
|
||||
|
||||
9.5. Пользователь вправе:
|
||||
|
||||
- получать информацию о своих персональных данных и их обработке (ст. 14 152-ФЗ);
|
||||
- требовать уточнения, блокирования, уничтожения персональных данных (ст. 14, 21 152-ФЗ);
|
||||
- отозвать согласие на обработку персональных данных в любое время (ст. 9 152-ФЗ), направив соответствующее требование Оператору. Отзыв согласия может повлечь невозможность использования Сервиса;
|
||||
- обжаловать действия Оператора в уполномоченный орган (Роскомнадзор) и в суд (ст. 17 152-ФЗ).
|
||||
|
||||
9.6. Порядок, сроки и условия обработки персональных данных, включая сведения о cookies и аналитике, подробно изложены в Политике конфиденциальности, размещённой по адресу: **[адрес политики]**.
|
||||
|
||||
9.7. Оператор не передаёт персональные данные Пользователя третьим лицам, за исключением случаев, предусмотренных законодательством РФ или прямо указанных в Политике конфиденциальности (в т.ч. техническим подрядчикам, обеспечивающим функционирование Сервиса).
|
||||
|
||||
9.8. Оператор обеспечивает запись, систематизацию, накопление, хранение, уточнение, извлечение персональных данных граждан Российской Федерации с использованием баз данных, находящихся на территории Российской Федерации (ст. 18 ч. 5 152-ФЗ).
|
||||
|
||||
## 10. Интеллектуальная собственность
|
||||
|
||||
10.1. Исключительные права на программное обеспечение Сервиса, его дизайн, интерфейс, логотипы, товарные знаки, тексты, базы данных и иные материалы, созданные Оператором (за исключением Пользовательского контента), принадлежат Оператору (ст. 1225, 1229, 1261 ГК РФ).
|
||||
|
||||
10.2. Использование материалов, указанных в п. 10.1, допускается только в пределах функций Сервиса. Запрещается копирование, распространение, переработка, использование материалов Сервиса вне Сервиса без письменного согласия Оператора, за исключением случаев, предусмотренных законодательством.
|
||||
|
||||
10.3. Пользователь обязуется не нарушать исключительные права Оператора и третьих лиц при использовании Сервиса. При нарушении исключительных прав правообладатель вправе требовать защиты способами, предусмотренными ст. 1252, 1301 ГК РФ.
|
||||
|
||||
10.4. Нарушение условий Соглашения о способах использования Сервиса (раздел 3) является нарушением условий лицензии и может являться основанием для её одностороннего прекращения Оператором (ст. 450.1, 1286.1 ГК РФ).
|
||||
|
||||
## 11. Ответственность сторон
|
||||
|
||||
11.1. За неисполнение или ненадлежащее исполнение обязательств по Соглашению стороны несут ответственность в соответствии с законодательством РФ (ст. 393, 401 ГК РФ).
|
||||
|
||||
11.2. Сервис предоставляется «как есть» (as is). Оператор не гарантирует:
|
||||
|
||||
- бесперебойную и безошибочную работу Сервиса;
|
||||
- соответствие Сервиса ожиданиям Пользователя;
|
||||
- сохранность Пользовательского контента (Пользователю рекомендуется создавать резервные копии значимых материалов);
|
||||
- отсутствие технических сбоев, в т.ч. связанных с работой сети Интернет, хостинга, оборудования.
|
||||
|
||||
11.3. Оператор не несёт ответственности за убытки Пользователя, возникшие в результате:
|
||||
|
||||
- невозможности использования Сервиса по причинам, не зависящим от Оператора;
|
||||
- действий (бездействия) Пользователя, включая передачу пароля третьим лицам и нарушение требований безопасности;
|
||||
- действий третьих лиц, включая неправомерный доступ к Учётной записи;
|
||||
- использования Пользователем Сервиса в целях, не предусмотренных Соглашением.
|
||||
|
||||
11.4. Размер ответственности Оператора по Соглашению ограничивается суммой **[размер, например: фактически уплаченных Пользователем средств за последние 12 месяцев / 10 000 (десять тысяч) рублей]**, за исключением случаев причинения вреда, за которые ограничение ответственности не допускается законом.
|
||||
|
||||
11.5. Оператор не несёт ответственности за действия Пользователей в рамках Игр и за содержание Пользовательского контента.
|
||||
|
||||
11.6. В части платных услуг (раздел 8) положения настоящего раздела применяются с учётом императивных требований Закона о защите прав потребителей.
|
||||
|
||||
## 12. Изменение, приостановление и прекращение Соглашения
|
||||
|
||||
12.1. Оператор вправе приостановить или прекратить предоставление Сервиса (полностью или частично) в любое время, в т.ч. в связи с техническими, юридическими или коммерческими причинами, уведомив Пользователей способами, доступными в Сервисе.
|
||||
|
||||
12.2. Пользователь вправе в одностороннем порядке отказаться от Соглашения в любое время, прекратив использование Сервиса и (или) удалив Учётную запись.
|
||||
|
||||
12.3. Оператор вправе расторгнуть Соглашение в одностороннем порядке при нарушении Пользователем условий Соглашения, в т.ч. в случаях, указанных в п. 4.5 и п. 6.2 Соглашения, без возмещения каких-либо убытков Пользователю, за исключением случаев, предусмотренных законом.
|
||||
|
||||
12.4. При прекращении Соглашения Пользователь утрачивает право использования Сервиса. Положения Соглашения, которые по своему характеру должны продолжать действовать после прекращения (в т.ч. разделы 7, 9, 10, 11, 13), сохраняют силу.
|
||||
|
||||
## 13. Разрешение споров
|
||||
|
||||
13.1. Все споры и разногласия, возникающие из Соглашения или в связи с ним, стороны разрешают путём переговоров. Претензионный порядок обязателен: срок рассмотрения претензии — **[30]** календарных дней с момента её получения.
|
||||
|
||||
13.2. При недостижении согласия спор подлежит рассмотрению в суде по месту нахождения Оператора, за исключением случаев, когда в соответствии с законодательством РФ, в т.ч. Законом о защите прав потребителей, предусмотрена подсудность по выбору Пользователя-потребителя (ст. 17 Закона о защите прав потребителей, ст. 29 ГПК РФ).
|
||||
|
||||
13.3. Применимым правом является право Российской Федерации.
|
||||
|
||||
## 14. Реквизиты Оператора
|
||||
|
||||
**Оператор Сервиса «Вечерний детектив»:**
|
||||
|
||||
- ФИО: **[ФИО Оператора]**
|
||||
- Адрес: **[адрес]**
|
||||
- Адрес электронной почты: **[email]**
|
||||
- Иные реквизиты: **[ИНН (при наличии), иные сведения]**
|
||||
|
||||
**Адрес для обращений по вопросам, связанным с Соглашением и обработкой персональных данных: [email].**
|
||||
|
||||
---
|
||||
|
||||
*Пользователь подтверждает, что ознакомился с условиями Соглашения, Политикой конфиденциальности, понимает их значение и согласен с ними.*
|
||||
@@ -0,0 +1,9 @@
|
||||
// Package docs встраивает юридические документы в бинарник и раздаёт их
|
||||
// по постоянным URL (/terms, /privacy, /consent), на которые сервис
|
||||
// ссылается при фиксации акцептов соглашений (user_agreements).
|
||||
package docs
|
||||
|
||||
import "embed"
|
||||
|
||||
//go:embed TERMS.md PRIVACY.md CONSENT.md
|
||||
var FS embed.FS
|
||||
@@ -0,0 +1,16 @@
|
||||
package docs
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestLegalDocsEmbedded(t *testing.T) {
|
||||
for _, name := range []string{"TERMS.md", "PRIVACY.md", "CONSENT.md"} {
|
||||
data, err := FS.ReadFile(name)
|
||||
if err != nil {
|
||||
t.Errorf("документ %s не встроен: %v", name, err)
|
||||
continue
|
||||
}
|
||||
if len(data) == 0 {
|
||||
t.Errorf("документ %s пустой", name)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,11 +3,11 @@ module evening_detective_server
|
||||
go 1.26
|
||||
|
||||
require (
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0
|
||||
github.com/stretchr/testify v1.11.1
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240513163218-0867130af1f8
|
||||
google.golang.org/grpc v1.64.0
|
||||
google.golang.org/protobuf v1.34.1
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.30.0
|
||||
github.com/stretchr/testify v1.12.0
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260810153831-ec0a7760b754
|
||||
google.golang.org/grpc v1.83.0
|
||||
google.golang.org/protobuf v1.36.11
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -47,11 +47,11 @@ require (
|
||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||
github.com/joho/godotenv v1.5.1
|
||||
github.com/kzzan/s3kit v0.0.4
|
||||
github.com/swaggest/swgui v1.8.8
|
||||
golang.org/x/crypto v0.53.0
|
||||
golang.org/x/net v0.55.0 // indirect
|
||||
golang.org/x/sync v0.21.0 // indirect
|
||||
golang.org/x/sys v0.46.0 // indirect
|
||||
golang.org/x/text v0.38.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240513163218-0867130af1f8 // indirect
|
||||
github.com/swaggest/swgui v1.8.9
|
||||
golang.org/x/crypto v0.55.0
|
||||
golang.org/x/net v0.57.0 // indirect
|
||||
golang.org/x/sync v0.22.0 // indirect
|
||||
golang.org/x/sys v0.47.0 // indirect
|
||||
golang.org/x/text v0.41.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260807164820-c8921c73eeea // indirect
|
||||
)
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"google.golang.org/grpc/metadata"
|
||||
)
|
||||
|
||||
func TestClientInfoFromContext(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
md metadata.MD
|
||||
wantIP string
|
||||
wantUserAgent string
|
||||
}{
|
||||
{
|
||||
name: "нет метаданных",
|
||||
wantIP: "",
|
||||
wantUserAgent: "",
|
||||
},
|
||||
{
|
||||
name: "x-forwarded-for через gateway: берём последний элемент",
|
||||
md: metadata.MD{
|
||||
"x-forwarded-for": []string{"203.0.113.1, 198.51.100.7"},
|
||||
"grpcgateway-user-agent": []string{"Mozilla/5.0 (test)"},
|
||||
},
|
||||
wantIP: "198.51.100.7",
|
||||
wantUserAgent: "Mozilla/5.0 (test)",
|
||||
},
|
||||
{
|
||||
name: "x-real-ip от доверенного прокси приоритетнее x-forwarded-for",
|
||||
md: metadata.MD{
|
||||
"x-real-ip": []string{"192.0.2.10"},
|
||||
"x-forwarded-for": []string{"203.0.113.1, 10.0.0.1"},
|
||||
"grpcgateway-user-agent": []string{"curl/8.0"},
|
||||
},
|
||||
wantIP: "192.0.2.10",
|
||||
wantUserAgent: "curl/8.0",
|
||||
},
|
||||
{
|
||||
name: "x-real-ip пустой — фолбэк на x-forwarded-for",
|
||||
md: metadata.MD{
|
||||
"x-real-ip": []string{" "},
|
||||
"x-forwarded-for": []string{"203.0.113.5"},
|
||||
},
|
||||
wantIP: "203.0.113.5",
|
||||
},
|
||||
{
|
||||
name: "user-agent для прямого gRPC-клиента",
|
||||
md: metadata.MD{
|
||||
"user-agent": []string{"grpc-go/1.60"},
|
||||
},
|
||||
wantUserAgent: "grpc-go/1.60",
|
||||
},
|
||||
{
|
||||
name: "x-forwarded-for с пробелами после запятых",
|
||||
md: metadata.MD{
|
||||
"x-forwarded-for": []string{"203.0.113.1 , 198.51.100.7 "},
|
||||
},
|
||||
wantIP: "198.51.100.7",
|
||||
},
|
||||
{
|
||||
name: "невалидный x-real-ip — фолбэк на валидный x-forwarded-for",
|
||||
md: metadata.MD{
|
||||
"x-real-ip": []string{"not-an-ip; drop me"},
|
||||
"x-forwarded-for": []string{"203.0.113.1, 198.51.100.7"},
|
||||
},
|
||||
wantIP: "198.51.100.7",
|
||||
},
|
||||
{
|
||||
name: "все источники невалидны — IP пустой (спуфинг)",
|
||||
md: metadata.MD{
|
||||
"x-real-ip": []string{"not-an-ip; drop me"},
|
||||
"x-forwarded-for": []string{"256.256.256.256"},
|
||||
},
|
||||
wantIP: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var ctx context.Context = context.Background()
|
||||
if tt.md != nil {
|
||||
ctx = metadata.NewIncomingContext(ctx, tt.md)
|
||||
}
|
||||
ip, userAgent := clientInfoFromContext(ctx)
|
||||
if ip != tt.wantIP {
|
||||
t.Errorf("ip = %q, want %q", ip, tt.wantIP)
|
||||
}
|
||||
if userAgent != tt.wantUserAgent {
|
||||
t.Errorf("userAgent = %q, want %q", userAgent, tt.wantUserAgent)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,15 @@ import (
|
||||
func mapGame(
|
||||
o *game_service.Game,
|
||||
) *proto.Game {
|
||||
var startedAt *timestamppb.Timestamp
|
||||
if o.StartedAt != nil {
|
||||
startedAt = timestamppb.New(*o.StartedAt)
|
||||
}
|
||||
var endedAt *timestamppb.Timestamp
|
||||
if o.EndedAt != nil {
|
||||
endedAt = timestamppb.New(*o.EndedAt)
|
||||
}
|
||||
|
||||
return &proto.Game{
|
||||
Id: int32(o.ID),
|
||||
Name: o.Name,
|
||||
@@ -18,6 +27,8 @@ func mapGame(
|
||||
Status: o.Status,
|
||||
Scenario: mapScenario(o.Scenario),
|
||||
Teams: mapTeams(o.Teams),
|
||||
StartedAt: startedAt,
|
||||
EndedAt: endedAt,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
proto "evening_detective_server/proto"
|
||||
)
|
||||
|
||||
func TestGetLegalDocs(t *testing.T) {
|
||||
s := &server{}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
call func() (text, errText string)
|
||||
wantPart string
|
||||
}{
|
||||
{
|
||||
name: "GetTerms отдаёт Пользовательское соглашение",
|
||||
call: func() (string, string) {
|
||||
rsp, err := s.GetTerms(context.Background(), &proto.GetTermsReq{})
|
||||
if err != nil {
|
||||
t.Fatalf("GetTerms error: %v", err)
|
||||
}
|
||||
return rsp.Text, rsp.Error
|
||||
},
|
||||
wantPart: "СОГЛАШЕНИЕ",
|
||||
},
|
||||
{
|
||||
name: "GetPrivacy отдаёт Политику конфиденциальности",
|
||||
call: func() (string, string) {
|
||||
rsp, err := s.GetPrivacy(context.Background(), &proto.GetPrivacyReq{})
|
||||
if err != nil {
|
||||
t.Fatalf("GetPrivacy error: %v", err)
|
||||
}
|
||||
return rsp.Text, rsp.Error
|
||||
},
|
||||
wantPart: "КОНФИДЕНЦИАЛЬНОСТИ",
|
||||
},
|
||||
{
|
||||
name: "GetConsent отдаёт текст согласия на обработку ПДн",
|
||||
call: func() (string, string) {
|
||||
rsp, err := s.GetConsent(context.Background(), &proto.GetConsentReq{})
|
||||
if err != nil {
|
||||
t.Fatalf("GetConsent error: %v", err)
|
||||
}
|
||||
return rsp.Text, rsp.Error
|
||||
},
|
||||
wantPart: "СОГЛАСИЕ",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
text, errText := tt.call()
|
||||
if errText != "" {
|
||||
t.Fatalf("ожидался успех, получена ошибка: %s", errText)
|
||||
}
|
||||
if !strings.Contains(text, tt.wantPart) {
|
||||
t.Errorf("текст документа не содержит %q", tt.wantPart)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
+334
-20
@@ -2,6 +2,10 @@ package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/netip"
|
||||
"strings"
|
||||
|
||||
"evening_detective_server/docs"
|
||||
"evening_detective_server/internal/modules/file_storage"
|
||||
"evening_detective_server/internal/modules/processor_jwt"
|
||||
"evening_detective_server/internal/modules/roles"
|
||||
@@ -14,6 +18,7 @@ import (
|
||||
|
||||
"google.golang.org/genproto/googleapis/api/httpbody"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/metadata"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
@@ -52,7 +57,16 @@ func (s *server) Echo(_ context.Context, req *proto.EchoReq) (*proto.EchoRsp, er
|
||||
}
|
||||
|
||||
func (s *server) Signup(ctx context.Context, req *proto.SignupReq) (*proto.SignupRsp, error) {
|
||||
err := s.usersService.AddUser(ctx, req.Username, req.Email)
|
||||
ip, userAgent := clientInfoFromContext(ctx)
|
||||
err := s.usersService.AddUser(
|
||||
ctx,
|
||||
req.Username,
|
||||
req.Email,
|
||||
req.AcceptTerms,
|
||||
req.AcceptPrivacy,
|
||||
ip,
|
||||
userAgent,
|
||||
)
|
||||
if err != nil {
|
||||
return &proto.SignupRsp{
|
||||
Error: err.Error(),
|
||||
@@ -61,6 +75,115 @@ func (s *server) Signup(ctx context.Context, req *proto.SignupReq) (*proto.Signu
|
||||
return &proto.SignupRsp{}, nil
|
||||
}
|
||||
|
||||
func (s *server) DeleteAccount(ctx context.Context, req *proto.DeleteAccountReq) (*proto.DeleteAccountRsp, error) {
|
||||
claims, ok := ctx.Value("claims").(*processor_jwt.JWTClaims)
|
||||
if !ok {
|
||||
return nil, status.Errorf(codes.Unauthenticated, "claims is not provided")
|
||||
}
|
||||
// Пароль приходит HTTP-заголовком X-Password (пробрасывается матчером
|
||||
// в main.go), чтобы креденшел не попадал в URL и логи. Для прямых
|
||||
// gRPC-клиентов заголовок передаётся в metadata под тем же ключом.
|
||||
password := ""
|
||||
if md, ok := metadata.FromIncomingContext(ctx); ok {
|
||||
if values := md.Get("x-password"); len(values) > 0 {
|
||||
password = values[0]
|
||||
}
|
||||
}
|
||||
err := s.usersService.DeleteAccount(ctx, claims.UserID, password)
|
||||
if err != nil {
|
||||
return &proto.DeleteAccountRsp{
|
||||
Error: err.Error(),
|
||||
}, nil
|
||||
}
|
||||
return &proto.DeleteAccountRsp{}, nil
|
||||
}
|
||||
|
||||
// GetTerms возвращает текст Пользовательского соглашения (постоянный URL
|
||||
// /api/terms, на который ссылается форма регистрации при акцепте).
|
||||
func (s *server) GetTerms(_ context.Context, _ *proto.GetTermsReq) (*proto.GetTermsRsp, error) {
|
||||
text, err := docs.FS.ReadFile("TERMS.md")
|
||||
if err != nil {
|
||||
return &proto.GetTermsRsp{Error: "Не удалось получить документ"}, nil
|
||||
}
|
||||
return &proto.GetTermsRsp{Text: string(text)}, nil
|
||||
}
|
||||
|
||||
// GetPrivacy возвращает текст Политики конфиденциальности (постоянный URL
|
||||
// /api/privacy).
|
||||
func (s *server) GetPrivacy(_ context.Context, _ *proto.GetPrivacyReq) (*proto.GetPrivacyRsp, error) {
|
||||
text, err := docs.FS.ReadFile("PRIVACY.md")
|
||||
if err != nil {
|
||||
return &proto.GetPrivacyRsp{Error: "Не удалось получить документ"}, nil
|
||||
}
|
||||
return &proto.GetPrivacyRsp{Text: string(text)}, nil
|
||||
}
|
||||
|
||||
// GetConsent возвращает текст согласия на обработку персональных данных
|
||||
// (постоянный URL /api/consent).
|
||||
func (s *server) GetConsent(_ context.Context, _ *proto.GetConsentReq) (*proto.GetConsentRsp, error) {
|
||||
text, err := docs.FS.ReadFile("CONSENT.md")
|
||||
if err != nil {
|
||||
return &proto.GetConsentRsp{Error: "Не удалось получить документ"}, nil
|
||||
}
|
||||
return &proto.GetConsentRsp{Text: string(text)}, nil
|
||||
}
|
||||
|
||||
// clientInfoFromContext извлекает IP-адрес и user-agent из gRPC-метаданных.
|
||||
//
|
||||
// grpc-gateway пробрасывает HTTP-заголовки в метаданные так
|
||||
// (runtime.annotateContext / runtime.DefaultHeaderMatcher):
|
||||
// - x-forwarded-for — всегда, при этом gateway дописывает реальный RemoteAddr
|
||||
// клиента последним элементом списка;
|
||||
// - постоянные HTTP-заголовки (включая User-Agent) — с префиксом
|
||||
// grpcgateway-;
|
||||
// - остальные заголовки по умолчанию не пробрасываются: X-Real-IP и
|
||||
// X-Password включаются явным матчером в main.go.
|
||||
//
|
||||
// Полученный IP валидируется netip.ParseAddr: при прямой экспозиции gateway
|
||||
// клиент может подделать эти заголовки, поэтому в БД попадает только
|
||||
// корректный адрес.
|
||||
func clientInfoFromContext(ctx context.Context) (ip string, userAgent string) {
|
||||
md, ok := metadata.FromIncomingContext(ctx)
|
||||
if !ok {
|
||||
return "", ""
|
||||
}
|
||||
|
||||
// X-Real-IP от доверенного прокси — приоритетен: в этом случае последним
|
||||
// элементом X-Forwarded-For является адрес прокси, а не клиента.
|
||||
if values := md.Get("x-real-ip"); len(values) > 0 {
|
||||
if v := strings.TrimSpace(values[0]); v != "" {
|
||||
if _, err := netip.ParseAddr(v); err == nil {
|
||||
ip = v
|
||||
}
|
||||
}
|
||||
}
|
||||
if ip == "" {
|
||||
if values := md.Get("x-forwarded-for"); len(values) > 0 {
|
||||
parts := strings.Split(values[0], ",")
|
||||
// Идём с конца списка: последний элемент — реальный RemoteAddr
|
||||
// клиента (первые элементы мог подделать сам клиент).
|
||||
for i := len(parts) - 1; i >= 0; i-- {
|
||||
v := strings.TrimSpace(parts[i])
|
||||
if v == "" {
|
||||
continue
|
||||
}
|
||||
if _, err := netip.ParseAddr(v); err == nil {
|
||||
ip = v
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if values := md.Get("grpcgateway-user-agent"); len(values) > 0 {
|
||||
userAgent = values[0]
|
||||
} else if values := md.Get("user-agent"); len(values) > 0 {
|
||||
userAgent = values[0]
|
||||
}
|
||||
|
||||
return ip, userAgent
|
||||
}
|
||||
|
||||
func (s *server) RefreshPassword(ctx context.Context, req *proto.RefreshPasswordReq) (*proto.RefreshPasswordRsp, error) {
|
||||
err := s.usersService.RefreshPassword(ctx, req.Email)
|
||||
if err != nil {
|
||||
@@ -299,6 +422,8 @@ func (s *server) UpdateScenario(ctx context.Context, req *proto.UpdateScenarioRe
|
||||
req.Name,
|
||||
req.Description,
|
||||
req.Image,
|
||||
claims.UserID,
|
||||
roles.HasRole(claims, roles.Admin),
|
||||
)
|
||||
if err != nil {
|
||||
return &proto.UpdateScenarioRsp{
|
||||
@@ -319,6 +444,8 @@ func (s *server) PublicScenario(ctx context.Context, req *proto.PublicScenarioRe
|
||||
ctx,
|
||||
int(req.Id),
|
||||
"public",
|
||||
claims.UserID,
|
||||
roles.HasRole(claims, roles.Admin), // публикация = момент отчуждения прав
|
||||
)
|
||||
if err != nil {
|
||||
return &proto.PublicScenarioRsp{
|
||||
@@ -331,7 +458,10 @@ func (s *server) PublicScenario(ctx context.Context, req *proto.PublicScenarioRe
|
||||
|
||||
func (s *server) DraftScenario(ctx context.Context, req *proto.DraftScenarioReq) (*proto.DraftScenarioRsp, error) {
|
||||
claims := ctx.Value("claims").(*processor_jwt.JWTClaims)
|
||||
if !roles.HasRole(claims, roles.Author) {
|
||||
// Снять с публикации опубликованный сценарий может только админ:
|
||||
// исключительное право отчуждено Оператору в момент публикации
|
||||
// (п. 7.2 Пользовательского соглашения, ст. 1234, 1269 ГК РФ).
|
||||
if !roles.HasRole(claims, roles.Admin) {
|
||||
return nil, status.Errorf(codes.PermissionDenied, "permission denied")
|
||||
}
|
||||
|
||||
@@ -339,6 +469,8 @@ func (s *server) DraftScenario(ctx context.Context, req *proto.DraftScenarioReq)
|
||||
ctx,
|
||||
int(req.Id),
|
||||
"draft",
|
||||
claims.UserID,
|
||||
true,
|
||||
)
|
||||
if err != nil {
|
||||
return &proto.DraftScenarioRsp{
|
||||
@@ -355,9 +487,13 @@ func (s *server) DeleteScenario(ctx context.Context, req *proto.DeleteScenarioRe
|
||||
return nil, status.Errorf(codes.PermissionDenied, "permission denied")
|
||||
}
|
||||
|
||||
// Автор не может удалить опубликованный сценарий (исключительное право
|
||||
// отчуждено); админ может.
|
||||
err := s.scenarioService.DeleteScenarioByID(
|
||||
ctx,
|
||||
int(req.Id),
|
||||
claims.UserID,
|
||||
roles.HasRole(claims, roles.Admin),
|
||||
)
|
||||
if err != nil {
|
||||
return &proto.DeleteScenarioRsp{
|
||||
@@ -378,6 +514,8 @@ func (s *server) AddScenarioPlace(ctx context.Context, req *proto.AddScenarioPla
|
||||
ctx,
|
||||
int(req.Id),
|
||||
convertPlace(req.Place),
|
||||
claims.UserID,
|
||||
roles.HasRole(claims, roles.Admin),
|
||||
)
|
||||
if err != nil {
|
||||
return &proto.AddScenarioPlaceRsp{
|
||||
@@ -399,6 +537,8 @@ func (s *server) UpdateScenarioPlace(ctx context.Context, req *proto.UpdateScena
|
||||
int(req.Id),
|
||||
req.Code,
|
||||
convertPlace(req.Place),
|
||||
claims.UserID,
|
||||
roles.HasRole(claims, roles.Admin),
|
||||
)
|
||||
if err != nil {
|
||||
return &proto.UpdateScenarioPlaceRsp{
|
||||
@@ -419,6 +559,8 @@ func (s *server) DeleteScenarioPlace(ctx context.Context, req *proto.DeleteScena
|
||||
ctx,
|
||||
int(req.Id),
|
||||
req.Code,
|
||||
claims.UserID,
|
||||
roles.HasRole(claims, roles.Admin),
|
||||
)
|
||||
if err != nil {
|
||||
return &proto.DeleteScenarioPlaceRsp{
|
||||
@@ -431,7 +573,7 @@ func (s *server) DeleteScenarioPlace(ctx context.Context, req *proto.DeleteScena
|
||||
|
||||
func (s *server) AddGame(ctx context.Context, req *proto.AddGameReq) (*proto.AddGameRsp, error) {
|
||||
claims := ctx.Value("claims").(*processor_jwt.JWTClaims)
|
||||
if !roles.HasRole(claims, roles.Organizer) {
|
||||
if !roles.HasRole(claims, roles.Organizer) && !roles.HasRole(claims, roles.Author) {
|
||||
return nil, status.Errorf(codes.PermissionDenied, "permission denied")
|
||||
}
|
||||
|
||||
@@ -459,7 +601,7 @@ func (s *server) AddGame(ctx context.Context, req *proto.AddGameReq) (*proto.Add
|
||||
|
||||
func (s *server) DeleteGame(ctx context.Context, req *proto.DeleteGameReq) (*proto.DeleteGameRsp, error) {
|
||||
claims := ctx.Value("claims").(*processor_jwt.JWTClaims)
|
||||
if !roles.HasRole(claims, roles.Organizer) {
|
||||
if !roles.HasRole(claims, roles.Organizer) && !roles.HasRole(claims, roles.Author) {
|
||||
return nil, status.Errorf(codes.PermissionDenied, "permission denied")
|
||||
}
|
||||
|
||||
@@ -478,11 +620,11 @@ func (s *server) DeleteGame(ctx context.Context, req *proto.DeleteGameReq) (*pro
|
||||
|
||||
func (s *server) GetGame(ctx context.Context, req *proto.GetGameReq) (*proto.GetGameRsp, error) {
|
||||
claims := ctx.Value("claims").(*processor_jwt.JWTClaims)
|
||||
if !roles.HasRole(claims, roles.Organizer) {
|
||||
if !roles.HasRole(claims, roles.Organizer) && !roles.HasRole(claims, roles.Author) {
|
||||
return nil, status.Errorf(codes.PermissionDenied, "permission denied")
|
||||
}
|
||||
|
||||
game, err := s.gameService.GetGame(ctx, int(req.Id))
|
||||
game, err := s.gameService.GetFullGame(ctx, int(req.Id))
|
||||
if err != nil {
|
||||
return &proto.GetGameRsp{
|
||||
Error: err.Error(),
|
||||
@@ -496,7 +638,7 @@ func (s *server) GetGame(ctx context.Context, req *proto.GetGameReq) (*proto.Get
|
||||
|
||||
func (s *server) GetGames(ctx context.Context, req *proto.GetGamesReq) (*proto.GetGamesRsp, error) {
|
||||
claims := ctx.Value("claims").(*processor_jwt.JWTClaims)
|
||||
if !roles.HasRole(claims, roles.Organizer) {
|
||||
if !roles.HasRole(claims, roles.Organizer) && !roles.HasRole(claims, roles.Author) {
|
||||
return nil, status.Errorf(codes.PermissionDenied, "permission denied")
|
||||
}
|
||||
|
||||
@@ -514,7 +656,7 @@ func (s *server) GetGames(ctx context.Context, req *proto.GetGamesReq) (*proto.G
|
||||
|
||||
func (s *server) UpdateGame(ctx context.Context, req *proto.UpdateGameReq) (*proto.UpdateGameRsp, error) {
|
||||
claims := ctx.Value("claims").(*processor_jwt.JWTClaims)
|
||||
if !roles.HasRole(claims, roles.Organizer) {
|
||||
if !roles.HasRole(claims, roles.Organizer) && !roles.HasRole(claims, roles.Author) {
|
||||
return nil, status.Errorf(codes.PermissionDenied, "permission denied")
|
||||
}
|
||||
|
||||
@@ -539,46 +681,218 @@ func (s *server) UpdateGame(ctx context.Context, req *proto.UpdateGameReq) (*pro
|
||||
return &proto.UpdateGameRsp{}, nil
|
||||
}
|
||||
|
||||
func (s *server) AddTeam(ctx context.Context, req *proto.AddTeamReq) (*proto.AddTeamRsp, error) {
|
||||
func (s *server) StartGame(ctx context.Context, req *proto.StartGameReq) (*proto.StartGameRsp, error) {
|
||||
claims := ctx.Value("claims").(*processor_jwt.JWTClaims)
|
||||
if !roles.HasRole(claims, roles.Organizer) {
|
||||
if !roles.HasRole(claims, roles.Organizer) && !roles.HasRole(claims, roles.Author) {
|
||||
return nil, status.Errorf(codes.PermissionDenied, "permission denied")
|
||||
}
|
||||
|
||||
panic("unimplemented")
|
||||
err := s.gameService.StartGame(ctx, int(req.Id))
|
||||
if err != nil {
|
||||
return &proto.StartGameRsp{
|
||||
Error: err.Error(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
return &proto.StartGameRsp{}, nil
|
||||
}
|
||||
|
||||
func (s *server) PauseGame(ctx context.Context, req *proto.PauseGameReq) (*proto.PauseGameRsp, error) {
|
||||
claims := ctx.Value("claims").(*processor_jwt.JWTClaims)
|
||||
if !roles.HasRole(claims, roles.Organizer) && !roles.HasRole(claims, roles.Author) {
|
||||
return nil, status.Errorf(codes.PermissionDenied, "permission denied")
|
||||
}
|
||||
|
||||
err := s.gameService.PauseGame(ctx, int(req.Id))
|
||||
if err != nil {
|
||||
return &proto.PauseGameRsp{
|
||||
Error: err.Error(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
return &proto.PauseGameRsp{}, nil
|
||||
}
|
||||
|
||||
func (s *server) PlayGame(ctx context.Context, req *proto.PlayGameReq) (*proto.PlayGameRsp, error) {
|
||||
claims := ctx.Value("claims").(*processor_jwt.JWTClaims)
|
||||
if !roles.HasRole(claims, roles.Organizer) && !roles.HasRole(claims, roles.Author) {
|
||||
return nil, status.Errorf(codes.PermissionDenied, "permission denied")
|
||||
}
|
||||
|
||||
err := s.gameService.PlayGame(ctx, int(req.Id))
|
||||
if err != nil {
|
||||
return &proto.PlayGameRsp{
|
||||
Error: err.Error(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
return &proto.PlayGameRsp{}, nil
|
||||
}
|
||||
|
||||
func (s *server) FinishGame(ctx context.Context, req *proto.FinishGameReq) (*proto.FinishGameRsp, error) {
|
||||
claims := ctx.Value("claims").(*processor_jwt.JWTClaims)
|
||||
if !roles.HasRole(claims, roles.Organizer) && !roles.HasRole(claims, roles.Author) {
|
||||
return nil, status.Errorf(codes.PermissionDenied, "permission denied")
|
||||
}
|
||||
|
||||
err := s.gameService.FinishGame(ctx, int(req.Id))
|
||||
if err != nil {
|
||||
return &proto.FinishGameRsp{
|
||||
Error: err.Error(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
return &proto.FinishGameRsp{}, nil
|
||||
}
|
||||
|
||||
func (s *server) ResetGame(ctx context.Context, req *proto.ResetGameReq) (*proto.ResetGameRsp, error) {
|
||||
claims := ctx.Value("claims").(*processor_jwt.JWTClaims)
|
||||
if !roles.HasRole(claims, roles.Organizer) && !roles.HasRole(claims, roles.Author) {
|
||||
return nil, status.Errorf(codes.PermissionDenied, "permission denied")
|
||||
}
|
||||
|
||||
err := s.gameService.ResetGame(ctx, int(req.Id))
|
||||
if err != nil {
|
||||
return &proto.ResetGameRsp{
|
||||
Error: err.Error(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
return &proto.ResetGameRsp{}, nil
|
||||
}
|
||||
|
||||
func (s *server) AddTeam(ctx context.Context, req *proto.AddTeamReq) (*proto.AddTeamRsp, error) {
|
||||
claims := ctx.Value("claims").(*processor_jwt.JWTClaims)
|
||||
if !roles.HasRole(claims, roles.Organizer) && !roles.HasRole(claims, roles.Author) {
|
||||
return nil, status.Errorf(codes.PermissionDenied, "permission denied")
|
||||
}
|
||||
|
||||
team, err := s.gameService.AddTeam(
|
||||
ctx,
|
||||
claims.UserID,
|
||||
int(req.GameId),
|
||||
req.Name,
|
||||
)
|
||||
if err != nil {
|
||||
return &proto.AddTeamRsp{
|
||||
Error: err.Error(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
return &proto.AddTeamRsp{
|
||||
Id: int32(team.ID),
|
||||
Password: team.Password,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *server) UpdateTeam(ctx context.Context, req *proto.UpdateTeamReq) (*proto.UpdateTeamRsp, error) {
|
||||
claims := ctx.Value("claims").(*processor_jwt.JWTClaims)
|
||||
if !roles.HasRole(claims, roles.Organizer) {
|
||||
if !roles.HasRole(claims, roles.Organizer) && !roles.HasRole(claims, roles.Author) {
|
||||
return nil, status.Errorf(codes.PermissionDenied, "permission denied")
|
||||
}
|
||||
|
||||
panic("unimplemented")
|
||||
err := s.gameService.UpdateTeam(
|
||||
ctx,
|
||||
int(req.Id),
|
||||
req.Name,
|
||||
)
|
||||
if err != nil {
|
||||
return &proto.UpdateTeamRsp{
|
||||
Error: err.Error(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
return &proto.UpdateTeamRsp{}, nil
|
||||
}
|
||||
|
||||
func (s *server) DeleteTeam(ctx context.Context, req *proto.DeleteTeamReq) (*proto.DeleteTeamRsp, error) {
|
||||
claims := ctx.Value("claims").(*processor_jwt.JWTClaims)
|
||||
if !roles.HasRole(claims, roles.Organizer) {
|
||||
if !roles.HasRole(claims, roles.Organizer) && !roles.HasRole(claims, roles.Author) {
|
||||
return nil, status.Errorf(codes.PermissionDenied, "permission denied")
|
||||
}
|
||||
|
||||
panic("unimplemented")
|
||||
err := s.gameService.DeleteTeam(
|
||||
ctx,
|
||||
int(req.Id),
|
||||
)
|
||||
if err != nil {
|
||||
return &proto.DeleteTeamRsp{
|
||||
Error: err.Error(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
return &proto.DeleteTeamRsp{}, nil
|
||||
}
|
||||
|
||||
func (s *server) GiveTeamApplications(ctx context.Context, req *proto.GiveTeamApplicationsReq) (*proto.GiveTeamApplicationsRsp, error) {
|
||||
claims := ctx.Value("claims").(*processor_jwt.JWTClaims)
|
||||
if !roles.HasRole(claims, roles.Organizer) {
|
||||
if !roles.HasRole(claims, roles.Organizer) && !roles.HasRole(claims, roles.Author) {
|
||||
return nil, status.Errorf(codes.PermissionDenied, "permission denied")
|
||||
}
|
||||
|
||||
panic("unimplemented")
|
||||
err := s.gameService.GiveTeamApplications(
|
||||
ctx,
|
||||
int(req.Id),
|
||||
req.Name,
|
||||
)
|
||||
if err != nil {
|
||||
return &proto.GiveTeamApplicationsRsp{
|
||||
Error: err.Error(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
return &proto.GiveTeamApplicationsRsp{}, nil
|
||||
}
|
||||
|
||||
func (s *server) GetTeamActions(ctx context.Context, req *proto.GetTeamActionsReq) (*proto.GetTeamActionsRsp, error) {
|
||||
panic("unimplemented")
|
||||
func (s *server) GetTeamStory(ctx context.Context, req *proto.GetTeamStoryReq) (*proto.GetTeamStoryRsp, error) {
|
||||
story, game, err := s.gameService.GetTeamActions(
|
||||
ctx,
|
||||
int(req.Id),
|
||||
req.Password,
|
||||
)
|
||||
if err != nil {
|
||||
return &proto.GetTeamStoryRsp{
|
||||
Error: err.Error(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
return &proto.GetTeamStoryRsp{
|
||||
Story: mapStory(story),
|
||||
Game: mapGame(game),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *server) AddTeamAction(ctx context.Context, req *proto.AddTeamActionReq) (*proto.AddTeamActionRsp, error) {
|
||||
panic("unimplemented")
|
||||
err := s.gameService.AddTeamAction(
|
||||
ctx,
|
||||
int(req.Id),
|
||||
req.Password,
|
||||
req.Code,
|
||||
)
|
||||
if err != nil {
|
||||
return &proto.AddTeamActionRsp{
|
||||
Error: err.Error(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
return &proto.AddTeamActionRsp{}, nil
|
||||
}
|
||||
|
||||
func (s *server) DeleteLastTeamAction(ctx context.Context, req *proto.DeleteLastTeamActionReq) (*proto.DeleteLastTeamActionRsp, error) {
|
||||
claims := ctx.Value("claims").(*processor_jwt.JWTClaims)
|
||||
if !roles.HasRole(claims, roles.Author) {
|
||||
return nil, status.Errorf(codes.PermissionDenied, "permission denied")
|
||||
}
|
||||
|
||||
err := s.gameService.DeleteLastTeamAction(
|
||||
ctx,
|
||||
int(req.Id),
|
||||
)
|
||||
if err != nil {
|
||||
return &proto.DeleteLastTeamActionRsp{
|
||||
Error: err.Error(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
return &proto.DeleteLastTeamActionRsp{}, nil
|
||||
}
|
||||
|
||||
@@ -11,6 +11,10 @@ func mapTeam(
|
||||
return &proto.Team{
|
||||
Id: int32(o.ID),
|
||||
Name: o.Name,
|
||||
Status: o.Status,
|
||||
Password: o.Password,
|
||||
ActionsCount: int32(o.ActionsCount),
|
||||
Applications: mapApplications(o.Applications),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,13 @@ import (
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
)
|
||||
|
||||
// mapUser преобразует пользователя в proto-структуру. Возвращает nil для
|
||||
// обезличенного автора (аккаунт удалён, контент сохранён) — клиент отображает
|
||||
// такого автора как «Автор удалён».
|
||||
func mapUser(o *repos.User) *proto.User {
|
||||
if o == nil {
|
||||
return nil
|
||||
}
|
||||
return &proto.User{
|
||||
Id: int32(o.ID),
|
||||
Username: o.Username,
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"evening_detective_server/internal/repos"
|
||||
)
|
||||
|
||||
func TestMapUser(t *testing.T) {
|
||||
t.Run("nil — обезличенный автор, возвращает nil", func(t *testing.T) {
|
||||
if got := mapUser(nil); got != nil {
|
||||
t.Errorf("mapUser(nil) = %v, want nil", got)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("обычный пользователь маппится полностью", func(t *testing.T) {
|
||||
u := &repos.User{
|
||||
ID: 42,
|
||||
Username: "detective",
|
||||
Email: "d@example.com",
|
||||
Roles: []string{"user"},
|
||||
IsActive: true,
|
||||
CreatedAt: time.Date(2026, 8, 18, 12, 0, 0, 0, time.UTC),
|
||||
}
|
||||
got := mapUser(u)
|
||||
if got == nil {
|
||||
t.Fatal("mapUser вернул nil для не-nil пользователя")
|
||||
}
|
||||
if got.Id != 42 || got.Username != "detective" || got.Email != "d@example.com" {
|
||||
t.Errorf("неожиданный маппинг: %+v", got)
|
||||
}
|
||||
if len(got.Roles) != 1 || got.Roles[0] != "user" {
|
||||
t.Errorf("roles = %v, want [user]", got.Roles)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -20,7 +20,6 @@ func NewGenerator(length int) IPasswordGenerator {
|
||||
}
|
||||
|
||||
func (g *generator) Generate() (string, error) {
|
||||
return "1", nil
|
||||
password := make([]byte, g.length)
|
||||
for i := range password {
|
||||
num, err := rand.Int(rand.Reader, big.NewInt(int64(len(charset))))
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
package storytelling
|
||||
|
||||
type ICleaner interface {
|
||||
ClearCode(code string) string
|
||||
ClearText(text string) string
|
||||
}
|
||||
@@ -1,9 +1,15 @@
|
||||
package storytelling
|
||||
|
||||
type story struct{}
|
||||
type story struct {
|
||||
cleaner ICleaner
|
||||
}
|
||||
|
||||
func NewStory() IStory {
|
||||
return &story{}
|
||||
func NewStory(
|
||||
cleaner ICleaner,
|
||||
) IStory {
|
||||
return &story{
|
||||
cleaner: cleaner,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *story) GetStory(
|
||||
@@ -69,7 +75,7 @@ func (s *story) findPlace(
|
||||
code string,
|
||||
) (*Place, bool) {
|
||||
for _, place := range scenario.Places {
|
||||
if place.Code == code {
|
||||
if s.cleaner.ClearCode(place.Code) == s.cleaner.ClearCode(code) {
|
||||
return place, true
|
||||
}
|
||||
}
|
||||
@@ -95,7 +101,7 @@ func (s *story) findResultPlace(
|
||||
}
|
||||
}
|
||||
for _, place := range scenario.Places {
|
||||
if place.Code == code {
|
||||
if s.cleaner.ClearCode(place.Code) == s.cleaner.ClearCode(code) {
|
||||
if place.Hidden && !open {
|
||||
return nil, false
|
||||
}
|
||||
@@ -130,7 +136,7 @@ func (s *story) mapPlace(
|
||||
applications = append(
|
||||
applications,
|
||||
&Application{
|
||||
Name: application.Name,
|
||||
Name: s.cleaner.ClearText(application.Name),
|
||||
Image: application.Image,
|
||||
},
|
||||
)
|
||||
@@ -175,7 +181,7 @@ func (s *story) mapPlace(
|
||||
return &Place{
|
||||
Code: place.Code,
|
||||
Name: place.Name,
|
||||
Text: place.Text,
|
||||
Text: s.cleaner.ClearText(place.Text),
|
||||
Image: place.Image,
|
||||
Applications: applications,
|
||||
Doors: doors,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package storytelling
|
||||
|
||||
import (
|
||||
"evening_detective_server/internal/modules/cleaner"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -49,6 +50,50 @@ func Test_story_GetStory(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Получение точки проверка регистра",
|
||||
scenario: &Story{
|
||||
Places: []*Place{
|
||||
{
|
||||
Code: "Ы-1",
|
||||
Name: "Название точки",
|
||||
Text: "Текст точки.",
|
||||
},
|
||||
},
|
||||
},
|
||||
codes: []string{"ы1"},
|
||||
want: &Story{
|
||||
Places: []*Place{
|
||||
{
|
||||
Code: "Ы-1",
|
||||
Name: "Название точки",
|
||||
Text: "Текст точки.",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Получение точки проверка очистки ссылок",
|
||||
scenario: &Story{
|
||||
Places: []*Place{
|
||||
{
|
||||
Code: "Ы-1",
|
||||
Name: "Название точки",
|
||||
Text: "Текст точки.([Ы-2])",
|
||||
},
|
||||
},
|
||||
},
|
||||
codes: []string{"Ы-1"},
|
||||
want: &Story{
|
||||
Places: []*Place{
|
||||
{
|
||||
Code: "Ы-1",
|
||||
Name: "Название точки",
|
||||
Text: "Текст точки.",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Получение 2 точек",
|
||||
scenario: &Story{
|
||||
@@ -709,7 +754,7 @@ func Test_story_GetStory(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := NewStory().GetStory(tt.scenario, tt.codes)
|
||||
got := NewStory(cleaner.NewCleaner()).GetStory(tt.scenario, tt.codes)
|
||||
assert.Equal(t, got, tt.want)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
package actions_repo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrActionsNotFound = errors.New("Ход не найден")
|
||||
)
|
||||
|
||||
type ActionsRepo struct {
|
||||
pool *pgxpool.Pool
|
||||
}
|
||||
|
||||
func NewActionsRepo(pool *pgxpool.Pool) *ActionsRepo {
|
||||
return &ActionsRepo{
|
||||
pool: pool,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *ActionsRepo) AddAction(ctx context.Context, teamId int, code string) (int, error) {
|
||||
id := 0
|
||||
err := r.pool.QueryRow(
|
||||
ctx,
|
||||
`INSERT INTO actions (code, team_id)
|
||||
VALUES ($1, $2)
|
||||
RETURNING id`,
|
||||
code,
|
||||
teamId,
|
||||
).Scan(&id)
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return id, nil
|
||||
}
|
||||
|
||||
func (r *ActionsRepo) GetActionsByTeamID(ctx context.Context, teamId int) ([]string, error) {
|
||||
rows, err := r.pool.Query(
|
||||
ctx,
|
||||
`SELECT
|
||||
code
|
||||
FROM actions
|
||||
WHERE team_id = $1
|
||||
ORDER BY created_at ASC`,
|
||||
teamId,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var codes []string
|
||||
for rows.Next() {
|
||||
code := ""
|
||||
err := rows.Scan(
|
||||
&code,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
codes = append(codes, code)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return codes, nil
|
||||
}
|
||||
|
||||
func (r *ActionsRepo) GetActionsCountByTeamIDs(ctx context.Context, teamIds []int) (map[int]int, error) {
|
||||
rows, err := r.pool.Query(
|
||||
ctx,
|
||||
`SELECT
|
||||
team_id,
|
||||
count(*)
|
||||
FROM actions
|
||||
WHERE team_id = ANY($1)
|
||||
GROUP BY team_id`,
|
||||
teamIds,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
res := make(map[int]int, len(teamIds))
|
||||
for rows.Next() {
|
||||
id := 0
|
||||
count := 0
|
||||
err := rows.Scan(
|
||||
&id,
|
||||
&count,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res[id] = count
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (r *ActionsRepo) DeleteLastActionByTeamId(ctx context.Context, teamId int) error {
|
||||
tag, err := r.pool.Exec(
|
||||
ctx,
|
||||
`DELETE FROM actions
|
||||
WHERE id IN (
|
||||
SELECT id
|
||||
FROM actions
|
||||
WHERE team_id = $1
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 1
|
||||
)`,
|
||||
teamId,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if tag.RowsAffected() == 0 {
|
||||
return ErrActionsNotFound
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package repos
|
||||
|
||||
type Application struct {
|
||||
Name string
|
||||
Image string
|
||||
TeamId int
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
package applications_repo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"evening_detective_server/internal/repos"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrApplicationNotFound = errors.New("Улика не найдена")
|
||||
)
|
||||
|
||||
type ApplicationsRepo struct {
|
||||
pool *pgxpool.Pool
|
||||
}
|
||||
|
||||
func NewApplicationsRepo(pool *pgxpool.Pool) *ApplicationsRepo {
|
||||
return &ApplicationsRepo{
|
||||
pool: pool,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *ApplicationsRepo) AddApplication(
|
||||
ctx context.Context,
|
||||
teamId int,
|
||||
name string,
|
||||
image string,
|
||||
) (int, error) {
|
||||
id := 0
|
||||
err := r.pool.QueryRow(
|
||||
ctx,
|
||||
`INSERT INTO applications (name, image, team_id)
|
||||
VALUES ($1, $2, $3)
|
||||
RETURNING id`,
|
||||
name,
|
||||
image,
|
||||
teamId,
|
||||
).Scan(&id)
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return id, nil
|
||||
}
|
||||
|
||||
func (r *ApplicationsRepo) GetApplicationsByTeamIDsAndState(
|
||||
ctx context.Context,
|
||||
teamIds []int,
|
||||
state string,
|
||||
) (map[int][]*repos.Application, error) {
|
||||
rows, err := r.pool.Query(
|
||||
ctx,
|
||||
`SELECT
|
||||
name,
|
||||
image,
|
||||
team_id
|
||||
FROM applications
|
||||
WHERE team_id = ANY($1) and status = $2`,
|
||||
teamIds,
|
||||
state,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
res := make(map[int][]*repos.Application, len(teamIds))
|
||||
for rows.Next() {
|
||||
application := &repos.Application{}
|
||||
err := rows.Scan(
|
||||
&application.Name,
|
||||
&application.Image,
|
||||
&application.TeamId,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res[application.TeamId] = append(res[application.TeamId], application)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (r *ApplicationsRepo) UpdateApplicationStatus(
|
||||
ctx context.Context,
|
||||
teamId int,
|
||||
name string,
|
||||
status string,
|
||||
) error {
|
||||
tag, err := r.pool.Exec(
|
||||
ctx,
|
||||
`UPDATE applications
|
||||
SET
|
||||
status = $1
|
||||
WHERE team_id = $2 and name = $3`,
|
||||
status,
|
||||
teamId,
|
||||
name,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if tag.RowsAffected() == 0 {
|
||||
return ErrApplicationNotFound
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *ApplicationsRepo) DeleteApplicationByTeamIdAndName(ctx context.Context, teamId int, name string) error {
|
||||
tag, err := r.pool.Exec(
|
||||
ctx,
|
||||
`DELETE FROM applications
|
||||
WHERE id IN (
|
||||
SELECT id
|
||||
FROM applications
|
||||
WHERE team_id = $1 and name = $2
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 1
|
||||
)`,
|
||||
teamId,
|
||||
name,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if tag.RowsAffected() == 0 {
|
||||
return ErrApplicationNotFound
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -13,4 +13,6 @@ type Game struct {
|
||||
ScenarioId int
|
||||
Scenario *Scenario
|
||||
Teams []*Team
|
||||
StartedAt *time.Time
|
||||
EndedAt *time.Time
|
||||
}
|
||||
|
||||
@@ -62,7 +62,9 @@ func (r *GamesRepo) GetGameByID(
|
||||
games.description,
|
||||
games.start_at,
|
||||
games.status,
|
||||
games.scenario_id
|
||||
games.scenario_id,
|
||||
games.started_at,
|
||||
games.ended_at
|
||||
FROM games
|
||||
WHERE id = $1`,
|
||||
id,
|
||||
@@ -74,6 +76,8 @@ func (r *GamesRepo) GetGameByID(
|
||||
&game.StartAt,
|
||||
&game.Status,
|
||||
&game.ScenarioId,
|
||||
&game.StartedAt,
|
||||
&game.EndedAt,
|
||||
)
|
||||
if err != nil {
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
@@ -177,3 +181,102 @@ func (r *GamesRepo) UpdateGame(
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *GamesRepo) StartGame(ctx context.Context, id int) error {
|
||||
tag, err := r.pool.Exec(
|
||||
ctx,
|
||||
`UPDATE games
|
||||
SET
|
||||
status = 'run',
|
||||
started_at = NOW(),
|
||||
updated_at = NOW()
|
||||
WHERE id = $1`,
|
||||
id,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if tag.RowsAffected() == 0 {
|
||||
return ErrGameNotFound
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *GamesRepo) PauseGame(ctx context.Context, id int) error {
|
||||
tag, err := r.pool.Exec(
|
||||
ctx,
|
||||
`UPDATE games
|
||||
SET
|
||||
status = 'pause',
|
||||
updated_at = NOW()
|
||||
WHERE id = $1`,
|
||||
id,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if tag.RowsAffected() == 0 {
|
||||
return ErrGameNotFound
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *GamesRepo) PlayGame(ctx context.Context, id int) error {
|
||||
tag, err := r.pool.Exec(
|
||||
ctx,
|
||||
`UPDATE games
|
||||
SET
|
||||
status = 'run',
|
||||
updated_at = NOW()
|
||||
WHERE id = $1`,
|
||||
id,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if tag.RowsAffected() == 0 {
|
||||
return ErrGameNotFound
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *GamesRepo) FinishGame(ctx context.Context, id int) error {
|
||||
tag, err := r.pool.Exec(
|
||||
ctx,
|
||||
`UPDATE games
|
||||
SET
|
||||
status = 'finish',
|
||||
ended_at = NOW(),
|
||||
updated_at = NOW()
|
||||
WHERE id = $1`,
|
||||
id,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if tag.RowsAffected() == 0 {
|
||||
return ErrGameNotFound
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *GamesRepo) ResetGame(ctx context.Context, id int) error {
|
||||
tag, err := r.pool.Exec(
|
||||
ctx,
|
||||
`UPDATE games
|
||||
SET
|
||||
status = 'draft',
|
||||
started_at = DEFAULT,
|
||||
ended_at = DEFAULT,
|
||||
updated_at = NOW()
|
||||
WHERE id = $1`,
|
||||
id,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if tag.RowsAffected() == 0 {
|
||||
return ErrGameNotFound
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -75,17 +75,17 @@ func (r *ScenariosRepo) GetScenariosByAuthorID(
|
||||
|
||||
var scenarios []*repos.Scenario
|
||||
for rows.Next() {
|
||||
scenario := &repos.Scenario{
|
||||
Author: &repos.User{},
|
||||
}
|
||||
scenario := &repos.Scenario{}
|
||||
var authorID *int
|
||||
var authorUsername *string
|
||||
err := rows.Scan(
|
||||
&scenario.ID,
|
||||
&scenario.Name,
|
||||
&scenario.Description,
|
||||
&scenario.Image,
|
||||
&scenario.Scenario,
|
||||
&scenario.Author.ID,
|
||||
&scenario.Author.Username,
|
||||
&authorID,
|
||||
&authorUsername,
|
||||
&scenario.Status,
|
||||
&scenario.UpdatedAt,
|
||||
&scenario.CreatedAt,
|
||||
@@ -94,6 +94,9 @@ func (r *ScenariosRepo) GetScenariosByAuthorID(
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if authorID != nil && authorUsername != nil {
|
||||
scenario.Author = &repos.User{ID: *authorID, Username: *authorUsername}
|
||||
}
|
||||
scenarios = append(scenarios, scenario)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
@@ -134,17 +137,17 @@ func (r *ScenariosRepo) GetScenariosByStatus(
|
||||
|
||||
var scenarios []*repos.Scenario
|
||||
for rows.Next() {
|
||||
scenario := &repos.Scenario{
|
||||
Author: &repos.User{},
|
||||
}
|
||||
scenario := &repos.Scenario{}
|
||||
var authorID *int
|
||||
var authorUsername *string
|
||||
err := rows.Scan(
|
||||
&scenario.ID,
|
||||
&scenario.Name,
|
||||
&scenario.Description,
|
||||
&scenario.Image,
|
||||
&scenario.Scenario,
|
||||
&scenario.Author.ID,
|
||||
&scenario.Author.Username,
|
||||
&authorID,
|
||||
&authorUsername,
|
||||
&scenario.Status,
|
||||
&scenario.UpdatedAt,
|
||||
&scenario.CreatedAt,
|
||||
@@ -153,6 +156,9 @@ func (r *ScenariosRepo) GetScenariosByStatus(
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if authorID != nil && authorUsername != nil {
|
||||
scenario.Author = &repos.User{ID: *authorID, Username: *authorUsername}
|
||||
}
|
||||
scenarios = append(scenarios, scenario)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
@@ -166,9 +172,9 @@ func (r *ScenariosRepo) GetScenarioByID(
|
||||
ctx context.Context,
|
||||
id int,
|
||||
) (*repos.Scenario, error) {
|
||||
scenario := &repos.Scenario{
|
||||
Author: &repos.User{},
|
||||
}
|
||||
scenario := &repos.Scenario{}
|
||||
var authorID *int
|
||||
var authorUsername *string
|
||||
row := r.pool.QueryRow(
|
||||
ctx,
|
||||
`SELECT
|
||||
@@ -195,8 +201,8 @@ func (r *ScenariosRepo) GetScenarioByID(
|
||||
&scenario.Description,
|
||||
&scenario.Image,
|
||||
&scenario.Scenario,
|
||||
&scenario.Author.ID,
|
||||
&scenario.Author.Username,
|
||||
&authorID,
|
||||
&authorUsername,
|
||||
&scenario.Status,
|
||||
&scenario.UpdatedAt,
|
||||
&scenario.CreatedAt,
|
||||
@@ -210,9 +216,15 @@ func (r *ScenariosRepo) GetScenarioByID(
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Автор может отсутствовать (обезличен после удаления аккаунта).
|
||||
if authorID != nil && authorUsername != nil {
|
||||
scenario.Author = &repos.User{ID: *authorID, Username: *authorUsername}
|
||||
}
|
||||
|
||||
return scenario, nil
|
||||
}
|
||||
|
||||
// UpdateScenarioByID меняет основные поля сценария.
|
||||
func (r *ScenariosRepo) UpdateScenarioByID(
|
||||
ctx context.Context,
|
||||
id int,
|
||||
@@ -243,50 +255,61 @@ func (r *ScenariosRepo) UpdateScenarioByID(
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateScenarioStatusByID меняет статус сценария. allowPublished=true
|
||||
// разрешает менять статус опубликованного сценария (только для админа).
|
||||
// Возвращает false, если строка не обновлена (например, не-админ пытается
|
||||
// снять с публикации опубликованный сценарий). published_at фиксируется
|
||||
// только при ПЕРВОЙ публикации: повторная публикация не двигает «момент
|
||||
// отчуждения» исключительного права (п. 7.2 Пользовательского соглашения).
|
||||
func (r *ScenariosRepo) UpdateScenarioStatusByID(
|
||||
ctx context.Context,
|
||||
id int,
|
||||
status string,
|
||||
) error {
|
||||
allowPublished bool,
|
||||
) (bool, error) {
|
||||
tag, err := r.pool.Exec(
|
||||
ctx,
|
||||
`UPDATE scenarios
|
||||
SET
|
||||
status = $1,
|
||||
published_at = NOW(),
|
||||
published_at = CASE
|
||||
WHEN $1 = 'public' AND published_at IS NULL THEN NOW()
|
||||
ELSE published_at
|
||||
END,
|
||||
updated_at = NOW()
|
||||
WHERE id = $2`,
|
||||
WHERE id = $2 AND ($3 OR status <> 'public')`,
|
||||
status,
|
||||
id,
|
||||
allowPublished,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
return false, err
|
||||
}
|
||||
if tag.RowsAffected() == 0 {
|
||||
return ErrScenarioNotFound
|
||||
}
|
||||
return nil
|
||||
return tag.RowsAffected() > 0, nil
|
||||
}
|
||||
|
||||
// DeleteScenarioByID помечает сценарий удалённым (soft delete).
|
||||
// allowPublished=true разрешает удаление опубликованного сценария
|
||||
// (только для админа). Возвращает false, если строка не обновлена
|
||||
// (например, не-админ пытается удалить опубликованный сценарий).
|
||||
func (r *ScenariosRepo) DeleteScenarioByID(
|
||||
ctx context.Context,
|
||||
id int,
|
||||
) error {
|
||||
allowPublished bool,
|
||||
) (bool, error) {
|
||||
tag, err := r.pool.Exec(
|
||||
ctx,
|
||||
`UPDATE scenarios
|
||||
SET
|
||||
is_deleted = TRUE
|
||||
WHERE id = $1`,
|
||||
WHERE id = $1 AND ($2 OR status <> 'public')`,
|
||||
id,
|
||||
allowPublished,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
return false, err
|
||||
}
|
||||
if tag.RowsAffected() == 0 {
|
||||
return ErrScenarioNotFound
|
||||
}
|
||||
return nil
|
||||
return tag.RowsAffected() > 0, nil
|
||||
}
|
||||
|
||||
func (r *ScenariosRepo) GetStoryByScenarioID(
|
||||
@@ -315,6 +338,7 @@ func (r *ScenariosRepo) GetStoryByScenarioID(
|
||||
return story, nil
|
||||
}
|
||||
|
||||
// UpdateStoryByScenarioID обновляет содержимое (историю) сценария.
|
||||
func (r *ScenariosRepo) UpdateStoryByScenarioID(
|
||||
ctx context.Context,
|
||||
id int,
|
||||
|
||||
@@ -3,5 +3,7 @@ package repos
|
||||
type Team struct {
|
||||
ID int
|
||||
Name string
|
||||
Status string
|
||||
Password string
|
||||
GameId int
|
||||
}
|
||||
|
||||
@@ -2,11 +2,17 @@ package teams_repo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"evening_detective_server/internal/repos"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrTeamNotFound = errors.New("Команда не найдена")
|
||||
)
|
||||
|
||||
type TeamsRepo struct {
|
||||
pool *pgxpool.Pool
|
||||
}
|
||||
@@ -17,6 +23,37 @@ func NewTeamsRepo(pool *pgxpool.Pool) *TeamsRepo {
|
||||
}
|
||||
}
|
||||
|
||||
func (r *TeamsRepo) GetTeamByID(ctx context.Context, id int) (*repos.Team, error) {
|
||||
team := &repos.Team{}
|
||||
row := r.pool.QueryRow(
|
||||
ctx,
|
||||
`SELECT
|
||||
id,
|
||||
name,
|
||||
status,
|
||||
password,
|
||||
game_id
|
||||
FROM teams
|
||||
WHERE id = $1`,
|
||||
id,
|
||||
)
|
||||
err := row.Scan(
|
||||
&team.ID,
|
||||
&team.Name,
|
||||
&team.Status,
|
||||
&team.Password,
|
||||
&team.GameId,
|
||||
)
|
||||
if err != nil {
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return nil, ErrTeamNotFound
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return team, nil
|
||||
}
|
||||
|
||||
func (r *TeamsRepo) GetTeamsByGameID(
|
||||
ctx context.Context,
|
||||
gameId int,
|
||||
@@ -24,20 +61,12 @@ func (r *TeamsRepo) GetTeamsByGameID(
|
||||
rows, err := r.pool.Query(
|
||||
ctx,
|
||||
`SELECT
|
||||
scenarios.id,
|
||||
scenarios.name,
|
||||
scenarios.description,
|
||||
scenarios.image,
|
||||
scenarios.scenario,
|
||||
users.id,
|
||||
users.username,
|
||||
scenarios.status,
|
||||
scenarios.updated_at,
|
||||
scenarios.created_at,
|
||||
scenarios.published_at
|
||||
FROM scenarios
|
||||
LEFT JOIN users on scenarios.author_id = users.id
|
||||
WHERE scenarios.author_id = $1 and is_deleted = FALSE
|
||||
id,
|
||||
name,
|
||||
status,
|
||||
password
|
||||
FROM teams
|
||||
WHERE game_id = $1
|
||||
ORDER BY created_at DESC`,
|
||||
gameId,
|
||||
)
|
||||
@@ -52,6 +81,7 @@ func (r *TeamsRepo) GetTeamsByGameID(
|
||||
err := rows.Scan(
|
||||
&team.ID,
|
||||
&team.Name,
|
||||
&team.Status,
|
||||
&team.Password,
|
||||
)
|
||||
if err != nil {
|
||||
@@ -65,3 +95,63 @@ func (r *TeamsRepo) GetTeamsByGameID(
|
||||
|
||||
return teams, nil
|
||||
}
|
||||
|
||||
func (r *TeamsRepo) AddTeam(
|
||||
ctx context.Context,
|
||||
creatorId int,
|
||||
gameId int,
|
||||
name string,
|
||||
password string,
|
||||
) (int, error) {
|
||||
id := 0
|
||||
err := r.pool.QueryRow(
|
||||
ctx,
|
||||
`INSERT INTO teams (name, creator_id, game_id, password)
|
||||
VALUES ($1, $2, $3, $4)
|
||||
RETURNING id`,
|
||||
name,
|
||||
creatorId,
|
||||
gameId,
|
||||
password,
|
||||
).Scan(&id)
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return id, nil
|
||||
}
|
||||
|
||||
func (r *TeamsRepo) UpdateTeam(ctx context.Context, teamId int, name string) error {
|
||||
tag, err := r.pool.Exec(
|
||||
ctx,
|
||||
`UPDATE teams
|
||||
SET
|
||||
name = $1
|
||||
WHERE id = $2`,
|
||||
name,
|
||||
teamId,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if tag.RowsAffected() == 0 {
|
||||
return ErrTeamNotFound
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *TeamsRepo) DeleteTeam(ctx context.Context, teamId int) error {
|
||||
tag, err := r.pool.Exec(
|
||||
ctx,
|
||||
`DELETE FROM teams
|
||||
WHERE id = $1`,
|
||||
teamId,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if tag.RowsAffected() == 0 {
|
||||
return ErrTeamNotFound
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package user_agreements_repo
|
||||
|
||||
// AgreementType — типы документов, акцептуемых пользователем.
|
||||
const (
|
||||
AgreementTypeTerms = "terms" // Пользовательское соглашение
|
||||
AgreementTypePrivacy = "privacy" // Политика конфиденциальности
|
||||
)
|
||||
|
||||
// Agreement — фиксируемый факт акцепта документа (ст. 9 152-ФЗ — согласие в
|
||||
// форме, позволяющей подтвердить факт его получения). Version должна
|
||||
// соответствовать опубликованной редакции документа; при изменении текста
|
||||
// документа версия обязана увеличиваться.
|
||||
type Agreement struct {
|
||||
AgreementType string
|
||||
Version string
|
||||
IP string
|
||||
UserAgent string
|
||||
}
|
||||
@@ -4,9 +4,11 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"evening_detective_server/internal/repos"
|
||||
"evening_detective_server/internal/repos/user_agreements_repo"
|
||||
"fmt"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/jackc/pgx/v5/pgconn"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
)
|
||||
|
||||
@@ -24,21 +26,40 @@ func NewUserRepo(pool *pgxpool.Pool) *UsersRepo {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *UsersRepo) AddUser(
|
||||
// AddUserWithAgreements создаёт пользователя и фиксирует акцепты документов
|
||||
// (user_agreements) в одной транзакции: пользователь либо существует вместе с
|
||||
// записями о согласиях, либо не существует вовсе (доказуемое согласие,
|
||||
// ст. 9 152-ФЗ). Возвращает id созданного пользователя.
|
||||
func (s *UsersRepo) AddUserWithAgreements(
|
||||
ctx context.Context,
|
||||
username string,
|
||||
email string,
|
||||
passwordHash string,
|
||||
roles []string,
|
||||
) error {
|
||||
tag, err := s.pool.Exec(
|
||||
agreements []user_agreements_repo.Agreement,
|
||||
) (int, error) {
|
||||
// Инвариант «пользователь не существует без записей о согласиях»
|
||||
// держится на том, что акцепты передаются всегда.
|
||||
if len(agreements) == 0 {
|
||||
return 0, errors.New("Не переданы записи об акцепте соглашений")
|
||||
}
|
||||
|
||||
tx, err := s.pool.Begin(ctx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer tx.Rollback(ctx) //nolint:errcheck
|
||||
|
||||
var id int
|
||||
err = tx.QueryRow(
|
||||
ctx,
|
||||
`INSERT INTO users (username, email, password_hash, roles)
|
||||
SELECT $1, $2, $3, $4
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM users
|
||||
WHERE username = $5 OR email = $6
|
||||
)`,
|
||||
)
|
||||
RETURNING id`,
|
||||
username,
|
||||
email,
|
||||
passwordHash,
|
||||
@@ -46,14 +67,41 @@ func (s *UsersRepo) AddUser(
|
||||
|
||||
username,
|
||||
email,
|
||||
)
|
||||
).Scan(&id)
|
||||
if err != nil {
|
||||
return err
|
||||
// Ничего не вставлено — занят username или email.
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return 0, errors.New("Username или email уже используется")
|
||||
}
|
||||
if tag.RowsAffected() == 0 {
|
||||
return errors.New("username или email уже используется")
|
||||
// Гонка: между проверкой NOT EXISTS и INSERT другой запрос успел
|
||||
// создать пользователя с тем же username/email — нарушение
|
||||
// уникальности. Возвращаем тот же понятный клиенту ответ.
|
||||
var pgErr *pgconn.PgError
|
||||
if errors.As(err, &pgErr) && pgErr.Code == "23505" { // unique_violation
|
||||
return 0, errors.New("Username или email уже используется")
|
||||
}
|
||||
return nil
|
||||
return 0, err
|
||||
}
|
||||
|
||||
for _, agreement := range agreements {
|
||||
if _, err = tx.Exec(
|
||||
ctx,
|
||||
`INSERT INTO user_agreements (user_id, agreement_type, version, ip, user_agent)
|
||||
VALUES ($1, $2, $3, $4, $5)`,
|
||||
id,
|
||||
agreement.AgreementType,
|
||||
agreement.Version,
|
||||
agreement.IP,
|
||||
agreement.UserAgent,
|
||||
); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
if err = tx.Commit(ctx); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return id, nil
|
||||
}
|
||||
|
||||
func (s *UsersRepo) UpdateUserPassword(
|
||||
@@ -207,3 +255,62 @@ func (s *UsersRepo) DeleteUserRole(
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteUser удаляет учётную запись и персональные данные пользователя
|
||||
// (ст. 21 152-ФЗ — уничтожение ПДн при удалении аккаунта). Выполняется в одной
|
||||
// транзакции. Публичный контент пользователя (сценарии, команды) сохраняется:
|
||||
// FK-ограничения с ON DELETE SET NULL (миграция
|
||||
// 20260818232725_keep_content_on_user_delete.sql) обнуляют привязку к автору —
|
||||
// контент остаётся в обезличенном виде.
|
||||
func (s *UsersRepo) DeleteUser(
|
||||
ctx context.Context,
|
||||
userId int,
|
||||
) error {
|
||||
tx, err := s.pool.Begin(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tx.Rollback(ctx) //nolint:errcheck
|
||||
|
||||
// Refresh-токены (FK CASCADE, удаляем явно для предсказуемости).
|
||||
if _, err = tx.Exec(
|
||||
ctx,
|
||||
`DELETE FROM refresh_tokens WHERE user_id = $1`,
|
||||
userId,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Зафиксированные акцепты соглашений НЕ удаляются: ст. 9 ч. 4 152-ФЗ
|
||||
// возлагает на оператора бремя доказывания получения согласия, поэтому
|
||||
// факт акцепта (документ, версия, время) сохраняется в обезличенном виде:
|
||||
// ссылка на пользователя и персональные данные внутри записи (ip,
|
||||
// user_agent) обнуляются. FK ON DELETE SET NULL (миграция
|
||||
// 20260818231943_create_user_agreements_table.sql) гарантирует, что при
|
||||
// удалении users строки акцептов остаются с user_id = NULL.
|
||||
if _, err = tx.Exec(
|
||||
ctx,
|
||||
`UPDATE user_agreements
|
||||
SET user_id = NULL, ip = NULL, user_agent = NULL
|
||||
WHERE user_id = $1`,
|
||||
userId,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Пользователь. Связанные сценарии (author_id) и команды (creator_id)
|
||||
// обнуляются автоматически через ON DELETE SET NULL.
|
||||
tag, err := tx.Exec(
|
||||
ctx,
|
||||
`DELETE FROM users WHERE id = $1`,
|
||||
userId,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if tag.RowsAffected() == 0 {
|
||||
return ErrUserNotFound
|
||||
}
|
||||
|
||||
return tx.Commit(ctx)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
package game_service
|
||||
|
||||
type Application struct {
|
||||
Name string
|
||||
Image string
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package game_service
|
||||
|
||||
import (
|
||||
"evening_detective_server/internal/modules/storytelling"
|
||||
"evening_detective_server/internal/repos"
|
||||
)
|
||||
|
||||
func mapApplications(o []*repos.Application) []*storytelling.Application {
|
||||
res := make([]*storytelling.Application, 0, len(o))
|
||||
for _, item := range o {
|
||||
res = append(res, mapApplication(item))
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func mapApplication(o *repos.Application) *storytelling.Application {
|
||||
return &storytelling.Application{
|
||||
Name: o.Name,
|
||||
Image: o.Image,
|
||||
}
|
||||
}
|
||||
@@ -13,4 +13,6 @@ type Game struct {
|
||||
Status string
|
||||
Scenario *scenarios_service.Scenario
|
||||
Teams []*Team
|
||||
StartedAt *time.Time
|
||||
EndedAt *time.Time
|
||||
}
|
||||
|
||||
@@ -17,6 +17,8 @@ func mapGame(
|
||||
Status: o.Status,
|
||||
Scenario: scenarios_service.MapScenarioLight(o.Scenario, domain),
|
||||
Teams: mapTeams(o.Teams),
|
||||
StartedAt: o.StartedAt,
|
||||
EndedAt: o.EndedAt,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,9 +2,15 @@ package game_service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"evening_detective_server/internal/modules/cleaner"
|
||||
"evening_detective_server/internal/modules/password_generator"
|
||||
"evening_detective_server/internal/modules/storytelling"
|
||||
"evening_detective_server/internal/repos/actions_repo"
|
||||
"evening_detective_server/internal/repos/applications_repo"
|
||||
"evening_detective_server/internal/repos/games_repo"
|
||||
"evening_detective_server/internal/repos/scenarios_repo"
|
||||
"evening_detective_server/internal/repos/teams_repo"
|
||||
"evening_detective_server/internal/services/scenarios_service"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -12,20 +18,38 @@ type GameService struct {
|
||||
gamesRepo *games_repo.GamesRepo
|
||||
teamsRepo *teams_repo.TeamsRepo
|
||||
scenariosRepo *scenarios_repo.ScenariosRepo
|
||||
scenarioService *scenarios_service.ScenarioService
|
||||
domain string
|
||||
passwordGenerator password_generator.IPasswordGenerator
|
||||
storyteller storytelling.IStory
|
||||
actionsRepo *actions_repo.ActionsRepo
|
||||
applicationsRepo *applications_repo.ApplicationsRepo
|
||||
cleaner cleaner.ICleaner
|
||||
}
|
||||
|
||||
func NewGameService(
|
||||
gamesRepo *games_repo.GamesRepo,
|
||||
teamsRepo *teams_repo.TeamsRepo,
|
||||
scenariosRepo *scenarios_repo.ScenariosRepo,
|
||||
scenarioService *scenarios_service.ScenarioService,
|
||||
domain string,
|
||||
passwordGenerator password_generator.IPasswordGenerator,
|
||||
storyteller storytelling.IStory,
|
||||
actionsRepo *actions_repo.ActionsRepo,
|
||||
applicationsRepo *applications_repo.ApplicationsRepo,
|
||||
cleaner cleaner.ICleaner,
|
||||
) *GameService {
|
||||
return &GameService{
|
||||
gamesRepo: gamesRepo,
|
||||
teamsRepo: teamsRepo,
|
||||
scenariosRepo: scenariosRepo,
|
||||
scenarioService: scenarioService,
|
||||
domain: domain,
|
||||
passwordGenerator: passwordGenerator,
|
||||
storyteller: storyteller,
|
||||
actionsRepo: actionsRepo,
|
||||
applicationsRepo: applicationsRepo,
|
||||
cleaner: cleaner,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +67,16 @@ func (s *GameService) AddGame(
|
||||
return id, nil
|
||||
}
|
||||
|
||||
func (s *GameService) GetGame(
|
||||
func (s *GameService) GetGame(ctx context.Context, id int) (*Game, error) {
|
||||
game, err := s.gamesRepo.GetGameByID(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res := mapGame(game, s.domain)
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *GameService) GetFullGame(
|
||||
ctx context.Context,
|
||||
id int,
|
||||
) (*Game, error) {
|
||||
@@ -61,7 +94,30 @@ func (s *GameService) GetGame(
|
||||
return nil, err
|
||||
}
|
||||
game.Teams = teams
|
||||
return mapGame(game, s.domain), nil
|
||||
|
||||
res := mapGame(game, s.domain)
|
||||
|
||||
teamIds := make([]int, 0, len(game.Teams))
|
||||
for _, team := range game.Teams {
|
||||
teamIds = append(teamIds, team.ID)
|
||||
}
|
||||
|
||||
actionsCountByTeamIDs, err := s.actionsRepo.GetActionsCountByTeamIDs(ctx, teamIds)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
needApplicationsByTeamIDs, err := s.applicationsRepo.GetApplicationsByTeamIDsAndState(ctx, teamIds, "need")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, team := range res.Teams {
|
||||
team.ActionsCount = actionsCountByTeamIDs[team.ID]
|
||||
team.Applications = mapApplications(needApplicationsByTeamIDs[team.ID])
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *GameService) DeleteGame(
|
||||
@@ -91,3 +147,161 @@ func (s *GameService) UpdateGame(
|
||||
) error {
|
||||
return s.gamesRepo.UpdateGame(ctx, id, name, description, startAt, scenarioId)
|
||||
}
|
||||
|
||||
func (s *GameService) StartGame(ctx context.Context, id int) error {
|
||||
return s.gamesRepo.StartGame(ctx, id)
|
||||
}
|
||||
|
||||
func (s *GameService) PauseGame(ctx context.Context, id int) error {
|
||||
return s.gamesRepo.PauseGame(ctx, id)
|
||||
}
|
||||
|
||||
func (s *GameService) PlayGame(ctx context.Context, id int) error {
|
||||
return s.gamesRepo.PlayGame(ctx, id)
|
||||
}
|
||||
|
||||
func (s *GameService) FinishGame(ctx context.Context, id int) error {
|
||||
return s.gamesRepo.FinishGame(ctx, id)
|
||||
}
|
||||
|
||||
func (s *GameService) ResetGame(ctx context.Context, id int) error {
|
||||
return s.gamesRepo.ResetGame(ctx, id)
|
||||
}
|
||||
|
||||
func (s *GameService) AddTeam(
|
||||
ctx context.Context,
|
||||
creatorId int,
|
||||
gameId int,
|
||||
name string,
|
||||
) (*Team, error) {
|
||||
password, err := s.passwordGenerator.Generate()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
id, err := s.teamsRepo.AddTeam(ctx, creatorId, gameId, name, password)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Team{
|
||||
ID: id,
|
||||
Password: password,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *GameService) UpdateTeam(ctx context.Context, teamId int, name string) error {
|
||||
return s.teamsRepo.UpdateTeam(ctx, teamId, name)
|
||||
}
|
||||
|
||||
func (s *GameService) DeleteTeam(ctx context.Context, teamId int) error {
|
||||
return s.teamsRepo.DeleteTeam(ctx, teamId)
|
||||
}
|
||||
|
||||
func (s *GameService) GiveTeamApplications(ctx context.Context, teamId int, name string) error {
|
||||
return s.applicationsRepo.UpdateApplicationStatus(ctx, teamId, name, "done")
|
||||
}
|
||||
|
||||
func (s *GameService) GetTeamActions(ctx context.Context, teamId int, password string) (*storytelling.Story, *Game, error) {
|
||||
team, err := s.teamsRepo.GetTeamByID(ctx, teamId)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if team.Password != password {
|
||||
return nil, nil, teams_repo.ErrTeamNotFound
|
||||
}
|
||||
|
||||
game, err := s.gamesRepo.GetGameByID(ctx, team.GameId)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
actions, err := s.actionsRepo.GetActionsByTeamID(ctx, teamId)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
scenario, err := s.scenarioService.GetFullScenarioByID(ctx, game.ScenarioId)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return s.storyteller.GetStory(scenario.Story, actions), mapGame(game, s.domain), nil
|
||||
}
|
||||
|
||||
func (s *GameService) AddTeamAction(ctx context.Context, teamId int, password string, actionCode string) error {
|
||||
team, err := s.teamsRepo.GetTeamByID(ctx, teamId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if team.Password != password {
|
||||
return teams_repo.ErrTeamNotFound
|
||||
}
|
||||
|
||||
_, err = s.actionsRepo.AddAction(ctx, teamId, actionCode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
game, err := s.gamesRepo.GetGameByID(ctx, team.GameId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
actions, err := s.actionsRepo.GetActionsByTeamID(ctx, teamId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
scenario, err := s.scenarioService.GetFullScenarioByID(ctx, game.ScenarioId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
teamStory := s.storyteller.GetStory(scenario.Story, actions)
|
||||
|
||||
lastPlace := teamStory.Places[len(teamStory.Places)-1]
|
||||
|
||||
for _, application := range lastPlace.Applications {
|
||||
_, err := s.applicationsRepo.AddApplication(ctx, teamId, s.cleaner.ClearText(application.Name), application.Image)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *GameService) DeleteLastTeamAction(ctx context.Context, teamId int) error {
|
||||
team, err := s.teamsRepo.GetTeamByID(ctx, teamId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
game, err := s.gamesRepo.GetGameByID(ctx, team.GameId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
actions, err := s.actionsRepo.GetActionsByTeamID(ctx, teamId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
scenario, err := s.scenarioService.GetFullScenarioByID(ctx, game.ScenarioId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
teamStory := s.storyteller.GetStory(scenario.Story, actions)
|
||||
|
||||
lastPlace := teamStory.Places[len(teamStory.Places)-1]
|
||||
|
||||
for _, application := range lastPlace.Applications {
|
||||
if err := s.applicationsRepo.DeleteApplicationByTeamIdAndName(ctx, teamId, application.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return s.actionsRepo.DeleteLastActionByTeamId(ctx, teamId)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
package game_service
|
||||
|
||||
import "evening_detective_server/internal/modules/storytelling"
|
||||
|
||||
type Team struct {
|
||||
ID int
|
||||
Name string
|
||||
Status string
|
||||
Password string
|
||||
ActionsCount int
|
||||
Applications []*storytelling.Application
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ func mapTeam(
|
||||
return &Team{
|
||||
ID: o.ID,
|
||||
Name: o.Name,
|
||||
Status: o.Status,
|
||||
Password: o.Password,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,22 +3,27 @@ package scenarios_service
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"evening_detective_server/internal/modules/cleaner"
|
||||
"evening_detective_server/internal/modules/storytelling"
|
||||
"evening_detective_server/internal/repos"
|
||||
"evening_detective_server/internal/repos/scenarios_repo"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type ScenarioService struct {
|
||||
scenariosRepo *scenarios_repo.ScenariosRepo
|
||||
cleaner cleaner.ICleaner
|
||||
domain string
|
||||
}
|
||||
|
||||
func NewScenarioService(
|
||||
scenariosRepo *scenarios_repo.ScenariosRepo,
|
||||
cleaner cleaner.ICleaner,
|
||||
domain string,
|
||||
) *ScenarioService {
|
||||
return &ScenarioService{
|
||||
scenariosRepo: scenariosRepo,
|
||||
cleaner: cleaner,
|
||||
domain: domain,
|
||||
}
|
||||
}
|
||||
@@ -84,30 +89,111 @@ func (s *ScenarioService) UpdateScenarioByID(
|
||||
name string,
|
||||
description string,
|
||||
image string,
|
||||
actorId int,
|
||||
isAdmin bool,
|
||||
) error {
|
||||
if _, err := s.getScenarioForChange(ctx, id, actorId, isAdmin); err != nil {
|
||||
return err
|
||||
}
|
||||
return s.scenariosRepo.UpdateScenarioByID(ctx, id, name, description, image)
|
||||
}
|
||||
|
||||
// ErrScenarioPublished — опубликованный сценарий нельзя снять с публикации
|
||||
// или удалить: при публикации исключительное право отчуждается Оператору
|
||||
// (п. 7.2 Пользовательского соглашения, ст. 1234 ГК РФ), а право на отзыв
|
||||
// действует только до обнародования (ст. 1269 ГК РФ).
|
||||
var ErrScenarioPublished = errors.New("Опубликованный сценарий нельзя снять с публикации или удалить: исключительное право отчуждено Оператору")
|
||||
|
||||
// ErrScenarioNotOwner — сценарий принадлежит другому пользователю.
|
||||
var ErrScenarioNotOwner = errors.New("Сценарий принадлежит другому пользователю")
|
||||
|
||||
// getScenarioForChange проверяет права актора на изменение сценария:
|
||||
// не-админ должен быть автором сценария, сценарий не должен быть удалён.
|
||||
// Возвращает сценарий, чтобы вызывающий код не делал повторное чтение.
|
||||
func (s *ScenarioService) getScenarioForChange(
|
||||
ctx context.Context,
|
||||
id int,
|
||||
actorId int,
|
||||
isAdmin bool,
|
||||
) (*repos.Scenario, error) {
|
||||
scenario, err := s.scenariosRepo.GetScenarioByID(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if scenario.IsDeleted {
|
||||
return nil, scenarios_repo.ErrScenarioNotFound
|
||||
}
|
||||
if isAdmin {
|
||||
return scenario, nil
|
||||
}
|
||||
if scenario.Author == nil || scenario.Author.ID != actorId {
|
||||
return nil, ErrScenarioNotOwner
|
||||
}
|
||||
return scenario, nil
|
||||
}
|
||||
|
||||
func (s *ScenarioService) UpdateScenarioStatusByID(
|
||||
ctx context.Context,
|
||||
id int,
|
||||
status string,
|
||||
actorId int,
|
||||
isAdmin bool,
|
||||
) error {
|
||||
return s.scenariosRepo.UpdateScenarioStatusByID(ctx, id, status)
|
||||
scenario, err := s.getScenarioForChange(ctx, id, actorId, isAdmin)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !isAdmin && scenario.Status == "public" {
|
||||
// Повторная публикация уже опубликованного — идемпотентный успех
|
||||
// (published_at не двигается: CASE WHEN published_at IS NULL).
|
||||
if status == "public" {
|
||||
return nil
|
||||
}
|
||||
return ErrScenarioPublished
|
||||
}
|
||||
// Guard в SQL защищает от гонки «чтение статуса → запись»: не-админ не
|
||||
// сможет перевести в draft сценарий, опубликованный между чтением и UPDATE.
|
||||
updated, err := s.scenariosRepo.UpdateScenarioStatusByID(ctx, id, status, isAdmin)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !updated {
|
||||
return ErrScenarioPublished
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ScenarioService) DeleteScenarioByID(
|
||||
ctx context.Context,
|
||||
id int,
|
||||
actorId int,
|
||||
isAdmin bool,
|
||||
) error {
|
||||
return s.scenariosRepo.DeleteScenarioByID(ctx, id)
|
||||
if _, err := s.getScenarioForChange(ctx, id, actorId, isAdmin); err != nil {
|
||||
return err
|
||||
}
|
||||
// Guard в SQL защищает от гонки «чтение статуса → запись»: не-админ не
|
||||
// сможет удалить сценарий, опубликованный между чтением и UPDATE.
|
||||
updated, err := s.scenariosRepo.DeleteScenarioByID(ctx, id, isAdmin)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !updated {
|
||||
return ErrScenarioPublished
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ScenarioService) AddScenarioPlace(
|
||||
ctx context.Context,
|
||||
id int,
|
||||
place *storytelling.Place,
|
||||
actorId int,
|
||||
isAdmin bool,
|
||||
) error {
|
||||
if _, err := s.getScenarioForChange(ctx, id, actorId, isAdmin); err != nil {
|
||||
return err
|
||||
}
|
||||
story, err := s.getStory(ctx, id)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -124,7 +210,12 @@ func (s *ScenarioService) UpdateScenarioPlace(
|
||||
id int,
|
||||
code string,
|
||||
place *storytelling.Place,
|
||||
actorId int,
|
||||
isAdmin bool,
|
||||
) error {
|
||||
if _, err := s.getScenarioForChange(ctx, id, actorId, isAdmin); err != nil {
|
||||
return err
|
||||
}
|
||||
story, err := s.getStory(ctx, id)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -142,7 +233,12 @@ func (s *ScenarioService) DeleteScenarioPlace(
|
||||
ctx context.Context,
|
||||
id int,
|
||||
code string,
|
||||
actorId int,
|
||||
isAdmin bool,
|
||||
) error {
|
||||
if _, err := s.getScenarioForChange(ctx, id, actorId, isAdmin); err != nil {
|
||||
return err
|
||||
}
|
||||
story, err := s.getStory(ctx, id)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -174,6 +270,15 @@ func (s *ScenarioService) updateStory(ctx context.Context, id int, story *storyt
|
||||
return errors.New("Такой код точки уже существует")
|
||||
}
|
||||
|
||||
cleanPlaces := make([]*storytelling.Place, 0, len(story.Places))
|
||||
for _, place := range story.Places {
|
||||
if place.Code == "" {
|
||||
continue
|
||||
}
|
||||
cleanPlaces = append(cleanPlaces, place)
|
||||
}
|
||||
story.Places = cleanPlaces
|
||||
|
||||
storyString, err := convertStory(story)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"evening_detective_server/internal/modules/roles"
|
||||
"evening_detective_server/internal/repos"
|
||||
"evening_detective_server/internal/repos/refresh_tokens_repo"
|
||||
"evening_detective_server/internal/repos/user_agreements_repo"
|
||||
"evening_detective_server/internal/repos/users_repo"
|
||||
"fmt"
|
||||
"slices"
|
||||
@@ -17,6 +18,17 @@ import (
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
// Версии документов, акцепт которых фиксируется. Должны совпадать с
|
||||
// редакциями, опубликованными по постоянным URL (/api/terms и /api/privacy,
|
||||
// rpc GetTerms/GetPrivacy, текст встраивается из пакета docs): при изменении
|
||||
// текста документа версия ОБЯЗАНА увеличиваться, иначе повторный акцепт
|
||||
// не будет зафиксирован как согласие с новой редакцией
|
||||
// (UNIQUE (user_id, agreement_type, version)).
|
||||
const (
|
||||
termsVersion = "1.0"
|
||||
privacyVersion = "1.0"
|
||||
)
|
||||
|
||||
type UsersService struct {
|
||||
usersRepo *users_repo.UsersRepo
|
||||
passwordGenerator password_generator.IPasswordGenerator
|
||||
@@ -41,11 +53,22 @@ func NewUsersService(
|
||||
}
|
||||
}
|
||||
|
||||
// ErrTermsNotAccepted — регистрация невозможна без акцепта соглашений.
|
||||
var ErrTermsNotAccepted = errors.New("Необходимо принять Пользовательское соглашение и Политику конфиденциальности")
|
||||
|
||||
func (s *UsersService) AddUser(
|
||||
ctx context.Context,
|
||||
username string,
|
||||
email string,
|
||||
acceptTerms bool,
|
||||
acceptPrivacy bool,
|
||||
ip string,
|
||||
userAgent string,
|
||||
) error {
|
||||
if !acceptTerms || !acceptPrivacy {
|
||||
return ErrTermsNotAccepted
|
||||
}
|
||||
|
||||
password, err := s.passwordGenerator.Generate()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -54,12 +77,28 @@ func (s *UsersService) AddUser(
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = s.usersRepo.AddUser(
|
||||
// Создание пользователя и фиксация акцептов — в одной транзакции:
|
||||
// без записей о согласиях пользователь не существует.
|
||||
_, err = s.usersRepo.AddUserWithAgreements(
|
||||
ctx,
|
||||
username,
|
||||
email,
|
||||
string(hashedPassword),
|
||||
[]string{"user"},
|
||||
[]user_agreements_repo.Agreement{
|
||||
{
|
||||
AgreementType: user_agreements_repo.AgreementTypeTerms,
|
||||
Version: termsVersion,
|
||||
IP: ip,
|
||||
UserAgent: userAgent,
|
||||
},
|
||||
{
|
||||
AgreementType: user_agreements_repo.AgreementTypePrivacy,
|
||||
Version: privacyVersion,
|
||||
IP: ip,
|
||||
UserAgent: userAgent,
|
||||
},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -74,6 +113,24 @@ func (s *UsersService) AddUser(
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteAccount удаляет учётную запись и все персональные данные пользователя
|
||||
// (ст. 21 152-ФЗ). Требует подтверждения паролем: обладатель украденного
|
||||
// access-токена не должен мочь удалить чужой аккаунт.
|
||||
func (s *UsersService) DeleteAccount(
|
||||
ctx context.Context,
|
||||
userId int,
|
||||
password string,
|
||||
) error {
|
||||
user, err := s.usersRepo.GetUserByID(ctx, userId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(password)); err != nil {
|
||||
return errors.New("Неверный пароль")
|
||||
}
|
||||
return s.usersRepo.DeleteUser(ctx, userId)
|
||||
}
|
||||
|
||||
func (s *UsersService) RefreshPassword(
|
||||
ctx context.Context,
|
||||
email string,
|
||||
|
||||
@@ -6,13 +6,11 @@ CREATE TABLE
|
||||
status VARCHAR(50) DEFAULT 'register',
|
||||
creator_id INTEGER NOT NULL,
|
||||
game_id INTEGER NOT NULL,
|
||||
scenario_id INTEGER NOT NULL,
|
||||
password TEXT,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW (),
|
||||
|
||||
CONSTRAINT fk_teams_game FOREIGN KEY (game_id) REFERENCES games (id) ON DELETE RESTRICT,
|
||||
CONSTRAINT fk_teams_creator FOREIGN KEY (creator_id) REFERENCES users (id) ON DELETE RESTRICT,
|
||||
CONSTRAINT fk_teams_scenario FOREIGN KEY (scenario_id) REFERENCES scenarios (id) ON DELETE RESTRICT
|
||||
CONSTRAINT fk_teams_creator FOREIGN KEY (creator_id) REFERENCES users (id) ON DELETE RESTRICT
|
||||
);
|
||||
|
||||
-- +goose Down
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
-- +goose Up
|
||||
CREATE TABLE
|
||||
IF NOT EXISTS links (
|
||||
id SERIAL PRIMARY KEY,
|
||||
team_id INTEGER,
|
||||
game_id INTEGER,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW (),
|
||||
|
||||
CONSTRAINT fk_links_team FOREIGN KEY (team_id) REFERENCES teams (id) ON DELETE RESTRICT,
|
||||
CONSTRAINT fk_links_game FOREIGN KEY (game_id) REFERENCES games (id) ON DELETE RESTRICT
|
||||
);
|
||||
|
||||
-- +goose Down
|
||||
DROP TABLE IF EXISTS links;
|
||||
@@ -0,0 +1,13 @@
|
||||
-- +goose Up
|
||||
CREATE TABLE
|
||||
IF NOT EXISTS actions (
|
||||
id SERIAL PRIMARY KEY,
|
||||
code TEXT,
|
||||
team_id INTEGER,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW (),
|
||||
|
||||
CONSTRAINT fk_actions_team FOREIGN KEY (team_id) REFERENCES teams (id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- +goose Down
|
||||
DROP TABLE IF EXISTS actions;
|
||||
@@ -0,0 +1,15 @@
|
||||
-- +goose Up
|
||||
CREATE TABLE
|
||||
IF NOT EXISTS applications (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name TEXT,
|
||||
image TEXT,
|
||||
team_id INTEGER,
|
||||
status VARCHAR(50) DEFAULT 'need',
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW (),
|
||||
|
||||
CONSTRAINT fk_applications_team FOREIGN KEY (team_id) REFERENCES teams (id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- +goose Down
|
||||
DROP TABLE IF EXISTS applications;
|
||||
@@ -0,0 +1,26 @@
|
||||
-- +goose Up
|
||||
CREATE TABLE
|
||||
IF NOT EXISTS user_agreements (
|
||||
id SERIAL PRIMARY KEY,
|
||||
-- user_id NULL-able: при удалении аккаунта FK-ограничение обнуляет
|
||||
-- ссылку (ON DELETE SET NULL), а сама запись об акцепте остаётся в
|
||||
-- обезличенном виде (факт согласия, ст. 9 ч. 4 152-ФЗ — бремя
|
||||
-- доказывания на операторе). Персональные данные внутри записи
|
||||
-- (ip, user_agent) обнуляет users_repo.DeleteUser.
|
||||
user_id INTEGER,
|
||||
agreement_type VARCHAR(50) NOT NULL,
|
||||
version VARCHAR(50) NOT NULL,
|
||||
accepted_at TIMESTAMP NOT NULL DEFAULT NOW (),
|
||||
ip VARCHAR(45),
|
||||
user_agent TEXT,
|
||||
|
||||
CONSTRAINT fk_user_agreements_user FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE SET NULL,
|
||||
CONSTRAINT uq_user_agreement UNIQUE (user_id, agreement_type, version),
|
||||
CONSTRAINT chk_user_agreement_type CHECK (agreement_type IN ('terms', 'privacy'))
|
||||
);
|
||||
|
||||
-- Отдельный индекс по user_id не нужен: UNIQUE (user_id, agreement_type, version)
|
||||
-- уже покрывает выборки по префиксу user_id.
|
||||
|
||||
-- +goose Down
|
||||
DROP TABLE IF EXISTS user_agreements;
|
||||
@@ -0,0 +1,39 @@
|
||||
-- +goose Up
|
||||
-- Сценарии и команды сохраняются при удалении аккаунта автора:
|
||||
-- привязка к пользователю обнуляется (обезличивание, ст. 3 152-ФЗ),
|
||||
-- сам контент остаётся в Сервисе (лицензия по п. 7.2 Пользовательского соглашения).
|
||||
ALTER TABLE scenarios ALTER COLUMN author_id DROP NOT NULL;
|
||||
|
||||
ALTER TABLE scenarios DROP CONSTRAINT IF EXISTS fk_scenarios_author;
|
||||
ALTER TABLE scenarios ADD CONSTRAINT fk_scenarios_author
|
||||
FOREIGN KEY (author_id) REFERENCES users (id) ON DELETE SET NULL;
|
||||
|
||||
ALTER TABLE teams ALTER COLUMN creator_id DROP NOT NULL;
|
||||
|
||||
ALTER TABLE teams DROP CONSTRAINT IF EXISTS fk_teams_creator;
|
||||
ALTER TABLE teams ADD CONSTRAINT fk_teams_creator
|
||||
FOREIGN KEY (creator_id) REFERENCES users (id) ON DELETE SET NULL;
|
||||
|
||||
-- status у сценариев всегда задан (default 'draft'): NULL-значение сломало бы
|
||||
-- guard'ы вида "status <> 'public'" в scenarios_repo (NULL даёт NULL, т.е. ложь).
|
||||
-- На БД с историческими NULL-данными сначала приводим их к default'у.
|
||||
UPDATE scenarios SET status = 'draft' WHERE status IS NULL;
|
||||
ALTER TABLE scenarios ALTER COLUMN status SET NOT NULL;
|
||||
|
||||
-- +goose Down
|
||||
-- Восстановление прежней схемы: автор обязателен, при удалении пользователя —
|
||||
-- удаление его контента запрещено (RESTRICT).
|
||||
DELETE FROM scenarios WHERE author_id IS NULL;
|
||||
DELETE FROM teams WHERE creator_id IS NULL;
|
||||
|
||||
ALTER TABLE scenarios ALTER COLUMN status DROP NOT NULL;
|
||||
|
||||
ALTER TABLE scenarios DROP CONSTRAINT IF EXISTS fk_scenarios_author;
|
||||
ALTER TABLE scenarios ADD CONSTRAINT fk_scenarios_author
|
||||
FOREIGN KEY (author_id) REFERENCES users (id) ON DELETE RESTRICT;
|
||||
ALTER TABLE scenarios ALTER COLUMN author_id SET NOT NULL;
|
||||
|
||||
ALTER TABLE teams DROP CONSTRAINT IF EXISTS fk_teams_creator;
|
||||
ALTER TABLE teams ADD CONSTRAINT fk_teams_creator
|
||||
FOREIGN KEY (creator_id) REFERENCES users (id) ON DELETE RESTRICT;
|
||||
ALTER TABLE teams ALTER COLUMN creator_id SET NOT NULL;
|
||||
+1514
-426
File diff suppressed because it is too large
Load Diff
+757
-19
@@ -199,6 +199,90 @@ func local_request_EveningDetectiveServer_Refresh_0(ctx context.Context, marshal
|
||||
return msg, metadata, err
|
||||
}
|
||||
|
||||
func request_EveningDetectiveServer_DeleteAccount_0(ctx context.Context, marshaler runtime.Marshaler, client EveningDetectiveServerClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var (
|
||||
protoReq DeleteAccountReq
|
||||
metadata runtime.ServerMetadata
|
||||
)
|
||||
if req.Body != nil {
|
||||
_, _ = io.Copy(io.Discard, req.Body)
|
||||
}
|
||||
msg, err := client.DeleteAccount(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return msg, metadata, err
|
||||
}
|
||||
|
||||
func local_request_EveningDetectiveServer_DeleteAccount_0(ctx context.Context, marshaler runtime.Marshaler, server EveningDetectiveServerServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var (
|
||||
protoReq DeleteAccountReq
|
||||
metadata runtime.ServerMetadata
|
||||
)
|
||||
msg, err := server.DeleteAccount(ctx, &protoReq)
|
||||
return msg, metadata, err
|
||||
}
|
||||
|
||||
func request_EveningDetectiveServer_GetTerms_0(ctx context.Context, marshaler runtime.Marshaler, client EveningDetectiveServerClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var (
|
||||
protoReq GetTermsReq
|
||||
metadata runtime.ServerMetadata
|
||||
)
|
||||
if req.Body != nil {
|
||||
_, _ = io.Copy(io.Discard, req.Body)
|
||||
}
|
||||
msg, err := client.GetTerms(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return msg, metadata, err
|
||||
}
|
||||
|
||||
func local_request_EveningDetectiveServer_GetTerms_0(ctx context.Context, marshaler runtime.Marshaler, server EveningDetectiveServerServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var (
|
||||
protoReq GetTermsReq
|
||||
metadata runtime.ServerMetadata
|
||||
)
|
||||
msg, err := server.GetTerms(ctx, &protoReq)
|
||||
return msg, metadata, err
|
||||
}
|
||||
|
||||
func request_EveningDetectiveServer_GetPrivacy_0(ctx context.Context, marshaler runtime.Marshaler, client EveningDetectiveServerClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var (
|
||||
protoReq GetPrivacyReq
|
||||
metadata runtime.ServerMetadata
|
||||
)
|
||||
if req.Body != nil {
|
||||
_, _ = io.Copy(io.Discard, req.Body)
|
||||
}
|
||||
msg, err := client.GetPrivacy(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return msg, metadata, err
|
||||
}
|
||||
|
||||
func local_request_EveningDetectiveServer_GetPrivacy_0(ctx context.Context, marshaler runtime.Marshaler, server EveningDetectiveServerServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var (
|
||||
protoReq GetPrivacyReq
|
||||
metadata runtime.ServerMetadata
|
||||
)
|
||||
msg, err := server.GetPrivacy(ctx, &protoReq)
|
||||
return msg, metadata, err
|
||||
}
|
||||
|
||||
func request_EveningDetectiveServer_GetConsent_0(ctx context.Context, marshaler runtime.Marshaler, client EveningDetectiveServerClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var (
|
||||
protoReq GetConsentReq
|
||||
metadata runtime.ServerMetadata
|
||||
)
|
||||
if req.Body != nil {
|
||||
_, _ = io.Copy(io.Discard, req.Body)
|
||||
}
|
||||
msg, err := client.GetConsent(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return msg, metadata, err
|
||||
}
|
||||
|
||||
func local_request_EveningDetectiveServer_GetConsent_0(ctx context.Context, marshaler runtime.Marshaler, server EveningDetectiveServerServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var (
|
||||
protoReq GetConsentReq
|
||||
metadata runtime.ServerMetadata
|
||||
)
|
||||
msg, err := server.GetConsent(ctx, &protoReq)
|
||||
return msg, metadata, err
|
||||
}
|
||||
|
||||
func request_EveningDetectiveServer_GetUsers_0(ctx context.Context, marshaler runtime.Marshaler, client EveningDetectiveServerClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var (
|
||||
protoReq GetUsersReq
|
||||
@@ -1071,6 +1155,231 @@ func local_request_EveningDetectiveServer_UpdateGame_0(ctx context.Context, mars
|
||||
return msg, metadata, err
|
||||
}
|
||||
|
||||
func request_EveningDetectiveServer_StartGame_0(ctx context.Context, marshaler runtime.Marshaler, client EveningDetectiveServerClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var (
|
||||
protoReq StartGameReq
|
||||
metadata runtime.ServerMetadata
|
||||
err error
|
||||
)
|
||||
if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
if req.Body != nil {
|
||||
_, _ = io.Copy(io.Discard, req.Body)
|
||||
}
|
||||
val, ok := pathParams["id"]
|
||||
if !ok {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id")
|
||||
}
|
||||
protoReq.Id, err = runtime.Int32(val)
|
||||
if err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err)
|
||||
}
|
||||
msg, err := client.StartGame(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return msg, metadata, err
|
||||
}
|
||||
|
||||
func local_request_EveningDetectiveServer_StartGame_0(ctx context.Context, marshaler runtime.Marshaler, server EveningDetectiveServerServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var (
|
||||
protoReq StartGameReq
|
||||
metadata runtime.ServerMetadata
|
||||
err error
|
||||
)
|
||||
if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
val, ok := pathParams["id"]
|
||||
if !ok {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id")
|
||||
}
|
||||
protoReq.Id, err = runtime.Int32(val)
|
||||
if err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err)
|
||||
}
|
||||
msg, err := server.StartGame(ctx, &protoReq)
|
||||
return msg, metadata, err
|
||||
}
|
||||
|
||||
func request_EveningDetectiveServer_PauseGame_0(ctx context.Context, marshaler runtime.Marshaler, client EveningDetectiveServerClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var (
|
||||
protoReq PauseGameReq
|
||||
metadata runtime.ServerMetadata
|
||||
err error
|
||||
)
|
||||
if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
if req.Body != nil {
|
||||
_, _ = io.Copy(io.Discard, req.Body)
|
||||
}
|
||||
val, ok := pathParams["id"]
|
||||
if !ok {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id")
|
||||
}
|
||||
protoReq.Id, err = runtime.Int32(val)
|
||||
if err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err)
|
||||
}
|
||||
msg, err := client.PauseGame(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return msg, metadata, err
|
||||
}
|
||||
|
||||
func local_request_EveningDetectiveServer_PauseGame_0(ctx context.Context, marshaler runtime.Marshaler, server EveningDetectiveServerServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var (
|
||||
protoReq PauseGameReq
|
||||
metadata runtime.ServerMetadata
|
||||
err error
|
||||
)
|
||||
if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
val, ok := pathParams["id"]
|
||||
if !ok {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id")
|
||||
}
|
||||
protoReq.Id, err = runtime.Int32(val)
|
||||
if err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err)
|
||||
}
|
||||
msg, err := server.PauseGame(ctx, &protoReq)
|
||||
return msg, metadata, err
|
||||
}
|
||||
|
||||
func request_EveningDetectiveServer_PlayGame_0(ctx context.Context, marshaler runtime.Marshaler, client EveningDetectiveServerClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var (
|
||||
protoReq PlayGameReq
|
||||
metadata runtime.ServerMetadata
|
||||
err error
|
||||
)
|
||||
if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
if req.Body != nil {
|
||||
_, _ = io.Copy(io.Discard, req.Body)
|
||||
}
|
||||
val, ok := pathParams["id"]
|
||||
if !ok {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id")
|
||||
}
|
||||
protoReq.Id, err = runtime.Int32(val)
|
||||
if err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err)
|
||||
}
|
||||
msg, err := client.PlayGame(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return msg, metadata, err
|
||||
}
|
||||
|
||||
func local_request_EveningDetectiveServer_PlayGame_0(ctx context.Context, marshaler runtime.Marshaler, server EveningDetectiveServerServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var (
|
||||
protoReq PlayGameReq
|
||||
metadata runtime.ServerMetadata
|
||||
err error
|
||||
)
|
||||
if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
val, ok := pathParams["id"]
|
||||
if !ok {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id")
|
||||
}
|
||||
protoReq.Id, err = runtime.Int32(val)
|
||||
if err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err)
|
||||
}
|
||||
msg, err := server.PlayGame(ctx, &protoReq)
|
||||
return msg, metadata, err
|
||||
}
|
||||
|
||||
func request_EveningDetectiveServer_FinishGame_0(ctx context.Context, marshaler runtime.Marshaler, client EveningDetectiveServerClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var (
|
||||
protoReq FinishGameReq
|
||||
metadata runtime.ServerMetadata
|
||||
err error
|
||||
)
|
||||
if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
if req.Body != nil {
|
||||
_, _ = io.Copy(io.Discard, req.Body)
|
||||
}
|
||||
val, ok := pathParams["id"]
|
||||
if !ok {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id")
|
||||
}
|
||||
protoReq.Id, err = runtime.Int32(val)
|
||||
if err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err)
|
||||
}
|
||||
msg, err := client.FinishGame(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return msg, metadata, err
|
||||
}
|
||||
|
||||
func local_request_EveningDetectiveServer_FinishGame_0(ctx context.Context, marshaler runtime.Marshaler, server EveningDetectiveServerServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var (
|
||||
protoReq FinishGameReq
|
||||
metadata runtime.ServerMetadata
|
||||
err error
|
||||
)
|
||||
if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
val, ok := pathParams["id"]
|
||||
if !ok {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id")
|
||||
}
|
||||
protoReq.Id, err = runtime.Int32(val)
|
||||
if err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err)
|
||||
}
|
||||
msg, err := server.FinishGame(ctx, &protoReq)
|
||||
return msg, metadata, err
|
||||
}
|
||||
|
||||
func request_EveningDetectiveServer_ResetGame_0(ctx context.Context, marshaler runtime.Marshaler, client EveningDetectiveServerClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var (
|
||||
protoReq ResetGameReq
|
||||
metadata runtime.ServerMetadata
|
||||
err error
|
||||
)
|
||||
if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
if req.Body != nil {
|
||||
_, _ = io.Copy(io.Discard, req.Body)
|
||||
}
|
||||
val, ok := pathParams["id"]
|
||||
if !ok {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id")
|
||||
}
|
||||
protoReq.Id, err = runtime.Int32(val)
|
||||
if err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err)
|
||||
}
|
||||
msg, err := client.ResetGame(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return msg, metadata, err
|
||||
}
|
||||
|
||||
func local_request_EveningDetectiveServer_ResetGame_0(ctx context.Context, marshaler runtime.Marshaler, server EveningDetectiveServerServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var (
|
||||
protoReq ResetGameReq
|
||||
metadata runtime.ServerMetadata
|
||||
err error
|
||||
)
|
||||
if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
val, ok := pathParams["id"]
|
||||
if !ok {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id")
|
||||
}
|
||||
protoReq.Id, err = runtime.Int32(val)
|
||||
if err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err)
|
||||
}
|
||||
msg, err := server.ResetGame(ctx, &protoReq)
|
||||
return msg, metadata, err
|
||||
}
|
||||
|
||||
func request_EveningDetectiveServer_DeleteGame_0(ctx context.Context, marshaler runtime.Marshaler, client EveningDetectiveServerClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var (
|
||||
protoReq DeleteGameReq
|
||||
@@ -1266,11 +1575,11 @@ func local_request_EveningDetectiveServer_GiveTeamApplications_0(ctx context.Con
|
||||
return msg, metadata, err
|
||||
}
|
||||
|
||||
var filter_EveningDetectiveServer_GetTeamActions_0 = &utilities.DoubleArray{Encoding: map[string]int{"id": 0}, Base: []int{1, 1, 0}, Check: []int{0, 1, 2}}
|
||||
var filter_EveningDetectiveServer_GetTeamStory_0 = &utilities.DoubleArray{Encoding: map[string]int{"id": 0}, Base: []int{1, 1, 0}, Check: []int{0, 1, 2}}
|
||||
|
||||
func request_EveningDetectiveServer_GetTeamActions_0(ctx context.Context, marshaler runtime.Marshaler, client EveningDetectiveServerClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
func request_EveningDetectiveServer_GetTeamStory_0(ctx context.Context, marshaler runtime.Marshaler, client EveningDetectiveServerClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var (
|
||||
protoReq GetTeamActionsReq
|
||||
protoReq GetTeamStoryReq
|
||||
metadata runtime.ServerMetadata
|
||||
err error
|
||||
)
|
||||
@@ -1288,16 +1597,16 @@ func request_EveningDetectiveServer_GetTeamActions_0(ctx context.Context, marsha
|
||||
if err := req.ParseForm(); err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_EveningDetectiveServer_GetTeamActions_0); err != nil {
|
||||
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_EveningDetectiveServer_GetTeamStory_0); err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
msg, err := client.GetTeamActions(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
msg, err := client.GetTeamStory(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return msg, metadata, err
|
||||
}
|
||||
|
||||
func local_request_EveningDetectiveServer_GetTeamActions_0(ctx context.Context, marshaler runtime.Marshaler, server EveningDetectiveServerServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
func local_request_EveningDetectiveServer_GetTeamStory_0(ctx context.Context, marshaler runtime.Marshaler, server EveningDetectiveServerServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var (
|
||||
protoReq GetTeamActionsReq
|
||||
protoReq GetTeamStoryReq
|
||||
metadata runtime.ServerMetadata
|
||||
err error
|
||||
)
|
||||
@@ -1312,10 +1621,10 @@ func local_request_EveningDetectiveServer_GetTeamActions_0(ctx context.Context,
|
||||
if err := req.ParseForm(); err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_EveningDetectiveServer_GetTeamActions_0); err != nil {
|
||||
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_EveningDetectiveServer_GetTeamStory_0); err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
msg, err := server.GetTeamActions(ctx, &protoReq)
|
||||
msg, err := server.GetTeamStory(ctx, &protoReq)
|
||||
return msg, metadata, err
|
||||
}
|
||||
|
||||
@@ -1364,6 +1673,45 @@ func local_request_EveningDetectiveServer_AddTeamAction_0(ctx context.Context, m
|
||||
return msg, metadata, err
|
||||
}
|
||||
|
||||
func request_EveningDetectiveServer_DeleteLastTeamAction_0(ctx context.Context, marshaler runtime.Marshaler, client EveningDetectiveServerClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var (
|
||||
protoReq DeleteLastTeamActionReq
|
||||
metadata runtime.ServerMetadata
|
||||
err error
|
||||
)
|
||||
if req.Body != nil {
|
||||
_, _ = io.Copy(io.Discard, req.Body)
|
||||
}
|
||||
val, ok := pathParams["id"]
|
||||
if !ok {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id")
|
||||
}
|
||||
protoReq.Id, err = runtime.Int64(val)
|
||||
if err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err)
|
||||
}
|
||||
msg, err := client.DeleteLastTeamAction(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return msg, metadata, err
|
||||
}
|
||||
|
||||
func local_request_EveningDetectiveServer_DeleteLastTeamAction_0(ctx context.Context, marshaler runtime.Marshaler, server EveningDetectiveServerServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var (
|
||||
protoReq DeleteLastTeamActionReq
|
||||
metadata runtime.ServerMetadata
|
||||
err error
|
||||
)
|
||||
val, ok := pathParams["id"]
|
||||
if !ok {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id")
|
||||
}
|
||||
protoReq.Id, err = runtime.Int64(val)
|
||||
if err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err)
|
||||
}
|
||||
msg, err := server.DeleteLastTeamAction(ctx, &protoReq)
|
||||
return msg, metadata, err
|
||||
}
|
||||
|
||||
// RegisterEveningDetectiveServerHandlerServer registers the http handlers for service EveningDetectiveServer to "mux".
|
||||
// UnaryRPC :call EveningDetectiveServerServer directly.
|
||||
// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906.
|
||||
@@ -1490,6 +1838,86 @@ func RegisterEveningDetectiveServerHandlerServer(ctx context.Context, mux *runti
|
||||
}
|
||||
forward_EveningDetectiveServer_Refresh_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
})
|
||||
mux.Handle(http.MethodDelete, pattern_EveningDetectiveServer_DeleteAccount_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
var stream runtime.ServerTransportStream
|
||||
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/crabs.evening_detective_server.EveningDetectiveServer/DeleteAccount", runtime.WithHTTPPathPattern("/api/auth/delete-account"))
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := local_request_EveningDetectiveServer_DeleteAccount_0(annotatedContext, inboundMarshaler, server, req, pathParams)
|
||||
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
|
||||
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
forward_EveningDetectiveServer_DeleteAccount_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
})
|
||||
mux.Handle(http.MethodGet, pattern_EveningDetectiveServer_GetTerms_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
var stream runtime.ServerTransportStream
|
||||
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/crabs.evening_detective_server.EveningDetectiveServer/GetTerms", runtime.WithHTTPPathPattern("/api/terms"))
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := local_request_EveningDetectiveServer_GetTerms_0(annotatedContext, inboundMarshaler, server, req, pathParams)
|
||||
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
|
||||
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
forward_EveningDetectiveServer_GetTerms_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
})
|
||||
mux.Handle(http.MethodGet, pattern_EveningDetectiveServer_GetPrivacy_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
var stream runtime.ServerTransportStream
|
||||
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/crabs.evening_detective_server.EveningDetectiveServer/GetPrivacy", runtime.WithHTTPPathPattern("/api/privacy"))
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := local_request_EveningDetectiveServer_GetPrivacy_0(annotatedContext, inboundMarshaler, server, req, pathParams)
|
||||
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
|
||||
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
forward_EveningDetectiveServer_GetPrivacy_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
})
|
||||
mux.Handle(http.MethodGet, pattern_EveningDetectiveServer_GetConsent_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
var stream runtime.ServerTransportStream
|
||||
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/crabs.evening_detective_server.EveningDetectiveServer/GetConsent", runtime.WithHTTPPathPattern("/api/consent"))
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := local_request_EveningDetectiveServer_GetConsent_0(annotatedContext, inboundMarshaler, server, req, pathParams)
|
||||
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
|
||||
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
forward_EveningDetectiveServer_GetConsent_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
})
|
||||
mux.Handle(http.MethodGet, pattern_EveningDetectiveServer_GetUsers_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
@@ -1970,6 +2398,106 @@ func RegisterEveningDetectiveServerHandlerServer(ctx context.Context, mux *runti
|
||||
}
|
||||
forward_EveningDetectiveServer_UpdateGame_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
})
|
||||
mux.Handle(http.MethodPut, pattern_EveningDetectiveServer_StartGame_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
var stream runtime.ServerTransportStream
|
||||
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/crabs.evening_detective_server.EveningDetectiveServer/StartGame", runtime.WithHTTPPathPattern("/api/games/{id}/start"))
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := local_request_EveningDetectiveServer_StartGame_0(annotatedContext, inboundMarshaler, server, req, pathParams)
|
||||
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
|
||||
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
forward_EveningDetectiveServer_StartGame_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
})
|
||||
mux.Handle(http.MethodPut, pattern_EveningDetectiveServer_PauseGame_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
var stream runtime.ServerTransportStream
|
||||
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/crabs.evening_detective_server.EveningDetectiveServer/PauseGame", runtime.WithHTTPPathPattern("/api/games/{id}/pause"))
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := local_request_EveningDetectiveServer_PauseGame_0(annotatedContext, inboundMarshaler, server, req, pathParams)
|
||||
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
|
||||
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
forward_EveningDetectiveServer_PauseGame_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
})
|
||||
mux.Handle(http.MethodPut, pattern_EveningDetectiveServer_PlayGame_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
var stream runtime.ServerTransportStream
|
||||
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/crabs.evening_detective_server.EveningDetectiveServer/PlayGame", runtime.WithHTTPPathPattern("/api/games/{id}/play"))
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := local_request_EveningDetectiveServer_PlayGame_0(annotatedContext, inboundMarshaler, server, req, pathParams)
|
||||
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
|
||||
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
forward_EveningDetectiveServer_PlayGame_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
})
|
||||
mux.Handle(http.MethodPut, pattern_EveningDetectiveServer_FinishGame_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
var stream runtime.ServerTransportStream
|
||||
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/crabs.evening_detective_server.EveningDetectiveServer/FinishGame", runtime.WithHTTPPathPattern("/api/games/{id}/finish"))
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := local_request_EveningDetectiveServer_FinishGame_0(annotatedContext, inboundMarshaler, server, req, pathParams)
|
||||
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
|
||||
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
forward_EveningDetectiveServer_FinishGame_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
})
|
||||
mux.Handle(http.MethodPut, pattern_EveningDetectiveServer_ResetGame_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
var stream runtime.ServerTransportStream
|
||||
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/crabs.evening_detective_server.EveningDetectiveServer/ResetGame", runtime.WithHTTPPathPattern("/api/games/{id}/reset"))
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := local_request_EveningDetectiveServer_ResetGame_0(annotatedContext, inboundMarshaler, server, req, pathParams)
|
||||
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
|
||||
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
forward_EveningDetectiveServer_ResetGame_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
})
|
||||
mux.Handle(http.MethodDelete, pattern_EveningDetectiveServer_DeleteGame_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
@@ -2070,25 +2598,25 @@ func RegisterEveningDetectiveServerHandlerServer(ctx context.Context, mux *runti
|
||||
}
|
||||
forward_EveningDetectiveServer_GiveTeamApplications_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
})
|
||||
mux.Handle(http.MethodGet, pattern_EveningDetectiveServer_GetTeamActions_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
mux.Handle(http.MethodGet, pattern_EveningDetectiveServer_GetTeamStory_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
var stream runtime.ServerTransportStream
|
||||
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/crabs.evening_detective_server.EveningDetectiveServer/GetTeamActions", runtime.WithHTTPPathPattern("/api/teams/{id}/actions"))
|
||||
annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/crabs.evening_detective_server.EveningDetectiveServer/GetTeamStory", runtime.WithHTTPPathPattern("/api/teams/{id}/story"))
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := local_request_EveningDetectiveServer_GetTeamActions_0(annotatedContext, inboundMarshaler, server, req, pathParams)
|
||||
resp, md, err := local_request_EveningDetectiveServer_GetTeamStory_0(annotatedContext, inboundMarshaler, server, req, pathParams)
|
||||
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
|
||||
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
forward_EveningDetectiveServer_GetTeamActions_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
forward_EveningDetectiveServer_GetTeamStory_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
})
|
||||
mux.Handle(http.MethodPost, pattern_EveningDetectiveServer_AddTeamAction_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
@@ -2110,6 +2638,26 @@ func RegisterEveningDetectiveServerHandlerServer(ctx context.Context, mux *runti
|
||||
}
|
||||
forward_EveningDetectiveServer_AddTeamAction_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
})
|
||||
mux.Handle(http.MethodDelete, pattern_EveningDetectiveServer_DeleteLastTeamAction_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
var stream runtime.ServerTransportStream
|
||||
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/crabs.evening_detective_server.EveningDetectiveServer/DeleteLastTeamAction", runtime.WithHTTPPathPattern("/api/teams/{id}/last-actions"))
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := local_request_EveningDetectiveServer_DeleteLastTeamAction_0(annotatedContext, inboundMarshaler, server, req, pathParams)
|
||||
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
|
||||
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
forward_EveningDetectiveServer_DeleteLastTeamAction_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -2252,6 +2800,74 @@ func RegisterEveningDetectiveServerHandlerClient(ctx context.Context, mux *runti
|
||||
}
|
||||
forward_EveningDetectiveServer_Refresh_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
})
|
||||
mux.Handle(http.MethodDelete, pattern_EveningDetectiveServer_DeleteAccount_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/crabs.evening_detective_server.EveningDetectiveServer/DeleteAccount", runtime.WithHTTPPathPattern("/api/auth/delete-account"))
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := request_EveningDetectiveServer_DeleteAccount_0(annotatedContext, inboundMarshaler, client, req, pathParams)
|
||||
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
forward_EveningDetectiveServer_DeleteAccount_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
})
|
||||
mux.Handle(http.MethodGet, pattern_EveningDetectiveServer_GetTerms_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/crabs.evening_detective_server.EveningDetectiveServer/GetTerms", runtime.WithHTTPPathPattern("/api/terms"))
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := request_EveningDetectiveServer_GetTerms_0(annotatedContext, inboundMarshaler, client, req, pathParams)
|
||||
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
forward_EveningDetectiveServer_GetTerms_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
})
|
||||
mux.Handle(http.MethodGet, pattern_EveningDetectiveServer_GetPrivacy_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/crabs.evening_detective_server.EveningDetectiveServer/GetPrivacy", runtime.WithHTTPPathPattern("/api/privacy"))
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := request_EveningDetectiveServer_GetPrivacy_0(annotatedContext, inboundMarshaler, client, req, pathParams)
|
||||
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
forward_EveningDetectiveServer_GetPrivacy_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
})
|
||||
mux.Handle(http.MethodGet, pattern_EveningDetectiveServer_GetConsent_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/crabs.evening_detective_server.EveningDetectiveServer/GetConsent", runtime.WithHTTPPathPattern("/api/consent"))
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := request_EveningDetectiveServer_GetConsent_0(annotatedContext, inboundMarshaler, client, req, pathParams)
|
||||
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
forward_EveningDetectiveServer_GetConsent_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
})
|
||||
mux.Handle(http.MethodGet, pattern_EveningDetectiveServer_GetUsers_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
@@ -2660,6 +3276,91 @@ func RegisterEveningDetectiveServerHandlerClient(ctx context.Context, mux *runti
|
||||
}
|
||||
forward_EveningDetectiveServer_UpdateGame_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
})
|
||||
mux.Handle(http.MethodPut, pattern_EveningDetectiveServer_StartGame_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/crabs.evening_detective_server.EveningDetectiveServer/StartGame", runtime.WithHTTPPathPattern("/api/games/{id}/start"))
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := request_EveningDetectiveServer_StartGame_0(annotatedContext, inboundMarshaler, client, req, pathParams)
|
||||
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
forward_EveningDetectiveServer_StartGame_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
})
|
||||
mux.Handle(http.MethodPut, pattern_EveningDetectiveServer_PauseGame_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/crabs.evening_detective_server.EveningDetectiveServer/PauseGame", runtime.WithHTTPPathPattern("/api/games/{id}/pause"))
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := request_EveningDetectiveServer_PauseGame_0(annotatedContext, inboundMarshaler, client, req, pathParams)
|
||||
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
forward_EveningDetectiveServer_PauseGame_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
})
|
||||
mux.Handle(http.MethodPut, pattern_EveningDetectiveServer_PlayGame_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/crabs.evening_detective_server.EveningDetectiveServer/PlayGame", runtime.WithHTTPPathPattern("/api/games/{id}/play"))
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := request_EveningDetectiveServer_PlayGame_0(annotatedContext, inboundMarshaler, client, req, pathParams)
|
||||
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
forward_EveningDetectiveServer_PlayGame_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
})
|
||||
mux.Handle(http.MethodPut, pattern_EveningDetectiveServer_FinishGame_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/crabs.evening_detective_server.EveningDetectiveServer/FinishGame", runtime.WithHTTPPathPattern("/api/games/{id}/finish"))
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := request_EveningDetectiveServer_FinishGame_0(annotatedContext, inboundMarshaler, client, req, pathParams)
|
||||
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
forward_EveningDetectiveServer_FinishGame_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
})
|
||||
mux.Handle(http.MethodPut, pattern_EveningDetectiveServer_ResetGame_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/crabs.evening_detective_server.EveningDetectiveServer/ResetGame", runtime.WithHTTPPathPattern("/api/games/{id}/reset"))
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := request_EveningDetectiveServer_ResetGame_0(annotatedContext, inboundMarshaler, client, req, pathParams)
|
||||
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
forward_EveningDetectiveServer_ResetGame_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
})
|
||||
mux.Handle(http.MethodDelete, pattern_EveningDetectiveServer_DeleteGame_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
@@ -2745,22 +3446,22 @@ func RegisterEveningDetectiveServerHandlerClient(ctx context.Context, mux *runti
|
||||
}
|
||||
forward_EveningDetectiveServer_GiveTeamApplications_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
})
|
||||
mux.Handle(http.MethodGet, pattern_EveningDetectiveServer_GetTeamActions_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
mux.Handle(http.MethodGet, pattern_EveningDetectiveServer_GetTeamStory_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/crabs.evening_detective_server.EveningDetectiveServer/GetTeamActions", runtime.WithHTTPPathPattern("/api/teams/{id}/actions"))
|
||||
annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/crabs.evening_detective_server.EveningDetectiveServer/GetTeamStory", runtime.WithHTTPPathPattern("/api/teams/{id}/story"))
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := request_EveningDetectiveServer_GetTeamActions_0(annotatedContext, inboundMarshaler, client, req, pathParams)
|
||||
resp, md, err := request_EveningDetectiveServer_GetTeamStory_0(annotatedContext, inboundMarshaler, client, req, pathParams)
|
||||
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
forward_EveningDetectiveServer_GetTeamActions_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
forward_EveningDetectiveServer_GetTeamStory_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
})
|
||||
mux.Handle(http.MethodPost, pattern_EveningDetectiveServer_AddTeamAction_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
@@ -2779,6 +3480,23 @@ func RegisterEveningDetectiveServerHandlerClient(ctx context.Context, mux *runti
|
||||
}
|
||||
forward_EveningDetectiveServer_AddTeamAction_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
})
|
||||
mux.Handle(http.MethodDelete, pattern_EveningDetectiveServer_DeleteLastTeamAction_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/crabs.evening_detective_server.EveningDetectiveServer/DeleteLastTeamAction", runtime.WithHTTPPathPattern("/api/teams/{id}/last-actions"))
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := request_EveningDetectiveServer_DeleteLastTeamAction_0(annotatedContext, inboundMarshaler, client, req, pathParams)
|
||||
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
forward_EveningDetectiveServer_DeleteLastTeamAction_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -2789,6 +3507,10 @@ var (
|
||||
pattern_EveningDetectiveServer_RefreshPassword_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"api", "auth", "refresh-password"}, ""))
|
||||
pattern_EveningDetectiveServer_Login_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"api", "auth", "login"}, ""))
|
||||
pattern_EveningDetectiveServer_Refresh_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"api", "auth", "refresh"}, ""))
|
||||
pattern_EveningDetectiveServer_DeleteAccount_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"api", "auth", "delete-account"}, ""))
|
||||
pattern_EveningDetectiveServer_GetTerms_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"api", "terms"}, ""))
|
||||
pattern_EveningDetectiveServer_GetPrivacy_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"api", "privacy"}, ""))
|
||||
pattern_EveningDetectiveServer_GetConsent_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"api", "consent"}, ""))
|
||||
pattern_EveningDetectiveServer_GetUsers_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"api", "users"}, ""))
|
||||
pattern_EveningDetectiveServer_GetUserById_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2}, []string{"api", "users", "id"}, ""))
|
||||
pattern_EveningDetectiveServer_GetMe_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"api", "users", "me"}, ""))
|
||||
@@ -2813,13 +3535,19 @@ var (
|
||||
pattern_EveningDetectiveServer_GetGames_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"api", "games"}, ""))
|
||||
pattern_EveningDetectiveServer_GetGame_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2}, []string{"api", "games", "id"}, ""))
|
||||
pattern_EveningDetectiveServer_UpdateGame_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2}, []string{"api", "games", "id"}, ""))
|
||||
pattern_EveningDetectiveServer_StartGame_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2, 2, 3}, []string{"api", "games", "id", "start"}, ""))
|
||||
pattern_EveningDetectiveServer_PauseGame_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2, 2, 3}, []string{"api", "games", "id", "pause"}, ""))
|
||||
pattern_EveningDetectiveServer_PlayGame_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2, 2, 3}, []string{"api", "games", "id", "play"}, ""))
|
||||
pattern_EveningDetectiveServer_FinishGame_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2, 2, 3}, []string{"api", "games", "id", "finish"}, ""))
|
||||
pattern_EveningDetectiveServer_ResetGame_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2, 2, 3}, []string{"api", "games", "id", "reset"}, ""))
|
||||
pattern_EveningDetectiveServer_DeleteGame_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2}, []string{"api", "games", "id"}, ""))
|
||||
pattern_EveningDetectiveServer_AddTeam_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"api", "teams"}, ""))
|
||||
pattern_EveningDetectiveServer_UpdateTeam_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2}, []string{"api", "teams", "id"}, ""))
|
||||
pattern_EveningDetectiveServer_DeleteTeam_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2}, []string{"api", "teams", "id"}, ""))
|
||||
pattern_EveningDetectiveServer_GiveTeamApplications_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2, 2, 3}, []string{"api", "teams", "id", "applications"}, ""))
|
||||
pattern_EveningDetectiveServer_GetTeamActions_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2, 2, 3}, []string{"api", "teams", "id", "actions"}, ""))
|
||||
pattern_EveningDetectiveServer_GetTeamStory_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2, 2, 3}, []string{"api", "teams", "id", "story"}, ""))
|
||||
pattern_EveningDetectiveServer_AddTeamAction_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2, 2, 3}, []string{"api", "teams", "id", "actions"}, ""))
|
||||
pattern_EveningDetectiveServer_DeleteLastTeamAction_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2, 2, 3}, []string{"api", "teams", "id", "last-actions"}, ""))
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -2829,6 +3557,10 @@ var (
|
||||
forward_EveningDetectiveServer_RefreshPassword_0 = runtime.ForwardResponseMessage
|
||||
forward_EveningDetectiveServer_Login_0 = runtime.ForwardResponseMessage
|
||||
forward_EveningDetectiveServer_Refresh_0 = runtime.ForwardResponseMessage
|
||||
forward_EveningDetectiveServer_DeleteAccount_0 = runtime.ForwardResponseMessage
|
||||
forward_EveningDetectiveServer_GetTerms_0 = runtime.ForwardResponseMessage
|
||||
forward_EveningDetectiveServer_GetPrivacy_0 = runtime.ForwardResponseMessage
|
||||
forward_EveningDetectiveServer_GetConsent_0 = runtime.ForwardResponseMessage
|
||||
forward_EveningDetectiveServer_GetUsers_0 = runtime.ForwardResponseMessage
|
||||
forward_EveningDetectiveServer_GetUserById_0 = runtime.ForwardResponseMessage
|
||||
forward_EveningDetectiveServer_GetMe_0 = runtime.ForwardResponseMessage
|
||||
@@ -2853,11 +3585,17 @@ var (
|
||||
forward_EveningDetectiveServer_GetGames_0 = runtime.ForwardResponseMessage
|
||||
forward_EveningDetectiveServer_GetGame_0 = runtime.ForwardResponseMessage
|
||||
forward_EveningDetectiveServer_UpdateGame_0 = runtime.ForwardResponseMessage
|
||||
forward_EveningDetectiveServer_StartGame_0 = runtime.ForwardResponseMessage
|
||||
forward_EveningDetectiveServer_PauseGame_0 = runtime.ForwardResponseMessage
|
||||
forward_EveningDetectiveServer_PlayGame_0 = runtime.ForwardResponseMessage
|
||||
forward_EveningDetectiveServer_FinishGame_0 = runtime.ForwardResponseMessage
|
||||
forward_EveningDetectiveServer_ResetGame_0 = runtime.ForwardResponseMessage
|
||||
forward_EveningDetectiveServer_DeleteGame_0 = runtime.ForwardResponseMessage
|
||||
forward_EveningDetectiveServer_AddTeam_0 = runtime.ForwardResponseMessage
|
||||
forward_EveningDetectiveServer_UpdateTeam_0 = runtime.ForwardResponseMessage
|
||||
forward_EveningDetectiveServer_DeleteTeam_0 = runtime.ForwardResponseMessage
|
||||
forward_EveningDetectiveServer_GiveTeamApplications_0 = runtime.ForwardResponseMessage
|
||||
forward_EveningDetectiveServer_GetTeamActions_0 = runtime.ForwardResponseMessage
|
||||
forward_EveningDetectiveServer_GetTeamStory_0 = runtime.ForwardResponseMessage
|
||||
forward_EveningDetectiveServer_AddTeamAction_0 = runtime.ForwardResponseMessage
|
||||
forward_EveningDetectiveServer_DeleteLastTeamAction_0 = runtime.ForwardResponseMessage
|
||||
)
|
||||
|
||||
+395
-15
@@ -26,6 +26,10 @@ const (
|
||||
EveningDetectiveServer_RefreshPassword_FullMethodName = "/crabs.evening_detective_server.EveningDetectiveServer/RefreshPassword"
|
||||
EveningDetectiveServer_Login_FullMethodName = "/crabs.evening_detective_server.EveningDetectiveServer/Login"
|
||||
EveningDetectiveServer_Refresh_FullMethodName = "/crabs.evening_detective_server.EveningDetectiveServer/Refresh"
|
||||
EveningDetectiveServer_DeleteAccount_FullMethodName = "/crabs.evening_detective_server.EveningDetectiveServer/DeleteAccount"
|
||||
EveningDetectiveServer_GetTerms_FullMethodName = "/crabs.evening_detective_server.EveningDetectiveServer/GetTerms"
|
||||
EveningDetectiveServer_GetPrivacy_FullMethodName = "/crabs.evening_detective_server.EveningDetectiveServer/GetPrivacy"
|
||||
EveningDetectiveServer_GetConsent_FullMethodName = "/crabs.evening_detective_server.EveningDetectiveServer/GetConsent"
|
||||
EveningDetectiveServer_GetUsers_FullMethodName = "/crabs.evening_detective_server.EveningDetectiveServer/GetUsers"
|
||||
EveningDetectiveServer_GetUserById_FullMethodName = "/crabs.evening_detective_server.EveningDetectiveServer/GetUserById"
|
||||
EveningDetectiveServer_GetMe_FullMethodName = "/crabs.evening_detective_server.EveningDetectiveServer/GetMe"
|
||||
@@ -50,13 +54,19 @@ const (
|
||||
EveningDetectiveServer_GetGames_FullMethodName = "/crabs.evening_detective_server.EveningDetectiveServer/GetGames"
|
||||
EveningDetectiveServer_GetGame_FullMethodName = "/crabs.evening_detective_server.EveningDetectiveServer/GetGame"
|
||||
EveningDetectiveServer_UpdateGame_FullMethodName = "/crabs.evening_detective_server.EveningDetectiveServer/UpdateGame"
|
||||
EveningDetectiveServer_StartGame_FullMethodName = "/crabs.evening_detective_server.EveningDetectiveServer/StartGame"
|
||||
EveningDetectiveServer_PauseGame_FullMethodName = "/crabs.evening_detective_server.EveningDetectiveServer/PauseGame"
|
||||
EveningDetectiveServer_PlayGame_FullMethodName = "/crabs.evening_detective_server.EveningDetectiveServer/PlayGame"
|
||||
EveningDetectiveServer_FinishGame_FullMethodName = "/crabs.evening_detective_server.EveningDetectiveServer/FinishGame"
|
||||
EveningDetectiveServer_ResetGame_FullMethodName = "/crabs.evening_detective_server.EveningDetectiveServer/ResetGame"
|
||||
EveningDetectiveServer_DeleteGame_FullMethodName = "/crabs.evening_detective_server.EveningDetectiveServer/DeleteGame"
|
||||
EveningDetectiveServer_AddTeam_FullMethodName = "/crabs.evening_detective_server.EveningDetectiveServer/AddTeam"
|
||||
EveningDetectiveServer_UpdateTeam_FullMethodName = "/crabs.evening_detective_server.EveningDetectiveServer/UpdateTeam"
|
||||
EveningDetectiveServer_DeleteTeam_FullMethodName = "/crabs.evening_detective_server.EveningDetectiveServer/DeleteTeam"
|
||||
EveningDetectiveServer_GiveTeamApplications_FullMethodName = "/crabs.evening_detective_server.EveningDetectiveServer/GiveTeamApplications"
|
||||
EveningDetectiveServer_GetTeamActions_FullMethodName = "/crabs.evening_detective_server.EveningDetectiveServer/GetTeamActions"
|
||||
EveningDetectiveServer_GetTeamStory_FullMethodName = "/crabs.evening_detective_server.EveningDetectiveServer/GetTeamStory"
|
||||
EveningDetectiveServer_AddTeamAction_FullMethodName = "/crabs.evening_detective_server.EveningDetectiveServer/AddTeamAction"
|
||||
EveningDetectiveServer_DeleteLastTeamAction_FullMethodName = "/crabs.evening_detective_server.EveningDetectiveServer/DeleteLastTeamAction"
|
||||
)
|
||||
|
||||
// EveningDetectiveServerClient is the client API for EveningDetectiveServer service.
|
||||
@@ -69,6 +79,10 @@ type EveningDetectiveServerClient interface {
|
||||
RefreshPassword(ctx context.Context, in *RefreshPasswordReq, opts ...grpc.CallOption) (*RefreshPasswordRsp, error)
|
||||
Login(ctx context.Context, in *LoginReq, opts ...grpc.CallOption) (*LoginRsp, error)
|
||||
Refresh(ctx context.Context, in *RefreshReq, opts ...grpc.CallOption) (*RefreshRsp, error)
|
||||
DeleteAccount(ctx context.Context, in *DeleteAccountReq, opts ...grpc.CallOption) (*DeleteAccountRsp, error)
|
||||
GetTerms(ctx context.Context, in *GetTermsReq, opts ...grpc.CallOption) (*GetTermsRsp, error)
|
||||
GetPrivacy(ctx context.Context, in *GetPrivacyReq, opts ...grpc.CallOption) (*GetPrivacyRsp, error)
|
||||
GetConsent(ctx context.Context, in *GetConsentReq, opts ...grpc.CallOption) (*GetConsentRsp, error)
|
||||
GetUsers(ctx context.Context, in *GetUsersReq, opts ...grpc.CallOption) (*GetUsersRsp, error)
|
||||
GetUserById(ctx context.Context, in *GetUserByIdReq, opts ...grpc.CallOption) (*GetUserByIdRsp, error)
|
||||
GetMe(ctx context.Context, in *GetMeReq, opts ...grpc.CallOption) (*GetMeRsp, error)
|
||||
@@ -93,13 +107,19 @@ type EveningDetectiveServerClient interface {
|
||||
GetGames(ctx context.Context, in *GetGamesReq, opts ...grpc.CallOption) (*GetGamesRsp, error)
|
||||
GetGame(ctx context.Context, in *GetGameReq, opts ...grpc.CallOption) (*GetGameRsp, error)
|
||||
UpdateGame(ctx context.Context, in *UpdateGameReq, opts ...grpc.CallOption) (*UpdateGameRsp, error)
|
||||
StartGame(ctx context.Context, in *StartGameReq, opts ...grpc.CallOption) (*StartGameRsp, error)
|
||||
PauseGame(ctx context.Context, in *PauseGameReq, opts ...grpc.CallOption) (*PauseGameRsp, error)
|
||||
PlayGame(ctx context.Context, in *PlayGameReq, opts ...grpc.CallOption) (*PlayGameRsp, error)
|
||||
FinishGame(ctx context.Context, in *FinishGameReq, opts ...grpc.CallOption) (*FinishGameRsp, error)
|
||||
ResetGame(ctx context.Context, in *ResetGameReq, opts ...grpc.CallOption) (*ResetGameRsp, error)
|
||||
DeleteGame(ctx context.Context, in *DeleteGameReq, opts ...grpc.CallOption) (*DeleteGameRsp, error)
|
||||
AddTeam(ctx context.Context, in *AddTeamReq, opts ...grpc.CallOption) (*AddTeamRsp, error)
|
||||
UpdateTeam(ctx context.Context, in *UpdateTeamReq, opts ...grpc.CallOption) (*UpdateTeamRsp, error)
|
||||
DeleteTeam(ctx context.Context, in *DeleteTeamReq, opts ...grpc.CallOption) (*DeleteTeamRsp, error)
|
||||
GiveTeamApplications(ctx context.Context, in *GiveTeamApplicationsReq, opts ...grpc.CallOption) (*GiveTeamApplicationsRsp, error)
|
||||
GetTeamActions(ctx context.Context, in *GetTeamActionsReq, opts ...grpc.CallOption) (*GetTeamActionsRsp, error)
|
||||
GetTeamStory(ctx context.Context, in *GetTeamStoryReq, opts ...grpc.CallOption) (*GetTeamStoryRsp, error)
|
||||
AddTeamAction(ctx context.Context, in *AddTeamActionReq, opts ...grpc.CallOption) (*AddTeamActionRsp, error)
|
||||
DeleteLastTeamAction(ctx context.Context, in *DeleteLastTeamActionReq, opts ...grpc.CallOption) (*DeleteLastTeamActionRsp, error)
|
||||
}
|
||||
|
||||
type eveningDetectiveServerClient struct {
|
||||
@@ -170,6 +190,46 @@ func (c *eveningDetectiveServerClient) Refresh(ctx context.Context, in *RefreshR
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *eveningDetectiveServerClient) DeleteAccount(ctx context.Context, in *DeleteAccountReq, opts ...grpc.CallOption) (*DeleteAccountRsp, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(DeleteAccountRsp)
|
||||
err := c.cc.Invoke(ctx, EveningDetectiveServer_DeleteAccount_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *eveningDetectiveServerClient) GetTerms(ctx context.Context, in *GetTermsReq, opts ...grpc.CallOption) (*GetTermsRsp, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(GetTermsRsp)
|
||||
err := c.cc.Invoke(ctx, EveningDetectiveServer_GetTerms_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *eveningDetectiveServerClient) GetPrivacy(ctx context.Context, in *GetPrivacyReq, opts ...grpc.CallOption) (*GetPrivacyRsp, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(GetPrivacyRsp)
|
||||
err := c.cc.Invoke(ctx, EveningDetectiveServer_GetPrivacy_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *eveningDetectiveServerClient) GetConsent(ctx context.Context, in *GetConsentReq, opts ...grpc.CallOption) (*GetConsentRsp, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(GetConsentRsp)
|
||||
err := c.cc.Invoke(ctx, EveningDetectiveServer_GetConsent_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *eveningDetectiveServerClient) GetUsers(ctx context.Context, in *GetUsersReq, opts ...grpc.CallOption) (*GetUsersRsp, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(GetUsersRsp)
|
||||
@@ -410,6 +470,56 @@ func (c *eveningDetectiveServerClient) UpdateGame(ctx context.Context, in *Updat
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *eveningDetectiveServerClient) StartGame(ctx context.Context, in *StartGameReq, opts ...grpc.CallOption) (*StartGameRsp, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(StartGameRsp)
|
||||
err := c.cc.Invoke(ctx, EveningDetectiveServer_StartGame_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *eveningDetectiveServerClient) PauseGame(ctx context.Context, in *PauseGameReq, opts ...grpc.CallOption) (*PauseGameRsp, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(PauseGameRsp)
|
||||
err := c.cc.Invoke(ctx, EveningDetectiveServer_PauseGame_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *eveningDetectiveServerClient) PlayGame(ctx context.Context, in *PlayGameReq, opts ...grpc.CallOption) (*PlayGameRsp, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(PlayGameRsp)
|
||||
err := c.cc.Invoke(ctx, EveningDetectiveServer_PlayGame_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *eveningDetectiveServerClient) FinishGame(ctx context.Context, in *FinishGameReq, opts ...grpc.CallOption) (*FinishGameRsp, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(FinishGameRsp)
|
||||
err := c.cc.Invoke(ctx, EveningDetectiveServer_FinishGame_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *eveningDetectiveServerClient) ResetGame(ctx context.Context, in *ResetGameReq, opts ...grpc.CallOption) (*ResetGameRsp, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(ResetGameRsp)
|
||||
err := c.cc.Invoke(ctx, EveningDetectiveServer_ResetGame_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *eveningDetectiveServerClient) DeleteGame(ctx context.Context, in *DeleteGameReq, opts ...grpc.CallOption) (*DeleteGameRsp, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(DeleteGameRsp)
|
||||
@@ -460,10 +570,10 @@ func (c *eveningDetectiveServerClient) GiveTeamApplications(ctx context.Context,
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *eveningDetectiveServerClient) GetTeamActions(ctx context.Context, in *GetTeamActionsReq, opts ...grpc.CallOption) (*GetTeamActionsRsp, error) {
|
||||
func (c *eveningDetectiveServerClient) GetTeamStory(ctx context.Context, in *GetTeamStoryReq, opts ...grpc.CallOption) (*GetTeamStoryRsp, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(GetTeamActionsRsp)
|
||||
err := c.cc.Invoke(ctx, EveningDetectiveServer_GetTeamActions_FullMethodName, in, out, cOpts...)
|
||||
out := new(GetTeamStoryRsp)
|
||||
err := c.cc.Invoke(ctx, EveningDetectiveServer_GetTeamStory_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -480,6 +590,16 @@ func (c *eveningDetectiveServerClient) AddTeamAction(ctx context.Context, in *Ad
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *eveningDetectiveServerClient) DeleteLastTeamAction(ctx context.Context, in *DeleteLastTeamActionReq, opts ...grpc.CallOption) (*DeleteLastTeamActionRsp, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(DeleteLastTeamActionRsp)
|
||||
err := c.cc.Invoke(ctx, EveningDetectiveServer_DeleteLastTeamAction_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// EveningDetectiveServerServer is the server API for EveningDetectiveServer service.
|
||||
// All implementations must embed UnimplementedEveningDetectiveServerServer
|
||||
// for forward compatibility.
|
||||
@@ -490,6 +610,10 @@ type EveningDetectiveServerServer interface {
|
||||
RefreshPassword(context.Context, *RefreshPasswordReq) (*RefreshPasswordRsp, error)
|
||||
Login(context.Context, *LoginReq) (*LoginRsp, error)
|
||||
Refresh(context.Context, *RefreshReq) (*RefreshRsp, error)
|
||||
DeleteAccount(context.Context, *DeleteAccountReq) (*DeleteAccountRsp, error)
|
||||
GetTerms(context.Context, *GetTermsReq) (*GetTermsRsp, error)
|
||||
GetPrivacy(context.Context, *GetPrivacyReq) (*GetPrivacyRsp, error)
|
||||
GetConsent(context.Context, *GetConsentReq) (*GetConsentRsp, error)
|
||||
GetUsers(context.Context, *GetUsersReq) (*GetUsersRsp, error)
|
||||
GetUserById(context.Context, *GetUserByIdReq) (*GetUserByIdRsp, error)
|
||||
GetMe(context.Context, *GetMeReq) (*GetMeRsp, error)
|
||||
@@ -514,13 +638,19 @@ type EveningDetectiveServerServer interface {
|
||||
GetGames(context.Context, *GetGamesReq) (*GetGamesRsp, error)
|
||||
GetGame(context.Context, *GetGameReq) (*GetGameRsp, error)
|
||||
UpdateGame(context.Context, *UpdateGameReq) (*UpdateGameRsp, error)
|
||||
StartGame(context.Context, *StartGameReq) (*StartGameRsp, error)
|
||||
PauseGame(context.Context, *PauseGameReq) (*PauseGameRsp, error)
|
||||
PlayGame(context.Context, *PlayGameReq) (*PlayGameRsp, error)
|
||||
FinishGame(context.Context, *FinishGameReq) (*FinishGameRsp, error)
|
||||
ResetGame(context.Context, *ResetGameReq) (*ResetGameRsp, error)
|
||||
DeleteGame(context.Context, *DeleteGameReq) (*DeleteGameRsp, error)
|
||||
AddTeam(context.Context, *AddTeamReq) (*AddTeamRsp, error)
|
||||
UpdateTeam(context.Context, *UpdateTeamReq) (*UpdateTeamRsp, error)
|
||||
DeleteTeam(context.Context, *DeleteTeamReq) (*DeleteTeamRsp, error)
|
||||
GiveTeamApplications(context.Context, *GiveTeamApplicationsReq) (*GiveTeamApplicationsRsp, error)
|
||||
GetTeamActions(context.Context, *GetTeamActionsReq) (*GetTeamActionsRsp, error)
|
||||
GetTeamStory(context.Context, *GetTeamStoryReq) (*GetTeamStoryRsp, error)
|
||||
AddTeamAction(context.Context, *AddTeamActionReq) (*AddTeamActionRsp, error)
|
||||
DeleteLastTeamAction(context.Context, *DeleteLastTeamActionReq) (*DeleteLastTeamActionRsp, error)
|
||||
mustEmbedUnimplementedEveningDetectiveServerServer()
|
||||
}
|
||||
|
||||
@@ -549,6 +679,18 @@ func (UnimplementedEveningDetectiveServerServer) Login(context.Context, *LoginRe
|
||||
func (UnimplementedEveningDetectiveServerServer) Refresh(context.Context, *RefreshReq) (*RefreshRsp, error) {
|
||||
return nil, status.Error(codes.Unimplemented, "method Refresh not implemented")
|
||||
}
|
||||
func (UnimplementedEveningDetectiveServerServer) DeleteAccount(context.Context, *DeleteAccountReq) (*DeleteAccountRsp, error) {
|
||||
return nil, status.Error(codes.Unimplemented, "method DeleteAccount not implemented")
|
||||
}
|
||||
func (UnimplementedEveningDetectiveServerServer) GetTerms(context.Context, *GetTermsReq) (*GetTermsRsp, error) {
|
||||
return nil, status.Error(codes.Unimplemented, "method GetTerms not implemented")
|
||||
}
|
||||
func (UnimplementedEveningDetectiveServerServer) GetPrivacy(context.Context, *GetPrivacyReq) (*GetPrivacyRsp, error) {
|
||||
return nil, status.Error(codes.Unimplemented, "method GetPrivacy not implemented")
|
||||
}
|
||||
func (UnimplementedEveningDetectiveServerServer) GetConsent(context.Context, *GetConsentReq) (*GetConsentRsp, error) {
|
||||
return nil, status.Error(codes.Unimplemented, "method GetConsent not implemented")
|
||||
}
|
||||
func (UnimplementedEveningDetectiveServerServer) GetUsers(context.Context, *GetUsersReq) (*GetUsersRsp, error) {
|
||||
return nil, status.Error(codes.Unimplemented, "method GetUsers not implemented")
|
||||
}
|
||||
@@ -621,6 +763,21 @@ func (UnimplementedEveningDetectiveServerServer) GetGame(context.Context, *GetGa
|
||||
func (UnimplementedEveningDetectiveServerServer) UpdateGame(context.Context, *UpdateGameReq) (*UpdateGameRsp, error) {
|
||||
return nil, status.Error(codes.Unimplemented, "method UpdateGame not implemented")
|
||||
}
|
||||
func (UnimplementedEveningDetectiveServerServer) StartGame(context.Context, *StartGameReq) (*StartGameRsp, error) {
|
||||
return nil, status.Error(codes.Unimplemented, "method StartGame not implemented")
|
||||
}
|
||||
func (UnimplementedEveningDetectiveServerServer) PauseGame(context.Context, *PauseGameReq) (*PauseGameRsp, error) {
|
||||
return nil, status.Error(codes.Unimplemented, "method PauseGame not implemented")
|
||||
}
|
||||
func (UnimplementedEveningDetectiveServerServer) PlayGame(context.Context, *PlayGameReq) (*PlayGameRsp, error) {
|
||||
return nil, status.Error(codes.Unimplemented, "method PlayGame not implemented")
|
||||
}
|
||||
func (UnimplementedEveningDetectiveServerServer) FinishGame(context.Context, *FinishGameReq) (*FinishGameRsp, error) {
|
||||
return nil, status.Error(codes.Unimplemented, "method FinishGame not implemented")
|
||||
}
|
||||
func (UnimplementedEveningDetectiveServerServer) ResetGame(context.Context, *ResetGameReq) (*ResetGameRsp, error) {
|
||||
return nil, status.Error(codes.Unimplemented, "method ResetGame not implemented")
|
||||
}
|
||||
func (UnimplementedEveningDetectiveServerServer) DeleteGame(context.Context, *DeleteGameReq) (*DeleteGameRsp, error) {
|
||||
return nil, status.Error(codes.Unimplemented, "method DeleteGame not implemented")
|
||||
}
|
||||
@@ -636,12 +793,15 @@ func (UnimplementedEveningDetectiveServerServer) DeleteTeam(context.Context, *De
|
||||
func (UnimplementedEveningDetectiveServerServer) GiveTeamApplications(context.Context, *GiveTeamApplicationsReq) (*GiveTeamApplicationsRsp, error) {
|
||||
return nil, status.Error(codes.Unimplemented, "method GiveTeamApplications not implemented")
|
||||
}
|
||||
func (UnimplementedEveningDetectiveServerServer) GetTeamActions(context.Context, *GetTeamActionsReq) (*GetTeamActionsRsp, error) {
|
||||
return nil, status.Error(codes.Unimplemented, "method GetTeamActions not implemented")
|
||||
func (UnimplementedEveningDetectiveServerServer) GetTeamStory(context.Context, *GetTeamStoryReq) (*GetTeamStoryRsp, error) {
|
||||
return nil, status.Error(codes.Unimplemented, "method GetTeamStory not implemented")
|
||||
}
|
||||
func (UnimplementedEveningDetectiveServerServer) AddTeamAction(context.Context, *AddTeamActionReq) (*AddTeamActionRsp, error) {
|
||||
return nil, status.Error(codes.Unimplemented, "method AddTeamAction not implemented")
|
||||
}
|
||||
func (UnimplementedEveningDetectiveServerServer) DeleteLastTeamAction(context.Context, *DeleteLastTeamActionReq) (*DeleteLastTeamActionRsp, error) {
|
||||
return nil, status.Error(codes.Unimplemented, "method DeleteLastTeamAction not implemented")
|
||||
}
|
||||
func (UnimplementedEveningDetectiveServerServer) mustEmbedUnimplementedEveningDetectiveServerServer() {
|
||||
}
|
||||
func (UnimplementedEveningDetectiveServerServer) testEmbeddedByValue() {}
|
||||
@@ -772,6 +932,78 @@ func _EveningDetectiveServer_Refresh_Handler(srv interface{}, ctx context.Contex
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _EveningDetectiveServer_DeleteAccount_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(DeleteAccountReq)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(EveningDetectiveServerServer).DeleteAccount(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: EveningDetectiveServer_DeleteAccount_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(EveningDetectiveServerServer).DeleteAccount(ctx, req.(*DeleteAccountReq))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _EveningDetectiveServer_GetTerms_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(GetTermsReq)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(EveningDetectiveServerServer).GetTerms(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: EveningDetectiveServer_GetTerms_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(EveningDetectiveServerServer).GetTerms(ctx, req.(*GetTermsReq))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _EveningDetectiveServer_GetPrivacy_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(GetPrivacyReq)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(EveningDetectiveServerServer).GetPrivacy(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: EveningDetectiveServer_GetPrivacy_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(EveningDetectiveServerServer).GetPrivacy(ctx, req.(*GetPrivacyReq))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _EveningDetectiveServer_GetConsent_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(GetConsentReq)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(EveningDetectiveServerServer).GetConsent(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: EveningDetectiveServer_GetConsent_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(EveningDetectiveServerServer).GetConsent(ctx, req.(*GetConsentReq))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _EveningDetectiveServer_GetUsers_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(GetUsersReq)
|
||||
if err := dec(in); err != nil {
|
||||
@@ -1204,6 +1436,96 @@ func _EveningDetectiveServer_UpdateGame_Handler(srv interface{}, ctx context.Con
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _EveningDetectiveServer_StartGame_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(StartGameReq)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(EveningDetectiveServerServer).StartGame(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: EveningDetectiveServer_StartGame_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(EveningDetectiveServerServer).StartGame(ctx, req.(*StartGameReq))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _EveningDetectiveServer_PauseGame_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(PauseGameReq)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(EveningDetectiveServerServer).PauseGame(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: EveningDetectiveServer_PauseGame_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(EveningDetectiveServerServer).PauseGame(ctx, req.(*PauseGameReq))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _EveningDetectiveServer_PlayGame_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(PlayGameReq)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(EveningDetectiveServerServer).PlayGame(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: EveningDetectiveServer_PlayGame_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(EveningDetectiveServerServer).PlayGame(ctx, req.(*PlayGameReq))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _EveningDetectiveServer_FinishGame_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(FinishGameReq)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(EveningDetectiveServerServer).FinishGame(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: EveningDetectiveServer_FinishGame_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(EveningDetectiveServerServer).FinishGame(ctx, req.(*FinishGameReq))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _EveningDetectiveServer_ResetGame_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(ResetGameReq)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(EveningDetectiveServerServer).ResetGame(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: EveningDetectiveServer_ResetGame_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(EveningDetectiveServerServer).ResetGame(ctx, req.(*ResetGameReq))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _EveningDetectiveServer_DeleteGame_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(DeleteGameReq)
|
||||
if err := dec(in); err != nil {
|
||||
@@ -1294,20 +1616,20 @@ func _EveningDetectiveServer_GiveTeamApplications_Handler(srv interface{}, ctx c
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _EveningDetectiveServer_GetTeamActions_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(GetTeamActionsReq)
|
||||
func _EveningDetectiveServer_GetTeamStory_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(GetTeamStoryReq)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(EveningDetectiveServerServer).GetTeamActions(ctx, in)
|
||||
return srv.(EveningDetectiveServerServer).GetTeamStory(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: EveningDetectiveServer_GetTeamActions_FullMethodName,
|
||||
FullMethod: EveningDetectiveServer_GetTeamStory_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(EveningDetectiveServerServer).GetTeamActions(ctx, req.(*GetTeamActionsReq))
|
||||
return srv.(EveningDetectiveServerServer).GetTeamStory(ctx, req.(*GetTeamStoryReq))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
@@ -1330,6 +1652,24 @@ func _EveningDetectiveServer_AddTeamAction_Handler(srv interface{}, ctx context.
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _EveningDetectiveServer_DeleteLastTeamAction_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(DeleteLastTeamActionReq)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(EveningDetectiveServerServer).DeleteLastTeamAction(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: EveningDetectiveServer_DeleteLastTeamAction_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(EveningDetectiveServerServer).DeleteLastTeamAction(ctx, req.(*DeleteLastTeamActionReq))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
// EveningDetectiveServer_ServiceDesc is the grpc.ServiceDesc for EveningDetectiveServer service.
|
||||
// It's only intended for direct use with grpc.RegisterService,
|
||||
// and not to be introspected or modified (even as a copy)
|
||||
@@ -1361,6 +1701,22 @@ var EveningDetectiveServer_ServiceDesc = grpc.ServiceDesc{
|
||||
MethodName: "Refresh",
|
||||
Handler: _EveningDetectiveServer_Refresh_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "DeleteAccount",
|
||||
Handler: _EveningDetectiveServer_DeleteAccount_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "GetTerms",
|
||||
Handler: _EveningDetectiveServer_GetTerms_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "GetPrivacy",
|
||||
Handler: _EveningDetectiveServer_GetPrivacy_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "GetConsent",
|
||||
Handler: _EveningDetectiveServer_GetConsent_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "GetUsers",
|
||||
Handler: _EveningDetectiveServer_GetUsers_Handler,
|
||||
@@ -1457,6 +1813,26 @@ var EveningDetectiveServer_ServiceDesc = grpc.ServiceDesc{
|
||||
MethodName: "UpdateGame",
|
||||
Handler: _EveningDetectiveServer_UpdateGame_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "StartGame",
|
||||
Handler: _EveningDetectiveServer_StartGame_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "PauseGame",
|
||||
Handler: _EveningDetectiveServer_PauseGame_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "PlayGame",
|
||||
Handler: _EveningDetectiveServer_PlayGame_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "FinishGame",
|
||||
Handler: _EveningDetectiveServer_FinishGame_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "ResetGame",
|
||||
Handler: _EveningDetectiveServer_ResetGame_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "DeleteGame",
|
||||
Handler: _EveningDetectiveServer_DeleteGame_Handler,
|
||||
@@ -1478,13 +1854,17 @@ var EveningDetectiveServer_ServiceDesc = grpc.ServiceDesc{
|
||||
Handler: _EveningDetectiveServer_GiveTeamApplications_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "GetTeamActions",
|
||||
Handler: _EveningDetectiveServer_GetTeamActions_Handler,
|
||||
MethodName: "GetTeamStory",
|
||||
Handler: _EveningDetectiveServer_GetTeamStory_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "AddTeamAction",
|
||||
Handler: _EveningDetectiveServer_AddTeamAction_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "DeleteLastTeamAction",
|
||||
Handler: _EveningDetectiveServer_DeleteLastTeamAction_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{},
|
||||
Metadata: "main.proto",
|
||||
|
||||
Reference in New Issue
Block a user