This commit is contained in:
Vladislav 2026-07-19 22:01:29 -04:00
parent 60aa58844c
commit f72102e7b1
10 changed files with 69 additions and 62 deletions

View File

@ -11,7 +11,9 @@ PROD = os.getenv("PROD", "True").lower() != "false"
ATOKEN = os.getenv("ATOKEN") ATOKEN = os.getenv("ATOKEN")
MONGO_PASSWORD = os.getenv("MONGO_INITDB_ROOT_PASSWORD") MONGO_PASSWORD = os.getenv("MONGO_INITDB_ROOT_PASSWORD")
MONGODB_HOST = os.getenv("MONGODB_SERVICE_HOST") MONGODB_HOST = os.getenv("MONGODB_SERVICE_HOST")
MONGO_PORT = os.getenv("MONGODB_SERVICE_PORT", 27017)
REDIS_HOST = os.getenv("REDIS_SERVICE_HOST") REDIS_HOST = os.getenv("REDIS_SERVICE_HOST")
REDIS_PORT = os.getenv("REDIS_SERVICE_PORT", 6379)
FROM_NAME = "Avtopost Kultmost" FROM_NAME = "Avtopost Kultmost"
BOT_TOKEN = os.getenv("BOT_TOKEN") BOT_TOKEN = os.getenv("BOT_TOKEN")
FULL_DOMAIN = os.getenv("FULL_PATH_DOMAIN") FULL_DOMAIN = os.getenv("FULL_PATH_DOMAIN")
@ -28,7 +30,9 @@ REFRESH_TOKEN_EXPIRE_DAYS = 170
TOKEN_TTL_DAYS = 7 TOKEN_TTL_DAYS = 7
ACCESS_COOKIE = "access_token" ACCESS_COOKIE = "access_token"
REFRESH_COOKIE = "refresh_token" REFRESH_COOKIE = "refresh_token"
RABBITMQ_HOST = "rabbit.spiritofweb.ru:31023" RABBITMQ_USER = os.getenv("RABBITMQ_USER", 'admin')
RABBITMQ_HOST = os.getenv("RABBITMQ_HOST")
RABBITMQ_PORT = os.getenv("RABBITMQ_PORT", 5671)
EMAIL_SUBJECT = 'Культмост' EMAIL_SUBJECT = 'Культмост'
PROTECTED_FIELDS_USER = {"_id", "img", 'original_img', "hashed_password", "password", "roles"} PROTECTED_FIELDS_USER = {"_id", "img", 'original_img', "hashed_password", "password", "roles"}
VK_CLIENT_ID = os.getenv("VK_CLIENT_ID") VK_CLIENT_ID = os.getenv("VK_CLIENT_ID")

11
db.py
View File

@ -2,7 +2,7 @@ import os
from motor.motor_asyncio import AsyncIOMotorClient, AsyncIOMotorGridFSBucket from motor.motor_asyncio import AsyncIOMotorClient, AsyncIOMotorGridFSBucket
import redis.asyncio as redis import redis.asyncio as redis
import asyncio import asyncio
from config import PROD, MONGO_PASSWORD, MONGODB_HOST, REDIS_HOST from config import *
# Create a new event loop for asyncio # Create a new event loop for asyncio
loop = asyncio.new_event_loop() loop = asyncio.new_event_loop()
@ -13,7 +13,6 @@ if not all([MONGO_PASSWORD, MONGODB_HOST, REDIS_HOST]):
raise EnvironmentError("Missing required environment variables for MongoDB/Redis") raise EnvironmentError("Missing required environment variables for MongoDB/Redis")
# ========================= DATABASE URI ========================= # ========================= DATABASE URI =========================
MONGO_PORT = 27017 if PROD else 30002
mongo_uri = f"mongodb://admin:{MONGO_PASSWORD}@{MONGODB_HOST}:{MONGO_PORT}/" mongo_uri = f"mongodb://admin:{MONGO_PASSWORD}@{MONGODB_HOST}:{MONGO_PORT}/"
# ========================= MONGO CLIENT ========================= # ========================= MONGO CLIENT =========================
@ -51,8 +50,6 @@ gridfs_map = {
# ========================= REDIS CLIENT ========================= # ========================= REDIS CLIENT =========================
REDIS_PORT = 6379 if PROD else 30003
redis_client = redis.Redis( redis_client = redis.Redis(
host=REDIS_HOST, host=REDIS_HOST,
port=REDIS_PORT, port=REDIS_PORT,
@ -60,13 +57,17 @@ redis_client = redis.Redis(
decode_responses=True decode_responses=True
) )
redis_uri = f'redis://{REDIS_HOST}:{REDIS_PORT}/'
rabbit_mq_uri = f'amqps://{RABBITMQ_USER}:{RABBITMQ_PASS}@{RABBITMQ_HOST}:{RABBITMQ_PORT}/'
# Exporting collections and clients # Exporting collections and clients
__all__ = [ __all__ = [
"competitions_col", "competition_request_col", "competitions_col", "competition_request_col",
"users_collection", "users_token_collection", "users_collection", "users_token_collection",
"notify_collection", "social_collection", "notify_collection", "social_collection",
"posts_collection", "news_collection", "posts_collection", "news_collection",
"gridfs_map", "redis_client", "gridfs_map", "redis_client", "redis_uri"
"mongo_client", "invitations_collection", "mongo_client", "invitations_collection",
"delivery_collection", "exhibition_collection", "delivery_collection", "exhibition_collection",
"rabbit_mq_uri"
] ]

View File

@ -52,6 +52,7 @@
"from email.mime.text import MIMEText\n", "from email.mime.text import MIMEText\n",
"from celery import Celery\n", "from celery import Celery\n",
"from config import *\n", "from config import *\n",
"from db import *\n",
"from templates import templates\n", "from templates import templates\n",
"import logging\n", "import logging\n",
"from typing import Optional\n", "from typing import Optional\n",
@ -95,7 +96,7 @@
" \n", " \n",
" msg.attach(MIMEText(html_content, 'html'))\n", " msg.attach(MIMEText(html_content, 'html'))\n",
" \n", " \n",
" return msg" " return msg\n"
] ]
}, },
{ {
@ -111,7 +112,6 @@
" return server\n", " return server\n",
"\n", "\n",
"\n", "\n",
"\n",
"def send_email(\n", "def send_email(\n",
" to_email: str,\n", " to_email: str,\n",
" html_content: str,\n", " html_content: str,\n",
@ -125,7 +125,6 @@
" user_id=user_id,\n", " user_id=user_id,\n",
" ))\n", " ))\n",
"\n", "\n",
"\n",
" try:\n", " try:\n",
" with connect(EMAIL_SMTP_SERVER, EMAIL_SMTP_PORT) as server:\n", " with connect(EMAIL_SMTP_SERVER, EMAIL_SMTP_PORT) as server:\n",
" server.login(FROM_EMAIL, EMAIL_PASS)\n", " server.login(FROM_EMAIL, EMAIL_PASS)\n",
@ -150,7 +149,7 @@
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [ "source": [
"celery_app_email = Celery('emails', broker=f'amqps://admin:{RABBITMQ_PASS}@{RABBITMQ_HOST}/email', backend=f'redis://{REDIS_HOST}:{6379 if PROD else 30003}/0', task_track_started=True )\n" "celery_app_email = Celery('emails', broker=f'{redis_uri}2', backend=f'{redis_uri}2', task_track_started=True)\n"
] ]
}, },
{ {
@ -189,7 +188,7 @@
" logging.warning(\n", " logging.warning(\n",
" f\"Ошибка при отправке email на {to_email} (попытка {self.request.retries + 1}): {exc}\"\n", " f\"Ошибка при отправке email на {to_email} (попытка {self.request.retries + 1}): {exc}\"\n",
" )\n", " )\n",
" raise self.retry(exc=exc)" " raise self.retry(exc=exc)\n"
] ]
}, },
{ {

View File

@ -35,6 +35,7 @@ from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText from email.mime.text import MIMEText
from celery import Celery from celery import Celery
from config import * from config import *
from db import *
from templates import templates from templates import templates
import logging import logging
from typing import Optional from typing import Optional
@ -73,13 +74,13 @@ async def create_email_message(
return msg return msg
def connect(email_server, email_port): def connect(email_server, email_port):
server = smtplib.SMTP(email_server, email_port) server = smtplib.SMTP(email_server, email_port)
server.starttls(context=ctx) server.starttls(context=ctx)
return server return server
def send_email( def send_email(
to_email: str, to_email: str,
html_content: str, html_content: str,
@ -93,7 +94,6 @@ def send_email(
user_id=user_id, user_id=user_id,
)) ))
try: try:
with connect(EMAIL_SMTP_SERVER, EMAIL_SMTP_PORT) as server: with connect(EMAIL_SMTP_SERVER, EMAIL_SMTP_PORT) as server:
server.login(FROM_EMAIL, EMAIL_PASS) server.login(FROM_EMAIL, EMAIL_PASS)
@ -111,7 +111,7 @@ def send_email(
return False return False
celery_app_email = Celery('emails', broker=f'amqps://admin:{RABBITMQ_PASS}@{RABBITMQ_HOST}/email', backend=f'redis://{REDIS_HOST}:{6379 if PROD else 30003}/0', task_track_started=True ) celery_app_email = Celery('emails', broker=f'{redis_uri}2', backend=f'{redis_uri}2', task_track_started=True)
@celery_app_email.task( @celery_app_email.task(
@ -143,4 +143,4 @@ def notify(self, to_email: str, data: dict, subject: str = EMAIL_SUBJECT, templa
logging.warning( logging.warning(
f"Ошибка при отправке email на {to_email} (попытка {self.request.retries + 1}): {exc}" f"Ошибка при отправке email на {to_email} (попытка {self.request.retries + 1}): {exc}"
) )
raise self.retry(exc=exc) raise self.retry(exc=exc)

View File

@ -2759,8 +2759,9 @@
" user: Dict = Security(get_current_user)\n", " user: Dict = Security(get_current_user)\n",
"):\n", "):\n",
" post = await posts_collection.find_one({'_id':ObjectId(post_id)})\n", " post = await posts_collection.find_one({'_id':ObjectId(post_id)})\n",
" data = json.dumps(post, cls=MongoJSONEncoder, ensure_ascii=False)\n",
" return templates.TemplateResponse(\n", " return templates.TemplateResponse(\n",
" request=request, name=\"form/post_edit.html\", context={\"post\": post, \"user\": user}\n", " request=request, name=\"form/post_edit.html\", context={\"post\": data, \"user\": user}\n",
" )" " )"
] ]
}, },
@ -3076,7 +3077,7 @@
" \n", " \n",
"\n", "\n",
" background_tasks.add_task(update_social_post, doc_out)\n", " background_tasks.add_task(update_social_post, doc_out)\n",
" background_tasks.add_task(replace_new_medias, bson_clear(res), links_media)\n", " background_tasks.add_task(replace_new_medias, bson_clear(doc_out), links_media)\n",
" return doc_out\n", " return doc_out\n",
"\n", "\n",
"\n", "\n",

View File

@ -2072,8 +2072,9 @@ async def form_post_edit(
user: Dict = Security(get_current_user) user: Dict = Security(get_current_user)
): ):
post = await posts_collection.find_one({'_id':ObjectId(post_id)}) post = await posts_collection.find_one({'_id':ObjectId(post_id)})
data = json.dumps(post, cls=MongoJSONEncoder, ensure_ascii=False)
return templates.TemplateResponse( return templates.TemplateResponse(
request=request, name="form/post_edit.html", context={"post": post, "user": user} request=request, name="form/post_edit.html", context={"post": data, "user": user}
) )
@app.get("/form/news", response_class=HTMLResponse) @app.get("/form/news", response_class=HTMLResponse)
@ -2335,7 +2336,7 @@ async def put_painting(
background_tasks.add_task(update_social_post, doc_out) background_tasks.add_task(update_social_post, doc_out)
background_tasks.add_task(replace_new_medias, bson_clear(res), links_media) background_tasks.add_task(replace_new_medias, bson_clear(doc_out), links_media)
return doc_out return doc_out

View File

@ -343,7 +343,7 @@ function bindLinkValidation({
</script> </script>
<script defer> <script defer>
const competition = JSON.parse({{competition|tojson}}) const competition = JSON.parse({{competition|tojson}})
const competitionId = competition._id const competitionId = competition._id
document.getElementById('title').value = competition.title; document.getElementById('title').value = competition.title;

View File

@ -43,22 +43,6 @@
<span class="c_som9s">Сохранить</span> <span class="c_som9s">Сохранить</span>
</button> </button>
</div> </div>
<script>
document.addEventListener('DOMContentLoaded', () => {
document.getElementById('author').value = '{{post.author|safe or ""}}';
{% if user.get('grade') is none %}
document.getElementById('grade').value = '{{post.grade|safe or ""}}';
{% endif %}
document.getElementById('title').value = '{{post.title|safe or ""}}';
document.getElementById('city').value = '{{post.city|safe or ""}}';
document.getElementById('size').value = '{{post.size|safe or ""}}';
document.getElementById('material').value = '{{post.material|safe or ""}}';
document.getElementById('genre').value = '{{post.genre|safe or ""}}';
document.getElementById('price').value = '{{post.price|safe or ""}}';
document.getElementById('description').value = `{{post.description|safe or ""}}`;
});
</script>
<div class="c_rtopc"> <div class="c_rtopc">
<label class="c_aq9et" for="fileInput"> <label class="c_aq9et" for="fileInput">
<span class="c_som9s">Добавить фото</span> <span class="c_som9s">Добавить фото</span>
@ -66,27 +50,39 @@
<input class="c_1gai0" id="fileInput" type="file" multiple accept="image/*" style="display: none;"/> <input class="c_1gai0" id="fileInput" type="file" multiple accept="image/*" style="display: none;"/>
<div class="preview" id="preview"></div> <div class="preview" id="preview"></div>
</div> </div>
</form>
</form> <script defer>
const post = JSON.parse({{post|tojson}})
<script> const postId = post._id
const fileInput = document.getElementById('fileInput'); const fileInput = document.getElementById('fileInput');
const preview = document.getElementById('preview'); const preview = document.getElementById('preview');
document.getElementById('author').value = post.author;
document.getElementById('grade').value = post.grade;
const inputEl = document.getElementById('grade');
let list = inputEl.dataset.list;
list = JSON.parse(list.replace(/'/g, '"'));
list.push(post.grade);
list = [...new Set(list)];
inputEl.dataset.list = JSON.stringify(list);
inputEl.dataset.value = post.grade;
document.getElementById('title').value = post.title;
document.getElementById('city').value = post.city;
document.getElementById('size').value = post.size;
document.getElementById('material').value = post.material;
document.getElementById('genre').value = post.genre;
document.getElementById('price').value = post.price;
document.getElementById('description').value = post.description;
let filesOrdered = []; // теперь храним строки (имена файлов) + существующие медиа let filesOrdered = []; // теперь храним строки (имена файлов) + существующие медиа
console.log(post.medias_original) // ["6a5c33dcc10bbb64e654ba63"]
const existingMedias = [ const existingMedias = (post?.medias_original ?? []).map((media) => ({ url: `/b/posts/${media}`, name: `${media}`, isExisting: true, type: "image",}));
{% if post %}
{% for media in post.medias_original %}
{
url: "/b/posts/{{ media }}",
name: "{{ media }}",
isExisting: true,
type: "image"
}{% if not loop.last %},{% endif %}
{% endfor %}
{% endif %}
];
async function uploadMediaFiles(files) { async function uploadMediaFiles(files) {
if (!files || files.length === 0) return []; if (!files || files.length === 0) return [];
@ -266,7 +262,6 @@
const grade = '{{user.grade}}'; const grade = '{{user.grade}}';
const form = document.getElementById('postForm'); const form = document.getElementById('postForm');
const createBtn = document.getElementById('createBtn'); const createBtn = document.getElementById('createBtn');
const postId= '{{post._id}}'
createBtn.addEventListener('click', async (e) => { createBtn.addEventListener('click', async (e) => {
e.preventDefault(); e.preventDefault();

View File

@ -79,7 +79,7 @@
"from fastapi import status\n", "from fastapi import status\n",
"from fastapi import APIRouter,Depends\n", "from fastapi import APIRouter,Depends\n",
"from fastapi.responses import RedirectResponse\n", "from fastapi.responses import RedirectResponse\n",
"from images import insert_logo" "from images import insert_logo\n"
] ]
}, },
{ {
@ -97,7 +97,7 @@
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [ "source": [
"celery_app_vk = Celery('vk ', broker=f'amqps://admin:{RABBITMQ_PASS}@{RABBITMQ_HOST}/vk', task_track_started=True )\n" "celery_app_vk = Celery('vk', broker=f'{redis_uri}1', backend=f'{redis_uri}1', task_track_started=True )\n"
] ]
}, },
{ {
@ -159,7 +159,7 @@
" background = insert_logo(Image.open(background).convert(\"RGBA\"), logo)\n", " background = insert_logo(Image.open(background).convert(\"RGBA\"), logo)\n",
" images[_id] = (\"photo\", (name, background, \"image/jpeg\"))\n", " images[_id] = (\"photo\", (name, background, \"image/jpeg\"))\n",
" \n", " \n",
" return images" " return images\n"
] ]
}, },
{ {
@ -231,7 +231,8 @@
" return {'ok': False}\n", " return {'ok': False}\n",
" update_spec = {\"$push\": {\"vk\": res}}\n", " update_spec = {\"$push\": {\"vk\": res}}\n",
" await mongo_client[db_name][item.get('col')].update_one({'_id': ObjectId(item.get('_id'))}, update_spec)\n", " await mongo_client[db_name][item.get('col')].update_one({'_id': ObjectId(item.get('_id'))}, update_spec)\n",
" return {\"ok\": True}\n" " return {\"ok\": True}\n",
"\n"
] ]
}, },
{ {
@ -306,7 +307,8 @@
" return {'ok': False}\n", " return {'ok': False}\n",
" update_spec = {\"$push\": {\"vk\": res}}\n", " update_spec = {\"$push\": {\"vk\": res}}\n",
" await mongo_client[db_name][item.get('col')].update_one({'_id': ObjectId(item.get('_id'))}, update_spec)\n", " await mongo_client[db_name][item.get('col')].update_one({'_id': ObjectId(item.get('_id'))}, update_spec)\n",
" return {\"ok\": True}\n" " return {\"ok\": True}\n",
"\n"
] ]
}, },
{ {
@ -338,7 +340,7 @@
" logging.info(doc)\n", " logging.info(doc)\n",
" logging.info(social)\n", " logging.info(social)\n",
"\n", "\n",
" owner_id = f\"-{doc['id']}\" if \"group\" in doc else doc[\"id\"]\n", " owner_id = f\"-{doc.get('id')}\" if \"group\" in doc else doc.get(\"id\")\n",
"\n", "\n",
" # ---- VK request ----\n", " # ---- VK request ----\n",
" async with httpx.AsyncClient(http2=True, verify=ctx) as client:\n", " async with httpx.AsyncClient(http2=True, verify=ctx) as client:\n",

8
vk.py
View File

@ -52,9 +52,10 @@ from fastapi import APIRouter,Depends
from fastapi.responses import RedirectResponse from fastapi.responses import RedirectResponse
from images import insert_logo from images import insert_logo
### Конфигурация ### Конфигурация
celery_app_vk = Celery('vk ', broker=f'amqps://admin:{RABBITMQ_PASS}@{RABBITMQ_HOST}/vk', task_track_started=True ) celery_app_vk = Celery('vk', broker=f'{redis_uri}1', backend=f'{redis_uri}1', task_track_started=True )
token_manager = TokenManager() token_manager = TokenManager()
@ -96,6 +97,7 @@ async def get_images(db_name: str, file_ids: list, sign_avatar: bool = True):
return images return images
@router.post("/send") @router.post("/send")
async def send_vk(data:dict, request: Request): async def send_vk(data:dict, request: Request):
auth = request.headers.get("Authorization") auth = request.headers.get("Authorization")
@ -161,6 +163,7 @@ async def send_vk(data:dict, request: Request):
return {"ok": True} return {"ok": True}
@router.post("/edit") @router.post("/edit")
async def edit_vk(data:dict, request: Request): async def edit_vk(data:dict, request: Request):
auth = request.headers.get("Authorization") auth = request.headers.get("Authorization")
@ -229,6 +232,7 @@ async def edit_vk(data:dict, request: Request):
return {"ok": True} return {"ok": True}
@router.post("/delete") @router.post("/delete")
async def wall_delete(data: dict, request: Request): async def wall_delete(data: dict, request: Request):
# ---- auth ---- # ---- auth ----
@ -251,7 +255,7 @@ async def wall_delete(data: dict, request: Request):
logging.info(doc) logging.info(doc)
logging.info(social) logging.info(social)
owner_id = f"-{doc['id']}" if "group" in doc else doc["id"] owner_id = f"-{doc.get('id')}" if "group" in doc else doc.get("id")
# ---- VK request ---- # ---- VK request ----
async with httpx.AsyncClient(http2=True, verify=ctx) as client: async with httpx.AsyncClient(http2=True, verify=ctx) as client: