This commit is contained in:
Vladislav 2026-07-19 18:20:59 -04:00
parent a21e6f47f8
commit 60aa58844c
9 changed files with 184 additions and 93 deletions

View File

@ -28,7 +28,7 @@ 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 = "rabbitmq-0.rabbitmq.kultmost.svc.cluster.local" if PROD else "rabbit.spiritofweb.ru:31023" RABBITMQ_HOST = "rabbit.spiritofweb.ru:31023"
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")

View File

@ -7,7 +7,7 @@
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [ "source": [
"# ignore\n", "#ignore\n",
"import os\n", "import os\n",
"\n", "\n",
"def load_env(file_path):\n", "def load_env(file_path):\n",

View File

@ -1,3 +1,35 @@
#ignore
import os
def load_env(file_path):
"""Загружает переменные окружения из файла .env с поддержкой булевых значений."""
with open(file_path) as f:
for line in f:
line = line.strip()
if not line or line.startswith('#'):
continue
try:
key, value = line.split('=', 1)
key = key.strip()
value = value.strip('"\' ')
# Преобразование строковых значений в булевые
if value.lower() in ['true', '1', 'yes']:
value = True
elif value.lower() in ['false', '0', 'no']:
value = False
# Проверка, что ключ не пустой
if key:
os.environ[key] = str(value) # Храним как строку
except ValueError:
print(f"Ошибка: неверный формат в строке '{line}'")
try:
load_env('env')
except:
load_env('env_example')
import smtplib import smtplib
from email.mime.multipart import MIMEMultipart from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText from email.mime.text import MIMEText

View File

@ -2701,8 +2701,9 @@
" user: Dict = Security(get_current_user, scopes=[\"create:super_items\"])\n", " user: Dict = Security(get_current_user, scopes=[\"create:super_items\"])\n",
"): \n", "): \n",
" competition = await competitions_col.find_one({'_id': ObjectId(competition_id)})\n", " competition = await competitions_col.find_one({'_id': ObjectId(competition_id)})\n",
" data = json.dumps(competition, cls=MongoJSONEncoder, ensure_ascii=False)\n",
" return templates.TemplateResponse(\n", " return templates.TemplateResponse(\n",
" request=request, name=\"form/competition_edit.html\", context={\"competition\": competition, \"user\": user}\n", " request=request, name=\"form/competition_edit.html\", context={\"competition\": str(data), \"user\": user}\n",
" )\n" " )\n"
] ]
}, },
@ -2815,17 +2816,6 @@
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [ "source": [
"import json\n",
"from datetime import datetime, date\n",
"from bson import ObjectId\n",
"\n",
"class MongoJSONEncoder(json.JSONEncoder):\n",
" def default(self, o):\n",
" if isinstance(o, ObjectId):\n",
" return str(o)\n",
" if isinstance(o, (datetime, date)):\n",
" return o.isoformat()\n",
" return super().default(o)\n",
"\n", "\n",
"\n", "\n",
"@app.get(\"/form/exhibition/{exhibition_id}\", response_class=HTMLResponse)\n", "@app.get(\"/form/exhibition/{exhibition_id}\", response_class=HTMLResponse)\n",
@ -2835,8 +2825,7 @@
" user: Dict = Security(get_current_user)\n", " user: Dict = Security(get_current_user)\n",
"):\n", "):\n",
" exhibition = await exhibition_collection.find_one({'_id': ObjectId(exhibition_id)},{'posts':0,'vk':0,'tg':0,'fb':0,'inst':0,'max':0})\n", " exhibition = await exhibition_collection.find_one({'_id': ObjectId(exhibition_id)},{'posts':0,'vk':0,'tg':0,'fb':0,'inst':0,'max':0})\n",
" data= json.dumps(exhibition, cls=MongoJSONEncoder, ensure_ascii=False)\n", " data = json.dumps(exhibition, cls=MongoJSONEncoder, ensure_ascii=False)\n",
" print(data)\n",
" return templates.TemplateResponse(\n", " return templates.TemplateResponse(\n",
" request=request, name=\"form/exhibition_edit.html\", context={\"id\": 1, \"user\": user, \"exhibition\": str(data)}\n", " request=request, name=\"form/exhibition_edit.html\", context={\"id\": 1, \"user\": user, \"exhibition\": str(data)}\n",
" )" " )"

17
main.py
View File

@ -2028,8 +2028,9 @@ async def form_(
user: Dict = Security(get_current_user, scopes=["create:super_items"]) user: Dict = Security(get_current_user, scopes=["create:super_items"])
): ):
competition = await competitions_col.find_one({'_id': ObjectId(competition_id)}) competition = await competitions_col.find_one({'_id': ObjectId(competition_id)})
data = json.dumps(competition, cls=MongoJSONEncoder, ensure_ascii=False)
return templates.TemplateResponse( return templates.TemplateResponse(
request=request, name="form/competition_edit.html", context={"competition": competition, "user": user} request=request, name="form/competition_edit.html", context={"competition": str(data), "user": user}
) )
@ -2104,17 +2105,6 @@ async def form_exhibition(
request=request, name="form/exhibition.html", context={"id": 1, "user": user} request=request, name="form/exhibition.html", context={"id": 1, "user": user}
) )
import json
from datetime import datetime, date
from bson import ObjectId
class MongoJSONEncoder(json.JSONEncoder):
def default(self, o):
if isinstance(o, ObjectId):
return str(o)
if isinstance(o, (datetime, date)):
return o.isoformat()
return super().default(o)
@app.get("/form/exhibition/{exhibition_id}", response_class=HTMLResponse) @app.get("/form/exhibition/{exhibition_id}", response_class=HTMLResponse)
@ -2124,8 +2114,7 @@ async def form_exhibition(
user: Dict = Security(get_current_user) user: Dict = Security(get_current_user)
): ):
exhibition = await exhibition_collection.find_one({'_id': ObjectId(exhibition_id)},{'posts':0,'vk':0,'tg':0,'fb':0,'inst':0,'max':0}) exhibition = await exhibition_collection.find_one({'_id': ObjectId(exhibition_id)},{'posts':0,'vk':0,'tg':0,'fb':0,'inst':0,'max':0})
data= json.dumps(exhibition, cls=MongoJSONEncoder, ensure_ascii=False) data = json.dumps(exhibition, cls=MongoJSONEncoder, ensure_ascii=False)
print(data)
return templates.TemplateResponse( return templates.TemplateResponse(
request=request, name="form/exhibition_edit.html", context={"id": 1, "user": user, "exhibition": str(data)} request=request, name="form/exhibition_edit.html", context={"id": 1, "user": user, "exhibition": str(data)}
) )

View File

@ -2,24 +2,6 @@
{% import "components/checkbox.html" as checkbox %} {% import "components/checkbox.html" as checkbox %}
{% macro section(user, competition) -%} {% macro section(user, competition) -%}
<script>
var type = '{{competition.type_}}';
function toggleType(clickedId) {
const artist = document.getElementById('artistCheck');
const paint = document.getElementById('paintCheck');
if (clickedId === 'artistCheck') {
artist.checked = clickedId === 'artistCheck';
paint.checked = clickedId === 'paintCheck';
type = 'artist';
} else if (clickedId === 'paintCheck') {
artist.checked = clickedId === 'artistCheck';
paint.checked = clickedId === 'paintCheck';
type = 'paint';
}
}
</script>
<form class="c_sbrxk"> <form class="c_sbrxk">
<div class="c_dtjoh"> <div class="c_dtjoh">
<h3>Выбор</h3> <h3>Выбор</h3>
@ -28,7 +10,6 @@
<input type="checkbox" <input type="checkbox"
id="artistCheck" id="artistCheck"
class="offer-checkbox" class="offer-checkbox"
{% if competition.type_ == 'artist'%}checked{%endif%}
onchange="toggleType('artistCheck')"> onchange="toggleType('artistCheck')">
<span class="offer-text">Художников</span> <span class="offer-text">Художников</span>
</label> </label>
@ -37,7 +18,6 @@
<input type="checkbox" <input type="checkbox"
id="paintCheck" id="paintCheck"
class="offer-checkbox" class="offer-checkbox"
{% if competition.type_ == 'paint'%}checked{%endif%}
onchange="toggleType('paintCheck')"> onchange="toggleType('paintCheck')">
<span class="offer-text">Картин</span> <span class="offer-text">Картин</span>
</label> </label>
@ -168,26 +148,24 @@
<input type="number" id="max_val" name="max_paintings" class="c_l8kil" value="10" min="1" max="99"> <input type="number" id="max_val" name="max_paintings" class="c_l8kil" value="10" min="1" max="99">
<!-- <input type="range" id="select_val" name="selected_count" class="c_5qh93" min="1" max="10" value="3"> --> <!-- <input type="range" id="select_val" name="selected_count" class="c_5qh93" min="1" max="10" value="3"> -->
</div> </div>
<!-- {% if competition.get('publish') is none %} <!--
<div class="fl"> <div class="fl">
<span class="c_2fr39 b_tfnz2">Публикация запланирована на</span> <span class="c_2fr39 b_tfnz2">Публикация запланирована на</span>
</div> </div>
{{ (competition.datetime_start.strftime('%Y.%m.%d %H:%M' )) if competition.datetime_start else ''}}
<label class="offer-label"> <label class="offer-label">
<input type="checkbox" id="emailsNotify" class="offer-checkbox" {% if competition.emailsNotify %}checked{% endif %}> <input type="checkbox" id="emailsNotify" class="offer-checkbox">
<span class="offer-text">Рассылка художникам приглашения на почту</span> <span class="offer-text">Рассылка художникам приглашения на почту</span>
</label> </label>
<label class="offer-label"> <label class="offer-label">
<input type="checkbox" id="serviceSocial" class="offer-checkbox" {% if competition.serviceSocial %}checked{% endif %}> <input type="checkbox" id="serviceSocial" class="offer-checkbox">
<span class="offer-text">Публикация в соц. сетях сервиса</span> <span class="offer-text">Публикация в соц. сетях сервиса</span>
</label> </label>
<label class="offer-label"> <label class="offer-label">
<input type="checkbox" id="userSocial" class="offer-checkbox" {% if competition.userSocial %}checked{% endif %}> <input type="checkbox" id="userSocial" class="offer-checkbox">
<span class="offer-text">Публикация в личных соц. сетях</span> <span class="offer-text">Публикация в личных соц. сетях</span>
</label> </label>
-->
{% endif %} -->
<button class="c_aq9et" id="createBtn"> <button class="c_aq9et" id="createBtn">
<span class="c_som9s">Сохранить изменения</span> <span class="c_som9s">Сохранить изменения</span>
</button> </button>
@ -202,7 +180,7 @@
style="position:absolute;inset:0;filter:blur(3px)"> style="position:absolute;inset:0;filter:blur(3px)">
</div> </div>
<picture> <picture>
<img id="preview_poster" src="/b/competition/{{competition.poster_file_id}}" crossorigin="anonymous" fetchpriority="high" loading="eager" alt="Загрузите афишу" class="b_ornr9"> <img id="preview_poster" crossorigin="anonymous" fetchpriority="high" loading="eager" alt="Загрузите афишу" class="b_ornr9">
</picture> </picture>
</div> </div>
<div class="c_z3dpx b_73p10"> <div class="c_z3dpx b_73p10">
@ -210,9 +188,9 @@
<span class="c_95csi b_0jst1">Формируется</span> <span class="c_95csi b_0jst1">Формируется</span>
</div> </div>
<header class="c_m876y b_9xtz9"> <header class="c_m876y b_9xtz9">
<h3 class="c_hysjg b_8f3uh" id="preview_title">{{competition.title or ""}}</h3> <h3 class="c_hysjg b_8f3uh" id="preview_title"></h3>
</header> </header>
<p class="c_2fr39 b_tfnz2" id="preview_topic">{{competition.topic or ""}}</p> <p class="c_2fr39 b_tfnz2" id="preview_topic"></p>
<div class="c_dbbe4 b_otttb"> <div class="c_dbbe4 b_otttb">
{% if user %} {% if user %}
<img crossorigin="anonymous" fetchpriority="high" loading="eager" class="c_pm80w b_a68fq" src="/a/{{user._id}}" alt="{{ user.username | e }}"> <img crossorigin="anonymous" fetchpriority="high" loading="eager" class="c_pm80w b_a68fq" src="/a/{{user._id}}" alt="{{ user.username | e }}">
@ -221,7 +199,7 @@
</div> </div>
{% endif %} {% endif %}
</div> </div>
<p class="c_sihck b_s6ool" id="preview_desc">{{competition.short_desc or ""}}</p> <p class="c_sihck b_s6ool" id="preview_desc"></p>
<p class="c_2fr39 b_tfnz2">Подробнее</p> <p class="c_2fr39 b_tfnz2">Подробнее</p>
<p class="c_drpqj b_mp1wb">Дата создания</p> <p class="c_drpqj b_mp1wb">Дата создания</p>
</div> </div>
@ -365,12 +343,51 @@ function bindLinkValidation({
</script> </script>
<script defer> <script defer>
document.getElementById('title').value = `{{competition.title or ""}}`; const competition = JSON.parse({{competition|tojson}})
document.getElementById('short_desc').value = `{{competition.short_desc or ""}}`; const competitionId = competition._id
document.getElementById('address').value = `{{competition.address or ""}}`;
document.getElementById('topic').value = `{{competition.topic or ""}}`; document.getElementById('title').value = competition.title;
document.getElementById('rules_link').value = `{{competition.get('rules_link') or ""}}`; document.getElementById('preview_title').textContent = competition.title;
{% if competition.get('rules_link') %}
function setDateOrDateTimeLocal(elId, isoString) {
if (!isoString) return;
const s = isoString.replace(/^"|"$/g, ""); // на случай двойных кавычек
const [datePart, timePart = "00:00:00"] = s.split("T");
const [y, m, d] = datePart.split("-").map(Number);
const [hh, mm, ss = "0"] = timePart.split(":");
// Date constructor: month is 0-based
const dt = new Date(y, m - 1, d, Number(hh), Number(mm), Number(ss));
// Подготовим строку для input (локальное время)
const pad = (n) => String(n).padStart(2, "0");
const value = `${dt.getFullYear()}-${pad(dt.getMonth() + 1)}-${pad(dt.getDate())}` +
`T${pad(dt.getHours())}:${pad(dt.getMinutes())}:${pad(dt.getSeconds())}`;
document.getElementById(elId).value = value;
}
function setDateOnly(elId, isoString) {
if (!isoString) return;
const s = isoString.replace(/^"|"$/g, "");
const datePart = s.split("T")[0];
document.getElementById(elId).value = datePart; // YYYY-MM-DD
}
setDateOnly("date_finish", competition.date_finish);
setDateOrDateTimeLocal("datetime_start", competition.datetime_start);
document.getElementById('short_desc').value = competition.short_desc ;
document.getElementById('preview_desc').textContent = competition.short_desc ;
document.getElementById('address').value = competition.address;
document.getElementById('topic').value = competition.topic;
document.getElementById('preview_topic').textContent = competition.topic;
if (competition.rules_link){
document.getElementById('rules_link').value = competition.rules_link;
document.getElementById('file-block').style.display = 'none'; document.getElementById('file-block').style.display = 'none';
document.getElementById('link-block').style.display = 'flex'; document.getElementById('link-block').style.display = 'flex';
const select = document.querySelectorAll('.toggle-option') const select = document.querySelectorAll('.toggle-option')
@ -378,38 +395,57 @@ document.getElementById('rules_link').value = `{{competition.get('rules_link') o
select[1].classList.add('active'); select[1].classList.add('active');
select[0].querySelector('input').checked=false select[0].querySelector('input').checked=false
select[1].querySelector('input').checked=true select[1].querySelector('input').checked=true
{% endif %} }
document.getElementById('nominations').dataset.value = `{{competition.nominations | safe or []}}`; if (competition.type_ == 'artist'){
document.getElementById('nominations').dataset.list = `{{competition.nominations | safe or []}}`; document.getElementById('artistCheck').checked = true
}
if (competition.type_ == 'paint'){
document.getElementById('paintCheck').checked = true
}
const inputEl = document.getElementById('nominations'); const inputEl = document.getElementById('nominations');
const raw = inputEl.getAttribute('data-list') || '[]'; const list = competition.nominations;
const fixed = raw.replace(/'/g, '"');
const list = JSON.parse(fixed);
inputEl.dataset.list = fixed inputEl.dataset.list = list
inputEl.dataset.value = fixed inputEl.dataset.value = list
document.getElementById('nominations').value = list.join(', '); document.getElementById('nominations').value = list.join(', ');
document.getElementById('min_val').value = `{{competition.min_paintings or ""}}`; document.getElementById('min_val').value = competition.min_paintings;
document.getElementById('max_val').value = `{{competition.max_paintings or ""}}`; document.getElementById('max_val').value = competition.max_paintings;
document.getElementById('date_finish').value = `{{competition.date_finish or ""}}`;
document.getElementById('datetime_start').value = `{{competition.datetime_start or ""}}`;
document.getElementById('preview_poster').src = '/b/competition/{{competition.poster_file_id}}'
const competitionId = '{{competition._id}}'
const input = document.getElementById('datetime_start');
const date_finish = document.getElementById('date_finish');
const date_finish_date = new Date(`{{competition.date_finish or ""}}`);
const datetime_start_date = new Date('{{competition.datetime_start or ""}}');
const MOSCOW_OFFSET_MS = 3 * 60 * 60 * 1000;
const moscowInstant = new Date(datetime_start_date.getTime() + MOSCOW_OFFSET_MS);
input.value = moscowInstant.toISOString().slice(0, 16);
date_finish.value = date_finish_date.toISOString().slice(0, 10); document.getElementById('preview_poster').src = `/b/competition/${competition.poster_file_id}`
// const input = document.getElementById('datetime_start');
// const date_finish = document.getElementById('date_finish');
// const date_finish_date = new Date(competition.date_finish);
// const datetime_start_date = new Date(competition.datetime_start);
// const MOSCOW_OFFSET_MS = 3 * 60 * 60 * 1000;
// const moscowInstant = new Date(datetime_start_date.getTime() + MOSCOW_OFFSET_MS);
// input.value = moscowInstant.toISOString().slice(0, 16);
// date_finish.value = date_finish_date.toISOString().slice(0, 10);
</script>
<script>
var type = competition.type_;
function toggleType(clickedId) {
const artist = document.getElementById('artistCheck');
const paint = document.getElementById('paintCheck');
if (clickedId === 'artistCheck') {
artist.checked = clickedId === 'artistCheck';
paint.checked = clickedId === 'paintCheck';
type = 'artist';
} else if (clickedId === 'paintCheck') {
artist.checked = clickedId === 'artistCheck';
paint.checked = clickedId === 'paintCheck';
type = 'paint';
}
}
</script> </script>
<script defer> <script defer>
const minInput = document.getElementById('min_val'); const minInput = document.getElementById('min_val');

View File

@ -2,7 +2,7 @@ from fastapi import HTTPException, Request
from fastapi.security import OAuth2PasswordBearer from fastapi.security import OAuth2PasswordBearer
from passlib.context import CryptContext from passlib.context import CryptContext
import jwt import jwt
from datetime import datetime, timedelta, timezone from datetime import datetime, timedelta, timezone, date
import hashlib import hashlib
from typing import List, Dict, Optional, Set, Any from typing import List, Dict, Optional, Set, Any
from datetime import timedelta from datetime import timedelta
@ -17,6 +17,8 @@ import httpx
from cryptography.hazmat.primitives.ciphers.aead import AESGCM from cryptography.hazmat.primitives.ciphers.aead import AESGCM
import os import os
import base64 import base64
import json
pwd_context = CryptContext(schemes=["argon2"], deprecated="auto") pwd_context = CryptContext(schemes=["argon2"], deprecated="auto")
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="login") oauth2_scheme = OAuth2PasswordBearer(tokenUrl="login")
@ -25,6 +27,15 @@ OP_MAP = {"like_in": "like"}
ENC_KEY = base64.b64decode(ENC_KEY_B64) ENC_KEY = base64.b64decode(ENC_KEY_B64)
class MongoJSONEncoder(json.JSONEncoder):
def default(self, o):
if isinstance(o, ObjectId):
return str(o)
if isinstance(o, (datetime, date)):
return o.isoformat()
return super().default(o)
def encrypt(plaintext: bytes) -> str: def encrypt(plaintext: bytes) -> str:
aesgcm = AESGCM(ENC_KEY) aesgcm = AESGCM(ENC_KEY)

View File

@ -15,11 +15,11 @@
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [ "source": [
"# ignore\n", "#ignore\n",
"import os\n", "import os\n",
"\n", "\n",
"def load_env(file_path):\n", "def load_env(file_path):\n",
" \"\"\"Загружает переменные окружения из файла .env с поддержкой булевых значений.\"\"\"\n", " \"\"\"Загружает переменные окружения из файла env с поддержкой булевых значений.\"\"\"\n",
" with open(file_path) as f:\n", " with open(file_path) as f:\n",
" for line in f:\n", " for line in f:\n",
" line = line.strip()\n", " line = line.strip()\n",

34
vk.py
View File

@ -1,5 +1,39 @@
# VK # VK
#ignore
import os
def load_env(file_path):
"""Загружает переменные окружения из файла env с поддержкой булевых значений."""
with open(file_path) as f:
for line in f:
line = line.strip()
if not line or line.startswith('#'):
continue
try:
key, value = line.split('=', 1)
key = key.strip()
value = value.strip('"\' ')
# Преобразование строковых значений в булевые
if value.lower() in ['true', '1', 'yes']:
value = True
elif value.lower() in ['false', '0', 'no']:
value = False
# Проверка, что ключ не пустой
if key:
os.environ[key] = str(value) # Храним как строку
except ValueError:
print(f"Ошибка: неверный формат в строке '{line}'")
try:
load_env('env')
except:
load_env('env_example')
## Class ## Class
import httpx import httpx