Merge branch 'dev_unstable' of github.com:vvsu-rent-project/dev_rent into dev_unstable
This commit is contained in:
commit
0af62ee05d
|
@ -0,0 +1,10 @@
|
||||||
|
APP_DEBUG=false
|
||||||
|
APP_PORT=80
|
||||||
|
|
||||||
|
DB_HOST=db
|
||||||
|
DB_PORT=5432
|
||||||
|
DB_USER=root
|
||||||
|
DB_PASSWORD=
|
||||||
|
DB_DATABASE=pairent
|
||||||
|
|
||||||
|
DJANGO_KEY=
|
|
@ -28,7 +28,4 @@ local_settings.py
|
||||||
db.sqlite3
|
db.sqlite3
|
||||||
|
|
||||||
# macOS moment
|
# macOS moment
|
||||||
pairent_backend/.DS_Store
|
|
||||||
# macOS moment жиза
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
|
|
82
README.md
82
README.md
|
@ -1,76 +1,8 @@
|
||||||
# Аренда квартир для студентов
|
<h1 align='center'>Точка</h1>
|
||||||
|
Добро пожаловать в репозиторий проекта "точка"
|
||||||
|
|
||||||
В проекте есть три ветки:
|
# Запуск локального сайта
|
||||||
|
1. Создайте `.env` файл, рекомендуем использовать для этого шаблон `.env.example`
|
||||||
- main — в эту ветку загружается код после положительного тестирования и ревью.
|
2. Сгенерируйте ключи `genkeys.sh`
|
||||||
- dev_stable — в эту ветку загружается стабильный код, который будет использоваться в продашкене.
|
3. Постройте статичные файлы `build.sh`
|
||||||
- dev_unstable — основная ветка для разработчиков.
|
3. Поднимите контейнеры `docker-compose up -d`
|
||||||
|
|
||||||
## ТУТ НАЗВАНЫ ПАПКИ ПОД РАЗРАБОТКУ И ФРОНТ В БЕК НЕ ЗАХОДИТ И НИЧЕГО НЕ МЕНЯЕТ!
|
|
||||||
|
|
||||||
## Как начать работать?
|
|
||||||
|
|
||||||
- Устанавливаем [python](https://www.python.org/downloads/) версия 3.11.1 и [git](https://git-scm.com/)
|
|
||||||
- [Настройте git](https://tproger.ru/curriculum/git-guide/). В основном нужно только ввести имя пользователя и почту, пароль запросит при клонировании
|
|
||||||
|
|
||||||
Если не хотите заморачиваться с терминалом. Советую использовать [GitHub Desktop](https://desktop.github.com/)
|
|
||||||
|
|
||||||
### Клонирование проекта через терминал
|
|
||||||
|
|
||||||
Команды написаны под windows. Вводить в командную строку. Проверяйте, что в терминале вы находитесь в правильной директории
|
|
||||||
|
|
||||||
- Клонируем репозиторий
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git clone https://github.com/vvsu-rent-project/dev_rent.github
|
|
||||||
```
|
|
||||||
|
|
||||||
- В консоли переходим в папку проекта
|
|
||||||
- Далее переходим на ветку unstable
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git switch dev_unstable
|
|
||||||
```
|
|
||||||
|
|
||||||
### Клонирование проекта через GitHub Desktop
|
|
||||||
|
|
||||||
- Заходим в настройки
|
|
||||||
|
|
||||||
![Заходим в настройки](misc/1_step.png)
|
|
||||||
|
|
||||||
- Входим в аккаунт
|
|
||||||
|
|
||||||
![Входим в аккаунт](misc/2_step.png)
|
|
||||||
|
|
||||||
- Клонируем репозиторий
|
|
||||||
|
|
||||||
![Клонируем репозиторий](misc/3_step.png)
|
|
||||||
|
|
||||||
![Клонируем репозиторий](misc/4_step.png)
|
|
||||||
|
|
||||||
### Подготовка проекта к работе
|
|
||||||
|
|
||||||
- Открываем VS Code, открываем папку проекта и запускаем терминал
|
|
||||||
- В терминале переходим в папку pairent_backend
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd pairent_backend
|
|
||||||
```
|
|
||||||
|
|
||||||
- Создаем виртуальное окружение
|
|
||||||
|
|
||||||
```bash
|
|
||||||
python -m venv venv
|
|
||||||
```
|
|
||||||
|
|
||||||
- Активируем виртуальное окружение. Активировать окружение нужно: когда запускаете сервер, устанавливаете зависимости, добавляете новое django приложение
|
|
||||||
|
|
||||||
```bash
|
|
||||||
venv\Scripts\activate
|
|
||||||
```
|
|
||||||
|
|
||||||
- После активации, устанавливаем зависимости.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pip install -r requirements.txt
|
|
||||||
```
|
|
|
@ -0,0 +1,74 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# ----- Start commands -----
|
||||||
|
|
||||||
|
fatal_err() {
|
||||||
|
echo -e "\033[31mFatal error $*\033[0m"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ----- End commands -----
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# ----- Start safeguards -----
|
||||||
|
|
||||||
|
if [[ "$1" != '-a' ]]; then
|
||||||
|
fatal_err
|
||||||
|
echo " This script will potentially break any existing instance of Pairent."
|
||||||
|
echo " To execute this script, re-run it with option -a as first argument."
|
||||||
|
exit -1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! [ -f .env ]; then
|
||||||
|
fatal_err
|
||||||
|
echo -e " No .env file was found."
|
||||||
|
echo -e " Please use the \033[32m.env.example\033[0m to create a dotenv file:"
|
||||||
|
echo -e " 1. \033[34mcp .env.example .env\033[0m"
|
||||||
|
echo -e " 2. Edit your \033[32m.env\033[0m in your favourite editor"
|
||||||
|
exit -1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ----- End safeguards -----
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# ----- Start bootstrap front -----
|
||||||
|
|
||||||
|
echo Building frontend static files...
|
||||||
|
|
||||||
|
cd pairent_frontend_react
|
||||||
|
|
||||||
|
NPMS=('npm' 'pnpm' 'yarn')
|
||||||
|
NPM='None'
|
||||||
|
|
||||||
|
for n in ${NPMS[@]}; do
|
||||||
|
if [ -x "$(command -v "$n")" ]; then
|
||||||
|
NPM=$n
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ "$NPM" == 'None' ]]; then
|
||||||
|
fatal_err
|
||||||
|
echo ' No node package manager was found in your system.'
|
||||||
|
echo ' Please install one of the following:'
|
||||||
|
for n in ${NPMS[@]}; do
|
||||||
|
echo -e " $n"
|
||||||
|
done
|
||||||
|
exit -1
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm -rf node_modules
|
||||||
|
|
||||||
|
$NPM install
|
||||||
|
$NPM run build
|
||||||
|
|
||||||
|
DIST_FOLDER=$(realpath dist)
|
||||||
|
|
||||||
|
cd ..
|
||||||
|
cp -r $DIST_FOLDER/* static
|
||||||
|
cp $DIST_FOLDER/index.html pairent_backend/pairent_app/templates
|
||||||
|
cp $DIST_FOLDER/../public/* static -r
|
||||||
|
|
||||||
|
echo Done building frontend static files
|
||||||
|
|
||||||
|
# ----- End bootstrap front -----
|
|
@ -0,0 +1,2 @@
|
||||||
|
*
|
||||||
|
!.gitignore
|
|
@ -0,0 +1,49 @@
|
||||||
|
types {
|
||||||
|
text/html html htm shtml;
|
||||||
|
text/css css;
|
||||||
|
text/xml xml rss;
|
||||||
|
image/gif gif;
|
||||||
|
image/jpeg jpeg jpg;
|
||||||
|
application/x-javascript js;
|
||||||
|
text/plain txt;
|
||||||
|
text/x-component htc;
|
||||||
|
text/mathml mml;
|
||||||
|
image/png png;
|
||||||
|
image/x-icon ico;
|
||||||
|
image/x-jng jng;
|
||||||
|
image/vnd.wap.wbmp wbmp;
|
||||||
|
image/svg+xml svg svgz;
|
||||||
|
application/java-archive jar war ear;
|
||||||
|
application/mac-binhex40 hqx;
|
||||||
|
application/pdf pdf;
|
||||||
|
application/x-cocoa cco;
|
||||||
|
application/x-java-archive-diff jardiff;
|
||||||
|
application/x-java-jnlp-file jnlp;
|
||||||
|
application/x-makeself run;
|
||||||
|
application/x-perl pl pm;
|
||||||
|
application/x-pilot prc pdb;
|
||||||
|
application/x-rar-compressed rar;
|
||||||
|
application/x-redhat-package-manager rpm;
|
||||||
|
application/x-sea sea;
|
||||||
|
application/x-shockwave-flash swf;
|
||||||
|
application/x-stuffit sit;
|
||||||
|
application/x-tcl tcl tk;
|
||||||
|
application/x-x509-ca-cert der pem crt;
|
||||||
|
application/x-xpinstall xpi;
|
||||||
|
application/zip zip;
|
||||||
|
application/octet-stream deb;
|
||||||
|
application/octet-stream bin exe dll;
|
||||||
|
application/octet-stream dmg;
|
||||||
|
application/octet-stream eot;
|
||||||
|
application/octet-stream iso img;
|
||||||
|
application/octet-stream msi msp msm;
|
||||||
|
audio/mpeg mp3;
|
||||||
|
audio/x-realaudio ra;
|
||||||
|
video/mpeg mpeg mpg;
|
||||||
|
video/quicktime mov;
|
||||||
|
video/x-flv flv;
|
||||||
|
video/x-msvideo avi;
|
||||||
|
video/x-ms-wmv wmv;
|
||||||
|
video/x-ms-asf asx asf;
|
||||||
|
video/x-mng mng;
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
user nobody nobody;
|
||||||
|
worker_processes 5;
|
||||||
|
error_log logs/error.log;
|
||||||
|
pid logs/nginx.pid;
|
||||||
|
worker_rlimit_nofile 8192;
|
||||||
|
|
||||||
|
events {
|
||||||
|
worker_connections 4096;
|
||||||
|
}
|
||||||
|
|
||||||
|
http {
|
||||||
|
include mime.types;
|
||||||
|
include /etc/nginx/proxy.conf;
|
||||||
|
index index.html index.htm;
|
||||||
|
|
||||||
|
default_type application/octet-stream;
|
||||||
|
log_format main '$remote_addr - $remote_user [$time_local] $status '
|
||||||
|
'"$request" $body_bytes_sent "$http_referer" '
|
||||||
|
'"$http_user_agent" "$http_x_forwarded_for"';
|
||||||
|
access_log logs/access.log main;
|
||||||
|
sendfile on;
|
||||||
|
tcp_nopush on;
|
||||||
|
gzip on;
|
||||||
|
server_names_hash_bucket_size 128; # this seems to be required for some vhosts
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
|
||||||
|
location ~ ^/(api|admin)/ {
|
||||||
|
proxy_pass http://back;
|
||||||
|
}
|
||||||
|
|
||||||
|
location / {
|
||||||
|
root /var/www/html;
|
||||||
|
try_files $uri $uri/ /;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ~ ^/\. {
|
||||||
|
deny all;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
proxy_redirect off;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
client_max_body_size 10m;
|
||||||
|
client_body_buffer_size 128k;
|
||||||
|
proxy_connect_timeout 90;
|
||||||
|
proxy_send_timeout 90;
|
||||||
|
proxy_read_timeout 90;
|
||||||
|
proxy_buffers 32 4k;
|
|
@ -0,0 +1,2 @@
|
||||||
|
*
|
||||||
|
!.gitignore
|
|
@ -0,0 +1,46 @@
|
||||||
|
version: '3'
|
||||||
|
services:
|
||||||
|
nginx:
|
||||||
|
image: nginx:alpine
|
||||||
|
volumes:
|
||||||
|
- './conf/nginx:/etc/nginx'
|
||||||
|
- './static:/var/www/html'
|
||||||
|
restart: always
|
||||||
|
ports:
|
||||||
|
- '${APP_PORT:-80}:80'
|
||||||
|
networks:
|
||||||
|
pairent:
|
||||||
|
aliases:
|
||||||
|
- nginx
|
||||||
|
depends_on:
|
||||||
|
- back
|
||||||
|
back:
|
||||||
|
build:
|
||||||
|
context: ./pairent_backend
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
networks:
|
||||||
|
pairent:
|
||||||
|
aliases:
|
||||||
|
- back
|
||||||
|
volumes:
|
||||||
|
- './pairent_backend:/opt/code'
|
||||||
|
environment:
|
||||||
|
DB_PASS: '${DB_PASSWORD}'
|
||||||
|
depends_on:
|
||||||
|
- db
|
||||||
|
db:
|
||||||
|
image: postgres
|
||||||
|
networks:
|
||||||
|
pairent:
|
||||||
|
aliases:
|
||||||
|
- db
|
||||||
|
volumes:
|
||||||
|
- './data/db:/etc/mysql'
|
||||||
|
environment:
|
||||||
|
POSTGRES_PASSWORD: '${DB_PASSWORD}'
|
||||||
|
POSTGRES_DB: 'pairent_db'
|
||||||
|
POSTGRES_USER: 'root'
|
||||||
|
|
||||||
|
networks:
|
||||||
|
pairent:
|
||||||
|
driver: bridge
|
|
@ -0,0 +1,58 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# ----- Start commands -----
|
||||||
|
|
||||||
|
fatal_err() {
|
||||||
|
echo -e "\033[31mFatal error $*\033[0m"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ----- End commands -----
|
||||||
|
|
||||||
|
# ----- Start safeguards -----
|
||||||
|
|
||||||
|
if [[ "$1" != '-a' ]]; then
|
||||||
|
fatal_err
|
||||||
|
echo " This script will potentially break any existing instance of Pairent."
|
||||||
|
echo " To execute this script, re-run it with option -a as first argument."
|
||||||
|
exit -1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! [ -f .env ]; then
|
||||||
|
fatal_err
|
||||||
|
echo -e " No .env file was found."
|
||||||
|
echo -e " Please use the \033[32m.env.example\033[0m to create a dotenv file:"
|
||||||
|
echo -e " 1. \033[34mcp .env.example .env\033[0m"
|
||||||
|
echo -e " 2. Edit your \033[32m.env\033[0m in your favourite editor"
|
||||||
|
exit -1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! [ -x "$(command -v python3)" ]; then
|
||||||
|
fatal_err
|
||||||
|
echo -e " Python is not installed."
|
||||||
|
echo -e " Please install Python 3.11 on your system"
|
||||||
|
exit -1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ----- End safeguards -----
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# ----- Start bootstrap back -----
|
||||||
|
|
||||||
|
echo Generating keys...
|
||||||
|
KEY=$(python -c 'from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())')
|
||||||
|
sed -Ei "s/^DJANGO_KEY=.*$/DJANGO_KEY=/" .env
|
||||||
|
sed -Ei "s/^DJANGO_KEY=.*$/DJANGO_KEY='$KEY'/" .env
|
||||||
|
|
||||||
|
DB_PASS=$(tr -dc A-Za-z0-9 </dev/urandom | head -c $(( $RANDOM % 32 + 32 )))
|
||||||
|
sed -Ei "s/DB_PASSWORD=\w*/DB_PASSWORD=$DB_PASS/" .env
|
||||||
|
|
||||||
|
echo Using these keys:
|
||||||
|
echo -e " \033[32mDjango key: \033[0m$KEY"
|
||||||
|
echo -e " \033[32mDatabase key: \033[0m$DB_PASS"
|
||||||
|
|
||||||
|
ln -s .env pairent_backend/.env
|
||||||
|
|
||||||
|
# ----- End bootstrap back -----
|
||||||
|
|
||||||
|
exit
|
BIN
misc/1_step.png
BIN
misc/1_step.png
Binary file not shown.
Before Width: | Height: | Size: 47 KiB |
BIN
misc/2_step.png
BIN
misc/2_step.png
Binary file not shown.
Before Width: | Height: | Size: 60 KiB |
BIN
misc/3_step.png
BIN
misc/3_step.png
Binary file not shown.
Before Width: | Height: | Size: 87 KiB |
BIN
misc/4_step.png
BIN
misc/4_step.png
Binary file not shown.
Before Width: | Height: | Size: 15 KiB |
|
@ -0,0 +1,19 @@
|
||||||
|
FROM python:alpine
|
||||||
|
|
||||||
|
# mysql mariadb-connector-c-dev
|
||||||
|
|
||||||
|
RUN apk add --no-cache postgresql-libs && \
|
||||||
|
apk add --no-cache --virtual .build-deps gcc musl-dev postgresql-dev && \
|
||||||
|
mkdir /opt/code && \
|
||||||
|
apk --purge del .build-deps
|
||||||
|
|
||||||
|
COPY requirements.txt /opt/code/requirements.txt
|
||||||
|
WORKDIR /opt/code
|
||||||
|
|
||||||
|
RUN pip install -r requirements.txt
|
||||||
|
|
||||||
|
USER nobody
|
||||||
|
|
||||||
|
COPY . /opt/code
|
||||||
|
|
||||||
|
CMD [ "/bin/sh", "docker-entry.sh" ]
|
|
@ -0,0 +1,6 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
PYTHON='/usr/bin/env python3'
|
||||||
|
|
||||||
|
$PYTHON manage.py migrate
|
||||||
|
$PYTHON -m gunicorn -w 3 -b 0.0.0.0:80 pairent_backend.wsgi
|
|
@ -0,0 +1 @@
|
||||||
|
templates/index.html
|
|
@ -1,32 +1,22 @@
|
||||||
|
from rest_framework.request import Request
|
||||||
|
|
||||||
from django.http import HttpResponseBadRequest, HttpResponse, JsonResponse, HttpRequest
|
from django.http import HttpResponseBadRequest, HttpResponse, JsonResponse, HttpRequest
|
||||||
|
|
||||||
def VVSUAuthProxy(req: HttpRequest):
|
from .models import User, AuthToken
|
||||||
proxy = 'https://vvsu.ru/connect' + req.path[len('/api/auth/vvsu'):];
|
|
||||||
|
|
||||||
preq = requests.request(req.method, proxy, headers={
|
|
||||||
'User-Agent': 'OIDC Client / Pairent',
|
|
||||||
'Origin': 'http://pairent.vvsu.ru',
|
|
||||||
'Referer': 'http://pairent.vvsu.ru'
|
|
||||||
});
|
|
||||||
|
|
||||||
resp = HttpResponse(preq.content);
|
import ipware as iplib, time, requests, uuid
|
||||||
resp.headers['Content-Type'] = preq.headers['Content-Type'];
|
ipware = iplib.IpWare();
|
||||||
|
|
||||||
return resp;
|
def client_ip(req: HttpRequest):
|
||||||
|
return ipware.get_client_ip(req.META)[0].exploded;
|
||||||
|
|
||||||
def register(oid, provider_id, name):
|
def register(oid, provider_id, name):
|
||||||
user = User(
|
user = User(
|
||||||
favorites_apartments='',
|
favorites_apartments='',
|
||||||
comparison_apartments='',
|
comparison_apartments='',
|
||||||
name=name,
|
name=name,
|
||||||
# date_of_birth=,
|
|
||||||
about_me='',
|
about_me='',
|
||||||
gender='?',
|
gender='?',
|
||||||
phone='+00000',
|
|
||||||
# email=,
|
|
||||||
# telegram=,
|
|
||||||
# discord=,
|
|
||||||
# city=,
|
|
||||||
role='s',
|
role='s',
|
||||||
photo_provider='VVSU',
|
photo_provider='VVSU',
|
||||||
openid_addr=oid,
|
openid_addr=oid,
|
||||||
|
@ -80,8 +70,36 @@ def verify_auth_token(key, ip):
|
||||||
token.delete();
|
token.delete();
|
||||||
return False;
|
return False;
|
||||||
|
|
||||||
if (token.expires > time.time()):
|
if (token.expires < time.time()):
|
||||||
token.delete();
|
token.delete();
|
||||||
return False;
|
return False;
|
||||||
|
|
||||||
return True;
|
return True;
|
||||||
|
|
||||||
|
def auth_required(func):
|
||||||
|
"""
|
||||||
|
Use authorization for this route.
|
||||||
|
"""
|
||||||
|
def inner(req: HttpRequest, *args, **kwargs):
|
||||||
|
|
||||||
|
if ('Authorization' not in req.headers.keys()):
|
||||||
|
return JsonResponse({'error': 'no auth token'});
|
||||||
|
if (not verify_auth_token(req.headers['Authorization'], client_ip(req))):
|
||||||
|
return JsonResponse({'error': 'auth token invalid or expired'});
|
||||||
|
func(req, *args, **kwargs);
|
||||||
|
|
||||||
|
return inner;
|
||||||
|
|
||||||
|
def rest_auth_required(func):
|
||||||
|
"""
|
||||||
|
Use authorization for this restframework view.
|
||||||
|
"""
|
||||||
|
def inner(self, req: HttpRequest, *args, **kwargs):
|
||||||
|
|
||||||
|
if ('Authorization' not in req.headers.keys()):
|
||||||
|
return JsonResponse({'error': 'no auth token'});
|
||||||
|
if (not verify_auth_token(req.headers['Authorization'], client_ip(req))):
|
||||||
|
return JsonResponse({'error': 'auth token invalid or expired'});
|
||||||
|
func(self, req, *args, **kwargs);
|
||||||
|
|
||||||
|
return inner;
|
|
@ -0,0 +1,34 @@
|
||||||
|
import django.core.validators
|
||||||
|
from django.core.validators import RegexValidator, MaxValueValidator
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
from pairent_app.models import User
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('pairent_app', '0005_user'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='PsychTestAnswers',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('first_question', models.IntegerField(validators=[django.core.validators.MaxValueValidator(5)], verbose_name='Ответ на первый вопрос')),
|
||||||
|
('second_question', models.IntegerField(validators=[django.core.validators.MaxValueValidator(5)], verbose_name='Ответ на второй вопрос')),
|
||||||
|
('third_question', models.IntegerField(validators=[django.core.validators.MaxValueValidator(5)], verbose_name='Ответ на третий вопрос')),
|
||||||
|
('fourth_question', models.IntegerField(validators=[django.core.validators.MaxValueValidator(5)], verbose_name='Ответ на четвертый вопрос')),
|
||||||
|
('fifth_question', models.IntegerField(validators=[django.core.validators.MaxValueValidator(5)], verbose_name='Ответ на пятый вопрос')),
|
||||||
|
('sixth_question', models.IntegerField(validators=[django.core.validators.MaxValueValidator(5)], verbose_name='Ответ на шестой вопрос')),
|
||||||
|
('seventh_question', models.IntegerField(validators=[django.core.validators.MaxValueValidator(5)], verbose_name='Ответ на седьмой вопрос')),
|
||||||
|
('eighth_question', models.IntegerField(validators=[django.core.validators.MaxValueValidator(5)], verbose_name='Ответ на восьмой вопрос')),
|
||||||
|
('nineth_question', models.IntegerField(validators=[django.core.validators.MaxValueValidator(5)], verbose_name='Ответ на девятый вопрос')),
|
||||||
|
('tenth_question', models.IntegerField(validators=[django.core.validators.MaxValueValidator(5)], verbose_name='Ответ на десятый вопрос')),
|
||||||
|
('eleventh_question', models.IntegerField(validators=[django.core.validators.MaxValueValidator(5)], verbose_name='Ответ на одиннадцатый вопрос')),
|
||||||
|
('twelfth_question', models.IntegerField(validators=[django.core.validators.MaxValueValidator(5)], verbose_name='Ответ на двенадцатый вопрос')),
|
||||||
|
# ('users', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='pairent_app.user', verbose_name='Пользователь')),
|
||||||
|
('user', models.BigIntegerField(verbose_name='ID Пользователя')),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
]
|
|
@ -28,7 +28,11 @@ class PsychTestAddResultSerializer(serializers.ModelSerializer):
|
||||||
class PublicUserSerializer(serializers.ModelSerializer):
|
class PublicUserSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = User
|
model = User
|
||||||
exclude = ('favorites_apartments', 'comparison_apartments')
|
exclude = (
|
||||||
|
'favorites_apartments',
|
||||||
|
'comparison_apartments',
|
||||||
|
'openid_id',
|
||||||
|
)
|
||||||
|
|
||||||
# class PsychTestReultsSerializer(serializers.ModelSerializer):
|
# class PsychTestReultsSerializer(serializers.ModelSerializer):
|
||||||
# class Meta:
|
# class Meta:
|
||||||
|
|
|
@ -20,7 +20,7 @@ from .serializer import (ApartamentListSerializer,
|
||||||
|
|
||||||
from .authlib import *
|
from .authlib import *
|
||||||
|
|
||||||
import json, math, random, re, requests, oidc_client, base64, uuid, time, ipware as iplib
|
import json, math, random, re, requests, base64, uuid, time, ipware as iplib
|
||||||
ipware = iplib.IpWare();
|
ipware = iplib.IpWare();
|
||||||
|
|
||||||
class ApartamentViewSet(viewsets.ReadOnlyModelViewSet):
|
class ApartamentViewSet(viewsets.ReadOnlyModelViewSet):
|
||||||
|
@ -106,33 +106,21 @@ class PsychTestAddResultViewSet(viewsets.ViewSet):
|
||||||
|
|
||||||
return Response({'successfully': 'results post'})
|
return Response({'successfully': 'results post'})
|
||||||
|
|
||||||
# TODO: remake
|
|
||||||
# class CompatibleUsersView(viewsets.ViewSet):
|
|
||||||
# def list(self, req: Request):
|
|
||||||
# user_data = dict(req.data);
|
|
||||||
|
|
||||||
# # TODO: Verify auth
|
|
||||||
# vvsu_login = user_data['openid'];
|
|
||||||
|
|
||||||
# # Exclude already viewed users
|
|
||||||
# exclude = [];
|
|
||||||
# if ('exclude' in user_data.keys()):
|
|
||||||
# exclude = user_data['exclude'];
|
|
||||||
|
|
||||||
# try:
|
class CompatibleUsersView(viewsets.ViewSet):
|
||||||
# validate_email(vvsu_login);
|
@rest_auth_required
|
||||||
# except ValidationError:
|
def list(self, req: Request):
|
||||||
# return Request({'error': 'bad login'}, 400);
|
user_data = dict(req.data);
|
||||||
|
|
||||||
# try:
|
|
||||||
# this_user = User.objects.get(openid_addr=vvsu_login);
|
|
||||||
# except User.DoesNotExist:
|
|
||||||
# return Response({'error': 'user not found'}, 404);
|
|
||||||
|
|
||||||
# try:
|
token = AuthToken.objects.get(key=req.headers['authorization']);
|
||||||
# answers_this_user = PsychTestReultsSerializer(PsychTestAnswers.objects.get(user=this_user)).dict;
|
|
||||||
# except PsychTestAnswers.DoesNotExist:
|
this_user = User.objects.get(pk=token.user);
|
||||||
# return Response({'error': 'answers not found'}, 404);
|
|
||||||
|
answers_this_user = None;
|
||||||
|
try:
|
||||||
|
answers_this_user = PsychTestReultsSerializer(PsychTestAnswers.objects.get(user=this_user.id)).dict;
|
||||||
|
except PsychTestAnswers.DoesNotExist:
|
||||||
|
return Response({'error': 'answers not found'}, 404);
|
||||||
|
|
||||||
# users_answers_query = PsychTestReultsSerializer(PsychTestAnswers.objects.all(), many=True).dict
|
# users_answers_query = PsychTestReultsSerializer(PsychTestAnswers.objects.all(), many=True).dict
|
||||||
|
|
||||||
|
@ -146,12 +134,8 @@ class PsychTestAddResultViewSet(viewsets.ViewSet):
|
||||||
# if score / 12 * 100 > 30:
|
# if score / 12 * 100 > 30:
|
||||||
# users.append(UserSerializer(User.objects.get(pk=user_answers[0])).data)
|
# users.append(UserSerializer(User.objects.get(pk=user_answers[0])).data)
|
||||||
|
|
||||||
# # for user in users_query:
|
|
||||||
# # if (abs(user.psych_test_result - score) < 20):
|
|
||||||
# # users.append(PublicUserSerializer(user).data);
|
|
||||||
|
|
||||||
|
|
||||||
# return Response(users);
|
return Response(users);
|
||||||
|
|
||||||
class UserLogin(APIView):
|
class UserLogin(APIView):
|
||||||
|
|
||||||
|
@ -243,3 +227,17 @@ class UserGet(APIView):
|
||||||
|
|
||||||
return JsonResponse(PublicUserSerializer(user).data);
|
return JsonResponse(PublicUserSerializer(user).data);
|
||||||
|
|
||||||
|
|
||||||
|
def VVSUAuthProxy(req: HttpRequest):
|
||||||
|
proxy = 'https://vvsu.ru/connect' + req.path[len('/api/auth/vvsu'):];
|
||||||
|
|
||||||
|
preq = requests.request(req.method, proxy, headers={
|
||||||
|
'User-Agent': 'OIDC Client / Pairent',
|
||||||
|
'Origin': 'http://pairent.vvsu.ru',
|
||||||
|
'Referer': 'http://pairent.vvsu.ru'
|
||||||
|
});
|
||||||
|
|
||||||
|
resp = HttpResponse(preq.content);
|
||||||
|
resp.headers['Content-Type'] = preq.headers['Content-Type'];
|
||||||
|
|
||||||
|
return resp;
|
|
@ -10,6 +10,7 @@ For the full list of settings and their values, see
|
||||||
https://docs.djangoproject.com/en/4.1/ref/settings/
|
https://docs.djangoproject.com/en/4.1/ref/settings/
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from dotenv import dotenv_values
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||||
|
@ -19,11 +20,12 @@ BASE_DIR = Path(__file__).resolve().parent.parent
|
||||||
# Quick-start development settings - unsuitable for production
|
# Quick-start development settings - unsuitable for production
|
||||||
# See https://docs.djangoproject.com/en/4.1/howto/deployment/checklist/
|
# See https://docs.djangoproject.com/en/4.1/howto/deployment/checklist/
|
||||||
|
|
||||||
# SECURITY WARNING: keep the secret key used in production secret!
|
DOTENV = dotenv_values(BASE_DIR / '.env');
|
||||||
SECRET_KEY = 'django-insecure-=(7b+4ka=5jak-mokuh4#-6_14f58#^0kjwqsz7wyon$4i@sel'
|
|
||||||
|
|
||||||
|
# SECURITY WARNING: keep the secret key used in production secret!
|
||||||
|
SECRET_KEY = DOTENV['DJANGO_KEY']
|
||||||
# SECURITY WARNING: don't run with debug turned on in production!
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
DEBUG = True
|
DEBUG = True#DOTENV['APP_DEBUG'] == 'true'
|
||||||
|
|
||||||
ALLOWED_HOSTS = ['*']
|
ALLOWED_HOSTS = ['*']
|
||||||
|
|
||||||
|
@ -72,7 +74,9 @@ ROOT_URLCONF = 'pairent_backend.urls'
|
||||||
TEMPLATES = [
|
TEMPLATES = [
|
||||||
{
|
{
|
||||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||||
'DIRS': [],
|
'DIRS': [
|
||||||
|
'templates/'
|
||||||
|
],
|
||||||
'APP_DIRS': True,
|
'APP_DIRS': True,
|
||||||
'OPTIONS': {
|
'OPTIONS': {
|
||||||
'context_processors': [
|
'context_processors': [
|
||||||
|
@ -93,15 +97,16 @@ WSGI_APPLICATION = 'pairent_backend.wsgi.application'
|
||||||
|
|
||||||
DATABASES = {
|
DATABASES = {
|
||||||
'default': {
|
'default': {
|
||||||
'ENGINE': 'django.db.backends.mysql',
|
'ENGINE': 'django.db.backends.postgresql',
|
||||||
'NAME': 'pairent_db',
|
'NAME': 'pairent_db',
|
||||||
'USER': 'django',
|
'USER': DOTENV['DB_USER'],
|
||||||
'PASSWORD': 'Budnya1924$',
|
'PASSWORD': DOTENV['DB_PASSWORD'],
|
||||||
'HOST': '178.20.44.123',
|
'HOST': DOTENV['DB_HOST'],
|
||||||
'PORT': '3306',
|
'PORT': DOTENV['DB_PORT'],
|
||||||
'OPTIONS': {
|
# 'OPTIONS': {
|
||||||
'init_command': "SET sql_mode='STRICT_TRANS_TABLES'"
|
# 'init_command': "SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci",
|
||||||
}
|
# 'charset': 'utf8mb4'
|
||||||
|
# }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,10 +13,16 @@ Including another URLconf
|
||||||
1. Import the include() function: from django.urls import include, path
|
1. Import the include() function: from django.urls import include, path
|
||||||
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||||
"""
|
"""
|
||||||
|
from django.template import loader
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.urls import path, include
|
from django.urls import path, include
|
||||||
|
from django.http import HttpResponse
|
||||||
|
|
||||||
|
def root_url(req):
|
||||||
|
return HttpResponse(loader.get_template('index.html').render())
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('admin/', admin.site.urls),
|
path('admin/', admin.site.urls),
|
||||||
path('api/', include('pairent_app.urls')),
|
path('api/', include('pairent_app.urls')),
|
||||||
|
path('', root_url)
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
mysqlclient
|
psycopg2-binary
|
||||||
django
|
django
|
||||||
djangorestframework
|
djangorestframework
|
||||||
django-cors-headers
|
django-cors-headers
|
||||||
Pillow
|
Pillow
|
||||||
requests
|
requests
|
||||||
python-ipware
|
python-ipware
|
||||||
|
python-dotenv
|
||||||
|
gunicorn
|
||||||
|
factory-boy
|
|
@ -3,6 +3,7 @@
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
"homepage": "/static/dist",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^1.4.0",
|
"axios": "^1.4.0",
|
||||||
"bootstrap": "^5.2.3",
|
"bootstrap": "^5.2.3",
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 45 KiB |
Binary file not shown.
After Width: | Height: | Size: 39 KiB |
|
@ -0,0 +1,30 @@
|
||||||
|
import { IAPIObject } from "./IAPIObject";
|
||||||
|
|
||||||
|
class APIToken extends IAPIObject {
|
||||||
|
|
||||||
|
static storage_key = 'pairent_api_key';
|
||||||
|
|
||||||
|
constructor(data) {
|
||||||
|
super();
|
||||||
|
this.user = data.user;
|
||||||
|
this.key = data.key;
|
||||||
|
this.expires = data.expires;
|
||||||
|
this.ip = data.ip;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @type {number} */
|
||||||
|
user;
|
||||||
|
|
||||||
|
/** @type {string} */
|
||||||
|
key;
|
||||||
|
|
||||||
|
/** A Unix timestamp (when the token will expire)
|
||||||
|
* @type {number}
|
||||||
|
*/
|
||||||
|
expires;
|
||||||
|
|
||||||
|
/** @type {string} */
|
||||||
|
ip;
|
||||||
|
}
|
||||||
|
|
||||||
|
export { APIToken };
|
|
@ -0,0 +1,72 @@
|
||||||
|
import axios, { Axios } from "axios";
|
||||||
|
|
||||||
|
import { User } from "./User";
|
||||||
|
import { APIToken } from './APIToken';
|
||||||
|
|
||||||
|
class ClientCreateOptions {
|
||||||
|
/** Key used to access APIs, if you don't have user data
|
||||||
|
* @type {APIToken?}
|
||||||
|
*/
|
||||||
|
key;
|
||||||
|
|
||||||
|
/** Current user's data
|
||||||
|
* @type {User?}
|
||||||
|
*/
|
||||||
|
user;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An API client which is basically a User but with
|
||||||
|
* API token and with access to private fields.
|
||||||
|
*/
|
||||||
|
class Client extends User {
|
||||||
|
/** @type {APIToken} */
|
||||||
|
key;
|
||||||
|
|
||||||
|
/** @type {string} */
|
||||||
|
openid_id;
|
||||||
|
|
||||||
|
/** @type {string} */
|
||||||
|
favorites_apartments;
|
||||||
|
|
||||||
|
/** @type {string} */
|
||||||
|
comparison_apartments;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {ClientCreateOptions} options
|
||||||
|
*/
|
||||||
|
constructor(options) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
if (options.key === undefined & options.user === undefined) {
|
||||||
|
throw new Error('Either key or user is required.');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.key = options.key;
|
||||||
|
if (options.user) {
|
||||||
|
for (const key in options.user) {
|
||||||
|
this[key] = options.user[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {string} uri
|
||||||
|
* @param {string} method
|
||||||
|
* @param {import('axios').AxiosRequestConfig<any>} options
|
||||||
|
*/
|
||||||
|
fetchData(url, method, options) {
|
||||||
|
axios.request({
|
||||||
|
url,
|
||||||
|
method,
|
||||||
|
headers: {
|
||||||
|
...(options.headers ? options.headers : {}),
|
||||||
|
'Authorization': this.key.key
|
||||||
|
},
|
||||||
|
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
import { IAPIObject } from "./IAPIObject";
|
||||||
|
import { Client } from './Client';
|
||||||
|
|
||||||
|
class Tinder extends IAPIObject {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {Client} client Client to use to create requests
|
||||||
|
*/
|
||||||
|
constructor(client) {
|
||||||
|
super();
|
||||||
|
this.client = client;
|
||||||
|
this.viewed = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
getCompatible() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,6 +2,7 @@ import axios from 'axios';
|
||||||
import constants from '../constants';
|
import constants from '../constants';
|
||||||
|
|
||||||
import { IAPIObject } from './IAPIObject';
|
import { IAPIObject } from './IAPIObject';
|
||||||
|
import { APIToken } from './APIToken';
|
||||||
|
|
||||||
const { API_ROOT, api_path } = constants;
|
const { API_ROOT, api_path } = constants;
|
||||||
|
|
||||||
|
@ -16,33 +17,6 @@ class UserLoginResponse {
|
||||||
id;
|
id;
|
||||||
}
|
}
|
||||||
|
|
||||||
class APIToken extends IAPIObject {
|
|
||||||
|
|
||||||
static storage_key = 'pairent_api_key';
|
|
||||||
|
|
||||||
constructor(data) {
|
|
||||||
super();
|
|
||||||
this.user = data.user;
|
|
||||||
this.key = data.key;
|
|
||||||
this.expires = data.expires;
|
|
||||||
this.ip = data.ip;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @type {number} */
|
|
||||||
user;
|
|
||||||
|
|
||||||
/** @type {string} */
|
|
||||||
key;
|
|
||||||
|
|
||||||
/** A Unix timestamp (when the token will expire)
|
|
||||||
* @type {number}
|
|
||||||
*/
|
|
||||||
expires;
|
|
||||||
|
|
||||||
/** @type {string} */
|
|
||||||
ip;
|
|
||||||
}
|
|
||||||
|
|
||||||
class User extends IAPIObject {
|
class User extends IAPIObject {
|
||||||
|
|
||||||
isLoggedIn() {
|
isLoggedIn() {
|
||||||
|
|
|
@ -64,7 +64,7 @@ const Header = function () {
|
||||||
</a>
|
</a>
|
||||||
<VerticalLine />
|
<VerticalLine />
|
||||||
<Link to='/'>
|
<Link to='/'>
|
||||||
<PairentLogo>Pairent</PairentLogo>
|
<PairentLogo>Точка.</PairentLogo>
|
||||||
</Link>
|
</Link>
|
||||||
<Link to="/">
|
<Link to="/">
|
||||||
<LocationButton>
|
<LocationButton>
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
import "./styles/PsychTestHeader.css";
|
|
||||||
|
|
||||||
const PsychTestHeader = () => {
|
|
||||||
return (
|
|
||||||
<div className="header">
|
|
||||||
<button type="button" className="header__btn">
|
|
||||||
Вернуться назад
|
|
||||||
</button>
|
|
||||||
<h1 className="header__title">Тест на совместимость</h1>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default PsychTestHeader;
|
|
|
@ -1,17 +1,90 @@
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import "./styles/PsychTestQuestion.css";
|
|
||||||
|
import { styled } from "styled-components";
|
||||||
|
|
||||||
|
const Question = styled.div`
|
||||||
|
padding-top: 4px;
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
margin: 0 auto;
|
||||||
|
width: 799px;
|
||||||
|
min-height: 151px;
|
||||||
|
border-bottom: 1px solid #cccccc;
|
||||||
|
margin-bottom: 34.5px;
|
||||||
|
& h2 {
|
||||||
|
color: #cccccc;
|
||||||
|
position: absolute;
|
||||||
|
top: 0px;
|
||||||
|
left: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
border: 2px solid #cccccc;
|
||||||
|
border-radius: 100px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const QuestionTitle = styled.p`
|
||||||
|
margin-bottom: 80px;
|
||||||
|
text-align: center;
|
||||||
|
width: 638px;
|
||||||
|
font-size: 16px;
|
||||||
|
margin-right: 0;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Answers = styled.div`
|
||||||
|
display: flex;
|
||||||
|
margin-bottom: 29.5px;
|
||||||
|
& p, & span {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
& input, & p {
|
||||||
|
margin-right: 32px;
|
||||||
|
transform: translateY(6px)
|
||||||
|
}
|
||||||
|
& label {
|
||||||
|
transform: translateY(6px)
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Answer = styled.label`
|
||||||
|
& span {
|
||||||
|
position: absolute;
|
||||||
|
top: -17px;
|
||||||
|
left: 3.5px
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Id = styled.div`
|
||||||
|
color: #cccccc;
|
||||||
|
position: absolute;
|
||||||
|
top: 0px;
|
||||||
|
left: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
border: 1px solid #b2b4b2;
|
||||||
|
border-radius: 100px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
|
||||||
const PsychTestQuestion = (props) => {
|
const PsychTestQuestion = (props) => {
|
||||||
const answerChangeHandler = (event) => {
|
const answerChangeHandler = (event) => {
|
||||||
props.onChangeAnswer(event.target.value, event.target.name);
|
props.onChangeAnswer(event.target.value, event.target.name);
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<div className="question">
|
<Question>
|
||||||
<div className="question__account">{+props.account + 1}</div>
|
<Id>{+props.account + 1}</Id>
|
||||||
<p className="question__title">{props.name}</p>
|
<QuestionTitle>{props.name}</QuestionTitle>
|
||||||
<div className="question__answers">
|
<Answers>
|
||||||
<p>Не важно</p>
|
<p>Не важно</p>
|
||||||
<label className="question__answer">
|
<Answer>
|
||||||
<input
|
<input
|
||||||
name={props.account}
|
name={props.account}
|
||||||
type="radio"
|
type="radio"
|
||||||
|
@ -19,8 +92,8 @@ const PsychTestQuestion = (props) => {
|
||||||
onChange={answerChangeHandler}
|
onChange={answerChangeHandler}
|
||||||
/>
|
/>
|
||||||
<span>1</span>
|
<span>1</span>
|
||||||
</label>
|
</Answer>
|
||||||
<label className="question__answer">
|
<Answer>
|
||||||
<input
|
<input
|
||||||
name={props.account}
|
name={props.account}
|
||||||
type="radio"
|
type="radio"
|
||||||
|
@ -28,8 +101,8 @@ const PsychTestQuestion = (props) => {
|
||||||
onChange={answerChangeHandler}
|
onChange={answerChangeHandler}
|
||||||
/>
|
/>
|
||||||
<span>2</span>
|
<span>2</span>
|
||||||
</label>
|
</Answer>
|
||||||
<label className="question__answer">
|
<Answer>
|
||||||
<input
|
<input
|
||||||
name={props.account}
|
name={props.account}
|
||||||
type="radio"
|
type="radio"
|
||||||
|
@ -37,8 +110,8 @@ const PsychTestQuestion = (props) => {
|
||||||
onChange={answerChangeHandler}
|
onChange={answerChangeHandler}
|
||||||
/>
|
/>
|
||||||
<span>3</span>
|
<span>3</span>
|
||||||
</label>
|
</Answer>
|
||||||
<label className="question__answer">
|
<Answer>
|
||||||
<input
|
<input
|
||||||
name={props.account}
|
name={props.account}
|
||||||
type="radio"
|
type="radio"
|
||||||
|
@ -46,8 +119,8 @@ const PsychTestQuestion = (props) => {
|
||||||
onChange={answerChangeHandler}
|
onChange={answerChangeHandler}
|
||||||
/>
|
/>
|
||||||
<span>4</span>
|
<span>4</span>
|
||||||
</label>
|
</Answer>
|
||||||
<label className="question__answer">
|
<Answer>
|
||||||
<input
|
<input
|
||||||
name={props.account}
|
name={props.account}
|
||||||
type="radio"
|
type="radio"
|
||||||
|
@ -55,10 +128,10 @@ const PsychTestQuestion = (props) => {
|
||||||
onChange={answerChangeHandler}
|
onChange={answerChangeHandler}
|
||||||
/>
|
/>
|
||||||
<span>5</span>
|
<span>5</span>
|
||||||
</label>
|
</Answer>
|
||||||
<p>Очень важно</p>
|
<p>Очень важно</p>
|
||||||
</div>
|
</Answers>
|
||||||
</div>
|
</Question>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -7,8 +7,8 @@ const api_path = path => API_ROOT + path;
|
||||||
* Api root path
|
* Api root path
|
||||||
* @type {string}
|
* @type {string}
|
||||||
*/
|
*/
|
||||||
const API_ROOT = window.location.protocol + '//127.0.0.1:8000';
|
const API_ROOT = window.location.protocol + '//' + window.location.host;
|
||||||
// ДЛЯ ПРОДА ПОСТАВИТЬ ЭТО: '//pairent.vvsu.ru'
|
// ДЛЯ ПРОДА ПОСТАВИТЬ ЭТО: '//' + window.location.host
|
||||||
|
|
||||||
/** OpenID Connect Client Config
|
/** OpenID Connect Client Config
|
||||||
* @type {import('oidc-client-ts').OidcClientSettings}
|
* @type {import('oidc-client-ts').OidcClientSettings}
|
||||||
|
|
|
@ -5,13 +5,15 @@ import { Filters } from "../../API/Filters";
|
||||||
|
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
// swiper customization
|
|
||||||
import './swiper.css';
|
|
||||||
import ApartamentService from "../../API/ApartamentService";
|
import ApartamentService from "../../API/ApartamentService";
|
||||||
import { HashLoader } from 'react-spinners';
|
import { HashLoader } from 'react-spinners';
|
||||||
|
|
||||||
import AwesomeSlider from 'react-awesome-slider';
|
import AwesomeSlider from 'react-awesome-slider';
|
||||||
|
import withAutoplay from 'react-awesome-slider/dist/autoplay';
|
||||||
import 'react-awesome-slider/dist/styles.css';
|
import 'react-awesome-slider/dist/styles.css';
|
||||||
|
// ras customization
|
||||||
|
import './ras.css';
|
||||||
|
const AutoplaySlider = withAutoplay(AwesomeSlider);
|
||||||
|
|
||||||
import constants from "../../constants";
|
import constants from "../../constants";
|
||||||
|
|
||||||
|
@ -376,10 +378,10 @@ export default class IndexPage extends React.Component {
|
||||||
return (
|
return (
|
||||||
<IndexPageRoot>
|
<IndexPageRoot>
|
||||||
<SliderContainer>
|
<SliderContainer>
|
||||||
<AwesomeSlider bullets={false}>
|
<AutoplaySlider play cancelOnInteraction={false} interval={8000} bullets={false}>
|
||||||
<div data-src='/images/house-s.jpg' />
|
<a href='https://vvsu.ithub.ru/dod' data-src='/images/OpenDoorDay.jpg' />
|
||||||
<div data-src='/images/house-s-2.jpg' />
|
<div data-src='/images/Business.jpg' />
|
||||||
</AwesomeSlider>
|
</AutoplaySlider>
|
||||||
</SliderContainer>
|
</SliderContainer>
|
||||||
|
|
||||||
<Title>
|
<Title>
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
.awssld {
|
||||||
|
--loader-bar-color: #0077aa !important;
|
||||||
|
/* --loader-bar-height: 8px !important; */
|
||||||
|
}
|
|
@ -1,12 +0,0 @@
|
||||||
:root {
|
|
||||||
--swiper-scrollbar-drag-bg-color: #e1e3e1a0;
|
|
||||||
--swiper-scrollbar-size: 5px;
|
|
||||||
--swiper-scrollbar-bottom: 0px;
|
|
||||||
--swiper-scrollbar-border-radius: 5px;
|
|
||||||
|
|
||||||
--swiper-navigation-color: white
|
|
||||||
}
|
|
||||||
|
|
||||||
.swiper {
|
|
||||||
box-shadow: 0 2px 12px #00000030;
|
|
||||||
}
|
|
|
@ -1,9 +1,11 @@
|
||||||
import PsychTestQuestion from "../PsychTestQuestion";
|
import PsychTestQuestion from "../../../components/PsychTestQuestion";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import PsychTestAddResult from "../../API/PsychTestAddResult";
|
import PsychTestAddResult from "../../../API/PsychTestAddResult";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import "./styles/PsychTestForm.css";
|
import "./styles/PsychTestForm.css";
|
||||||
|
|
||||||
|
import { styled } from "styled-components";
|
||||||
|
|
||||||
const PsychTestForm = () => {
|
const PsychTestForm = () => {
|
||||||
const [isValid, setIsValid] = useState(true);
|
const [isValid, setIsValid] = useState(true);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
|
@ -0,0 +1,37 @@
|
||||||
|
import { styled } from "styled-components";
|
||||||
|
import "./styles/PsychTestHeader.css";
|
||||||
|
|
||||||
|
const Header = styled.div`
|
||||||
|
display: flex;
|
||||||
|
padding-left: 40px;
|
||||||
|
padding-top: 14px;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 55px;
|
||||||
|
|
||||||
|
& h1 {
|
||||||
|
font-size: 20pt;
|
||||||
|
}
|
||||||
|
& button {
|
||||||
|
color: #bababa;
|
||||||
|
font-size: 15px;
|
||||||
|
width: 180px;
|
||||||
|
height: 36px;
|
||||||
|
background-color: white;
|
||||||
|
border: 1px solid #cccccc;
|
||||||
|
border-radius: 12px;
|
||||||
|
margin-right: 13px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const PsychTestHeader = () => {
|
||||||
|
return (
|
||||||
|
<Header>
|
||||||
|
<button type="button" onClick={window.history.back()}>
|
||||||
|
Вернуться назад
|
||||||
|
</button>
|
||||||
|
<h1>Тест на совместимость</h1>
|
||||||
|
</Header>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PsychTestHeader;
|
|
@ -1,5 +1,5 @@
|
||||||
import PsychTestForm from "../../components/PsychTestForm";
|
import PsychTestForm from "./PsychTestForm";
|
||||||
import PsychTestHeader from "../../components/PsychTestHeader";
|
import PsychTestHeader from "./PsychTestHeader";
|
||||||
|
|
||||||
const PsychTest = () => {
|
const PsychTest = () => {
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -2,6 +2,7 @@ import React from "react";
|
||||||
import styled, { keyframes } from 'styled-components';
|
import styled, { keyframes } from 'styled-components';
|
||||||
import SVGIcon from "../../components/UI/Icon/SVGIcon";
|
import SVGIcon from "../../components/UI/Icon/SVGIcon";
|
||||||
import Pagination from "../../components/UI/Pagination";
|
import Pagination from "../../components/UI/Pagination";
|
||||||
|
import { HashLoader } from 'react-spinners';
|
||||||
|
|
||||||
const ChatSVG = () => {
|
const ChatSVG = () => {
|
||||||
return <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16"><path d="M8 15c4.418 0 8-3.134 8-7s-3.582-7-8-7-8 3.134-8 7c0 1.76.743 3.37 1.97 4.6-.097 1.016-.417 2.13-.771 2.966-.079.186.074.394.273.362 2.256-.37 3.597-.938 4.18-1.234A9.06 9.06 0 0 0 8 15z"/></svg>;
|
return <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16"><path d="M8 15c4.418 0 8-3.134 8-7s-3.582-7-8-7-8 3.134-8 7c0 1.76.743 3.37 1.97 4.6-.097 1.016-.417 2.13-.771 2.966-.079.186.074.394.273.362 2.256-.37 3.597-.938 4.18-1.234A9.06 9.06 0 0 0 8 15z"/></svg>;
|
||||||
|
@ -101,7 +102,6 @@ const UserList = styled.div`
|
||||||
margin: 32px auto;
|
margin: 32px auto;
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
display: block;
|
display: block;
|
||||||
width: fit-content;
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|
||||||
|
@ -214,25 +214,32 @@ const RightBadge = styled(LeftBadge)`
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
class Users extends React.Component {
|
const LoadingText = styled.h2`
|
||||||
|
text-align: center;
|
||||||
|
line-height: 3.5em;
|
||||||
|
margin: 0;
|
||||||
|
padding: 100px 0;
|
||||||
|
|
||||||
|
& span {
|
||||||
|
display: inline;
|
||||||
|
margin: 0px auto
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
class UserDisplay extends React.Component {
|
||||||
|
|
||||||
|
/** @type {{ value: import('../../API/User').User[] }} */
|
||||||
|
props;
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<UserList>
|
<div>123
|
||||||
|
|
||||||
<h2 style={{textAlign: 'center', lineHeight: '11pt', marginBottom: 32}}>
|
|
||||||
Выбери соседа
|
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
<span style={{ fontSize: '11pt', fontWeight: 500 }}>
|
|
||||||
Не забывай, с этим человеком<br/>
|
|
||||||
придется жить бок-о-бок!
|
|
||||||
</span>
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
{
|
{
|
||||||
[...Array(11)].map((_, i) => {
|
this.props.value.map(x => {
|
||||||
if (i == 5) return <br/>;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<UserCard>
|
<UserCard>
|
||||||
<SVGIcon src='/images/icons/user.svg' width='100' height='100' />
|
<SVGIcon src='/images/icons/user.svg' width='100' height='100' />
|
||||||
|
@ -254,9 +261,50 @@ class Users extends React.Component {
|
||||||
</ContactButton>
|
</ContactButton>
|
||||||
|
|
||||||
</UserCard>
|
</UserCard>
|
||||||
);
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Users extends React.Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
data: [{}],
|
||||||
|
loading: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<UserList>
|
||||||
|
|
||||||
|
<h2 style={{textAlign: 'center', lineHeight: '11pt', marginBottom: 32}}>
|
||||||
|
Выбери соседа
|
||||||
|
<br/>
|
||||||
|
<br/>
|
||||||
|
<span style={{ fontSize: '11pt', fontWeight: 500 }}>
|
||||||
|
Не забывай, с этим человеком<br/>
|
||||||
|
придется жить бок-о-бок!
|
||||||
|
</span>
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
{
|
||||||
|
this.state.loading ?
|
||||||
|
<UserDisplay value={this.state.data} /> :
|
||||||
|
<LoadingText>
|
||||||
|
Пожалуйста подождите, идет загрузка данных<br/>
|
||||||
|
<HashLoader color='#0077aa' />
|
||||||
|
</LoadingText>
|
||||||
|
}
|
||||||
|
|
||||||
</UserList>
|
</UserList>
|
||||||
)
|
)
|
||||||
|
|
|
@ -4,6 +4,5 @@ import react from '@vitejs/plugin-react'
|
||||||
export default {
|
export default {
|
||||||
plugins: [
|
plugins: [
|
||||||
react()
|
react()
|
||||||
],
|
],
|
||||||
|
|
||||||
}
|
}
|
|
@ -0,0 +1,2 @@
|
||||||
|
*
|
||||||
|
!.gitignore
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,20 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<title>Pairent</title>
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||||
|
<link
|
||||||
|
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap"
|
||||||
|
rel="stylesheet"
|
||||||
|
/>
|
||||||
|
<script type="module" crossorigin src="/assets/index-22e3f5a9.js"></script>
|
||||||
|
<link rel="stylesheet" href="/assets/index-b130f2c5.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
|
<div id="root"></div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
Reference in New Issue