diff --git a/.gitignore b/.gitignore index 02d4c7e..902a5d5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,16 @@ # ---> Rust # Generated by Cargo # will have compiled files and executables -backend/debug/ -backend/target/ -backend/.env +frontend/debug/ +frontend/target/ +frontend/.env +frontend/dist # These are backup files generated by rustfmt **/*.rs.bk # MSVC Windows builds of rustc generate these, which store debugging information -backend/*.pdb +frontend/*.pdb + +.vscode +.vscode/* \ No newline at end of file diff --git a/backend/src/models/car_station.rs b/backend/src/models/car_station.rs deleted file mode 100644 index 1e208a2..0000000 --- a/backend/src/models/car_station.rs +++ /dev/null @@ -1,12 +0,0 @@ -use serde::{Serialize, Deserialize}; - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct CarStation { - pub id: i32, - pub address: String -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct BindingCarStation { - pub address: String -} \ No newline at end of file diff --git a/backend/src/models/client.rs b/backend/src/models/client.rs deleted file mode 100644 index 0e0f7a7..0000000 --- a/backend/src/models/client.rs +++ /dev/null @@ -1,18 +0,0 @@ -use serde::{Serialize, Deserialize}; - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Client { - pub id: i32, - pub name: String, - pub surname: String, - pub middlename: Option, - pub phone: String -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct BindingClient { - pub name: String, - pub surname: String, - pub middlename: Option, - pub phone: String -} \ No newline at end of file diff --git a/backend/src/storages/mod.rs b/backend/src/storages/mod.rs deleted file mode 100644 index 4acaacf..0000000 --- a/backend/src/storages/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod traits; -pub mod postgres; \ No newline at end of file diff --git a/frontend/app.py b/frontend/app.py new file mode 100644 index 0000000..72e406e --- /dev/null +++ b/frontend/app.py @@ -0,0 +1,423 @@ +from flask import Flask, request, render_template, session, redirect, url_for +import requests + +app = Flask(__name__) +app.secret_key = 'Kill me already' + + +@app.route('/login', methods=['GET', 'POST']) +def login(): + if request.method == 'POST': + if not request.form.get('username') or not request.form.get('password'): + return render_template('login.html', title='Авторизация', errors='Необходимо заполнить все поля', + logged_in=False) + + response = requests.post("http://localhost:8080/api/login", + json={ + 'username': request.form.get('username'), + 'password': request.form.get('password') + }) + + if response.status_code != 200: + return render_template('login.html', title='Авторизация', errors='Неверный логин или пароль', + logged_in=False) + + session['token'] = response.json()['token'] + session['administrator_id'] = response.json()['administrator_id'] + session['car_station_id'] = response.json()['car_station_id'] + + return redirect(url_for('rents')) + + return render_template('login.html', title='Авторизация', logged_in=False) + + +@app.route('/logout') +def logout(): + session.pop('token') + session.pop('administrator_id') + session.pop('car_station_id') + + requests.post('http://localhost:8080/api/logout') + + return redirect(url_for('login')) + + +@app.route('/rents', methods=['GET', 'POST']) +def rents(): + if not session['token']: + return redirect(url_for('login')) + + rents_data = requests.get('http://localhost:8080/api/rents/', headers={'Authorization': session['token']}).json() + cars = list( + filter(lambda c: c['car_station_id'] == session['car_station_id'], + requests.get('http://localhost:8080/api/cars/', headers={'Authorization': session['token']}).json() + ) + ) + clients = requests.get('http://localhost:8080/api/clients/', headers={'Authorization': session['token']}).json() + + for rent in rents_data: + from datetime import datetime + + date_format = '%Y-%m-%dT%H:%M:%S.%f' + + parsed_date = datetime.strptime(rent['start_time'], date_format) + rent['start_time'] = parsed_date.strftime('%Y-%m-%d %H:%M:%S') + + for car in cars: + if car['id'] == rent['car_id']: + rent['car'] = car + if not rent['time_amount']: + cars.remove(car) + break + for client in clients: + if client['id'] == rent['client_id']: + rent['client'] = client + break + + if request.method == 'POST': + if not request.form.get('id') and (not request.form.get('client_id') or not request.form.get('car_id')): + return render_template( + 'rents.html', + errors='Необходимо заполнить все поля', + title='Аренды', + rents=rents_data, + cars=cars, + clients=clients, + logged_in=True, + selected_client=int(request.args.get('client')) if request.args.get('client') else None, + selected_car=int(request.args.get('car')) if request.args.get('car') else None + ) + + response = None + if request.form.get('id'): + response = requests.get(f'http://localhost:8080/api/rents/{request.form.get("id")}/end', + headers={'Authorization': session['token']}) + else: + response = requests.post('http://localhost:8080/api/rents/', headers={'Authorization': session['token']}, json={ + "client_id": int(request.form.get('client_id')), + "car_id": int(request.form.get('car_id')) + }) + + if response.status_code != 200: + return render_template( + 'rents.html', + title='Аренды', + errors=response.json(), + rents=rents_data, + cars=cars, + clients=clients, + logged_in=True, + selected_client=int(request.args.get('client')) if request.args.get('client') else None, + selected_car=int(request.args.get('car')) if request.args.get('car') else None + ) + + if request.form.get('id'): + return redirect(url_for('rent', id=request.form.get('id'))) + + rents_data = requests.get('http://localhost:8080/api/rents/', + headers={'Authorization': session['token']}).json() + cars = list( + filter(lambda c: c['car_station_id'] == session['car_station_id'], + requests.get('http://localhost:8080/api/cars/', headers={'Authorization': session['token']}).json() + ) + ) + clients = requests.get('http://localhost:8080/api/clients/', headers={'Authorization': session['token']}).json() + + for rent in rents_data: + from datetime import datetime + + date_format = '%Y-%m-%dT%H:%M:%S.%f' + + parsed_date = datetime.strptime(rent['start_time'], date_format) + rent['start_time'] = parsed_date.strftime('%Y-%m-%d %H:%M:%S') + + for car in cars: + if car['id'] == rent['car_id']: + rent['car'] = car + if not rent['time_amount']: + cars.remove(car) + break + for client in clients: + if client['id'] == rent['client_id']: + rent['client'] = client + break + + return render_template( + 'rents.html', + title='Аренды', + rents=rents_data, + cars=cars, + clients=clients, + logged_in=True, + selected_client=int(request.args.get('client')) if request.args.get('client') else None, + selected_car=int(request.args.get('car')) if request.args.get('car') else None + ) + + +@app.route('/rents/', methods=['GET', 'POST']) +def rent(id): + if not session['token']: + return redirect(url_for('login')) + + rent = requests.get(f'http://localhost:8080/api/rents/{id}', headers={'Authorization': session['token']}).json() + client = requests.get(f'http://localhost:8080/api/clients/{rent["client_id"]}', headers={'Authorization': session['token']}).json() + car = requests.get(f'http://localhost:8080/api/cars/{rent["car_id"]}', headers={'Authorization': session['token']}).json() + + rent['client'] = client + rent['car'] = car + + from datetime import datetime + + date_format = '%Y-%m-%dT%H:%M:%S.%f' + + parsed_date = datetime.strptime(rent['start_time'], date_format) + rent['start_time'] = parsed_date.strftime('%Y-%m-%d %H:%M:%S') + + import math + return render_template('rent.html', title='Просмотр аренды', rent=rent, logged_in=True, float=float, int=int, math=math) + + +@app.route('/clients', defaults={'id': 0}, methods=['GET', 'POST']) +@app.route('/clients/', methods=['GET', 'POST']) +def clients(id): + if not session['token']: + return redirect(url_for('login')) + + clients = requests.get('http://localhost:8080/api/clients/', headers={'Authorization': session['token']}).json() + + if request.method == 'POST': + if id != 0: + if not request.form.get('name') or not request.form.get('surname') or not request.form.get('middlename') or not request.form.get('phone'): + return render_template( + 'clients.html', + title='Изменение клиента', + errors='Должны быть заполнены все поля!', + current_client=list(filter(lambda c: c['id'] == id, clients))[0], + clients=clients, + logged_in=True + ) + + response = requests.patch(f'http://localhost:8080/api/clients/{id}', headers={'Authorization': session['token']}, + json={ + 'name': request.form.get('name'), + 'surname': request.form.get('surname'), + 'middlename': request.form.get('middlename'), + 'phone': request.form.get('phone') + }) + + if response.status_code != 200: + return render_template( + 'clients.html', + title='Изменение клиента', + errors=response.json(), + clients=clients, + current_client=list(filter(lambda c: c['id'] == id, clients))[0], + logged_in=True + ) + + return redirect(url_for('clients')) + else: + if not request.form.get('name') or not request.form.get('surname') or not request.form.get('middlename') or not request.form.get('phone'): + return render_template('clients.html', title='Клиенты', errors='Должны быть заполнены все поля!', + clients=clients, logged_in=True) + + response = requests.post('http://localhost:8080/api/clients/', headers={'Authorization': session['token']}, + json={ + 'name': request.form.get('name'), + 'surname': request.form.get('surname'), + 'middlename': request.form.get('middlename'), + 'phone': request.form.get('phone') + }) + + if response.status_code != 200: + return render_template('clients.html', title='Клиенты', errors=response.json(), clients=clients, + logged_in=True) + + clients = requests.get('http://localhost:8080/api/clients/', headers={'Authorization': session['token']}).json() + + if id != 0: + return render_template('clients.html', title='Изменение клиента', clients=clients, logged_in=True, current_client=list(filter(lambda c: c['id'] == id, clients))[0]) + + return render_template('clients.html', title='Клиенты', clients=clients, logged_in=True) + + +@app.route('/owners', defaults={'id': 0}, methods=['GET', 'POST']) +@app.route('/owners/', methods=['GET', 'POST']) +def owners(id): + if not session['token']: + return redirect(url_for('login')) + + owners = requests.get('http://localhost:8080/api/owners/', headers={'Authorization': session['token']}).json() + cars = list( + filter(lambda c: c['car_station_id'] == session['car_station_id'], + requests.get('http://localhost:8080/api/cars/', headers={'Authorization': session['token']}).json() + ) + ) + + if request.method == 'POST': + if id != 0: + if not request.form.get('name') or not request.form.get('surname') or not request.form.get('middlename') or not request.form.get('phone'): + return render_template( + 'owners.html', + title='Изменение владельца', + errors='Должны быть заполнены все поля!', + current_owner=list(filter(lambda c: c['id'] == id, owners))[0], + owners=owners, + cars=cars, + logged_in=True + ) + + response = requests.patch(f'http://localhost:8080/api/owners/{id}', headers={'Authorization': session['token']}, + json={ + 'name': request.form.get('name'), + 'surname': request.form.get('surname'), + 'middlename': request.form.get('middlename'), + 'phone': request.form.get('phone') + }) + + if response.status_code != 200: + return render_template( + 'owners.html', + title='Изменение владельца', + errors=response.json(), + owners=owners, + cars=cars, + current_owner=list(filter(lambda c: c['id'] == id, owners))[0], + logged_in=True + ) + + return redirect(url_for('owners')) + else: + if not request.form.get('name') or not request.form.get('surname') or not request.form.get('middlename') or not request.form.get('phone'): + return render_template('owners.html', title='Владельцы', errors='Должны быть заполнены все поля!', + cars=cars, + owners=owners, logged_in=True) + + response = requests.post('http://localhost:8080/api/owners/', headers={'Authorization': session['token']}, + json={ + 'name': request.form.get('name'), + 'surname': request.form.get('surname'), + 'middlename': request.form.get('middlename'), + 'phone': request.form.get('phone') + }) + + if response.status_code != 200: + return render_template('owners.html', title='Владельцы', errors=response.json(), owners=owners, + cars=cars, + logged_in=True) + + owners = requests.get('http://localhost:8080/api/owners/', headers={'Authorization': session['token']}).json() + + if id != 0: + return render_template('owners.html', title='Изменение владельца', owners=owners, cars=cars, logged_in=True, current_owner=list(filter(lambda c: c['id'] == id, owners))[0]) + + return render_template('owners.html', title='Владельца', owners=owners, cars=cars, logged_in=True) + + +@app.route('/cars', defaults={'id': 0}, methods=['GET', 'POST']) +@app.route('/cars/', methods=['GET', 'POST']) +def cars(id): + if not session['token']: + return redirect(url_for('login')) + + owners = requests.get('http://localhost:8080/api/owners/', headers={'Authorization': session['token']}).json() + cars = list( + filter(lambda c: c['car_station_id'] == session['car_station_id'], + requests.get('http://localhost:8080/api/cars/', headers={'Authorization': session['token']}).json() + ) + ) + + if request.method == 'POST': + if id != 0: + if not request.form.get('brand') or not request.form.get('model') or not request.form.get('price') or not request.form.get('owner_id'): + return render_template( + 'cars.html', + title='Изменение автомобиля', + errors='Должны быть заполнены все поля!', + current_car=list(filter(lambda c: c['id'] == id, cars))[0], + cars=cars, + owners=owners, + logged_in=True, + selected_owner=int(request.args.get('owner')) if request.args.get('owner') else None + ) + + response = requests.patch(f'http://localhost:8080/api/cars/{id}', headers={'Authorization': session['token']}, + json={ + 'brand': request.form.get('brand'), + 'model': request.form.get('model'), + 'price': float(request.form.get('price')), + 'owner_id': int(request.form.get('owner_id')), + 'car_station_id': session['car_station_id'] + }) + + if response.status_code != 200: + return render_template( + 'cars.html', + title='Изменение автомобиля', + errors=response.json(), + cars=cars, + owners=owners, + current_car=list(filter(lambda c: c['id'] == id, cars))[0], + logged_in=True, + selected_owner=int(request.args.get('owner')) if request.args.get('owner') else None + ) + + return redirect(url_for('cars')) + else: + if not request.form.get('brand') or not request.form.get('model') or not request.form.get('price') or not request.form.get('owner_id'): + return render_template('cars.html', title='Автомобили', errors='Должны быть заполнены все поля!', + owners=owners, selected_owner=int(request.args.get('owner')) if request.args.get('owner') else None, + cars=cars, logged_in=True) + + response = requests.post('http://localhost:8080/api/cars/', headers={'Authorization': session['token']}, + json={ + 'brand': request.form.get('brand'), + 'model': request.form.get('model'), + 'price': float(request.form.get('price')), + 'owner_id': int(request.form.get('owner_id')), + 'car_station_id': session['car_station_id'] + }) + + if response.status_code != 200: + return render_template('cars.html', title='Автомобили', errors=response.json(), cars=cars, + owners=owners, selected_owner=int(request.args.get('owner')) if request.args.get('owner') else None, + logged_in=True) + + cars = requests.get('http://localhost:8080/api/cars/', headers={'Authorization': session['token']}).json() + + if id != 0: + return render_template('cars.html', title='Изменение автомобиля', cars=cars, logged_in=True, owners=owners, selected_owner=int(request.args.get('owner')) if request.args.get('owner') else None, current_car=list(filter(lambda c: c['id'] == id, cars))[0]) + + return render_template('cars.html', title='Автомобили', cars=cars, logged_in=True, owners=owners, selected_owner=int(request.args.get('owner')) if request.args.get('owner') else None) + + +@app.route('/report') +def report(): + if not session['token']: + return redirect(url_for('login')) + + response = requests.get('http://localhost:8080/api/cars/report', headers={'Authorization': session['token']}) + reports = list() + for report in response.json(): + report['income'] = float(report['income']) + reports.append(report) + + return render_template('report.html', title='Отчёт', reports=reports, logged_in=True) + + +@app.route('/admin', methods=['GET', 'POST']) +def admin(): + if not session['token']: + return redirect(url_for('login')) + + if request.method == 'POST': + response = requests.get('http://localhost:8080/benchmark', headers={'Authorization': session['token']}) + result = response.json() + + return render_template('admin.html', title='Панель администратора', logged_in=True, result=result) + + return render_template('admin.html', title='Панель администратора', logged_in=True) + + +if __name__ == '__main__': + app.run() diff --git a/frontend/requirements.txt b/frontend/requirements.txt new file mode 100644 index 0000000..ee6a2b8 Binary files /dev/null and b/frontend/requirements.txt differ diff --git a/frontend/templates/admin.html b/frontend/templates/admin.html new file mode 100644 index 0000000..ebb9da8 --- /dev/null +++ b/frontend/templates/admin.html @@ -0,0 +1,18 @@ +{% extends 'base.html' %} + +{% block content %} +
+
+
+
+ +
+ {% if result %} + + {% endif %} +
+
+
+{% endblock %} \ No newline at end of file diff --git a/frontend/templates/base.html b/frontend/templates/base.html new file mode 100644 index 0000000..2a54b58 --- /dev/null +++ b/frontend/templates/base.html @@ -0,0 +1,36 @@ + + + + + {{ title }} + + + + + + + + +
+ {% if logged_in %} + + {% endif %} + + {% block content %} + {% endblock %} +
+ {% block script %} + {% endblock %} + + \ No newline at end of file diff --git a/frontend/templates/cars.html b/frontend/templates/cars.html new file mode 100644 index 0000000..6c7b2e0 --- /dev/null +++ b/frontend/templates/cars.html @@ -0,0 +1,74 @@ +{% extends "base.html" %} + +{% block content %} +
+
+
+

{% if current_car %} Просмотр автомобиля {% else %} Регистрация автомобиля {% endif %}

+ {% if errors %} + + {% endif %} +
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+
+
+

Все автомобили на стоянке

+ + + + + + + + + {% for car in cars|sort(attribute="brand")|sort(attribute="model")|sort(attribute="price") %} + + + + + + + + {% endfor %} +
МаркаМодельЦенаВладелецДействия
{{ car.brand }}{{ car.model }}{{ car.price }}Перейти к владельцу + +
+
+
+{% endblock %} + +{% block script %} + +{% endblock %} \ No newline at end of file diff --git a/frontend/templates/clients.html b/frontend/templates/clients.html new file mode 100644 index 0000000..0d85302 --- /dev/null +++ b/frontend/templates/clients.html @@ -0,0 +1,59 @@ +{% extends "base.html" %} + +{% block content %} +
+
+
+

{% if current_client %} Просмотр клиента {% else %} Регистрация клиента {% endif %}

+ {% if errors %} + + {% endif %} +
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+
+
+

Все клиенты

+ + + + + + + + + {% for client in clients|sort(attribute="surname")|sort(attribute="name")|sort(attribute="middlename") %} + + + + + + + + {% endfor %} +
Номер телефонаФамилияИмяОтчествоДействия
{{ client.phone }}{{ client.surname }}{{ client.name }}{{ client.middlename }} + +
+
+
+{% endblock %} \ No newline at end of file diff --git a/frontend/templates/login.html b/frontend/templates/login.html new file mode 100644 index 0000000..6440a5f --- /dev/null +++ b/frontend/templates/login.html @@ -0,0 +1,24 @@ +{% extends "base.html" %} + +{% block content %} +
+
+
+ {% if errors %} + + {% endif %} +
+ +
+
+ +
+
+ +
+
+
+
+{% endblock %} \ No newline at end of file diff --git a/frontend/templates/owners.html b/frontend/templates/owners.html new file mode 100644 index 0000000..7c4cb6b --- /dev/null +++ b/frontend/templates/owners.html @@ -0,0 +1,91 @@ +{% extends "base.html" %} + +{% block content %} +
+
+
+

{% if current_owner %} Просмотр владельца {% else %} Регистрация владельца {% endif %}

+ {% if errors %} + + {% endif %} +
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+ {% if current_owner %} +
+
+

Автомобили владельца

+ + + + + + + + + {% for car in cars|sort(attribute="brand")|sort(attribute="model")|sort(attribute="price") %} + {% if car.owner_id == current_owner.id %} + + + + + + + + {% endif %} + {% endfor %} +
МаркаМодельЦенаВладелецДействия
{{ car.brand }}{{ car.model }}{{ car.price }}Перейти к владельцу + +
+
+
+ {% endif %} +
+
+

Все владельцы

+ + + + + + + + + {% for owner in owners|sort(attribute="surname")|sort(attribute="name")|sort(attribute="middlename") %} + + + + + + + + {% endfor %} +
Номер телефонаФамилияИмяОтчествоДействия
{{ owner.phone }}{{ owner.surname }}{{ owner.name }}{{ owner.middlename }} + +
+
+
+{% endblock %} \ No newline at end of file diff --git a/frontend/templates/rent.html b/frontend/templates/rent.html new file mode 100644 index 0000000..1a96e02 --- /dev/null +++ b/frontend/templates/rent.html @@ -0,0 +1,34 @@ +{% extends "base.html" %} + +{% block content %} +
+
+
+
+
+
+
+ Дата начала: {{ rent.start_time }} +
+
+ Номер телефона клиента: {{ rent.client.phone }} +
+ +
+ Автомобиль: {{ rent.car.brand }} {{ rent.car.model }} +
+
+ Количество минут: {{ rent.time_amount }} +
+
+ Итоговая стоимость: {{ math.ceil(float(rent.car.price) * int(rent.time_amount) * 100) / 100 }} +
+
+
+
+
+
+
+{% endblock %} \ No newline at end of file diff --git a/frontend/templates/rents.html b/frontend/templates/rents.html new file mode 100644 index 0000000..0da53a4 --- /dev/null +++ b/frontend/templates/rents.html @@ -0,0 +1,109 @@ +{% extends 'base.html' %} + +{% block content %} +
+
+
+

Создание аренды

+ {% if errors %} + + {% endif %} +
+ +
+
+ +
+
+ +
+
+
+
+
+
+

Действительные аренды

+ + + + + + + + + + {% for rent in rents|sort(reverse=true, attribute="start_time") %} + {% if not rent.time_amount %} + + + + + + + + + {% endif %} + {% endfor %} +
Дата началаНомер телефона клиентаКлиентАвтомобильЦена за минутуДействия
{{ rent.start_time }}{{ rent.client.phone }}{{ rent.client.surname }} {{ rent.client.name }} {{ rent.client.middlename }}{{ rent.car.brand }} {{ rent.car.model }}{{ rent.car.price }} +
+ + +
+
+
+
+
+
+

Завершённые аренды

+ + + + + + + + + + {% for rent in rents|sort(reverse=true, attribute="start_time") %} + {% if rent.time_amount %} + + + + + + + + + {% endif %} + {% endfor %} +
Дата началаНомер телефона клиентаКлиентАвтомобильЦена за минутуДействия
{{ rent.start_time }}{{ rent.client.phone }}{{ rent.client.surname }} {{ rent.client.name }} {{ rent.client.middlename }}{{ rent.car.brand }} {{ rent.car.model }}{{ rent.car.price }} + Посмотреть +
+
+
+{% endblock %} + +{% block script %} + +{% endblock %} \ No newline at end of file diff --git a/frontend/templates/report.html b/frontend/templates/report.html new file mode 100644 index 0000000..df0cfd8 --- /dev/null +++ b/frontend/templates/report.html @@ -0,0 +1,23 @@ +{% extends 'base.html' %} + +{% block content %} +
+
+

Отчёт по машинам

+ + + + + + + {% for report in reports|sort(reverse=true, attribute="income") %} + + + + + + {% endfor %} +
АвтомобильВзят в аренду в этом месяце (раз)Доход с машины за этот месяц
{{ report.brand }} {{ report.model }}{{ report.times }} {{ report.income }}
+
+
+{% endblock %} \ No newline at end of file