diff --git a/build.ipynb b/build.ipynb index 3a22959..052e2b9 100644 --- a/build.ipynb +++ b/build.ipynb @@ -12,8 +12,15 @@ "outputs": [], "source": [ "!pip install -r requirements.txt" - ], - "display_name": ".venv" + ] + }, + { + "cell_type": "markdown", + "id": "20340e34", + "metadata": {}, + "source": [ + "## BUILD" + ] }, { "cell_type": "markdown", @@ -65,8 +72,7 @@ "EOF\n", "\n", "chmod +x .git/hooks/pre-commit\n" - ], - "display_name": ".venv" + ] }, { "cell_type": "markdown", @@ -114,8 +120,56 @@ " load_env('env')\n", "except:\n", " load_env('env_example')" - ], - "display_name": ".venv" + ] + }, + { + "cell_type": "markdown", + "id": "74d7ec58", + "metadata": {}, + "source": [ + "### build (ipynb -> py)" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "2809c42c", + "metadata": {}, + "outputs": [], + "source": [ + "import json\n", + "import os\n", + "\n", + "IGNORE = ['build.ipynb', 'data.ipynb', 'migrate.ipynb']\n", + "def build():\n", + " for _filename in [i for i in os.listdir() if 'ipynb' in i and i not in IGNORE]:\n", + " filename_without_extension = os.path.splitext(os.path.basename(_filename))[0]\n", + "\n", + " with open(f'{filename_without_extension}.ipynb', 'r') as f:\n", + " data = f.read()\n", + " main = json.loads(data)\n", + " app = []\n", + " for cell in main['cells']:\n", + " if '## Run' in cell['source']:\n", + " break\n", + " if '# ignore\\n' in cell['source']:\n", + " continue\n", + " app.append(''.join(cell['source']))\n", + "\n", + " with open(f'{filename_without_extension}.py', 'w') as f:\n", + " f.write(f\"# `{filename_without_extension}.py` generate from `{filename_without_extension}.ipynb` by `build.ipynb` \\n\")\n", + " f.write('\\n\\n'.join(app))\n", + "\n", + "build()" + ] + }, + { + "cell_type": "markdown", + "id": "0490beed", + "metadata": {}, + "source": [ + "## KUBECTL / DOCKER" + ] }, { "cell_type": "markdown", @@ -141,8 +195,7 @@ "kubectl -n container-registry create secret generic registry-basic-auth \\\n", " --from-file=auth=/tmp/registry.htpasswd \\\n", " --dry-run=client -o yaml | kubectl apply -f -\n" - ], - "display_name": ".venv" + ] }, { "cell_type": "code", @@ -156,8 +209,7 @@ "outputs": [], "source": [ "!kubectl create secret docker-registry regcred --docker-server=$REGISTRY_HOST --docker-username='user' --docker-password=$REGISTRY_PASS --docker-email=\"$(git config --global --get user.email)\"" - ], - "display_name": ".venv" + ] }, { "cell_type": "code", @@ -172,8 +224,7 @@ "source": [ "!kubectl create namespace kultmost\n", "!kubectl create secret docker-registry regcred -n kultmost --docker-server=$REGISTRY_HOST --docker-username='user' --docker-password=$REGISTRY_PASS --docker-email=\"$(git config --global --get user.email)\"" - ], - "display_name": ".venv" + ] }, { "cell_type": "code", @@ -187,8 +238,7 @@ "outputs": [], "source": [ "!docker login $REGISTRY_HOST -u 'user' -p $REGISTRY_PASS" - ], - "display_name": ".venv" + ] }, { "cell_type": "markdown", @@ -210,8 +260,7 @@ "outputs": [], "source": [ "!docker pull $REGISTRY_HOST/python:3.13-alpine" - ], - "display_name": ".venv" + ] }, { "cell_type": "code", @@ -225,50 +274,8 @@ "outputs": [], "source": [ "!docker pull $REGISTRY_HOST/python:3.13-slim" - ], - "display_name": ".venv" - }, - { - "cell_type": "markdown", - "id": "74d7ec58", - "metadata": {}, - "source": [ - "### build (ipynb -> py)" ] }, - { - "cell_type": "code", - "execution_count": null, - "id": "2809c42c", - "metadata": {}, - "outputs": [], - "source": [ - "import json\n", - "import os\n", - "\n", - "IGNORE = ['build.ipynb', 'data.ipynb', 'migrate.ipynb']\n", - "def build():\n", - " for _filename in [i for i in os.listdir() if 'ipynb' in i and i not in IGNORE]:\n", - " filename_without_extension = os.path.splitext(os.path.basename(_filename))[0]\n", - "\n", - " with open(f'{filename_without_extension}.ipynb', 'r') as f:\n", - " data = f.read()\n", - " main = json.loads(data)\n", - " app = []\n", - " for cell in main['cells']:\n", - " if '## Run' in cell['source']:\n", - " break\n", - " if '# ignore\\n' in cell['source']:\n", - " continue\n", - " app.append(''.join(cell['source']))\n", - "\n", - " with open(f'{filename_without_extension}.py', 'w') as f:\n", - " f.write('\\n\\n'.join(app))\n", - "\n", - "build()" - ], - "display_name": ".venv" - }, { "cell_type": "markdown", "id": "fef1918f", @@ -277,17 +284,6 @@ "### tg build" ] }, - { - "cell_type": "code", - "execution_count": null, - "id": "4489de4b", - "metadata": {}, - "outputs": [], - "source": [ - "!docker tag mongo:latest registry.spiritofweb.ru/mongo:latest" - ], - "display_name": ".venv" - }, { "cell_type": "code", "execution_count": null, @@ -300,8 +296,7 @@ "outputs": [], "source": [ "!docker build -t $REGISTRY_HOST/api-tg:latest tg/." - ], - "display_name": ".venv" + ] }, { "cell_type": "code", @@ -315,8 +310,7 @@ "outputs": [], "source": [ "!docker push $REGISTRY_HOST/api-tg:latest" - ], - "display_name": ".venv" + ] }, { "cell_type": "markdown", @@ -338,8 +332,7 @@ "outputs": [], "source": [ "!docker build -t $REGISTRY_HOST/avtopost-api:latest ." - ], - "display_name": ".venv" + ] }, { "cell_type": "code", @@ -353,8 +346,7 @@ "outputs": [], "source": [ "!docker image push $REGISTRY_HOST/avtopost-api:latest" - ], - "display_name": ".venv" + ] }, { "cell_type": "code", @@ -368,8 +360,7 @@ "outputs": [], "source": [ "!kubectl rollout restart deployments/kultmost-api -n kultmost" - ], - "display_name": ".venv" + ] }, { "cell_type": "markdown", @@ -391,8 +382,7 @@ "outputs": [], "source": [ "!kubectl rollout restart deployments/vk-celery -n kultmost" - ], - "display_name": ".venv" + ] }, { "cell_type": "code", @@ -406,8 +396,7 @@ "outputs": [], "source": [ "!kubectl rollout restart deployments/email-celery -n kultmost" - ], - "display_name": ".venv" + ] }, { "cell_type": "markdown", @@ -432,8 +421,7 @@ "docker pull gitea/gitea:1.22\n", "docker tag gitea/gitea:1.22 $REGISTRY_HOST/gitea/gitea:1.22\n", "docker push $REGISTRY_HOST/gitea/gitea:1.22" - ], - "display_name": ".venv" + ] }, { "cell_type": "code", @@ -457,8 +445,7 @@ "docker tag busybox:1.36 $REGISTRY_HOST/busybox:1.36\n", "docker tag rabbitmq:3.11-management $REGISTRY_HOST/rabbitmq:3.11-management\n", "docker tag alpine:3.14 $REGISTRY_HOST/alpine:3.14" - ], - "display_name": ".venv" + ] }, { "cell_type": "code", @@ -477,8 +464,7 @@ "docker push $REGISTRY_HOST/busybox:1.36\n", "docker push $REGISTRY_HOST/rabbitmq:3.11-management\n", "docker push $REGISTRY_HOST/alpine:3.14" - ], - "display_name": ".venv" + ] }, { "cell_type": "markdown", @@ -500,8 +486,7 @@ "outputs": [], "source": [ "!docker build -t $REGISTRY_HOST/ssh-tunnel:2 tunnel/" - ], - "display_name": ".venv" + ] }, { "cell_type": "code", @@ -515,8 +500,7 @@ "outputs": [], "source": [ "!docker push $REGISTRY_HOST/ssh-tunnel:2" - ], - "display_name": ".venv" + ] }, { "cell_type": "code", @@ -530,8 +514,7 @@ "outputs": [], "source": [ "!kubectl create secret generic ssh-id-rsa --from-file=id_rsa=/home/user/.ssh/id_rsa -n kultmost" - ], - "display_name": ".venv" + ] }, { "cell_type": "code", @@ -545,8 +528,7 @@ "outputs": [], "source": [ "!kubectl apply -n kultmost -f k8s/stunnel.yaml" - ], - "display_name": ".venv" + ] }, { "cell_type": "markdown", @@ -568,8 +550,7 @@ "outputs": [], "source": [ "!envsubst < k8s/prod.yaml | kubectl apply -n kultmost -f -" - ], - "display_name": ".venv" + ] }, { "cell_type": "code", @@ -583,8 +564,7 @@ "outputs": [], "source": [ "!envsubst < k8s/kultmost.yaml | kubectl apply -n kultmost -f -" - ], - "display_name": ".venv" + ] }, { "cell_type": "code", @@ -598,8 +578,7 @@ "outputs": [], "source": [ "!envsubst < k8s/mq.yaml | kubectl apply -n kultmost -f -" - ], - "display_name": ".venv" + ] }, { "cell_type": "code", @@ -613,8 +592,7 @@ "outputs": [], "source": [ "!envsubst < k8s/gitea.yaml | kubectl apply -n gitea -f -" - ], - "display_name": ".venv" + ] }, { "cell_type": "code", @@ -628,8 +606,7 @@ "outputs": [], "source": [ "!kubectl get pod -n kultmost" - ], - "display_name": ".venv" + ] }, { "cell_type": "markdown", @@ -654,8 +631,7 @@ "echo \"$INIT_SSH'bash -s' <<'SCRIPT'\"\n", "echo \"kubectl create secret docker-registry regcred --docker-server=$REGISTRY_HOST --docker-username='user' --docker-password=$REGISTRY_PASS --docker-email=\\\"$(git config --global --get user.email)\\\" && clear && history -c && history -w\"\n", "echo \"SCRIPT\"" - ], - "display_name": ".venv" + ] }, { "cell_type": "code", @@ -678,8 +654,15 @@ "echo \"clear\"\n", "echo \"history -c\"\n", "echo \"history -w\"" - ], - "display_name": ".venv" + ] + }, + { + "cell_type": "markdown", + "id": "bbb93664", + "metadata": {}, + "source": [ + "## RSYNC" + ] }, { "cell_type": "markdown", @@ -701,8 +684,7 @@ "outputs": [], "source": [ "!rsync -avz -ogp --chown=www-data:www-data ./static/ kultmost:/var/www/static/" - ], - "display_name": ".venv" + ] }, { "cell_type": "code", @@ -716,8 +698,7 @@ "outputs": [], "source": [ "!echo $(git branch --show-current)" - ], - "display_name": ".venv" + ] }, { "cell_type": "code", @@ -731,8 +712,7 @@ "outputs": [], "source": [ "!rsync -avh --exclude-from=.rsyncignore --exclude=.venv/ . kultmost:/app/$(git branch --show-current)/" - ], - "display_name": ".venv" + ] }, { "cell_type": "markdown", @@ -770,8 +750,7 @@ "EOF\n", "\n", "ssh kultmost 'systemctl daemon-reload && systemctl enable --now production.service && systemctl status --no-pager -l production.service'\n" - ], - "display_name": ".venv" + ] }, { "cell_type": "code", @@ -801,8 +780,7 @@ "EOF\n", "\n", "ssh kultmost 'systemctl daemon-reload && systemctl enable --now fastapi.service && systemctl status --no-pager -l fastapi.service'\n" - ], - "display_name": ".venv" + ] }, { "cell_type": "markdown", @@ -840,8 +818,7 @@ "EOF\n", "\n", "ssh kultmost 'systemctl daemon-reload && systemctl enable --now staging.service && systemctl status --no-pager -l staging.service'\n" - ], - "display_name": ".venv" + ] }, { "cell_type": "markdown", @@ -877,8 +854,7 @@ "[Install]\n", "WantedBy=default.target\n", "EOF" - ], - "display_name": ".venv" + ] }, { "cell_type": "code", @@ -908,8 +884,7 @@ "EOF\n", "\n", "ssh kultmost 'systemctl daemon-reload && systemctl enable --now staging.service && systemctl status --no-pager -l staging.service'\n" - ], - "display_name": ".venv" + ] }, { "cell_type": "markdown", @@ -931,8 +906,7 @@ "outputs": [], "source": [ "!ssh kultmost 'systemctl restart production.service'" - ], - "display_name": ".venv" + ] }, { "cell_type": "code", @@ -946,8 +920,7 @@ "outputs": [], "source": [ "!ssh kultmost 'systemctl status production.service'" - ], - "display_name": ".venv" + ] }, { "cell_type": "code", @@ -961,8 +934,7 @@ "outputs": [], "source": [ "!ssh kultmost 'systemctl restart fastapi.service'" - ], - "display_name": ".venv" + ] }, { "cell_type": "code", @@ -976,8 +948,7 @@ "outputs": [], "source": [ "!ssh kultmost 'systemctl status fastapi.service'" - ], - "display_name": ".venv" + ] }, { "cell_type": "markdown", @@ -1015,8 +986,7 @@ "EOF\n", "\n", "ssh kultmost 'systemctl daemon-reload && systemctl enable --now vk-celery.service && systemctl status --no-pager -l vk-celery.service'\n" - ], - "display_name": ".venv" + ] }, { "cell_type": "code", @@ -1030,8 +1000,7 @@ "outputs": [], "source": [ "!ssh kultmost 'systemctl restart vk-celery.service'" - ], - "display_name": ".venv" + ] }, { "cell_type": "code", @@ -1045,8 +1014,7 @@ "outputs": [], "source": [ "!ssh kultmost 'systemctl status vk-celery.service'" - ], - "display_name": ".venv" + ] }, { "cell_type": "code", @@ -1076,8 +1044,7 @@ "EOF\n", "\n", "ssh kultmost 'systemctl daemon-reload && systemctl enable --now email-celery.service && systemctl status --no-pager -l email-celery.service'\n" - ], - "display_name": ".venv" + ] }, { "cell_type": "code", @@ -1091,8 +1058,7 @@ "outputs": [], "source": [ "!ssh kultmost 'systemctl restart email-celery.service'" - ], - "display_name": ".venv" + ] }, { "cell_type": "code", @@ -1106,8 +1072,7 @@ "outputs": [], "source": [ "!ssh kultmost 'systemctl status email-celery.service'" - ], - "display_name": ".venv" + ] }, { "cell_type": "markdown", @@ -1129,8 +1094,7 @@ "outputs": [], "source": [ "# !scp kultmost:/etc/nginx/sites-enabled/default nginx_conf " - ], - "display_name": ".venv" + ] }, { "cell_type": "code", @@ -1146,8 +1110,7 @@ "!scp nginx_conf kultmost:/tmp/default\n", "!ssh kultmost 'sudo mv /tmp/default /etc/nginx/sites-enabled/default'\n", "!ssh kultmost 'nginx -t'" - ], - "display_name": ".venv" + ] }, { "cell_type": "code", @@ -1161,15 +1124,14 @@ "outputs": [], "source": [ "!ssh kultmost 'nginx -s reload'" - ], - "display_name": ".venv" + ] }, { "cell_type": "markdown", "id": "a26138d2", "metadata": {}, "source": [ - "### microk8s" + "## microk8s" ] }, { @@ -1187,8 +1149,7 @@ "sudo usermod -a -G microk8s $USER\n", "newgrp microk8s\n", "microk8s status" - ], - "display_name": ".venv" + ] }, { "cell_type": "code", @@ -1204,15 +1165,14 @@ "curl -LO https://cert-manager.io/public-keys/cert-manager-keyring-2021-09-20-1020CF3C033D4F35BAE1C19E1226061C665DF13E.gpg\n", "helm install cert-manager oci://quay.io/jetstack/charts/cert-manager --version v1.20.2 --namespace cert-manager --create-namespace --verify --keyring ./cert-manager-keyring-2021-09-20-1020CF3C033D4F35BAE1C19E1226061C665DF13E.gpg --set crds.enabled=true\n", "microk8s helm install cert-manager oci://quay.io/jetstack/charts/cert-manager --version v1.20.2 --namespace cert-manager --create-namespace --verify --keyring ./cert-manager-keyring-2021-09-20-1020CF3C033D4F35BAE1C19E1226061C665DF13E.gpg --set crds.enabled=true" - ], - "display_name": ".venv" + ] }, { "cell_type": "markdown", "id": "622f00a3", "metadata": {}, "source": [ - "### Figma" + "## Figma" ] }, { @@ -1426,13 +1386,12 @@ "\n", "sss = await element_create(pc, tablet, mob)\n", "sss.innerHTML" - ], - "display_name": ".venv" + ] } ], "metadata": { "kernelspec": { - "display_name": ".venv (3.14.6.final.0)", + "display_name": ".venv", "language": "python", "name": "python3" }, @@ -1446,7 +1405,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.14.6" + "version": "3.13.5" } }, "nbformat": 4, diff --git a/email_task.ipynb b/email_task.ipynb index 457b2bc..d10e5d1 100644 --- a/email_task.ipynb +++ b/email_task.ipynb @@ -257,7 +257,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.14.6" + "version": "3.13.5" } }, "nbformat": 4, diff --git a/email_task.py b/email_task.py index e4d3c92..2299001 100644 --- a/email_task.py +++ b/email_task.py @@ -1,3 +1,4 @@ +# `email_task.py` generate from `email_task.ipynb` by `build.ipynb` #ignore import os diff --git a/k8s/gitea.yaml b/k8s/gitea.yaml index 50434df..a915d48 100644 --- a/k8s/gitea.yaml +++ b/k8s/gitea.yaml @@ -70,96 +70,87 @@ spec: targetPort: 22 nodePort: 30022 type: LoadBalancer -# --- -# kind: Ingress -# apiVersion: networking.k8s.io/v1 -# metadata: -# name: gitea -# namespace: gitea -# annotations: -# cert-manager.io/cluster-issuer: google-public-ca -# nginx.ingress.kubernetes.io/proxy-body-size: "0" -# nginx.ingress.kubernetes.io/ssl-redirect: "true" -# spec: -# ingressClassName: public -# tls: -# - hosts: -# - git.kultmost.spiritofweb.ru -# secretName: git-tls-cert -# rules: -# - host: git.kultmost.spiritofweb.ru -# http: -# paths: -# - path: / -# pathType: Prefix -# backend: -# service: -# name: gitea-ssh -# port: -# number: 80 -# - path: /robots.txt -# pathType: Exact -# backend: -# service: -# name: robots-static-svc -# port: -# number: 80 -# --- -# apiVersion: v1 -# kind: ConfigMap -# metadata: -# name: gitearobotscm -# namespace: gitea -# data: -# robots.txt: | -# User-agent: * -# Disallow: / -# --- -# apiVersion: apps/v1 -# kind: Deployment -# metadata: -# name: robots-static -# namespace: gitea -# spec: -# replicas: 1 -# selector: -# matchLabels: -# app: robots-static -# template: -# metadata: -# labels: -# app: robots-static -# spec: -# containers: -# - name: nginx -# image: nginx:alpine -# ports: -# - containerPort: 80 -# volumeMounts: -# - name: robots -# mountPath: /usr/share/nginx/html -# volumes: -# - name: robots -# configMap: -# name: gitearobotscm -# --- -# apiVersion: v1 -# kind: Service -# metadata: -# name: robots-static-svc -# namespace: gitea -# spec: -# selector: -# app: robots-static -# ports: -# - port: 80 -# targetPort: 80 -# --- -# apiVersion: v1 -# kind: Secret -# metadata: -# name: ssh-creds -# type: Opaque -# stringData: -# username: "root" -# password: "$ROOT_PASS" +--- +kind: Ingress +apiVersion: networking.k8s.io/v1 +metadata: + name: gitea + namespace: gitea + annotations: + cert-manager.io/cluster-issuer: google-public-ca + nginx.ingress.kubernetes.io/proxy-body-size: "0" + nginx.ingress.kubernetes.io/ssl-redirect: "true" +spec: + ingressClassName: public + tls: + - hosts: + - git.kultmost.spiritofweb.ru + secretName: git-tls-cert + rules: + - host: git.kultmost.spiritofweb.ru + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: gitea-ssh + port: + number: 80 + - path: /robots.txt + pathType: Exact + backend: + service: + name: robots-static-svc + port: + number: 80 +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: gitearobotscm + namespace: gitea +data: + robots.txt: | + User-agent: * + Disallow: / +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: robots-static + namespace: gitea +spec: + replicas: 1 + selector: + matchLabels: + app: robots-static + template: + metadata: + labels: + app: robots-static + spec: + containers: + - name: nginx + image: nginx:alpine + ports: + - containerPort: 80 + volumeMounts: + - name: robots + mountPath: /usr/share/nginx/html + volumes: + - name: robots + configMap: + name: gitearobotscm +--- +apiVersion: v1 +kind: Service +metadata: + name: robots-static-svc + namespace: gitea +spec: + selector: + app: robots-static + ports: + - port: 80 + targetPort: 80 diff --git a/main.ipynb b/main.ipynb index 485c0f6..ab076f3 100644 --- a/main.ipynb +++ b/main.ipynb @@ -234,7 +234,7 @@ " # save to buffer as JPEG\n", " buf = BytesIO()\n", " # img.save(buf, format='JPEG', quality=85)\n", - " img.save(buf, format=\"WEBP\", quality=quality, method=6)\n", + " img.save(buf, format=\"WEBP\", quality=85, method=6)\n", " buf.seek(0)\n", "\n", " # upload processed avatar to GridFS\n", @@ -1377,17 +1377,6 @@ "@app.get(\"/api/me\", response_model=dict)\n", "async def get_me(response: Response, current_user: Dict = Security(get_current_user, scopes=[\"read:self\"])):\n", " user = display_user(current_user)\n", - " refresh_exp = timedelta(days=REFRESH_TOKEN_EXPIRE_DAYS)\n", - " if current_user['email'] == 'kultmost@yandex.ru':\n", - " response.set_cookie(\n", - " domain='.kultmost.ru',\n", - " key=\"token\",\n", - " value='2124|bhcbiP7XEgSe6ooTAoPf6mklBWNExG6a5CmmwTvTfafc5d5b',\n", - " httponly=True,\n", - " secure=PROD,\n", - " samesite=\"strict\",\n", - " max_age=int(refresh_exp.total_seconds())\n", - " )\n", " return bson_clear(user)\n" ], "display_name": ".venv" @@ -2090,23 +2079,31 @@ " print(data)\n", " competition = await competitions_col.find_one({'_id':ObjectId(data.competition_id), 'user._id': current_user.get('_id')})\n", " if competition:\n", - " if PROD:\n", - " await competition_request_col.update_many(\n", - " {'user._id': data.user_id, 'competition_id': data.competition_id},\n", - " {'$set': {'status': True,\"posts.$[].status\": 'Проходит' }},\n", - " )\n", - " background_tasks.add_task(accept_competition_req, competition, data.user_id)\n", + " await competition_request_col.update_many(\n", + " {'user._id': data.user_id, 'competition_id': data.competition_id},\n", + " {'$set': {'status': True,\"posts.$[].status\": 'Проходит' }},\n", + " )\n", + " background_tasks.add_task(accept_competition_req, competition, data.user_id)\n", " social_vk = await social_collection.find_one({'user_id':current_user.get('_id'), 'group': {\"$exists\": False}, 'type':'vk'})\n", " user = display_user(current_user)\n", " user['social_vk'] = social_vk\n", - " await invitations_collection.insert_one({\n", + " await invitations_collection.update_one({\n", " \"user_id\": data.user_id,\n", - " \"item\": competition['title'],\n", + " \"item\": competition.get('title'),\n", " \"title\": \"Вы прошли отбор\",\n", + " \"type_\": competition.get(\"type_\"),\n", " \"user\": user,\n", " \"prod\": PROD,\n", " \"type\": \"Конкурс\"\n", - " })\n", + " },{'$set':{\n", + " \"user_id\": data.user_id,\n", + " \"item\": competition.get('title'),\n", + " \"title\": \"Вы прошли отбор\",\n", + " \"type_\": competition.get(\"type_\"),\n", + " \"user\": user,\n", + " \"prod\": PROD,\n", + " \"type\": \"Конкурс\"\n", + " }},upsert=True)\n", " return {'ok': True}\n", " else:\n", " return {'ok': False}\n" @@ -4173,9 +4170,32 @@ " user: Dict = Security(get_current_user)\n", "):\n", " data = await invitations_collection.find_one({'_id': ObjectId(invitation_id)})\n", + " match_ = {\n", + " \"$or\": [\n", + " {\"sender._id\": user.get('_id')},\n", + " {\"user._id\": user.get('_id')},\n", + " {\"receiver._id\": user.get('_id')}\n", + " ],\n", + " 'prod': PROD,\n", + " }\n", + " if data.get('competition'):\n", + " match_.update({'competition_id': data.get('competition').get('_id')})\n", + " if data.get('exhibition_id'):\n", + " match_.update({'exhibition_id': data.get('exhibition_id')})\n", + " cursor = delivery_collection.aggregate([\n", + " {\"$match\": match_},\n", + " {\"$sort\": {\"_id\": -1}},\n", + " {\"$group\": {\n", + " \"_id\": \"$post_id\",\n", + " \"doc\": {\"$first\": \"$$ROOT\"}\n", + " }},\n", + " {\"$replaceRoot\": {\"newRoot\": \"$doc\"}},\n", + " {\"$sort\": {\"_id\": -1}},\n", + " ])\n", "\n", + " posts = [doc async for doc in cursor]\n", " return templates.TemplateResponse(\n", - " request=request, name=\"profile/invitation.html\", context={ \"user\": user, \"doc\": data}\n", + " request=request, name=\"profile/invitation.html\", context={ \"user\": user, \"doc\": data, \"data\": posts}\n", " )" ], "display_name": ".venv" @@ -4528,6 +4548,86 @@ ], "display_name": ".venv" }, + { + "cell_type": "code", + "execution_count": null, + "id": "6157e497", + "metadata": {}, + "outputs": [], + "source": [ + "@app.get('/profile/delivery/competition/{competition_id}')\n", + "async def profile_delivery_by_competition(request: Request, competition_id:str, user: Dict = Security(get_current_user)):\n", + " cursor = delivery_collection.aggregate([\n", + " {\"$match\": {\n", + " \"$or\": [\n", + " {\"sender._id\": user.get('_id')},\n", + " {\"user._id\": user.get('_id')},\n", + " {\"receiver._id\": user.get('_id')}\n", + " ],\n", + " 'competition_id': ObjectId(competition_id),\n", + " 'prod': PROD,\n", + " }},\n", + " {\"$sort\": {\"_id\": -1}},\n", + " {\"$group\": {\n", + " \"_id\": \"$post_id\",\n", + " \"doc\": {\"$first\": \"$$ROOT\"}\n", + " }},\n", + " {\"$replaceRoot\": {\"newRoot\": \"$doc\"}},\n", + " {\"$sort\": {\"_id\": -1}},\n", + " ])\n", + "\n", + " data = [doc async for doc in cursor]\n", + " return templates.TemplateResponse(\n", + " request=request,\n", + " name=\"profile/delivery_old.html\",\n", + " context={\n", + " \"user\": user,\n", + " \"data\": data,\n", + " }\n", + " )\n" + ], + "display_name": ".venv" + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d68edb20", + "metadata": {}, + "outputs": [], + "source": [ + "@app.get('/profile/delivery/exhibition/{exhibition_id}')\n", + "async def profile_delivery_by_exhibition(request: Request, exhibition_id:str, user: Dict = Security(get_current_user)):\n", + " cursor = delivery_collection.aggregate([\n", + " {\"$match\": {\n", + " \"$or\": [\n", + " {\"sender._id\": user.get('_id')},\n", + " {\"user._id\": user.get('_id')},\n", + " {\"receiver._id\": user.get('_id')}\n", + " ],\n", + " 'exhibition_id': exhibition_id,\n", + " 'prod': PROD,\n", + " }},\n", + " {\"$sort\": {\"_id\": -1}},\n", + " {\"$group\": {\n", + " \"_id\": \"$post_id\",\n", + " \"doc\": {\"$first\": \"$$ROOT\"}\n", + " }},\n", + " {\"$replaceRoot\": {\"newRoot\": \"$doc\"}},\n", + " {\"$sort\": {\"_id\": -1}},\n", + " ])\n", + "\n", + " data = [doc async for doc in cursor]\n", + " return templates.TemplateResponse(\n", + " request=request,\n", + " name=\"profile/delivery_old.html\",\n", + " context={\n", + " \"user\": user,\n", + " \"data\": data,\n", + " }\n", + " )\n" + ], + "display_name": ".venv" + }, { "cell_type": "code", "execution_count": null, @@ -4585,18 +4685,20 @@ "\n", " data = [doc async for doc in cursor]\n", " exhibitions = await exhibition_collection.find({'_id': {'$in': [ObjectId(i.get('exhibition_id')) for i in data if i.get('exhibition_id')]}}).to_list()\n", - "\n", + " competitions = await competitions_col.find({'_id': {'$in': [i.get('competition_id') for i in data if i.get('competition_id')]}}).to_list()\n", " return templates.TemplateResponse(\n", " request=request,\n", " name=\"profile/delivery.html\",\n", " context={\n", " \"user\": user,\n", " \"exhibitions\": exhibitions,\n", + " \"competitions\": competitions,\n", " \"data\": data,\n", + " \"now\": datetime.now(),\n", " \"page_title\": \"Доставка\",\n", " \"active_section\": \"delivery\"\n", " }\n", - " )" + " )\n" ], "display_name": ".venv" }, @@ -4662,7 +4764,6 @@ "metadata": {}, "outputs": [], "source": [ - "\n", "@app.get(\"/profile/partner/exhibition/{exhibition_id}/catalog\", response_class=HTMLResponse)\n", "async def profile_partner_exhibitions_catalog(\n", " request: Request,\n", @@ -4727,7 +4828,7 @@ " posts = [doc async for doc in posts_collection.find().sort('_id', -1).limit(32)]\n", " return templates.TemplateResponse(\n", " request=request, name=\"profile/partner/exhibition catalog.html\", context={\"now\": datetime.now(), \"user\": user, \"galerists\": galerists, \"genres\": genres, \"living_places\": living_places, \"exhibition\": exhibition, \"posts\": posts}\n", - " )" + " )\n" ], "display_name": ".venv" }, @@ -4750,8 +4851,8 @@ " d_posts = await delivery_collection.find({'post_id':{'$in':[str(i.get('_id')) for i in posts]}}).to_list()\n", " for post in posts:\n", " for doc in d_posts:\n", - " if str(post.get('_id')) == doc.get('post_id'):\n", - " post.update({'status':doc.get('status'), 'd_id':doc.get('_id')})\n", + " if str(post.get('_id')) == doc.get('post_id') and doc.get('receiver') and doc.get('receiver').get('_id') == str(user.get('_id')):\n", + " post.update({'status':doc.get('status'), 'receiver': doc.get('receiver'), 'd_id':doc.get('_id')})\n", " return templates.TemplateResponse(\n", " request=request, name=\"profile/partner/exhibition.html\", context={\"now\": datetime.now(), \"user\": user, \"exhibition\": exhibition, \"posts\": posts}\n", " )\n", @@ -4765,7 +4866,7 @@ " data = await exhibition_collection.find({'user._id': user.get('_id')}).sort('_id', -1).to_list()\n", " return templates.TemplateResponse(\n", " request=request, name=\"profile/partner/exhibitions.html\", context={\"now\": datetime.now(), \"user\": user, \"data\": data}\n", - " )" + " )\n" ], "display_name": ".venv" }, @@ -4887,7 +4988,7 @@ ], "metadata": { "kernelspec": { - "display_name": ".venv (3.14.6.final.0)", + "display_name": ".venv", "language": "python", "name": "python3" }, @@ -4901,7 +5002,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.14.6" + "version": "3.13.5" } }, "nbformat": 4, diff --git a/main.py b/main.py index 1f50f5a..0df7ac5 100644 --- a/main.py +++ b/main.py @@ -1,3 +1,4 @@ +# `main.py` generate from `main.ipynb` by `build.ipynb` # FastAPI ## App @@ -126,7 +127,7 @@ async def logo_user_task(user: dict): # save to buffer as JPEG buf = BytesIO() # img.save(buf, format='JPEG', quality=85) - img.save(buf, format="WEBP", quality=quality, method=6) + img.save(buf, format="WEBP", quality=85, method=6) buf.seek(0) # upload processed avatar to GridFS @@ -1069,17 +1070,6 @@ async def logout( @app.get("/api/me", response_model=dict) async def get_me(response: Response, current_user: Dict = Security(get_current_user, scopes=["read:self"])): user = display_user(current_user) - refresh_exp = timedelta(days=REFRESH_TOKEN_EXPIRE_DAYS) - if current_user['email'] == 'kultmost@yandex.ru': - response.set_cookie( - domain='.kultmost.ru', - key="token", - value='2124|bhcbiP7XEgSe6ooTAoPf6mklBWNExG6a5CmmwTvTfafc5d5b', - httponly=True, - secure=PROD, - samesite="strict", - max_age=int(refresh_exp.total_seconds()) - ) return bson_clear(user) @@ -1556,23 +1546,31 @@ async def partner_competition_request_artist( print(data) competition = await competitions_col.find_one({'_id':ObjectId(data.competition_id), 'user._id': current_user.get('_id')}) if competition: - if PROD: - await competition_request_col.update_many( - {'user._id': data.user_id, 'competition_id': data.competition_id}, - {'$set': {'status': True,"posts.$[].status": 'Проходит' }}, - ) - background_tasks.add_task(accept_competition_req, competition, data.user_id) + await competition_request_col.update_many( + {'user._id': data.user_id, 'competition_id': data.competition_id}, + {'$set': {'status': True,"posts.$[].status": 'Проходит' }}, + ) + background_tasks.add_task(accept_competition_req, competition, data.user_id) social_vk = await social_collection.find_one({'user_id':current_user.get('_id'), 'group': {"$exists": False}, 'type':'vk'}) user = display_user(current_user) user['social_vk'] = social_vk - await invitations_collection.insert_one({ + await invitations_collection.update_one({ "user_id": data.user_id, - "item": competition['title'], + "item": competition.get('title'), "title": "Вы прошли отбор", + "type_": competition.get("type_"), "user": user, "prod": PROD, "type": "Конкурс" - }) + },{'$set':{ + "user_id": data.user_id, + "item": competition.get('title'), + "title": "Вы прошли отбор", + "type_": competition.get("type_"), + "user": user, + "prod": PROD, + "type": "Конкурс" + }},upsert=True) return {'ok': True} else: return {'ok': False} @@ -3187,9 +3185,32 @@ async def profile_invitation( user: Dict = Security(get_current_user) ): data = await invitations_collection.find_one({'_id': ObjectId(invitation_id)}) + match_ = { + "$or": [ + {"sender._id": user.get('_id')}, + {"user._id": user.get('_id')}, + {"receiver._id": user.get('_id')} + ], + 'prod': PROD, + } + if data.get('competition'): + match_.update({'competition_id': data.get('competition').get('_id')}) + if data.get('exhibition_id'): + match_.update({'exhibition_id': data.get('exhibition_id')}) + cursor = delivery_collection.aggregate([ + {"$match": match_}, + {"$sort": {"_id": -1}}, + {"$group": { + "_id": "$post_id", + "doc": {"$first": "$$ROOT"} + }}, + {"$replaceRoot": {"newRoot": "$doc"}}, + {"$sort": {"_id": -1}}, + ]) + posts = [doc async for doc in cursor] return templates.TemplateResponse( - request=request, name="profile/invitation.html", context={ "user": user, "doc": data} + request=request, name="profile/invitation.html", context={ "user": user, "doc": data, "data": posts} ) @app.get("/profile/invitation", response_class=HTMLResponse) @@ -3438,6 +3459,70 @@ async def profile_delivery_history_post(request: Request, post_id:str, user: Dic ) +@app.get('/profile/delivery/competition/{competition_id}') +async def profile_delivery_by_competition(request: Request, competition_id:str, user: Dict = Security(get_current_user)): + cursor = delivery_collection.aggregate([ + {"$match": { + "$or": [ + {"sender._id": user.get('_id')}, + {"user._id": user.get('_id')}, + {"receiver._id": user.get('_id')} + ], + 'competition_id': ObjectId(competition_id), + 'prod': PROD, + }}, + {"$sort": {"_id": -1}}, + {"$group": { + "_id": "$post_id", + "doc": {"$first": "$$ROOT"} + }}, + {"$replaceRoot": {"newRoot": "$doc"}}, + {"$sort": {"_id": -1}}, + ]) + + data = [doc async for doc in cursor] + return templates.TemplateResponse( + request=request, + name="profile/delivery_old.html", + context={ + "user": user, + "data": data, + } + ) + + +@app.get('/profile/delivery/exhibition/{exhibition_id}') +async def profile_delivery_by_exhibition(request: Request, exhibition_id:str, user: Dict = Security(get_current_user)): + cursor = delivery_collection.aggregate([ + {"$match": { + "$or": [ + {"sender._id": user.get('_id')}, + {"user._id": user.get('_id')}, + {"receiver._id": user.get('_id')} + ], + 'exhibition_id': exhibition_id, + 'prod': PROD, + }}, + {"$sort": {"_id": -1}}, + {"$group": { + "_id": "$post_id", + "doc": {"$first": "$$ROOT"} + }}, + {"$replaceRoot": {"newRoot": "$doc"}}, + {"$sort": {"_id": -1}}, + ]) + + data = [doc async for doc in cursor] + return templates.TemplateResponse( + request=request, + name="profile/delivery_old.html", + context={ + "user": user, + "data": data, + } + ) + + @app.get( "/profile/delivery", response_class=HTMLResponse, @@ -3488,19 +3573,22 @@ async def profile_delivery( data = [doc async for doc in cursor] exhibitions = await exhibition_collection.find({'_id': {'$in': [ObjectId(i.get('exhibition_id')) for i in data if i.get('exhibition_id')]}}).to_list() - + competitions = await competitions_col.find({'_id': {'$in': [i.get('competition_id') for i in data if i.get('competition_id')]}}).to_list() return templates.TemplateResponse( request=request, name="profile/delivery.html", context={ "user": user, "exhibitions": exhibitions, + "competitions": competitions, "data": data, + "now": datetime.now(), "page_title": "Доставка", "active_section": "delivery" } ) + @app.post("/api/exhibition/send/invitations") async def api_exhibition_send_invitations( background_tasks: BackgroundTasks, @@ -3540,7 +3628,6 @@ async def api_exhibition_post_select( return {"ok": True} - @app.get("/profile/partner/exhibition/{exhibition_id}/catalog", response_class=HTMLResponse) async def profile_partner_exhibitions_catalog( request: Request, @@ -3607,6 +3694,7 @@ async def profile_partner_exhibitions_catalog( request=request, name="profile/partner/exhibition catalog.html", context={"now": datetime.now(), "user": user, "galerists": galerists, "genres": genres, "living_places": living_places, "exhibition": exhibition, "posts": posts} ) + @app.get("/profile/partner/exhibition/{exhibition_id}", response_class=HTMLResponse) async def profile_partner_exhibitions( request: Request, @@ -3619,8 +3707,8 @@ async def profile_partner_exhibitions( d_posts = await delivery_collection.find({'post_id':{'$in':[str(i.get('_id')) for i in posts]}}).to_list() for post in posts: for doc in d_posts: - if str(post.get('_id')) == doc.get('post_id'): - post.update({'status':doc.get('status'), 'd_id':doc.get('_id')}) + if str(post.get('_id')) == doc.get('post_id') and doc.get('receiver') and doc.get('receiver').get('_id') == str(user.get('_id')): + post.update({'status':doc.get('status'), 'receiver': doc.get('receiver'), 'd_id':doc.get('_id')}) return templates.TemplateResponse( request=request, name="profile/partner/exhibition.html", context={"now": datetime.now(), "user": user, "exhibition": exhibition, "posts": posts} ) @@ -3634,4 +3722,4 @@ async def profile_exhibitions( data = await exhibition_collection.find({'user._id': user.get('_id')}).sort('_id', -1).to_list() return templates.TemplateResponse( request=request, name="profile/partner/exhibitions.html", context={"now": datetime.now(), "user": user, "data": data} - ) \ No newline at end of file + ) diff --git a/migrate.ipynb b/migrate.ipynb index 6cf8d3f..c5ae839 100644 --- a/migrate.ipynb +++ b/migrate.ipynb @@ -38,8 +38,7 @@ " load_env('env')\n", "except:\n", " load_env('env_example')" - ], - "display_name": ".venv" + ] }, { "cell_type": "code", @@ -49,8 +48,7 @@ "outputs": [], "source": [ "from db import *" - ], - "display_name": ".venv" + ] }, { "cell_type": "code", @@ -60,13 +58,12 @@ "outputs": [], "source": [ "await posts_collection.update_many({'status':'publish'},{'$set':{'status':'В каталоге'}})" - ], - "display_name": ".venv" + ] } ], "metadata": { "kernelspec": { - "display_name": ".venv (3.14.6.final.0)", + "display_name": ".venv", "language": "python", "name": "python3" }, @@ -80,7 +77,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.14.6" + "version": "3.13.5" } }, "nbformat": 4, diff --git a/tasks.ipynb b/tasks.ipynb index af4ec8d..4c05c16 100644 --- a/tasks.ipynb +++ b/tasks.ipynb @@ -810,7 +810,7 @@ " # Генерация login link\n", " token = await token_create(\n", " str(user['_id']), \n", - " url=f'/profile/delivery#main'\n", + " url=f'/profile/invitation#main'\n", " )\n", " login_link = f'{FULL_DOMAIN}/login/{token}'\n", "\n", @@ -860,7 +860,7 @@ " user_id=str(user['_id']),\n", " type_item='invitation',\n", " title='Приглашение',\n", - " link=f'/profile/delivery',\n", + " link=f'/profile/invitation',\n", " text=f'Вы прошли отбор на конкурсе \"{competition.get(\"title\")}\"'\n", " ))\n", " all_allowed = all(\n", @@ -871,7 +871,24 @@ "\n", " # Email-уведомление\n", " if user.get('email') and all_allowed and has_approved:\n", - " print([i['title'] for i in posts_sorted])\n", + " await invitations_collection.update_one({\n", + " \"user_id\": str(user['_id']),\n", + " \"item\": competition.get(\"title\"),\n", + " \"title\": \"Вы прошли отбор\",\n", + " \"type_\": competition.get(\"type_\"),\n", + " \"user\": display_user(competition_owner),\n", + " \"prod\": PROD,\n", + " \"type\": \"Конкурс\"\n", + " },{'$set':{\n", + " \"user_id\": str(user['_id']),\n", + " \"item\": competition.get(\"title\"),\n", + " \"title\": \"Вы прошли отбор\",\n", + " \"competition\": competition,\n", + " \"type_\": competition.get(\"type_\"),\n", + " \"user\": display_user(competition_owner),\n", + " \"prod\": PROD,\n", + " \"type\": \"Конкурс\"\n", + " }},upsert=True)\n", " if PROD and user.get('email') and all_allowed and has_approved:\n", " email_data = {\n", " 'text': 'Статус заявки обновлен',\n", @@ -937,7 +954,7 @@ ], "metadata": { "kernelspec": { - "display_name": ".venv (3.14.6.final.0)", + "display_name": ".venv", "language": "python", "name": "python3" }, @@ -951,7 +968,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.14.6" + "version": "3.13.5" } }, "nbformat": 4, diff --git a/tasks.py b/tasks.py index 211e064..eda1c62 100644 --- a/tasks.py +++ b/tasks.py @@ -1,3 +1,4 @@ +# `tasks.py` generate from `tasks.ipynb` by `build.ipynb` from config import * from db import * from typing import Dict, Any, List, Optional, Callable, Union @@ -630,7 +631,7 @@ async def update_competition_req(competition, user, posts): # Генерация login link token = await token_create( str(user['_id']), - url=f'/profile/delivery#main' + url=f'/profile/invitation#main' ) login_link = f'{FULL_DOMAIN}/login/{token}' @@ -680,7 +681,7 @@ async def update_competition_req(competition, user, posts): user_id=str(user['_id']), type_item='invitation', title='Приглашение', - link=f'/profile/delivery', + link=f'/profile/invitation', text=f'Вы прошли отбор на конкурсе "{competition.get("title")}"' )) all_allowed = all( @@ -691,7 +692,24 @@ async def update_competition_req(competition, user, posts): # Email-уведомление if user.get('email') and all_allowed and has_approved: - print([i['title'] for i in posts_sorted]) + await invitations_collection.update_one({ + "user_id": str(user['_id']), + "item": competition.get("title"), + "title": "Вы прошли отбор", + "type_": competition.get("type_"), + "user": display_user(competition_owner), + "prod": PROD, + "type": "Конкурс" + },{'$set':{ + "user_id": str(user['_id']), + "item": competition.get("title"), + "title": "Вы прошли отбор", + "competition": competition, + "type_": competition.get("type_"), + "user": display_user(competition_owner), + "prod": PROD, + "type": "Конкурс" + }},upsert=True) if PROD and user.get('email') and all_allowed and has_approved: email_data = { 'text': 'Статус заявки обновлен', diff --git a/templates/base.html b/templates/base.html index 6584d23..8057ce9 100644 --- a/templates/base.html +++ b/templates/base.html @@ -23,6 +23,10 @@ h1,h2,h3,p { html{display: flex;justify-content: center;} input, button {touch-action: manipulation;} picture{cursor: zoom-in;} +.ba { cursor: pointer; box-shadow: 0 4px 14px -4px rgba(95,23,152,.5); transition: transform .18s, box-shadow .18s, background .18s; } +.ba:hover { background: #6d1ba9; transform: translateY(-2px); box-shadow: 0 10px 24px -6px rgba(95,23,152,.6); } +.ba:active { transform: translateY(0); box-shadow: 0 4px 12px -4px rgba(95,23,152,.5); } +@media (prefers-reduced-motion: reduce) { .ba:hover, .ba:active { transform: none; } } *{margin:unset;box-sizing:border-box;position:relative;transition:300ms}button,input{all:unset}button,a{cursor:pointer}input{cursor:auto}button>span{text-wrap:nowrap}@font-face{font-family:"Inter";font-style:normal;font-weight:100;font-display:swap;src:url(/static/Inter-Thin.woff2) format("woff2")}@font-face{font-family:"Inter";font-style:normal;font-weight:200;font-display:swap;src:url(/static/Inter-ExtraLight.woff2) format("woff2")}@font-face{font-family:"Inter";font-style:normal;font-weight:300;font-display:swap;src:url(/static/Inter-Light.woff2) format("woff2")}@font-face{font-family:"Inter";font-style:normal;font-weight:400;font-display:swap;src:url(/static/Inter-Regular.woff2) format("woff2")}@font-face{font-family:"Inter";font-style:normal;font-weight:500;font-display:swap;src:url(/static/Inter-Medium.woff2) format("woff2")}@font-face{font-family:"Inter";font-style:normal;font-weight:600;font-display:swap;src:url(/static/Inter-SemiBold.woff2) format("woff2")}@font-face{font-family:"Inter";font-style:normal;font-weight:700;font-display:swap;src:url(/static/Inter-Bold.woff2) format("woff2")}@font-face{font-family:"Inter";font-style:normal;font-weight:800;font-display:swap;src:url(/static/Inter-ExtraBold.woff2) format("woff2")}@font-face{font-family:"Inter";font-style:normal;font-weight:900;font-display:swap;src:url(/static/Inter-Black.woff2) format("woff2")}@font-face{font-family:'Roboto';font-style:normal;font-weight:400;font-display:swap;src:url(/static/fonts/KFOmCnqEu92Fr1Mu72xKOzY.woff2) format("woff2");unicode-range:U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:'Roboto';font-style:normal;font-weight:400;font-display:swap;src:url(/static/fonts/KFOmCnqEu92Fr1Mu5mxKOzY.woff2) format("woff2");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:'Roboto';font-style:normal;font-weight:400;font-display:swap;src:url(/static/fonts/KFOmCnqEu92Fr1Mu7GxKOzY.woff2) format("woff2");unicode-range:U+0100-02AF,U+0304,U+0308,U+0329,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:'Roboto';font-style:normal;font-weight:400;font-display:swap;src:url(/static/fonts/KFOmCnqEu92Fr1Mu4mxK.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:'Roboto';font-style:normal;font-weight:500;font-display:swap;src:url(/static/fonts/KFOlCnqEu92Fr1MmEU9fCRc4EsA.woff2) format("woff2");unicode-range:U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:'Roboto';font-style:normal;font-weight:500;font-display:swap;src:url(/static/fonts/KFOlCnqEu92Fr1MmEU9fABc4EsA.woff2) format("woff2");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:'Roboto';font-style:normal;font-weight:500;font-display:swap;src:url(/static/fonts/KFOlCnqEu92Fr1MmEU9fChc4EsA.woff2) format("woff2");unicode-range:U+0100-02AF,U+0304,U+0308,U+0329,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:'Roboto';font-style:normal;font-weight:500;font-display:swap;src:url(/static/fonts/KFOlCnqEu92Fr1MmEU9fBBc4.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:'Roboto';font-style:normal;font-weight:700;font-display:swap;src:url(/static/fonts/KFOlCnqEu92Fr1MmWUlfCRc4EsA.woff2) format("woff2");unicode-range:U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:'Roboto';font-style:normal;font-weight:700;font-display:swap;src:url(/static/fonts/KFOlCnqEu92Fr1MmWUlfABc4EsA.woff2) format("woff2");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:'Roboto';font-style:normal;font-weight:700;font-display:swap;src:url(/static/fonts/KFOlCnqEu92Fr1MmWUlfChc4EsA.woff2) format("woff2");unicode-range:U+0100-02AF,U+0304,U+0308,U+0329,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:'Roboto';font-style:normal;font-weight:700;font-display:swap;src:url(/static/fonts/KFOlCnqEu92Fr1MmWUlfBBc4.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:'Roboto';font-style:normal;font-weight:900;font-display:swap;src:url(/static/fonts/KFOlCnqEu92Fr1MmYUtfCRc4EsA.woff2) format("woff2");unicode-range:U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:'Roboto';font-style:normal;font-weight:900;font-display:swap;src:url(/static/fonts/KFOlCnqEu92Fr1MmYUtfABc4EsA.woff2) format("woff2");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:'Roboto';font-style:normal;font-weight:900;font-display:swap;src:url(/static/fonts/KFOlCnqEu92Fr1MmYUtfChc4EsA.woff2) format("woff2");unicode-range:U+0100-02AF,U+0304,U+0308,U+0329,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:'Roboto';font-style:normal;font-weight:900;font-display:swap;src:url(/static/fonts/KFOlCnqEu92Fr1MmYUtfBBc4.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}::-webkit-scrollbar{height:5px}::-webkit-scrollbar-track{background:#0000}::-webkit-scrollbar-thumb{background:#BDBDBD}::-webkit-scrollbar-thumb:hover{background:#323d5b;border-radius:10px}body{scrollbar-width:none;max-width:1600px;display:grid;justify-content:center}body::-webkit-scrollbar{display:none}:root{font-family:Inter,sans-serif;font-feature-settings:"liga" 1,"calt" 1}a{text-decoration:none}*,::before,::after{box-sizing:border-box}.modal{transition:none;position:fixed;z-index:1000;left:0;top:0;width:100%;height:100%;overflow:auto;background-color:#000c;display:flex;justify-content:center;align-items:center}.close{position:fixed;top:20px;right:30px;color:#fff;font-size:40px;cursor:pointer}#modalLink{transition:none;display:flex;position:fixed;bottom:13px;justify-content:center;width:335px;border-radius:1em;height:38px;gap:5px;align-items:center;background:#5F1798} {% endraw %} diff --git a/templates/competition/edit.html b/templates/competition/edit.html index d631f7c..823034a 100644 --- a/templates/competition/edit.html +++ b/templates/competition/edit.html @@ -166,7 +166,7 @@ Публикация в личных соц. сетях --> - @@ -645,7 +645,7 @@ document.addEventListener('DOMContentLoaded', () => { {{ competition.style() }} {{ checkbox.style() }} + + +{{ footer.style() }} +{% endblock %} \ No newline at end of file diff --git a/templates/profile/invitation.html b/templates/profile/invitation.html index 913f1cf..cabf498 100644 --- a/templates/profile/invitation.html +++ b/templates/profile/invitation.html @@ -7,6 +7,90 @@ {% block title %}Приглашения{% endblock %} +{% macro render_doc(doc) %} +
+ +
+ {% if doc.status == 'Ожидает отправки' %} +
+ {{doc.status}} +
+ {% elif doc.status == 'Получено' %} +
+ {{doc.status}} +
+ {% elif doc.status == 'Ожидает получения' %} +
+ {{doc.status}} +
+ {% else %} +
+ {{doc.status}} +
+ {% endif %} + Посмотреть историю +

{{doc.title}}

+ {% if doc.type == 'exhibition' and doc.get('exhibition') %} +

{{doc.get('exhibition').title}}

+
+ +
+ Организатор + {{doc.receiver.username}} +
+
+ {% else %} + {% if doc.address %} +

Адрес: {{doc.address}}

+ {% endif %} + {% if doc.back_address %} +

Адрес для возврата: {{doc.back_address}}

+ {% endif %} + {% if doc.delivery_type %} +

Способ доставки: {{doc.delivery_type}}

+ {% endif %} + {% if doc.track %} +

Трэк-номер: {{doc.track}}

+ {% endif %} + {% if doc.sender %} +
+ +
+ Отправитель + {{doc.sender.username}} +
+
+ {% endif %} + {% if doc.receiver %} +
+ +
+ Получатель + {{doc.receiver.username}} + +7 *** *** {{ doc.receiver.phone[-4:] }} +
+
+ {% endif %} + {% if doc.status in ['Ожидает отправки', 'Возврат художнику'] and user._id == doc.sender._id%} + + {% endif %} + {% if doc.status == 'Ожидает получения' and user._id == doc.receiver._id%} + + {% endif %} + {% endif %} +

+ {{ (doc._id.generation_time.strftime('%d.%m.%Y')) if doc._id else ''}} + {{ '%02d' % ((doc._id.generation_time.hour + 3) % 24) if doc._id else '' }}:{{ '%02d' % doc._id.generation_time.minute if doc._id else '' }} +

+
+
+{% endmacro %} + + {% macro render_post(doc) %}
@@ -50,49 +134,251 @@ {% block content %} {{ header.section(user) }}
-
- {{ profile_card.profile_card(user) }} -
- {{ profile_nav.profile_nav(user, 'invitation') }} -
-
-
-
- {{doc.type}} -
-
-

{{ doc.title }}

-
-

{{doc.get('exhibition').title }}

-

Место проведения: {{ doc.get('exhibition').get('place') }}

-
- С {{ ( doc.get('exhibition').datetime_start.strftime('%d.%m.%Y')) if - doc.get('exhibition').datetime_start - else ''}} - По {{ ( doc.get('exhibition').date_finish.strftime('%d.%m.%Y')) if - doc.get('exhibition').date_finish else - ''}} -
- {{render_links(doc)}} -

Картин выбрано: 0

- - {% set user_contact = doc.get('user') %} - {{render_user_contacts(user_contact)}} -

{{ (doc._id.generation_time.strftime('%d.%m.%Y')) if doc._id else ''}}

-
-
- {% for post in doc.get('posts')%} - {{render_post(post)}} - {% endfor %} +
+{{ profile_card.profile_card(user) }} +
+{{ profile_nav.profile_nav(user, 'invitation') }} +
+ {% if doc.get('exhibition')%} +
+
+
+ {{doc.type}} +
+
+

{{ doc.title }}

+
+

{{doc.get('exhibition').title }}

+

Место проведения: {{ doc.get('exhibition').get('place') }}

+
+ С {{ ( doc.get('exhibition').datetime_start.strftime('%d.%m.%Y')) if + doc.get('exhibition').datetime_start + else ''}} + По {{ ( doc.get('exhibition').date_finish.strftime('%d.%m.%Y')) if + doc.get('exhibition').date_finish else + ''}} +
+ {{render_links(doc)}} +

Картин выбрано: 0

+ + {% set user_contact = doc.get('user') %} + {{ render_user_contacts(user_contact) }} +

{{ (doc._id.generation_time.strftime('%d.%m.%Y')) if doc._id else ''}}

+
+
+ {% for post in doc.get('posts')%} + {{render_post(post)}} + {% endfor %} +{%endif%} +{% if doc.get('type_')=='paint' %} +{% set statuses = ['Ожидает отправки', 'Возврат художнику'] %} +{% if data | selectattr('status', 'in', statuses) | list | length > 0 %} +
+
+
+

Настройки отправки

+
+

Можно менять перед каждой отправкой картины

+
+
+ + +
+
+ +
+
+{% endif %} + +{% for doc in data if doc.status in statuses %} +{{ render_doc(doc) }} +{% endfor %} +{% for doc in data if doc.status not in statuses %} +{{ render_doc(doc) }} +{% endfor %} + + + +{% endif %} +
+
+
{% elif competition.status == 1 %} -
Завершить конкурс +
Завершить конкурс
{% elif competition.status == 2 %} -
Конкурс завершен
+
Конкурс завершен
{%endif%}
@@ -278,7 +278,8 @@ document.querySelectorAll('.b_k2cle button').forEach(button => { {{ header.style() }} {{card.style()}} {{ profile_nav.style() }} -