Compare commits

..

3 Commits

5 changed files with 106 additions and 1 deletions
+1
View File
@@ -13,6 +13,7 @@ COPY --from=builder /usr/local/lib/python3.12/site-packages /usr/local/lib/pytho
COPY --from=builder /usr/local/bin /usr/local/bin COPY --from=builder /usr/local/bin /usr/local/bin
COPY app/ app/ COPY app/ app/
COPY static/ static/
COPY .env.example .env COPY .env.example .env
# Read-only rootfs — только /data для БД и /logs для аудита # Read-only rootfs — только /data для БД и /logs для аудита
+36 -1
View File
@@ -6,11 +6,12 @@ import logging
from contextlib import asynccontextmanager from contextlib import asynccontextmanager
from fastapi import FastAPI, Request from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse from fastapi.responses import FileResponse, HTMLResponse, JSONResponse
from app.database import init_db from app.database import init_db
from app.api.v1 import router as api_v1_router from app.api.v1 import router as api_v1_router
from app.api.audit import AuditMiddleware from app.api.audit import AuditMiddleware
from pathlib import Path
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(name)s: %(message)s") logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(name)s: %(message)s")
logger = logging.getLogger("md2vk") logger = logging.getLogger("md2vk")
@@ -38,6 +39,40 @@ app.add_middleware(AuditMiddleware)
app.include_router(api_v1_router) app.include_router(api_v1_router)
STATIC_DIR = Path(__file__).resolve().parent.parent / "static"
@app.get("/favicon.ico", include_in_schema=False)
async def favicon() -> FileResponse:
"""Favicon (SVG-бренд)."""
return FileResponse(STATIC_DIR / "favicon.svg", media_type="image/svg+xml")
@app.get("/", response_class=HTMLResponse, include_in_schema=False)
async def index() -> HTMLResponse:
"""Стартовая страница сервиса."""
return HTMLResponse(
"""<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>md2vk</title>
<style>
body { font-family: system-ui, sans-serif; max-width: 640px; margin: 80px auto; padding: 0 20px;
background: #0d1117; color: #e6edf3; }
h1 { color: #58a6ff; }
code { background: #1f2630; padding: 2px 6px; border-radius: 4px; }
</style>
</head>
<body>
<h1>md2vk</h1>
<p>Сервис публикации Markdown на стену ВКонтакте.</p>
<p>API: <code>/api/v1</code> · <a href="/docs" style="color:#58a6ff">Swagger UI</a> · <a href="/redoc" style="color:#58a6ff">ReDoc</a></p>
</body>
</html>"""
)
@app.exception_handler(Exception) @app.exception_handler(Exception)
async def global_exception_handler(request: Request, exc: Exception): async def global_exception_handler(request: Request, exc: Exception):
+1
View File
@@ -34,6 +34,7 @@
## Открытые задачи ## Открытые задачи
- [ ] **БЛОКЕР: VK API недоступен для физлиц.** По состоянию на 2026-09-20 доступ к VK API (создание приложения, токены) — только для юрлиц, ИП и самозанятых (VK ID, «подтвердите профиль бизнеса»). У пользователя статуса нет → публикация/тест поста невозможны до изменения условий VK. Кабинет приложений: https://id.vk.ru/about/business/go/accounts/465683/apps (типы приложений: Web/Android/iOS; Standalone убран). Когда доступ откроют: см. `docs/vk-token-howto.md`.
- [ ] Планировщик отложенных постов (status=scheduled → wall.post). Модель готова, worker нет. - [ ] Планировщик отложенных постов (status=scheduled → wall.post). Модель готова, worker нет.
- [ ] OAuth-флоу VK ID (кнопка «Прикрепить аккаунт»); нужны client_id/secret VK-приложения - [ ] OAuth-флоу VK ID (кнопка «Прикрепить аккаунт»); нужны client_id/secret VK-приложения
- [ ] Web UI (форма поста + превью) - [ ] Web UI (форма поста + превью)
+67
View File
@@ -0,0 +1,67 @@
# Как получить VK access_token и опубликовать первый пост (шпаргалка)
## 1. Создать VK-приложение (один раз, вручную, на сайте VK)
1. Открой https://id.vk.com/about/business → «VK ID» → «Мои приложения» (или https://dev.vk.com).
2. «Создать приложение» → тип **Standalone-приложение**.
3. После создания будет `client_id` (число, например `123456789`).
Адрес приложения/redirect можно оставить `https://oauth.vk.com/blank.html`.
## 2. Получить токен (открыть ссылку в браузере, войти в VK)
```
https://oauth.vk.com/authorize?client_id=<client_id>&scope=wall,offline&redirect_uri=https://oauth.vk.com/blank.html&response_type=token
```
После подтверждения браузер перейдёт на
`https://oauth.vk.com/blank.html#access_token=<TOKEN>&expires_in=0&user_id=<ID>`
(expires_in=0 — бессрочный, благодаря scope=offline).
- `user_id` — ID твоей страницы (нужен для `vk_user_id`).
- Токен — длинная строка, права `wall` (пост на стену), `offline` (долгоживущий).
## 3. Добавить аккаунт в md2vk (через Swagger)
Открой https://md2vk.nixg.ru/docs (введя пароль), найди `POST /api/v1/accounts`:
```json
{
"api_key": "<API-ключ из /opt/md2vk/secrets/estorozhenko_api_key.txt>",
"vk_user_id": <user_id из шага 2>,
"display_name": "<имя, например моя страница>",
"access_token": "<TOKEN из шага 2>",
"token_type": "user"
}
```
Ответ вернёт `"id": 1` — это `vk_account_id`.
## 4. Опубликовать пост
`POST /api/v1/publish`:
```json
{
"api_key": "<тот же ключ>",
"vk_account_id": 1,
"message_md": "Привет, мир! Это тестовый пост через md2vk."
}
```
Ответ: `"success": true, "url": "https://vk.com/wall<user_id>_<post_id>"`.
## Альтернатива: curl из terminal (без Swagger)
```bash
API_KEY=$(cat /opt/md2vk/secrets/estorozhenko_api_key.txt)
# добавить аккаунт
curl -s -u estorozhenko:<пароль> -X POST https://md2vk.nixg.ru/api/v1/accounts \
-H 'Content-Type: application/json' \
-d '{"api_key":"'$API_KEY'","vk_user_id":<user_id>,"display_name":"my","access_token":"<TOKEN>","token_type":"user"}'
# опубликовать
curl -s -u estorozhenko:<пароль> -X POST https://md2vk.nixg.ru/api/v1/publish \
-H 'Content-Type: application/json' \
-d '{"api_key":"'$API_KEY'","vk_account_id":1,"message_md":"Привет, мир! Это тестовый пост через md2vk."}'
```
> Пароль md2vk.nixg.ru — у пользователя (в конфиг/репо не пишем).
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><rect width="32" height="32" rx="6" fill="#0d1117"/><text x="16" y="22" font-family="Arial, sans-serif" font-size="16" font-weight="bold" fill="#58a6ff" text-anchor="middle">m</text></svg>

After

Width:  |  Height:  |  Size: 248 B