Compare commits
No commits in common. "release" and "master" have entirely different histories.
162
.gitignore
vendored
Normal file
162
.gitignore
vendored
Normal file
@ -0,0 +1,162 @@
|
||||
# ---> Python
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
cover/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
.pybuilder/
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
# For a library or package, you might want to ignore these files since the code is
|
||||
# intended to run in multiple environments; otherwise, check them in:
|
||||
# .python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# poetry
|
||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||
#poetry.lock
|
||||
|
||||
# pdm
|
||||
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||
#pdm.lock
|
||||
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
||||
# in version control.
|
||||
# https://pdm.fming.dev/#use-with-ide
|
||||
.pdm.toml
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# pytype static type analyzer
|
||||
.pytype/
|
||||
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
||||
|
||||
# PyCharm
|
||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
|
3
.idea/.gitignore
generated
vendored
3
.idea/.gitignore
generated
vendored
@ -1,3 +0,0 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
14
.idea/Internet_auto_parts_store.iml
generated
14
.idea/Internet_auto_parts_store.iml
generated
@ -1,14 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="PYTHON_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/venv" />
|
||||
</content>
|
||||
<orderEntry type="jdk" jdkName="Python 3.9 (Internet_auto_parts_store)" jdkType="Python SDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
<component name="PyDocumentationSettings">
|
||||
<option name="format" value="PLAIN" />
|
||||
<option name="myDocStringFormat" value="Plain" />
|
||||
</component>
|
||||
</module>
|
6
.idea/inspectionProfiles/profiles_settings.xml
generated
6
.idea/inspectionProfiles/profiles_settings.xml
generated
@ -1,6 +0,0 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<settings>
|
||||
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||
<version value="1.0" />
|
||||
</settings>
|
||||
</component>
|
10
.idea/misc.xml
generated
10
.idea/misc.xml
generated
@ -1,10 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Black">
|
||||
<option name="sdkName" value="Python 3.9 (Internet_auto_parts_store)" />
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.9 (Internet_auto_parts_store)" project-jdk-type="Python SDK" />
|
||||
<component name="PyCharmProfessionalAdvertiser">
|
||||
<option name="shown" value="true" />
|
||||
</component>
|
||||
</project>
|
8
.idea/modules.xml
generated
8
.idea/modules.xml
generated
@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/Internet_auto_parts_store.iml" filepath="$PROJECT_DIR$/.idea/Internet_auto_parts_store.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
6
.idea/vcs.xml
generated
6
.idea/vcs.xml
generated
@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
28
README.md
28
README.md
@ -1,28 +1,2 @@
|
||||
После скачивания проекта из сайта git.is.ulstu.ru
|
||||
Нужно установить пакеты из файла requirements.txt с помощью команды pip
|
||||
Нужно еще скачать контейнеры elasticsearch и kibana с помощью команды docker-compose up
|
||||
После этого нужно установить пакеты node js, c помощью команды npm install
|
||||
Для запуска проекта:
|
||||
1. Запустить виртуальную оболочку venv, затем нужно запустить файл main.py;
|
||||
2. Перейти в каталог front_admin и запустить команду npm run start;
|
||||
3. Перейти в каталог front и запустить предыдущую команду.
|
||||
# Internet_auto_parts_store
|
||||
|
||||
Описание
|
||||
Вес проект состоит из трех частей:
|
||||
1. Работа с хранилищем: для взаимодействия с сервером используется
|
||||
пакет Flask, для работы с базой данных – SQL Alchemy.
|
||||
2. Бизнес-логика: реализована во сервисе, и взаимодействует с контроллерами.
|
||||
3. Уровень пользовательского интерфейса: выполнен с помощью vue js.
|
||||
В диаграмме классов показаны подробно соответствующие классы и компоненты.
|
||||
В пакеты сервис, репозиторий и контроллеры, реализованы классы, которые используют функции add, update, delete, get и get_all.
|
||||
Функция add используется для добавления нового объекта, функция update используется для изменения данных определенного объекта по id, функция delete удаляет объект из базы по id.
|
||||
Функция get предоставляет данные определенного объекта по id.
|
||||
Функция get_all предоставляет информацию в качестве списка, определенной сущности из базы данных.
|
||||
В классе User из пакета сервис дополнительно еще реализованы функции get_hash и create_token.
|
||||
Функция get_hash используется для того чтобы зашифровать пароль пользователя, а также используется при аутентификация пользователя.
|
||||
Функция create_token создает token, которая нужна для того чтобы безопасно передавать данные из базы данных пользователю.
|
||||
Еще в этой программе дополнительно реализовано функции для вывода данных такие как get_all_parts, get_all_category_id, get_part_manufacturer и get_parts.
|
||||
Функции add_order_items и search – это еще дополнительные функции, которые были созданы в этой программе.
|
||||
Функция add_order_items нужно для добавления данных, т.е. целиком добавление всего списка запчастей из корзины в базу.
|
||||
Функция search – это основная функция, которая нужно для семантического поиска.
|
||||
Семантический поиск – реализован с помощью функций, которые в свою очередь используются библиотеку elasticsearch.
|
@ -1,26 +0,0 @@
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
from elasticsearch import Elasticsearch
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
MODE: str
|
||||
DB_HOST: str
|
||||
DB_PORT: int
|
||||
DB_USER: str
|
||||
DB_PASS: str
|
||||
DB_NAME: str
|
||||
|
||||
@property
|
||||
def DATABASE_URL(self):
|
||||
return f"postgresql+psycopg://{self.DB_USER}:{self.DB_PASS}@{self.DB_HOST}:{self.DB_PORT}/{self.DB_NAME}"
|
||||
|
||||
model_config = SettingsConfigDict(env_file=".env")
|
||||
|
||||
|
||||
class Config:
|
||||
SECRET_KEY = 'Ford Mustang ShowRoom'
|
||||
|
||||
|
||||
settings = Settings()
|
||||
|
||||
es = Elasticsearch(['http://localhost:9200'])
|
@ -1,83 +0,0 @@
|
||||
from flask import request
|
||||
from flask_restx import Resource, fields, abort, marshal_with
|
||||
from flask_jwt_extended import jwt_required, get_jwt_identity
|
||||
from json import loads, JSONDecodeError
|
||||
from sqlalchemy.exc import SQLAlchemyError
|
||||
from application.controllers.ReviewController import api, check_data
|
||||
from application.services.CartService import CartService
|
||||
|
||||
ns = api.namespace('cart', description='Cart operations')
|
||||
|
||||
part_fields_without_feature = ns.model("PartWithoutFeature", {
|
||||
'id': fields.Integer(readonly=True, description='The part unique identifier'),
|
||||
'name': fields.String(required=True, description='Name of the part'),
|
||||
'description': fields.String(required=True, description='Description of the part'),
|
||||
'price': fields.Float(required=True, description='Price of the part'),
|
||||
'category_id': fields.Integer(required=True, description='Category ID associated with this part'),
|
||||
'manufacturer_id': fields.Integer(required=True, description='Manufacturer ID associated with this part')
|
||||
})
|
||||
|
||||
cart_fields = ns.model("Cart", {
|
||||
'id': fields.Integer(readonly=True, description='The cart unique identifier'),
|
||||
'count': fields.Integer(required=True, description='Quantity of the item in the cart'),
|
||||
'part': fields.Nested(part_fields_without_feature, allow_null=True)
|
||||
})
|
||||
|
||||
|
||||
@ns.route('/')
|
||||
class CartList(Resource):
|
||||
@marshal_with(cart_fields)
|
||||
@jwt_required()
|
||||
def get(self):
|
||||
try:
|
||||
cart = CartService.get_all(int(get_jwt_identity()))
|
||||
return cart, 200
|
||||
except Exception as e:
|
||||
abort(500, message=f"Internal Server Error: {str(e)}")
|
||||
|
||||
@ns.doc(params={
|
||||
'id': {'description': 'The cart unique identifier', 'required': True, 'type': 'integer'},
|
||||
'count': {'description': 'Quantity of the item', 'required': True, 'type': 'integer'},
|
||||
'feature': {'description': 'Cart parts in JSON format', 'required': True, 'type': 'string'},
|
||||
})
|
||||
@marshal_with(cart_fields)
|
||||
@jwt_required()
|
||||
def post(self):
|
||||
id = request.args.get('id', default=0, type=int)
|
||||
count = request.args.get('count', default=0, type=int)
|
||||
part_str = request.args.get('feature', default='', type=str)
|
||||
|
||||
if not check_data([id, count]):
|
||||
abort(400, message="Incorrect input data.")
|
||||
|
||||
part = None
|
||||
if part_str:
|
||||
try:
|
||||
part = loads(part_str)
|
||||
except JSONDecodeError:
|
||||
abort(400, message="Invalid JSON format for part.")
|
||||
|
||||
try:
|
||||
cart = CartService.add(int(get_jwt_identity()), {"id": id, "count": count,
|
||||
"part": part["part"]})
|
||||
return cart, 201
|
||||
except SQLAlchemyError as e:
|
||||
abort(500, message=f"Database error: {str(e)}")
|
||||
|
||||
|
||||
@ns.route('/<int:id>')
|
||||
@ns.response(404, 'Cart not found')
|
||||
@ns.param('id', 'The part identifier')
|
||||
class Cart(Resource):
|
||||
@ns.response(200, "Part deleted")
|
||||
@marshal_with(cart_fields)
|
||||
@jwt_required()
|
||||
def delete(self, id):
|
||||
try:
|
||||
cart = CartService.get(id, int(get_jwt_identity()))
|
||||
if cart is None:
|
||||
abort(404, message=f"Part with ID {id} does not exist.")
|
||||
cart = CartService.delete(id, int(get_jwt_identity()))
|
||||
return cart, 200
|
||||
except SQLAlchemyError as e:
|
||||
abort(500, message=f"Database error: {str(e)}")
|
@ -1,127 +0,0 @@
|
||||
import json
|
||||
from flask import request
|
||||
from flask_restx import Resource, fields, abort, marshal_with
|
||||
from flask_jwt_extended import jwt_required
|
||||
from sqlalchemy.exc import SQLAlchemyError
|
||||
from application.controllers.UserController import api, check_data
|
||||
from application.dto.CategoryDTO import CategoryDTO
|
||||
from application.services.CategoryService import CategoryService
|
||||
|
||||
ns = api.namespace('categories', description='Category operations')
|
||||
|
||||
category_fields = ns.model("Category", {
|
||||
'id': fields.Integer(readonly=True, description='The category unique identifier'),
|
||||
'name': fields.String(required=True, description='Category name'),
|
||||
'description': fields.String(required=True, description='Category description'),
|
||||
'feature': fields.Raw(description='Category features in JSON format'),
|
||||
'parent_category_id': fields.Integer(description='Parent category ID')
|
||||
})
|
||||
|
||||
|
||||
@ns.route('/')
|
||||
class CategoryList(Resource):
|
||||
@marshal_with(category_fields)
|
||||
def get(self):
|
||||
try:
|
||||
categories = CategoryService.get_all()
|
||||
return categories, 200
|
||||
except Exception as e:
|
||||
abort(500, message=f"Internal Server Error: {str(e)}")
|
||||
|
||||
@ns.doc(params={
|
||||
'name': {'description': 'Category name', 'required': True, 'type': 'string'},
|
||||
'description': {'description': 'Category description', 'required': True, 'type': 'string'},
|
||||
'feature': {'description': 'Category features in JSON format', 'required': False, 'type': 'string'},
|
||||
'parent_category_id': {'description': 'Parent category ID', 'required': False, 'type': 'integer'}
|
||||
})
|
||||
@marshal_with(category_fields)
|
||||
@jwt_required()
|
||||
def post(self):
|
||||
name = request.args.get('name', default='', type=str)
|
||||
description = request.args.get('description', default='', type=str)
|
||||
feature_str = request.args.get('feature', default='', type=str)
|
||||
parent_category_id = request.args.get('parent_category_id', default=0, type=int)
|
||||
|
||||
if not check_data([name, description]):
|
||||
abort(400, message="Incorrect input data.")
|
||||
|
||||
feature = None
|
||||
if feature_str:
|
||||
try:
|
||||
feature = json.loads(feature_str)
|
||||
except json.JSONDecodeError:
|
||||
abort(400, message="Invalid JSON format for feature.")
|
||||
|
||||
try:
|
||||
if parent_category_id == 0:
|
||||
parent_category_id = None
|
||||
category_dto = CategoryDTO(name=name,
|
||||
description=description,
|
||||
feature=feature,
|
||||
parent_category_id=parent_category_id)
|
||||
new_category = CategoryService.add(category_dto)
|
||||
return new_category, 201
|
||||
except SQLAlchemyError as e:
|
||||
abort(500, message=f"Database error: {str(e)}")
|
||||
|
||||
|
||||
@ns.route('/<int:id>')
|
||||
@ns.response(404, 'Category not found')
|
||||
@ns.param('id', 'The category identifier')
|
||||
class Category(Resource):
|
||||
@marshal_with(category_fields)
|
||||
def get(self, id):
|
||||
try:
|
||||
category = CategoryService.get(id)
|
||||
if category is None:
|
||||
abort(404, message=f"Category with ID {id} does not exist.")
|
||||
return category, 200
|
||||
except Exception as e:
|
||||
abort(500, message=f"Internal Server Error: {str(e)}")
|
||||
|
||||
@ns.expect(category_fields)
|
||||
@marshal_with(category_fields)
|
||||
@jwt_required()
|
||||
def put(self, id):
|
||||
name = ns.payload['name']
|
||||
description = ns.payload['description']
|
||||
parent_category_id = ns.payload['parent_category_id']
|
||||
feature_str = ns.payload.get('feature', '')
|
||||
|
||||
feature = None
|
||||
if feature_str:
|
||||
try:
|
||||
feature = json.loads(feature_str) if isinstance(feature_str, str) else feature_str
|
||||
except json.JSONDecodeError:
|
||||
abort(400, message="Invalid JSON format for feature.")
|
||||
|
||||
if not check_data([name, description, feature]):
|
||||
abort(400, message="Incorrect input data.")
|
||||
|
||||
try:
|
||||
category = CategoryService.get(id)
|
||||
if category is None:
|
||||
abort(404, message=f"Category with ID {id} does not exist.")
|
||||
|
||||
if parent_category_id == 0:
|
||||
parent_category_id = None
|
||||
|
||||
category_dto = CategoryDTO(name=name, description=description, feature=feature,
|
||||
parent_category_id=parent_category_id)
|
||||
updated_category = CategoryService.update(id, category_dto)
|
||||
return updated_category, 200
|
||||
except SQLAlchemyError as e:
|
||||
abort(500, message=f"Database error: {str(e)}")
|
||||
|
||||
@ns.response(200, "Category deleted")
|
||||
@marshal_with(category_fields)
|
||||
@jwt_required()
|
||||
def delete(self, id):
|
||||
try:
|
||||
category = CategoryService.get(id)
|
||||
if category is None:
|
||||
abort(404, message=f"Category with ID {id} does not exist.")
|
||||
CategoryService.delete(id)
|
||||
return category, 200
|
||||
except SQLAlchemyError as e:
|
||||
abort(500, message=f"Database error: {str(e)}")
|
@ -1,95 +0,0 @@
|
||||
from flask import request
|
||||
from flask_restx import Resource, fields, abort, marshal_with
|
||||
from flask_jwt_extended import jwt_required
|
||||
from sqlalchemy.exc import SQLAlchemyError
|
||||
from application.controllers.CategoryController import api, check_data
|
||||
from application.dto.ManufacturerDTO import ManufacturerDTO
|
||||
from application.services.ManufacturerService import ManufacturerService
|
||||
|
||||
ns = api.namespace('manufacturers', description='Manufacturer operations')
|
||||
|
||||
manufacturer_fields = ns.model("Manufacturer", {
|
||||
'id': fields.Integer(readonly=True, description='The manufacturer unique identifier'),
|
||||
'name': fields.String(required=True, description='Name of the manufacturer'),
|
||||
'country': fields.String(required=True, description='Country of the manufacturer')
|
||||
})
|
||||
|
||||
|
||||
@ns.route('/')
|
||||
class ManufacturerList(Resource):
|
||||
@marshal_with(manufacturer_fields)
|
||||
def get(self):
|
||||
try:
|
||||
manufacturers = ManufacturerService.get_all()
|
||||
return manufacturers, 200
|
||||
except Exception as e:
|
||||
abort(500, message=f"Internal Server Error: {str(e)}")
|
||||
|
||||
@ns.doc(params={
|
||||
'name': {'description': 'Name of the manufacturer', 'required': True, 'type': 'string'},
|
||||
'country': {'description': 'Country of the manufacturer', 'required': True, 'type': 'string'}
|
||||
})
|
||||
@marshal_with(manufacturer_fields)
|
||||
@jwt_required()
|
||||
def post(self):
|
||||
name = request.args.get('name', default='', type=str)
|
||||
country = request.args.get('country', default='', type=str)
|
||||
|
||||
if not check_data([name, country]):
|
||||
abort(400, message="Incorrect input data.")
|
||||
|
||||
try:
|
||||
manufacturer_dto = ManufacturerDTO(name=name, country=country)
|
||||
new_manufacturer = ManufacturerService.add(manufacturer_dto)
|
||||
return new_manufacturer, 201
|
||||
except SQLAlchemyError as e:
|
||||
abort(500, message=f"Database error: {str(e)}")
|
||||
|
||||
|
||||
@ns.route('/<int:id>')
|
||||
@ns.response(404, 'Manufacturer not found')
|
||||
@ns.param('id', 'The manufacturer identifier')
|
||||
class Manufacturer(Resource):
|
||||
@marshal_with(manufacturer_fields)
|
||||
def get(self, id):
|
||||
try:
|
||||
manufacturer = ManufacturerService.get(id)
|
||||
if manufacturer is None:
|
||||
abort(404, message=f"Manufacturer with ID {id} does not exist.")
|
||||
return manufacturer, 200
|
||||
except Exception as e:
|
||||
abort(500, message=f"Internal Server Error: {str(e)}")
|
||||
|
||||
@ns.expect(manufacturer_fields)
|
||||
@marshal_with(manufacturer_fields)
|
||||
@jwt_required()
|
||||
def put(self, id):
|
||||
name = ns.payload['name']
|
||||
country = ns.payload['country']
|
||||
|
||||
if not check_data([name, country]):
|
||||
abort(400, message="Incorrect input data.")
|
||||
|
||||
try:
|
||||
manufacturer = ManufacturerService.get(id)
|
||||
if manufacturer is None:
|
||||
abort(404, message=f"Manufacturer with ID {id} does not exist.")
|
||||
|
||||
manufacturer_dto = ManufacturerDTO(name=name, country=country)
|
||||
updated_manufacturer = ManufacturerService.update(id, manufacturer_dto)
|
||||
return updated_manufacturer, 200
|
||||
except SQLAlchemyError as e:
|
||||
abort(500, message=f"Database error: {str(e)}")
|
||||
|
||||
@ns.response(200, "Manufacturer deleted")
|
||||
@marshal_with(manufacturer_fields)
|
||||
@jwt_required()
|
||||
def delete(self, id):
|
||||
try:
|
||||
manufacturer = ManufacturerService.get(id)
|
||||
if manufacturer is None:
|
||||
abort(404, message=f"Manufacturer with ID {id} does not exist.")
|
||||
ManufacturerService.delete(id)
|
||||
return manufacturer, 200
|
||||
except SQLAlchemyError as e:
|
||||
abort(500, message=f"Database error: {str(e)}")
|
@ -1,149 +0,0 @@
|
||||
import json
|
||||
from datetime import date
|
||||
from flask import request
|
||||
from flask_restx import Resource, fields, abort, marshal_with
|
||||
from flask_jwt_extended import jwt_required, get_jwt_identity
|
||||
from sqlalchemy.exc import SQLAlchemyError
|
||||
from application.controllers.ManufacturerController import api, check_data
|
||||
from application.dto.OrderDTO import OrderDTO
|
||||
from application.services.OrderService import OrderService
|
||||
|
||||
ns = api.namespace('orders', description='Order operations')
|
||||
|
||||
order_fields = ns.model("Order", {
|
||||
'id': fields.Integer(readonly=True, description='The order unique identifier'),
|
||||
'order_date': fields.Date(required=True, description='Date of the order'),
|
||||
'total_amount': fields.Float(required=True, description='Total amount of the order'),
|
||||
'status': fields.String(required=True, description='Status of the order'),
|
||||
'user_id': fields.Integer(required=True, description='User ID associated with this order')
|
||||
})
|
||||
|
||||
|
||||
@ns.route('/')
|
||||
class OrderList(Resource):
|
||||
@marshal_with(order_fields)
|
||||
@jwt_required()
|
||||
def get(self):
|
||||
try:
|
||||
orders = OrderService.get_all(int(get_jwt_identity()))
|
||||
return orders, 200
|
||||
except Exception as e:
|
||||
abort(500, message=f"Internal Server Error: {str(e)}")
|
||||
|
||||
@ns.doc(params={
|
||||
'order_date': {'description': 'Date of the order', 'required': True, 'type': 'string', 'format': 'date'},
|
||||
'total_amount': {'description': 'Total amount of the order', 'required': True, 'type': 'number'},
|
||||
'status': {'description': 'Status of the order', 'required': True, 'type': 'string'}
|
||||
})
|
||||
@marshal_with(order_fields)
|
||||
@jwt_required()
|
||||
def post(self):
|
||||
order_date_str = request.args.get('order_date', default='', type=str)
|
||||
total_amount = request.args.get('total_amount', default=0.0, type=float)
|
||||
status = request.args.get('status', default='', type=str)
|
||||
user_id = get_jwt_identity()
|
||||
order_date = date.today()
|
||||
|
||||
try:
|
||||
order_date = date.fromisoformat(order_date_str)
|
||||
except ValueError:
|
||||
abort(400, message="Invalid date format. Use YYYY-MM-DD.")
|
||||
|
||||
if not check_data([order_date_str, total_amount, status, user_id]):
|
||||
abort(400, message="Incorrect input data.")
|
||||
|
||||
try:
|
||||
order_dto = OrderDTO(order_date=order_date,
|
||||
total_amount=total_amount,
|
||||
status=int(status),
|
||||
user_id=user_id)
|
||||
new_order = OrderService.add(order_dto)
|
||||
return new_order, 201
|
||||
except SQLAlchemyError as e:
|
||||
abort(500, message=f"Database error: {str(e)}")
|
||||
|
||||
|
||||
@ns.route('/<int:id>')
|
||||
@ns.response(404, 'Order not found')
|
||||
@ns.param('id', 'The order identifier')
|
||||
class Order(Resource):
|
||||
@marshal_with(order_fields)
|
||||
@jwt_required()
|
||||
def get(self, id):
|
||||
try:
|
||||
order = OrderService.get(id)
|
||||
if order is None:
|
||||
abort(404, message=f"Order with ID {id} does not exist.")
|
||||
return order, 200
|
||||
except Exception as e:
|
||||
abort(500, message=f"Internal Server Error: {str(e)}")
|
||||
|
||||
@ns.response(200, "Order deleted")
|
||||
@marshal_with(order_fields)
|
||||
@jwt_required()
|
||||
def delete(self, id):
|
||||
try:
|
||||
order = OrderService.get(id)
|
||||
if order is None:
|
||||
abort(404, message=f"Order with ID {id} does not exist.")
|
||||
OrderService.delete(id)
|
||||
return order, 200
|
||||
except SQLAlchemyError as e:
|
||||
abort(500, message=f"Database error: {str(e)}")
|
||||
|
||||
|
||||
@ns.route('/orderwithitems')
|
||||
class OrderWithItems(Resource):
|
||||
@ns.doc(params={
|
||||
'order_date': {'description': 'Date of the order', 'required': True, 'type': 'string', 'format': 'date'},
|
||||
'total_amount': {'description': 'Total amount of the order', 'required': True, 'type': 'number'},
|
||||
'status': {'description': 'Status of the order', 'required': True, 'type': 'string'},
|
||||
'items': {
|
||||
'description': 'List of order items',
|
||||
'required': True,
|
||||
'type': 'array',
|
||||
'items': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'count': {'description': 'Quantity of the item', 'required': True, 'type': 'integer'},
|
||||
'price': {'description': 'Price of the item', 'required': True, 'type': 'number'},
|
||||
'part_id': {'description': 'ID of the associated part', 'required': True, 'type': 'integer'}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@marshal_with(order_fields)
|
||||
@jwt_required()
|
||||
def post(self):
|
||||
order_date_str = request.args.get('order_date', default='', type=str)
|
||||
total_amount = request.args.get('total_amount', default=0.0, type=float)
|
||||
status = request.args.get('status', default='', type=str)
|
||||
items_json = request.args.get('items', '[]')
|
||||
user_id = get_jwt_identity()
|
||||
order_date = date.today()
|
||||
|
||||
try:
|
||||
order_date = date.fromisoformat(order_date_str)
|
||||
except ValueError:
|
||||
abort(400, message="Invalid date format. Use YYYY-MM-DD.")
|
||||
|
||||
items = None
|
||||
if items_json:
|
||||
try:
|
||||
items = json.loads(items_json)
|
||||
except json.JSONDecodeError:
|
||||
abort(400, message="Invalid JSON format for items.")
|
||||
|
||||
if not check_data([order_date_str, total_amount, status, user_id]):
|
||||
abort(400, message="Incorrect input data.")
|
||||
|
||||
try:
|
||||
order_dto = OrderDTO(order_date=order_date,
|
||||
total_amount=total_amount,
|
||||
status=int(status),
|
||||
user_id=user_id)
|
||||
new_order = OrderService.add_order_items(order_dto, items)
|
||||
print(new_order)
|
||||
return new_order, 201
|
||||
except SQLAlchemyError as e:
|
||||
abort(500, message=f"Database error: {str(e)}")
|
@ -1,116 +0,0 @@
|
||||
from flask import request
|
||||
from flask_restx import Resource, fields, abort, marshal_with
|
||||
from flask_jwt_extended import jwt_required
|
||||
from sqlalchemy.exc import SQLAlchemyError
|
||||
from application.controllers.OrderController import api, check_data
|
||||
from application.dto.OrderItemDTO import OrderItemDTO
|
||||
from application.services.OrderItemService import OrderItemService
|
||||
|
||||
ns = api.namespace('order_items', description='Order Item operations')
|
||||
|
||||
order_item_fields = ns.model("OrderItem", {
|
||||
'id': fields.Integer(readonly=True, description='The order item unique identifier'),
|
||||
'count': fields.Integer(required=True, description='Quantity of the item'),
|
||||
'price': fields.Float(required=True, description='Price of the item'),
|
||||
'part_id': fields.Integer(required=True, description='ID of the associated part'),
|
||||
'order_id': fields.Integer(required=True, description='ID of the associated order')
|
||||
})
|
||||
|
||||
order_item_parts_fields = ns.model("OrderItemList", {
|
||||
'id': fields.Integer(readonly=True, description='The order item unique identifier'),
|
||||
'count': fields.Integer(required=True, description='Quantity of the item'),
|
||||
'price': fields.Float(required=True, description='Price of the item'),
|
||||
'order_id': fields.Integer(required=True, description='ID of the associated order'),
|
||||
'name_part': fields.String(required=True, description='The name of part')
|
||||
})
|
||||
|
||||
|
||||
@ns.route('/')
|
||||
class OrderItemCreate(Resource):
|
||||
@ns.doc(params={
|
||||
'count': {'description': 'Quantity of the item', 'required': True, 'type': 'integer'},
|
||||
'price': {'description': 'Price of the item', 'required': True, 'type': 'number'},
|
||||
'part_id': {'description': 'ID of the associated part', 'required': True, 'type': 'integer'},
|
||||
'order_id': {'description': 'ID of the associated order', 'required': True, 'type': 'integer'}
|
||||
})
|
||||
@marshal_with(order_item_fields)
|
||||
@jwt_required()
|
||||
def post(self):
|
||||
count = request.args.get('count', default=0, type=int)
|
||||
price = request.args.get('price', default=0.0, type=float)
|
||||
part_id = request.args.get('part_id', default=0, type=int)
|
||||
order_id = request.args.get('order_id', default=0, type=int)
|
||||
|
||||
if not check_data([count, price, part_id, order_id]):
|
||||
abort(400, message="Incorrect input data.")
|
||||
|
||||
try:
|
||||
order_item_dto = OrderItemDTO(count=count,
|
||||
price=price,
|
||||
part_id=part_id,
|
||||
order_id=order_id)
|
||||
new_order_item = OrderItemService.add(order_item_dto)
|
||||
return new_order_item, 201
|
||||
except SQLAlchemyError as e:
|
||||
abort(500, message=f"Database error: {str(e)}")
|
||||
|
||||
|
||||
@ns.route('/<int:id>')
|
||||
@ns.response(404, 'Order item not found')
|
||||
@ns.param('id', 'The order item identifier')
|
||||
class OrderItem(Resource):
|
||||
@marshal_with(order_item_fields)
|
||||
@jwt_required()
|
||||
def get(self, id):
|
||||
try:
|
||||
order_item = OrderItemService.get(id)
|
||||
if order_item is None:
|
||||
abort(404, message=f"Order item with ID {id} does not exist.")
|
||||
return order_item, 200
|
||||
except Exception as e:
|
||||
abort(500, message=f"Internal Server Error: {str(e)}")
|
||||
|
||||
@ns.response(200, "Order item deleted")
|
||||
@marshal_with(order_item_fields)
|
||||
@jwt_required()
|
||||
def delete(self, id):
|
||||
try:
|
||||
order_item = OrderItemService.get(id)
|
||||
if order_item is None:
|
||||
abort(404, message=f"Order item with ID {id} does not exist.")
|
||||
OrderItemService.delete(id)
|
||||
return order_item, 200
|
||||
except SQLAlchemyError as e:
|
||||
abort(500, message=f"Database error: {str(e)}")
|
||||
|
||||
|
||||
@ns.route('/get_all_order_items/<int:order_id>')
|
||||
@ns.response(404, 'Order items not found')
|
||||
@ns.param('order_id', 'The order item identifier')
|
||||
class OrderItemList(Resource):
|
||||
@marshal_with(order_item_fields)
|
||||
@jwt_required()
|
||||
def get(self, order_id):
|
||||
try:
|
||||
if order_id <= 0:
|
||||
abort(404, "Invalid order id")
|
||||
order_items = OrderItemService.get_all(order_id)
|
||||
return order_items, 200
|
||||
except Exception as e:
|
||||
abort(500, message=f"Internal Server Error: {str(e)}")
|
||||
|
||||
|
||||
@ns.route('/get_all_order_items_parts/<int:order_id>')
|
||||
@ns.response(404, 'Order items not found')
|
||||
@ns.param('order_id', 'The order item identifier')
|
||||
class OrderItemListParts(Resource):
|
||||
@marshal_with(order_item_parts_fields)
|
||||
@jwt_required()
|
||||
def get(self, order_id):
|
||||
try:
|
||||
if order_id <= 0:
|
||||
abort(404, "Invalid order id")
|
||||
order_items = OrderItemService.get_all_parts(order_id)
|
||||
return order_items, 200
|
||||
except Exception as e:
|
||||
abort(500, message=f"Internal Server Error: {str(e)}")
|
@ -1,190 +0,0 @@
|
||||
import json
|
||||
from flask import request
|
||||
from flask_restx import Resource, fields, abort, marshal_with
|
||||
from flask_jwt_extended import jwt_required
|
||||
from sqlalchemy.exc import SQLAlchemyError
|
||||
from application.controllers.OrderItemController import api, check_data
|
||||
from application.controllers.ManufacturerController import manufacturer_fields
|
||||
from application.dto.PartDTO import PartDTO
|
||||
from application.services.PartService import PartService, PartIndexManager
|
||||
|
||||
ns = api.namespace('parts', description='Part operations')
|
||||
|
||||
part_fields = ns.model("Part", {
|
||||
'id': fields.Integer(readonly=True, description='The part unique identifier'),
|
||||
'name': fields.String(required=True, description='Name of the part'),
|
||||
'description': fields.String(required=True, description='Description of the part'),
|
||||
'feature': fields.Raw(description='Additional features of the part'),
|
||||
'price': fields.Float(required=True, description='Price of the part'),
|
||||
'category_id': fields.Integer(required=True, description='Category ID associated with this part'),
|
||||
'manufacturer_id': fields.Integer(required=True, description='Manufacturer ID associated with this part')
|
||||
})
|
||||
|
||||
part_manufacturer_fields = ns.model("PartManufacturer", {
|
||||
'id': fields.Integer(readonly=True, description='The part unique identifier'),
|
||||
'name': fields.String(required=True, description='Name of the part'),
|
||||
'description': fields.String(required=True, description='Description of the part'),
|
||||
'feature': fields.Raw(description='Additional features of the part'),
|
||||
'price': fields.Float(required=True, description='Price of the part'),
|
||||
'category_id': fields.Integer(required=True, description='Category ID associated with this part'),
|
||||
'manufacturer': fields.Nested(manufacturer_fields)
|
||||
})
|
||||
|
||||
|
||||
@ns.route('/get_all_parts/<int:category_id>')
|
||||
@ns.response(404, 'Parts not found')
|
||||
@ns.param('category_id', 'The category identifier')
|
||||
class PartCategory(Resource):
|
||||
@marshal_with(part_fields)
|
||||
def get(self, category_id):
|
||||
try:
|
||||
if category_id <= 0:
|
||||
abort(404, "Invalid category id")
|
||||
parts = PartService.get_all_category_id(category_id)
|
||||
return parts, 200
|
||||
except Exception as e:
|
||||
abort(500, message=f"Internal Server Error: {str(e)}")
|
||||
|
||||
|
||||
@ns.route('/')
|
||||
class PartList(Resource):
|
||||
@marshal_with(part_fields)
|
||||
def get(self):
|
||||
try:
|
||||
parts = PartService.get_all()
|
||||
return parts, 200
|
||||
except Exception as e:
|
||||
abort(500, message=f"Internal Server Error: {str(e)}")
|
||||
|
||||
@ns.doc(params={
|
||||
'name': {'description': 'Name of the part', 'required': True, 'type': 'string'},
|
||||
'description': {'description': 'Description of the part', 'required': True, 'type': 'string'},
|
||||
'feature': {'description': 'Category features in JSON format', 'required': False, 'type': 'string'},
|
||||
'price': {'description': 'Price of the part', 'required': True, 'type': 'number'},
|
||||
'category_id': {'description': 'Category ID associated with this part', 'required': True, 'type': 'integer'},
|
||||
'manufacturer_id': {'description': 'Manufacturer ID associated with this part', 'required': True, 'type': 'integer'}
|
||||
})
|
||||
@marshal_with(part_fields)
|
||||
@jwt_required()
|
||||
def post(self):
|
||||
name = request.args.get('name', default='', type=str)
|
||||
description = request.args.get('description', default='', type=str)
|
||||
feature_str = request.args.get('feature', default='', type=str)
|
||||
price = request.args.get('price', default=0.0, type=float)
|
||||
category_id = request.args.get('category_id', default=0, type=int)
|
||||
manufacturer_id = request.args.get('manufacturer_id', default=0, type=int)
|
||||
|
||||
if not check_data([name, description, price, category_id, manufacturer_id]):
|
||||
abort(400, message="Incorrect input data.")
|
||||
|
||||
feature = None
|
||||
if feature_str:
|
||||
try:
|
||||
feature = json.loads(feature_str)
|
||||
except json.JSONDecodeError:
|
||||
abort(400, message="Invalid JSON format for feature.")
|
||||
|
||||
try:
|
||||
part_dto = PartDTO(name=name,
|
||||
description=description,
|
||||
feature=feature,
|
||||
price=price,
|
||||
category_id=category_id,
|
||||
manufacturer_id=manufacturer_id)
|
||||
new_part = PartService.add(part_dto)
|
||||
return new_part, 201
|
||||
except SQLAlchemyError as e:
|
||||
abort(500, message=f"Database error: {str(e)}")
|
||||
|
||||
|
||||
@ns.route('/<int:id>')
|
||||
@ns.response(404, 'Part not found')
|
||||
@ns.param('id', 'The part identifier')
|
||||
class Part(Resource):
|
||||
@marshal_with(part_fields)
|
||||
def get(self, id):
|
||||
try:
|
||||
part = PartService.get(id)
|
||||
if part is None:
|
||||
abort(404, message=f"Part with ID {id} does not exist.")
|
||||
return part, 200
|
||||
except Exception as e:
|
||||
abort(500, message=f"Internal Server Error: {str(e)}")
|
||||
|
||||
@ns.expect(part_fields)
|
||||
@marshal_with(part_fields)
|
||||
@jwt_required()
|
||||
def put(self, id):
|
||||
name = ns.payload['name']
|
||||
description = ns.payload['description']
|
||||
feature_str = ns.payload.get('feature', '')
|
||||
price = ns.payload['price']
|
||||
category_id = ns.payload['category_id']
|
||||
manufacturer_id = ns.payload['manufacturer_id']
|
||||
|
||||
feature = None
|
||||
if feature_str:
|
||||
try:
|
||||
feature = json.loads(feature_str) if isinstance(feature_str, str) else feature_str
|
||||
except json.JSONDecodeError:
|
||||
abort(400, message="Invalid JSON format for feature.")
|
||||
|
||||
if not check_data([name, description, feature, price, category_id, manufacturer_id]):
|
||||
abort(400, message="Incorrect input data.")
|
||||
|
||||
try:
|
||||
part = PartService.get(id)
|
||||
if part is None:
|
||||
abort(404, message=f"Part with ID {id} does not exist.")
|
||||
|
||||
part_dto = PartDTO(name=name, description=description, feature=feature,
|
||||
price=price, category_id=category_id, manufacturer_id=manufacturer_id)
|
||||
updated_part = PartService.update(id, part_dto)
|
||||
return updated_part, 200
|
||||
except SQLAlchemyError as e:
|
||||
abort(500, message=f"Database error: {str(e)}")
|
||||
|
||||
@ns.response(200, "Part deleted")
|
||||
@marshal_with(part_fields)
|
||||
@jwt_required()
|
||||
def delete(self, id):
|
||||
try:
|
||||
part = PartService.get(id)
|
||||
if part is None:
|
||||
abort(404, message=f"Part with ID {id} does not exist.")
|
||||
PartService.delete(id)
|
||||
return part, 200
|
||||
except SQLAlchemyError as e:
|
||||
abort(500, message=f"Database error: {str(e)}")
|
||||
|
||||
|
||||
@ns.route('/get_part_manufacturer/<int:part_id>')
|
||||
@ns.response(404, 'Part not found')
|
||||
@ns.param('part_id', 'The part identifier')
|
||||
class PartManufacturer(Resource):
|
||||
@marshal_with(part_manufacturer_fields)
|
||||
def get(self, part_id):
|
||||
try:
|
||||
part = PartService.get_part_manufacturer(part_id)
|
||||
if part is None:
|
||||
abort(404, message=f"Part with ID {id} does not exist.")
|
||||
return part, 200
|
||||
except Exception as e:
|
||||
abort(500, message=f"Internal Server Error: {str(e)}")
|
||||
|
||||
|
||||
@ns.route('/get_part_review')
|
||||
class PartReview(Resource):
|
||||
@ns.doc(params={
|
||||
'search_query': {'description': 'Field for the searching information in the system', 'required': True,
|
||||
'type': 'string'}
|
||||
})
|
||||
@marshal_with(part_fields)
|
||||
def get(self):
|
||||
try:
|
||||
search_query = request.args.get('search_query', default='', type=str)
|
||||
result_search = PartIndexManager.search(search_query)
|
||||
parts = PartIndexManager.get_parts(result_search)
|
||||
return parts, 200
|
||||
except Exception as e:
|
||||
abort(500, message=f"Internal Server Error: {str(e)}")
|
@ -1,104 +0,0 @@
|
||||
from datetime import date
|
||||
from flask import request
|
||||
from flask_restx import Resource, fields, abort, marshal_with
|
||||
from flask_jwt_extended import jwt_required, get_jwt_identity
|
||||
from sqlalchemy.exc import SQLAlchemyError
|
||||
from application.controllers.PartController import api, check_data
|
||||
from application.dto.ReviewDTO import ReviewDTO
|
||||
from application.services.ReviewService import ReviewService
|
||||
|
||||
ns = api.namespace('reviews', description='Review operations')
|
||||
|
||||
review_fields = ns.model("Review", {
|
||||
'id': fields.Integer(readonly=True, description='The review unique identifier'),
|
||||
'rating': fields.Integer(required=True, description='Rating given in the review'),
|
||||
'comment': fields.String(required=True, description='Comment in the review'),
|
||||
'created_at': fields.Date(required=True, description='Date when the review was created'),
|
||||
'updated_at': fields.Date(required=True, description='Date when the review was last updated'),
|
||||
'user_id': fields.Integer(required=True, description='User ID associated with this review'),
|
||||
'part_id': fields.Integer(required=True, description='Part ID associated with this review')
|
||||
})
|
||||
|
||||
review_user_fields = ns.model("ReviewUser", {
|
||||
'id': fields.Integer(readonly=True, description='The review unique identifier'),
|
||||
'rating': fields.Integer(required=True, description='Rating given in the review'),
|
||||
'comment': fields.String(required=True, description='Comment in the review'),
|
||||
'email': fields.String(required=True, description='The email of the user'),
|
||||
'user_name': fields.String(required=True, description='The first name last name patronymic of the user'),
|
||||
'part_id': fields.Integer(required=True, description='Part ID associated with this review')
|
||||
})
|
||||
|
||||
|
||||
@ns.route('/get_all_reviews/<int:part_id>')
|
||||
@ns.response(404, 'Reviews not found')
|
||||
@ns.param('part_id', 'The part identifier')
|
||||
class ReviewList(Resource):
|
||||
@marshal_with(review_user_fields)
|
||||
def get(self, part_id):
|
||||
try:
|
||||
if part_id <= 0:
|
||||
abort(404, "Invalid part id")
|
||||
engines = ReviewService.get_all(part_id)
|
||||
return engines, 200
|
||||
except Exception as e:
|
||||
abort(500, message=f"Internal Server Error: {str(e)}")
|
||||
|
||||
|
||||
@ns.route('/')
|
||||
class ReviewCreate(Resource):
|
||||
@ns.doc(params={
|
||||
'rating': {'description': 'Rating given in the review', 'required': True, 'type': 'integer'},
|
||||
'comment': {'description': 'Comment in the review', 'required': True, 'type': 'string'},
|
||||
'part_id': {'description': 'Part ID associated with this review', 'required': True, 'type': 'integer'}
|
||||
})
|
||||
@marshal_with(review_fields)
|
||||
@jwt_required()
|
||||
def post(self):
|
||||
rating = request.args.get('rating', default=0, type=int)
|
||||
comment = request.args.get('comment', default='', type=str)
|
||||
user_id = get_jwt_identity()
|
||||
part_id = request.args.get('part_id', default=0, type=int)
|
||||
|
||||
if not check_data([rating, comment, user_id, part_id]):
|
||||
abort(400, message="Incorrect input data.")
|
||||
|
||||
try:
|
||||
current_date = date.today()
|
||||
review_dto = ReviewDTO(rating=rating,
|
||||
comment=comment,
|
||||
created_at=current_date,
|
||||
updated_at=current_date,
|
||||
user_id=user_id,
|
||||
part_id=part_id)
|
||||
new_review = ReviewService.add(review_dto)
|
||||
return new_review, 201
|
||||
except SQLAlchemyError as e:
|
||||
abort(500, message=f"Database error: {str(e)}")
|
||||
|
||||
|
||||
@ns.route('/<int:id>')
|
||||
@ns.response(404, 'Review not found')
|
||||
@ns.param('id', 'The review identifier')
|
||||
class Review(Resource):
|
||||
@marshal_with(review_fields)
|
||||
def get(self, id):
|
||||
try:
|
||||
review = ReviewService.get(id)
|
||||
if review is None:
|
||||
abort(404, message=f"Review with ID {id} does not exist.")
|
||||
return review, 200
|
||||
except Exception as e:
|
||||
abort(500, message=f"Internal Server Error: {str(e)}")
|
||||
|
||||
@ns.response(200, "Review deleted")
|
||||
@marshal_with(review_fields)
|
||||
@jwt_required()
|
||||
def delete(self, id):
|
||||
try:
|
||||
review = ReviewService.get(id)
|
||||
if review is None:
|
||||
abort(404, message=f"Review with ID {id} does not exist.")
|
||||
ReviewService.delete(id)
|
||||
return review, 200
|
||||
except SQLAlchemyError as e:
|
||||
abort(500, message=f"Database error: {str(e)}")
|
@ -1,178 +0,0 @@
|
||||
from flask import Flask, request, make_response
|
||||
from flask_cors import CORS
|
||||
from flask_restx import Api, Resource, fields, abort, marshal_with
|
||||
from flask_jwt_extended import JWTManager, get_jwt_identity, jwt_required
|
||||
from sqlalchemy.exc import SQLAlchemyError
|
||||
from application.dto.UserDTO import UserDTO
|
||||
from application.services.UserService import UserService
|
||||
from application.config import Config
|
||||
|
||||
app = Flask(__name__)
|
||||
app.config['JWT_SECRET_KEY'] = Config.SECRET_KEY
|
||||
jwt = JWTManager(app)
|
||||
CORS(app, resources={r"/*": {"origins": "*", "allow_headers": ["Content-Type", "Authorization"],
|
||||
"expose_headers": ["Authorization"]}})
|
||||
|
||||
|
||||
authorizations = {
|
||||
'Bearer Auth': {
|
||||
'type': 'apiKey',
|
||||
'in': 'header',
|
||||
'name': 'Authorization'
|
||||
},
|
||||
}
|
||||
|
||||
api = Api(app, version='1.0', title='Internet auto parts store API',
|
||||
description='The Internet auto parts store API', doc='/swagger', specs_url='/api/swagger.json',
|
||||
authorizations=authorizations, security='Bearer Auth')
|
||||
|
||||
ns = api.namespace('users', description='User operations')
|
||||
|
||||
user_fields = api.model("User", {
|
||||
'id': fields.Integer(readonly=True, description='The user unique identifier'),
|
||||
'email': fields.String(required=True, description='The email of the user'),
|
||||
'name': fields.String(required=True, description='The first name last name patronymic of the user'),
|
||||
'phone': fields.String(required=True, description='The phone of the user'),
|
||||
'password_hash': fields.String(required=True, description='The password of the user'),
|
||||
'address': fields.String(required=True, description='The address of the user'),
|
||||
'role': fields.Integer(required=True, description='The role of the user')
|
||||
})
|
||||
|
||||
user_token = api.model("UserToken", {
|
||||
'id': fields.Integer(readonly=True, description='The user unique identifier'),
|
||||
'email': fields.String(required=True, description='The email of the user'),
|
||||
'name': fields.String(required=True, description='The first name last name patronymic of the user'),
|
||||
'role': fields.String(required=True, description='The role of the user'),
|
||||
'token': fields.String(readonly=True, description='The token of the user')
|
||||
})
|
||||
|
||||
|
||||
def check_data(values):
|
||||
check = True
|
||||
i = 0
|
||||
while (i < len(values)) and ((type(values[i]) == str and values[i] != '') or
|
||||
(type(values[i]) == int and values[i] != 0) or
|
||||
(type(values[i]) == float and values[i] != 0.0)):
|
||||
i += 1
|
||||
if i > len(values):
|
||||
check = False
|
||||
return check
|
||||
|
||||
|
||||
@app.before_request
|
||||
def handle_preflight():
|
||||
if request.method == "OPTIONS":
|
||||
response = make_response()
|
||||
response.headers.add("Access-Control-Allow-Origin", "*")
|
||||
response.headers.add("Access-Control-Allow-Headers", "Content-Type,Authorization")
|
||||
response.headers.add("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,OPTIONS")
|
||||
return response
|
||||
|
||||
|
||||
@ns.route('/')
|
||||
class UserList(Resource):
|
||||
@marshal_with(user_fields)
|
||||
@jwt_required()
|
||||
def get(self):
|
||||
try:
|
||||
user = UserService.get_user_id(int(get_jwt_identity()))
|
||||
if user["role"].value == 1:
|
||||
abort(403, message="This method is forbidding for this user.")
|
||||
if user["id"] == 1:
|
||||
return UserService.get_all_users()
|
||||
return UserService.get_all_users_role()
|
||||
except Exception as e:
|
||||
abort(500, message=f"Internal Server Error: {str(e)}")
|
||||
|
||||
@ns.doc(params={
|
||||
'email': {'description': 'The email of the user', 'required': True, 'type': 'string'},
|
||||
'name': {'description': 'The first name last name patronymic of the user', 'required': True, 'type': 'string'},
|
||||
'phone': {'description': 'The address of the user', 'required': True, 'type': 'string'},
|
||||
'password_hash': {'description': 'The password of the user', 'required': True, 'type': 'string'},
|
||||
'address': {'description': 'The address of the user', 'required': True, 'type': 'string'},
|
||||
'role': {'description': 'The role of the user', 'required': True, 'type': 'integer'}
|
||||
})
|
||||
@marshal_with(user_token)
|
||||
def post(self):
|
||||
email = request.args.get('email', default='', type=str)
|
||||
name = request.args.get('name', default='', type=str)
|
||||
phone = request.args.get('phone', default='', type=str)
|
||||
password_hash = request.args.get('password_hash', default='', type=str)
|
||||
address = request.args.get('address', default='', type=str)
|
||||
role = request.args.get('role', default='', type=int)
|
||||
if not check_data([email, name, phone, password_hash, address, role]):
|
||||
abort(400, message="Incorrect input data.")
|
||||
try:
|
||||
user = UserDTO(email=email, name=name, phone=phone, password_hash=password_hash,
|
||||
address=address, role=role)
|
||||
new_user = UserService.add_user(user)
|
||||
return new_user, 201
|
||||
except SQLAlchemyError as e:
|
||||
abort(500, message=f"Database error: {str(e)}")
|
||||
|
||||
|
||||
@ns.route('/<int:id>')
|
||||
@ns.response(404, 'User not found')
|
||||
@ns.param('id', 'The user identifier')
|
||||
class User(Resource):
|
||||
@marshal_with(user_fields)
|
||||
@jwt_required()
|
||||
def get(self, id):
|
||||
user = UserService.get_user_id(int(get_jwt_identity()))
|
||||
if user["role"].value == 1:
|
||||
abort(403, message="This method is forbidding for this user.")
|
||||
|
||||
if user is None:
|
||||
abort(404, message=f"Model with ID {id} does not exist.")
|
||||
try:
|
||||
user = UserService.get_user_id(id, True)
|
||||
return user, 200
|
||||
except Exception as e:
|
||||
abort(500, message=f"Internal Server Error: {str(e)}")
|
||||
|
||||
@ns.expect(user_fields)
|
||||
@marshal_with(user_fields)
|
||||
@jwt_required()
|
||||
def put(self, id):
|
||||
email = api.payload['email']
|
||||
name = api.payload['name']
|
||||
phone = api.payload['phone']
|
||||
password_hash = api.payload['password_hash']
|
||||
address = api.payload['address']
|
||||
role = api.payload['role']
|
||||
if not check_data([email, name, password_hash, address, role]):
|
||||
abort(400, message="Incorrect input data.")
|
||||
try:
|
||||
user = UserService.get_user_id(int(get_jwt_identity()))
|
||||
if user["role"].value == "user":
|
||||
abort(403, message="This method is forbidding for this user.")
|
||||
user = UserService.get_user_id(id)
|
||||
if user is None:
|
||||
abort(404, message=f"Model with ID {id} does not exist.")
|
||||
user = UserDTO(email=email, name=name, phone=phone, password_hash=password_hash,
|
||||
address=address, role=role)
|
||||
updated_user = UserService.update_user(id, user)
|
||||
return updated_user, 201
|
||||
except SQLAlchemyError as e:
|
||||
abort(500, message=f"Database error: {str(e)}")
|
||||
|
||||
|
||||
@ns.route('/getuser')
|
||||
class UserAuthentication(Resource):
|
||||
@ns.doc(params={
|
||||
'email': {'description': 'User email', 'required': True, 'type': 'string'},
|
||||
'password': {'description': 'User password', 'required': True, 'type': 'string'}
|
||||
})
|
||||
@marshal_with(user_token)
|
||||
def get(self):
|
||||
email = request.args.get('email', default='', type=str)
|
||||
password = request.args.get('password', default='', type=str)
|
||||
if not check_data([email, password]):
|
||||
abort(400, message="Incorrect input data.")
|
||||
try:
|
||||
user = UserService.get_user(email, password)
|
||||
if user is None:
|
||||
abort(404, message="User not found!")
|
||||
return user, 200
|
||||
except Exception as e:
|
||||
abort(500, message=f"Internal Server Error: {str(e)}")
|
@ -1,15 +0,0 @@
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import DeclarativeBase, sessionmaker
|
||||
|
||||
from config import settings
|
||||
|
||||
engine = create_engine(
|
||||
url=settings.DATABASE_URL,
|
||||
echo=True
|
||||
)
|
||||
|
||||
SessionFactory = sessionmaker(engine)
|
||||
|
||||
|
||||
class Base(DeclarativeBase):
|
||||
pass
|
@ -1,21 +0,0 @@
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import Optional
|
||||
|
||||
|
||||
class CategoryDTO(BaseModel):
|
||||
id: Optional[int] = Field(default=None)
|
||||
name: str = Field(default="Default Category Name")
|
||||
description: str = Field(default="Default Description")
|
||||
feature: dict = Field(default={"part_1": "part"})
|
||||
parent_category_id: Optional[int] = Field(default=None)
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, type(self)):
|
||||
return False
|
||||
for attr in ["name", "description", "feature", "parent_category_id"]:
|
||||
if getattr(self, attr) != getattr(other, attr):
|
||||
return False
|
||||
return True
|
||||
|
||||
def to_dictionary(self) -> dict:
|
||||
return self.model_dump(exclude={"id"})
|
@ -1,19 +0,0 @@
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import Optional
|
||||
|
||||
|
||||
class ManufacturerDTO(BaseModel):
|
||||
id: Optional[int] = Field(default=None)
|
||||
name: str = Field(default="Default Manufacturer Name")
|
||||
country: str = Field(default="USA")
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, type(self)):
|
||||
return False
|
||||
for attr in ["name", "country"]:
|
||||
if getattr(self, attr) != getattr(other, attr):
|
||||
return False
|
||||
return True
|
||||
|
||||
def to_dictionary(self) -> dict:
|
||||
return self.model_dump(exclude={"id"})
|
@ -1,27 +0,0 @@
|
||||
from pydantic import BaseModel, Field
|
||||
from datetime import date
|
||||
from typing import Optional
|
||||
from application.models.Orders import OrderStatus
|
||||
|
||||
|
||||
class OrderDTO(BaseModel):
|
||||
id: Optional[int] = Field(default=None)
|
||||
order_date: date = Field(default_factory=date.today)
|
||||
total_amount: float = Field(default=0.0)
|
||||
status: OrderStatus = Field(default=OrderStatus.in_processing)
|
||||
user_id: int = Field(default=1)
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, type(self)):
|
||||
return False
|
||||
for attr in ["order_date", "total_amount", "status", "user_id"]:
|
||||
if getattr(self, attr) != getattr(other, attr):
|
||||
return False
|
||||
return True
|
||||
|
||||
def to_dictionary(self) -> dict:
|
||||
return self.model_dump(exclude={"id"})
|
||||
|
||||
|
||||
class OrderDTOInt(OrderDTO):
|
||||
status: int = Field(default=0)
|
@ -1,21 +0,0 @@
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import Optional
|
||||
|
||||
|
||||
class OrderItemDTO(BaseModel):
|
||||
id: Optional[int] = Field(default=None)
|
||||
count: int = Field(default=1)
|
||||
price: float = Field(default=0.0)
|
||||
part_id: int = Field(default=1)
|
||||
order_id: int = Field(default=1)
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, type(self)):
|
||||
return False
|
||||
for attr in ["count", "price", "part_id", "order_id"]:
|
||||
if getattr(self, attr) != getattr(other, attr):
|
||||
return False
|
||||
return True
|
||||
|
||||
def to_dictionary(self) -> dict:
|
||||
return self.model_dump(exclude={"id"})
|
@ -1,38 +0,0 @@
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import Optional
|
||||
|
||||
|
||||
class PartDTO(BaseModel):
|
||||
id: Optional[int] = Field(default=None)
|
||||
name: str = Field(default="Default Part Name")
|
||||
description: str = Field(default="Default Description")
|
||||
feature: dict = Field(default={"part_1": "part"})
|
||||
price: float = Field(default=0.0)
|
||||
category_id: int = Field(default=1)
|
||||
manufacturer_id: int = Field(default=1)
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, type(self)):
|
||||
return False
|
||||
for attr in ["name", "description", "feature", "price", "category_id", "manufacturer_id"]:
|
||||
if getattr(self, attr) != getattr(other, attr):
|
||||
return False
|
||||
return True
|
||||
|
||||
def to_dictionary(self) -> dict:
|
||||
return self.model_dump(exclude={"id"})
|
||||
|
||||
|
||||
parts_mapping = {
|
||||
"mappings": {
|
||||
"properties": {
|
||||
"id": {"type": "integer"},
|
||||
"name": {"type": "keyword"},
|
||||
"description": {"type": "text"},
|
||||
"feature": {"type": "object"},
|
||||
"price": {"type": "float"},
|
||||
"category_id": {"type": "integer"},
|
||||
"manufacturer_id": {"type": "integer"}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,39 +0,0 @@
|
||||
from pydantic import BaseModel, Field
|
||||
from datetime import date
|
||||
from typing import Optional
|
||||
|
||||
|
||||
class ReviewDTO(BaseModel):
|
||||
id: Optional[int] = Field(default=None)
|
||||
rating: int = Field(default=5)
|
||||
comment: str = Field(default="Default Comment")
|
||||
created_at: date = Field(default_factory=date.today)
|
||||
updated_at: date = Field(default_factory=date.today)
|
||||
user_id: int = Field(default=1)
|
||||
part_id: int = Field(default=1)
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, type(self)):
|
||||
return False
|
||||
for attr in ["rating", "comment", "created_at", "updated_at", "user_id", "part_id"]:
|
||||
if getattr(self, attr) != getattr(other, attr):
|
||||
return False
|
||||
return True
|
||||
|
||||
def to_dictionary(self) -> dict:
|
||||
return self.model_dump(exclude={"id"})
|
||||
|
||||
|
||||
reviews_mapping = {
|
||||
"mappings": {
|
||||
"properties": {
|
||||
"id": {"type": "integer"},
|
||||
"rating": {"type": "integer"},
|
||||
"comment": {"type": "text"},
|
||||
"created_at": {"type": "date"},
|
||||
"updated_at": {"type": "date"},
|
||||
"user_id": {"type": "integer"},
|
||||
"part_id": {"type": "integer"}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import Optional
|
||||
from application.models.Users import RoleUser
|
||||
|
||||
|
||||
class UserDTO(BaseModel):
|
||||
id: Optional[int] = Field(default=None)
|
||||
email: str = Field(default="default@email.com")
|
||||
name: str = Field(default="Default User Name")
|
||||
phone: str = Field(default="+1234567890")
|
||||
password_hash: str = Field(default="123")
|
||||
address: str = Field(default="Default Address")
|
||||
role: RoleUser = Field(default=RoleUser.user)
|
||||
cart: dict = Field(default={})
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, type(self)):
|
||||
return False
|
||||
for attr in ["email", "name", "phone", "password_hash", "address", "role", "cart"]:
|
||||
if getattr(self, attr) != getattr(other, attr):
|
||||
return False
|
||||
return True
|
||||
|
||||
def to_dictionary(self) -> dict:
|
||||
return self.model_dump(exclude={"id"})
|
||||
|
||||
|
||||
class UserDTOInt(UserDTO):
|
||||
role: int = Field(default=1)
|
||||
|
||||
|
||||
class UserDTOToken(BaseModel):
|
||||
id: Optional[int] = Field(default=1)
|
||||
email: str = Field(default="user@mail.ru")
|
||||
name: str = Field(default="User User")
|
||||
role: int = Field(default=1)
|
||||
token: str = Field(default="Token")
|
@ -1,55 +0,0 @@
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from elasticsearch.exceptions import NotFoundError
|
||||
|
||||
sys.path.append(str(Path(__file__).parent.parent))
|
||||
|
||||
from application.config import es
|
||||
from application.database import Base, engine
|
||||
from application.dto.UserDTO import UserDTO
|
||||
from application.services.UserService import UserService
|
||||
from application.controllers.CartController import api
|
||||
from application.dto.PartDTO import parts_mapping
|
||||
from application.dto.ReviewDTO import reviews_mapping
|
||||
|
||||
|
||||
def create_tables():
|
||||
engine.echo = False
|
||||
Base.metadata.drop_all(engine)
|
||||
Base.metadata.create_all(engine)
|
||||
user = UserDTO(email="admin@gmail.com", name="admin", password_hash="admin", phone="+992(501)226245",
|
||||
address="everywhere", role=0, cart={})
|
||||
add_user = UserService.add_default_user(user)
|
||||
engine.echo = True
|
||||
print(f"{add_user=}")
|
||||
|
||||
|
||||
def create_indices():
|
||||
if not es.indices.exists(index='parts'):
|
||||
es.indices.create(index='parts', body=parts_mapping)
|
||||
print("Индекс parts успешно создан")
|
||||
|
||||
if not es.indices.exists(index='reviews'):
|
||||
es.indices.create(index='reviews', body=reviews_mapping)
|
||||
print("Индекс reviews успешно создан")
|
||||
|
||||
|
||||
def delete_indices():
|
||||
indices = es.indices.get_alias().keys()
|
||||
|
||||
for index in indices:
|
||||
try:
|
||||
if (index == "parts") or (index == "reviews"):
|
||||
es.indices.delete(index=index)
|
||||
print(f"Индекс {index} успешно удален")
|
||||
except NotFoundError:
|
||||
print(f"Индекс {index} не найден")
|
||||
except Exception as e:
|
||||
print(f"Ошибка при удалении индекса {index}: {str(e)}")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
delete_indices()
|
||||
create_tables()
|
||||
create_indices()
|
||||
api.app.run(port=5001)
|
@ -1,18 +0,0 @@
|
||||
from sqlalchemy import String, Text, ForeignKey
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
from sqlalchemy.dialects.postgresql import JSONB
|
||||
from application.database import Base
|
||||
|
||||
|
||||
class Categories(Base):
|
||||
__tablename__: str = "categories"
|
||||
|
||||
id: Mapped[int] = mapped_column(primary_key=True)
|
||||
name: Mapped[str] = mapped_column(String(40), nullable=False)
|
||||
description: Mapped[str] = mapped_column(Text, nullable=False)
|
||||
feature: Mapped[dict] = mapped_column(JSONB, nullable=True)
|
||||
parent_category_id: Mapped[int] = mapped_column(ForeignKey("categories.id", ondelete="CASCADE"), nullable=True)
|
||||
|
||||
parts: Mapped[list["Parts"]] = relationship(
|
||||
back_populates="category"
|
||||
)
|
@ -1,15 +0,0 @@
|
||||
from sqlalchemy import String
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
from application.database import Base
|
||||
|
||||
|
||||
class Manufacturers(Base):
|
||||
__tablename__: str = "manufacturers"
|
||||
|
||||
id: Mapped[int] = mapped_column(primary_key=True)
|
||||
name: Mapped[str] = mapped_column(String(40), nullable=False)
|
||||
country: Mapped[str] = mapped_column(String(40), nullable=False)
|
||||
|
||||
parts: Mapped[list["Parts"]] = relationship(
|
||||
back_populates="manufacturer"
|
||||
)
|
@ -1,28 +0,0 @@
|
||||
from sqlalchemy import ForeignKey, CheckConstraint, Index, Integer
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
from application.database import Base
|
||||
|
||||
|
||||
class OrderItems(Base):
|
||||
__tablename__: str = "order_items"
|
||||
|
||||
id: Mapped[int] = mapped_column(primary_key=True)
|
||||
count: Mapped[int] = mapped_column(Integer, nullable=False)
|
||||
price: Mapped[float] = mapped_column(nullable=False)
|
||||
part_id: Mapped[int] = mapped_column(ForeignKey("parts.id", ondelete="CASCADE"))
|
||||
order_id: Mapped[int] = mapped_column(ForeignKey("orders.id", ondelete="CASCADE"))
|
||||
|
||||
part: Mapped["Parts"] = relationship(
|
||||
back_populates="order_items"
|
||||
)
|
||||
|
||||
order: Mapped["Orders"] = relationship(
|
||||
back_populates="order_items"
|
||||
)
|
||||
|
||||
__table_args__ = (
|
||||
Index("order_items_price_index", "price"),
|
||||
CheckConstraint("price > 0", name="check_price"),
|
||||
Index("count_index", "count"),
|
||||
CheckConstraint("count > 0", name="check_count")
|
||||
)
|
@ -1,35 +0,0 @@
|
||||
from datetime import date
|
||||
from enum import Enum
|
||||
from sqlalchemy import ForeignKey, Date, CheckConstraint, Index
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
from application.database import Base
|
||||
|
||||
|
||||
class OrderStatus(Enum):
|
||||
in_processing = 0
|
||||
shipped = 1
|
||||
delivered = 2
|
||||
|
||||
|
||||
class Orders(Base):
|
||||
__tablename__ = "orders"
|
||||
|
||||
id: Mapped[int] = mapped_column(primary_key=True)
|
||||
order_date: Mapped[date] = mapped_column(Date, nullable=False)
|
||||
total_amount: Mapped[float] = mapped_column(nullable=False)
|
||||
status: Mapped[OrderStatus] = mapped_column(nullable=False)
|
||||
user_id: Mapped[int] = mapped_column(ForeignKey("users.id", ondelete="CASCADE"))
|
||||
|
||||
order_items: Mapped[list["OrderItems"]] = relationship(
|
||||
back_populates="order"
|
||||
)
|
||||
|
||||
user: Mapped["Users"] = relationship(
|
||||
back_populates="orders"
|
||||
)
|
||||
|
||||
__table_args__ = (
|
||||
CheckConstraint("total_amount > 0", name="check_total_amount"),
|
||||
Index("order_date_index", "order_date"),
|
||||
Index("status_index", "status"),
|
||||
)
|
@ -1,37 +0,0 @@
|
||||
from sqlalchemy import String, Text, ForeignKey, Index, CheckConstraint
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
from sqlalchemy.dialects.postgresql import JSONB
|
||||
from application.database import Base
|
||||
|
||||
|
||||
class Parts(Base):
|
||||
__tablename__: str = "parts"
|
||||
|
||||
id: Mapped[int] = mapped_column(primary_key=True)
|
||||
name: Mapped[str] = mapped_column(String(40), nullable=False)
|
||||
description: Mapped[str] = mapped_column(Text, nullable=False)
|
||||
feature: Mapped[dict] = mapped_column(JSONB, nullable=True)
|
||||
price: Mapped[float] = mapped_column(nullable=False)
|
||||
category_id: Mapped[int] = mapped_column(ForeignKey("categories.id", ondelete="CASCADE"))
|
||||
manufacturer_id: Mapped[int] = mapped_column(ForeignKey("manufacturers.id", ondelete="CASCADE"))
|
||||
|
||||
order_items: Mapped[list["OrderItems"]] = relationship(
|
||||
back_populates="part"
|
||||
)
|
||||
|
||||
reviews: Mapped[list["Reviews"]] = relationship(
|
||||
back_populates="part"
|
||||
)
|
||||
|
||||
category: Mapped["Categories"] = relationship(
|
||||
back_populates="parts"
|
||||
)
|
||||
|
||||
manufacturer: Mapped["Manufacturers"] = relationship(
|
||||
back_populates="parts"
|
||||
)
|
||||
|
||||
__table_args__ = (
|
||||
Index("parts_price_index", "price"),
|
||||
CheckConstraint("price > 0", name="check_price")
|
||||
)
|
@ -1,29 +0,0 @@
|
||||
from datetime import date
|
||||
from sqlalchemy import ForeignKey, CheckConstraint, Index, Integer, Text, Date
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
from application.database import Base
|
||||
|
||||
|
||||
class Reviews(Base):
|
||||
__tablename__: str = "reviews"
|
||||
|
||||
id: Mapped[int] = mapped_column(primary_key=True)
|
||||
rating: Mapped[int] = mapped_column(Integer, nullable=False)
|
||||
comment: Mapped[str] = mapped_column(Text, nullable=False)
|
||||
created_at: Mapped[date] = mapped_column(Date, nullable=False)
|
||||
updated_at: Mapped[date] = mapped_column(Date, nullable=False)
|
||||
user_id: Mapped[int] = mapped_column(ForeignKey("users.id", ondelete="CASCADE"))
|
||||
part_id: Mapped[int] = mapped_column(ForeignKey("parts.id", ondelete="CASCADE"))
|
||||
|
||||
part: Mapped["Parts"] = relationship(
|
||||
back_populates="reviews"
|
||||
)
|
||||
|
||||
user: Mapped["Users"] = relationship(
|
||||
back_populates="reviews"
|
||||
)
|
||||
|
||||
__table_args__ = (
|
||||
Index("rating_index", "rating"),
|
||||
CheckConstraint("rating > 0", name="check_rating")
|
||||
)
|
@ -1,31 +0,0 @@
|
||||
from enum import Enum
|
||||
from sqlalchemy import String
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
from sqlalchemy.dialects.postgresql import JSONB
|
||||
from application.database import Base
|
||||
|
||||
|
||||
class RoleUser(Enum):
|
||||
admin = 0
|
||||
user = 1
|
||||
|
||||
|
||||
class Users(Base):
|
||||
__tablename__: str = "users"
|
||||
|
||||
id: Mapped[int] = mapped_column(primary_key=True)
|
||||
email: Mapped[str] = mapped_column(String(40), nullable=False)
|
||||
name: Mapped[str] = mapped_column(String(40), nullable=False)
|
||||
password_hash: Mapped[str] = mapped_column(String(70), nullable=False)
|
||||
phone: Mapped[str] = mapped_column(String(40), nullable=False)
|
||||
address: Mapped[str] = mapped_column(String(40), nullable=False)
|
||||
role: Mapped[RoleUser] = mapped_column(nullable=False)
|
||||
cart: Mapped[dict] = mapped_column(JSONB, nullable=True)
|
||||
|
||||
orders: Mapped[list["Orders"]] = relationship(
|
||||
back_populates="user"
|
||||
)
|
||||
|
||||
reviews: Mapped[list["Reviews"]] = relationship(
|
||||
back_populates="user"
|
||||
)
|
@ -1,17 +0,0 @@
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
|
||||
class AbstractBasicRepository(ABC):
|
||||
@classmethod
|
||||
@abstractmethod
|
||||
def add(cls, values: dict):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_all(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
@abstractmethod
|
||||
def delete(cls, id: int):
|
||||
pass
|
@ -1,27 +0,0 @@
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
|
||||
class AbstractRepository(ABC):
|
||||
@classmethod
|
||||
@abstractmethod
|
||||
def add(cls, values: dict):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
@abstractmethod
|
||||
def get(cls, id: int):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_all(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
@abstractmethod
|
||||
def update(cls, id: int, values: dict):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
@abstractmethod
|
||||
def delete(cls, id: int):
|
||||
pass
|
@ -1,21 +0,0 @@
|
||||
from sqlalchemy import select, update
|
||||
from application.database import SessionFactory
|
||||
from application.models.Users import Users
|
||||
|
||||
|
||||
class CartRepository:
|
||||
@classmethod
|
||||
def add(cls, id: int, values: dict):
|
||||
stmt = update(Users).where(Users.id == id).values(cart=values)
|
||||
with SessionFactory() as session:
|
||||
session.execute(stmt)
|
||||
session.commit()
|
||||
return cls.get(id)
|
||||
|
||||
@classmethod
|
||||
def get(cls, id: int):
|
||||
query = select(Users.cart).filter_by(id=id)
|
||||
with SessionFactory() as session:
|
||||
cart = session.execute(query)
|
||||
session.commit()
|
||||
return cart.mappings().one_or_none()
|
@ -1,48 +0,0 @@
|
||||
from abc import ABC
|
||||
from sqlalchemy import delete, insert, select, update
|
||||
from application.database import SessionFactory
|
||||
from application.repositories.AbstractRepository import AbstractRepository
|
||||
from application.models.Categories import Categories
|
||||
|
||||
|
||||
class CategoryRepository(AbstractRepository, ABC):
|
||||
@classmethod
|
||||
def add(cls, values: dict):
|
||||
with SessionFactory() as session:
|
||||
stmt = insert(Categories).values(**values).returning(Categories)
|
||||
new_category = session.execute(stmt)
|
||||
session.commit()
|
||||
return new_category.scalar_one()
|
||||
|
||||
@classmethod
|
||||
def get(cls, id: int):
|
||||
query = select(Categories.__table__.columns).filter_by(id=id)
|
||||
with SessionFactory() as session:
|
||||
category = session.execute(query)
|
||||
session.commit()
|
||||
return category.mappings().one_or_none()
|
||||
|
||||
@classmethod
|
||||
def get_all(cls):
|
||||
query = select(Categories.__table__.columns)
|
||||
with SessionFactory() as session:
|
||||
categories = session.execute(query)
|
||||
session.commit()
|
||||
return categories.mappings().all()
|
||||
|
||||
@classmethod
|
||||
def update(cls, id: int, values: dict):
|
||||
stmt = update(Categories).where(Categories.id == id).values(**values)
|
||||
with SessionFactory() as session:
|
||||
session.execute(stmt)
|
||||
session.commit()
|
||||
return cls.get(id)
|
||||
|
||||
@classmethod
|
||||
def delete(cls, id: int):
|
||||
category = cls.get(id)
|
||||
stmt = delete(Categories).where(Categories.id == id)
|
||||
with SessionFactory() as session:
|
||||
session.execute(stmt)
|
||||
session.commit()
|
||||
return category
|
@ -1,48 +0,0 @@
|
||||
from abc import ABC
|
||||
from sqlalchemy import delete, insert, select, update
|
||||
from application.database import SessionFactory
|
||||
from application.repositories.AbstractRepository import AbstractRepository
|
||||
from application.models.Manufacturers import Manufacturers
|
||||
|
||||
|
||||
class ManufacturerRepository(AbstractRepository, ABC):
|
||||
@classmethod
|
||||
def add(cls, values: dict):
|
||||
with SessionFactory() as session:
|
||||
stmt = insert(Manufacturers).values(**values).returning(Manufacturers)
|
||||
new_manufacturer = session.execute(stmt)
|
||||
session.commit()
|
||||
return new_manufacturer.scalar_one()
|
||||
|
||||
@classmethod
|
||||
def get(cls, id: int):
|
||||
query = select(Manufacturers.__table__.columns).filter_by(id=id)
|
||||
with SessionFactory() as session:
|
||||
manufacturer = session.execute(query)
|
||||
session.commit()
|
||||
return manufacturer.mappings().one_or_none()
|
||||
|
||||
@classmethod
|
||||
def get_all(cls):
|
||||
query = select(Manufacturers.__table__.columns)
|
||||
with SessionFactory() as session:
|
||||
manufacturers = session.execute(query)
|
||||
session.commit()
|
||||
return manufacturers.mappings().all()
|
||||
|
||||
@classmethod
|
||||
def update(cls, id: int, values: dict):
|
||||
stmt = update(Manufacturers).where(Manufacturers.id == id).values(**values)
|
||||
with SessionFactory() as session:
|
||||
session.execute(stmt)
|
||||
session.commit()
|
||||
return cls.get(id)
|
||||
|
||||
@classmethod
|
||||
def delete(cls, id: int):
|
||||
manufacturer = cls.get(id)
|
||||
stmt = delete(Manufacturers).where(Manufacturers.id == id)
|
||||
with SessionFactory() as session:
|
||||
session.execute(stmt)
|
||||
session.commit()
|
||||
return manufacturer
|
@ -1,40 +0,0 @@
|
||||
from abc import ABC
|
||||
from sqlalchemy import delete, insert, select
|
||||
from application.database import SessionFactory
|
||||
from application.repositories.AbstractBasicRepository import AbstractBasicRepository
|
||||
from application.models.OrderItems import OrderItems
|
||||
|
||||
|
||||
class OrderItemRepository(AbstractBasicRepository, ABC):
|
||||
@classmethod
|
||||
def add(cls, values: dict):
|
||||
with SessionFactory() as session:
|
||||
stmt = insert(OrderItems).values(**values).returning(OrderItems)
|
||||
new_order_item = session.execute(stmt)
|
||||
session.commit()
|
||||
return new_order_item.scalar_one()
|
||||
|
||||
@classmethod
|
||||
def get(cls, id: int):
|
||||
query = select(OrderItems.__table__.columns).filter_by(id=id)
|
||||
with SessionFactory() as session:
|
||||
order_item = session.execute(query)
|
||||
session.commit()
|
||||
return order_item.mappings().one_or_none()
|
||||
|
||||
@classmethod
|
||||
def get_all(cls, order_id: int):
|
||||
query = (select(OrderItems.__table__.columns).filter_by(order_id=order_id))
|
||||
with SessionFactory() as session:
|
||||
order_items = session.execute(query)
|
||||
session.commit()
|
||||
return order_items.mappings().all()
|
||||
|
||||
@classmethod
|
||||
def delete(cls, id: int):
|
||||
order_item = cls.get(id)
|
||||
stmt = delete(OrderItems).where(OrderItems.id == id)
|
||||
with SessionFactory() as session:
|
||||
session.execute(stmt)
|
||||
session.commit()
|
||||
return order_item
|
@ -1,40 +0,0 @@
|
||||
from abc import ABC
|
||||
from sqlalchemy import delete, insert, select
|
||||
from application.database import SessionFactory
|
||||
from application.repositories.AbstractBasicRepository import AbstractBasicRepository
|
||||
from application.models.Orders import Orders
|
||||
|
||||
|
||||
class OrderRepository(AbstractBasicRepository, ABC):
|
||||
@classmethod
|
||||
def add(cls, values: dict):
|
||||
with SessionFactory() as session:
|
||||
stmt = insert(Orders).values(**values).returning(Orders)
|
||||
new_order = session.execute(stmt)
|
||||
session.commit()
|
||||
return new_order.scalar_one()
|
||||
|
||||
@classmethod
|
||||
def get(cls, id: int):
|
||||
query = select(Orders.__table__.columns).filter_by(id=id)
|
||||
with SessionFactory() as session:
|
||||
order = session.execute(query)
|
||||
session.commit()
|
||||
return order.mappings().one_or_none()
|
||||
|
||||
@classmethod
|
||||
def get_all(cls, user_id: int):
|
||||
query = (select(Orders.__table__.columns).filter_by(user_id=user_id))
|
||||
with SessionFactory() as session:
|
||||
orders = session.execute(query)
|
||||
session.commit()
|
||||
return orders.mappings().all()
|
||||
|
||||
@classmethod
|
||||
def delete(cls, id: int):
|
||||
order = cls.get(id)
|
||||
stmt = delete(Orders).where(Orders.id == id)
|
||||
with SessionFactory() as session:
|
||||
session.execute(stmt)
|
||||
session.commit()
|
||||
return order
|
@ -1,56 +0,0 @@
|
||||
from abc import ABC
|
||||
from sqlalchemy import delete, insert, select, update
|
||||
from application.database import SessionFactory
|
||||
from application.repositories.AbstractRepository import AbstractRepository
|
||||
from application.models.Parts import Parts
|
||||
|
||||
|
||||
class PartRepository(AbstractRepository, ABC):
|
||||
@classmethod
|
||||
def add(cls, values: dict):
|
||||
with SessionFactory() as session:
|
||||
stmt = insert(Parts).values(**values).returning(Parts)
|
||||
new_part = session.execute(stmt)
|
||||
session.commit()
|
||||
return new_part.scalar_one()
|
||||
|
||||
@classmethod
|
||||
def get(cls, id: int):
|
||||
query = select(Parts.__table__.columns).filter_by(id=id)
|
||||
with SessionFactory() as session:
|
||||
part = session.execute(query)
|
||||
session.commit()
|
||||
return part.mappings().one_or_none()
|
||||
|
||||
@classmethod
|
||||
def get_all(cls):
|
||||
query = select(Parts.__table__.columns)
|
||||
with SessionFactory() as session:
|
||||
parts = session.execute(query)
|
||||
session.commit()
|
||||
return parts.mappings().all()
|
||||
|
||||
@classmethod
|
||||
def update(cls, id: int, values: dict):
|
||||
stmt = update(Parts).where(Parts.id == id).values(**values)
|
||||
with SessionFactory() as session:
|
||||
session.execute(stmt)
|
||||
session.commit()
|
||||
return cls.get(id)
|
||||
|
||||
@classmethod
|
||||
def delete(cls, id: int):
|
||||
part = cls.get(id)
|
||||
stmt = delete(Parts).where(Parts.id == id)
|
||||
with SessionFactory() as session:
|
||||
session.execute(stmt)
|
||||
session.commit()
|
||||
return part
|
||||
|
||||
@classmethod
|
||||
def get_all_category_id(cls, category_id: int):
|
||||
query = (select(Parts.__table__.columns).filter_by(category_id=category_id))
|
||||
with SessionFactory() as session:
|
||||
parts = session.execute(query)
|
||||
session.commit()
|
||||
return parts.mappings().all()
|
@ -1,41 +0,0 @@
|
||||
from abc import ABC
|
||||
from sqlalchemy import delete, insert, select
|
||||
from application.database import SessionFactory
|
||||
from application.repositories.AbstractBasicRepository import AbstractBasicRepository
|
||||
from application.models.Reviews import Reviews
|
||||
|
||||
|
||||
class ReviewRepository(AbstractBasicRepository, ABC):
|
||||
@classmethod
|
||||
def add(cls, values: dict):
|
||||
with SessionFactory() as session:
|
||||
stmt = insert(Reviews).values(**values).returning(Reviews)
|
||||
new_review = session.execute(stmt)
|
||||
session.commit()
|
||||
return new_review.scalar_one()
|
||||
|
||||
@classmethod
|
||||
def get(cls, id: int):
|
||||
query = select(Reviews.__table__.columns).filter_by(id=id)
|
||||
with SessionFactory() as session:
|
||||
review = session.execute(query)
|
||||
session.commit()
|
||||
return review.mappings().one_or_none()
|
||||
|
||||
@classmethod
|
||||
def get_all(cls, part_id: int):
|
||||
query = (select(Reviews.__table__.columns).filter_by(part_id=part_id))
|
||||
with SessionFactory() as session:
|
||||
reviews = session.execute(query)
|
||||
session.commit()
|
||||
return reviews.mappings().all()
|
||||
|
||||
@classmethod
|
||||
def delete(cls, id: int):
|
||||
review = cls.get(id)
|
||||
stmt = delete(Reviews).where(Reviews.id == id)
|
||||
with SessionFactory() as session:
|
||||
session.execute(stmt)
|
||||
session.commit()
|
||||
return review
|
||||
|
@ -1,54 +0,0 @@
|
||||
from sqlalchemy import insert, select, update
|
||||
from application.database import SessionFactory
|
||||
from application.models.Users import Users, RoleUser
|
||||
|
||||
|
||||
class UserRepository:
|
||||
@classmethod
|
||||
def add_user(cls, values: dict):
|
||||
with SessionFactory() as session:
|
||||
stmt = insert(Users).values(**values).returning(Users)
|
||||
new_user = session.execute(stmt)
|
||||
session.commit()
|
||||
return new_user.scalar_one()
|
||||
|
||||
@classmethod
|
||||
def get_user(cls, email: str, password: str):
|
||||
query = (select(Users.__table__.columns)
|
||||
.filter_by(email=email).filter_by(password_hash=password))
|
||||
with SessionFactory() as session:
|
||||
user = session.execute(query)
|
||||
session.commit()
|
||||
return user.mappings().one_or_none()
|
||||
|
||||
@classmethod
|
||||
def get_user_id(cls, id: int):
|
||||
query = select(Users.__table__.columns).filter_by(id=id)
|
||||
with SessionFactory() as session:
|
||||
user = session.execute(query)
|
||||
session.commit()
|
||||
return user.mappings().one_or_none()
|
||||
|
||||
@classmethod
|
||||
def get_all_users_role(cls):
|
||||
query = select(Users.__table__.columns).filter_by(role=RoleUser.user)
|
||||
with SessionFactory() as session:
|
||||
users = session.execute(query)
|
||||
session.commit()
|
||||
return users.mappings().all()
|
||||
|
||||
@classmethod
|
||||
def get_all_users(cls):
|
||||
query = select(Users)
|
||||
with SessionFactory() as session:
|
||||
users = session.execute(query)
|
||||
session.commit()
|
||||
return users.scalars().all()
|
||||
|
||||
@classmethod
|
||||
def update_user(cls, id: int, values: dict):
|
||||
stmt = update(Users).where(Users.id == id).values(**values)
|
||||
with SessionFactory() as session:
|
||||
session.execute(stmt)
|
||||
session.commit()
|
||||
return cls.get_user_id(id)
|
@ -1,6 +0,0 @@
|
||||
from abc import ABC
|
||||
from application.repositories.AbstractBasicRepository import AbstractBasicRepository
|
||||
|
||||
|
||||
class AbstractBasicService(AbstractBasicRepository, ABC):
|
||||
pass
|
@ -1,6 +0,0 @@
|
||||
from abc import ABC
|
||||
from application.repositories.AbstractRepository import AbstractRepository
|
||||
|
||||
|
||||
class AbstractService(AbstractRepository, ABC):
|
||||
pass
|
@ -1,51 +0,0 @@
|
||||
from application.repositories.CartRepository import CartRepository
|
||||
from application.services.AbstractBasicService import AbstractBasicService
|
||||
|
||||
|
||||
class CartService(AbstractBasicService):
|
||||
@classmethod
|
||||
def add(cls, user_id: int, values: dict):
|
||||
user = CartRepository.get(user_id)
|
||||
current_cart = user["cart"]
|
||||
if len(current_cart) > 0:
|
||||
if len(current_cart["cart"]) > 0:
|
||||
current_cart["cart"] += [values]
|
||||
else:
|
||||
current_cart = {"cart": [values]}
|
||||
cart = CartRepository.add(user_id, current_cart)
|
||||
if len(cart["cart"]) > 0:
|
||||
if len(cart["cart"]["cart"]) > 0:
|
||||
cart = cart["cart"]["cart"][len(cart["cart"]["cart"]) - 1]
|
||||
return cart
|
||||
|
||||
@classmethod
|
||||
def get_all(cls, user_id: int):
|
||||
cart = CartRepository.get(user_id)
|
||||
print(f"all carts from database: {cart}")
|
||||
if len(cart["cart"]) > 0:
|
||||
if len(cart["cart"]["cart"]) > 0:
|
||||
cart = cart["cart"]["cart"]
|
||||
return cart
|
||||
|
||||
@classmethod
|
||||
def get(cls, id: int, user_id: int):
|
||||
carts = cls.get_all(user_id)
|
||||
current_cart = None
|
||||
for cart in carts:
|
||||
if cart["id"] == id:
|
||||
current_cart = cart
|
||||
break
|
||||
return current_cart
|
||||
|
||||
@classmethod
|
||||
def delete(cls, id: int, user_id: int):
|
||||
carts = cls.get_all(user_id)
|
||||
cart = cls.get(id, user_id)
|
||||
if isinstance(carts, list):
|
||||
carts.remove(cart)
|
||||
if len(carts) > 0:
|
||||
CartRepository.add(user_id, {"cart": carts})
|
||||
else:
|
||||
CartRepository.add(user_id, {})
|
||||
return cart
|
||||
|
@ -1,33 +0,0 @@
|
||||
from pydantic import TypeAdapter
|
||||
from application.dto.CategoryDTO import CategoryDTO
|
||||
from application.repositories.CategoryRepository import CategoryRepository
|
||||
from application.services.AbstractService import AbstractService
|
||||
|
||||
|
||||
class CategoryService(AbstractService):
|
||||
@classmethod
|
||||
def add(cls, category: CategoryDTO):
|
||||
category_dictionary = category.to_dictionary()
|
||||
new_category = CategoryRepository.add(category_dictionary)
|
||||
return TypeAdapter(CategoryDTO).dump_python(new_category)
|
||||
|
||||
@classmethod
|
||||
def get(cls, id: int):
|
||||
category = CategoryRepository.get(id)
|
||||
return TypeAdapter(CategoryDTO).dump_python(category)
|
||||
|
||||
@classmethod
|
||||
def get_all(cls):
|
||||
categories = CategoryRepository.get_all()
|
||||
return TypeAdapter(list[CategoryDTO]).dump_python(categories)
|
||||
|
||||
@classmethod
|
||||
def update(cls, id: int, category: CategoryDTO):
|
||||
category_dictionary = category.to_dictionary()
|
||||
new_category = CategoryRepository.update(id, category_dictionary)
|
||||
return TypeAdapter(CategoryDTO).dump_python(new_category)
|
||||
|
||||
@classmethod
|
||||
def delete(cls, id: int):
|
||||
category = CategoryRepository.delete(id)
|
||||
return TypeAdapter(CategoryDTO).dump_python(category)
|
@ -1,33 +0,0 @@
|
||||
from pydantic import TypeAdapter
|
||||
from application.dto.ManufacturerDTO import ManufacturerDTO
|
||||
from application.repositories.ManufacturerRepository import ManufacturerRepository
|
||||
from application.services.AbstractService import AbstractService
|
||||
|
||||
|
||||
class ManufacturerService(AbstractService):
|
||||
@classmethod
|
||||
def add(cls, manufacturer: ManufacturerDTO):
|
||||
manufacturer_dictionary = manufacturer.to_dictionary()
|
||||
new_manufacturer = ManufacturerRepository.add(manufacturer_dictionary)
|
||||
return TypeAdapter(ManufacturerDTO).dump_python(new_manufacturer)
|
||||
|
||||
@classmethod
|
||||
def get(cls, id: int):
|
||||
manufacturer = ManufacturerRepository.get(id)
|
||||
return TypeAdapter(ManufacturerDTO).dump_python(manufacturer)
|
||||
|
||||
@classmethod
|
||||
def get_all(cls):
|
||||
manufacturers = ManufacturerRepository.get_all()
|
||||
return TypeAdapter(list[ManufacturerDTO]).dump_python(manufacturers)
|
||||
|
||||
@classmethod
|
||||
def update(cls, id: int, manufacturer: ManufacturerDTO):
|
||||
manufacturer_dictionary = manufacturer.to_dictionary()
|
||||
new_manufacturer = ManufacturerRepository.update(id, manufacturer_dictionary)
|
||||
return TypeAdapter(ManufacturerDTO).dump_python(new_manufacturer)
|
||||
|
||||
@classmethod
|
||||
def delete(cls, id: int):
|
||||
manufacturer = ManufacturerRepository.delete(id)
|
||||
return TypeAdapter(ManufacturerDTO).dump_python(manufacturer)
|
@ -1,39 +0,0 @@
|
||||
from pydantic import TypeAdapter
|
||||
from application.dto.OrderItemDTO import OrderItemDTO
|
||||
from application.repositories.PartRepository import PartRepository
|
||||
from application.repositories.OrderItemRepository import OrderItemRepository
|
||||
from application.services.AbstractBasicService import AbstractBasicService
|
||||
|
||||
|
||||
class OrderItemService(AbstractBasicService):
|
||||
@classmethod
|
||||
def add(cls, order_item: OrderItemDTO):
|
||||
order_item_dictionary = order_item.to_dictionary()
|
||||
new_order_item = OrderItemRepository.add(order_item_dictionary)
|
||||
return TypeAdapter(OrderItemDTO).dump_python(new_order_item)
|
||||
|
||||
@classmethod
|
||||
def get(cls, id: int):
|
||||
order_item = OrderItemRepository.get(id)
|
||||
return TypeAdapter(OrderItemDTO).dump_python(order_item)
|
||||
|
||||
@classmethod
|
||||
def get_all(cls, order_id: int):
|
||||
order_items = OrderItemRepository.get_all(order_id)
|
||||
return TypeAdapter(list[OrderItemDTO]).dump_python(order_items)
|
||||
|
||||
@classmethod
|
||||
def delete(cls, id: int):
|
||||
order_item = OrderItemRepository.delete(id)
|
||||
return TypeAdapter(OrderItemDTO).dump_python(order_item)
|
||||
|
||||
@classmethod
|
||||
def get_all_parts(cls, order_id: int):
|
||||
order_items = OrderItemRepository.get_all(order_id)
|
||||
new_order_items = []
|
||||
for order_item in order_items:
|
||||
part = PartRepository.get(order_item["part_id"])
|
||||
new_order_items += [{"id": order_item["id"], "count": order_item["count"],
|
||||
"price": order_item["price"], "order_id": order_item["order_id"],
|
||||
"name_part": part["name"]}]
|
||||
return new_order_items
|
@ -1,54 +0,0 @@
|
||||
from pydantic import TypeAdapter
|
||||
from typing import List
|
||||
from application.dto.OrderDTO import OrderDTO, OrderDTOInt
|
||||
from application.dto.OrderItemDTO import OrderItemDTO
|
||||
from application.repositories.OrderRepository import OrderRepository
|
||||
from application.repositories.OrderItemRepository import OrderItemRepository
|
||||
from application.services.AbstractBasicService import AbstractBasicService
|
||||
|
||||
|
||||
class OrderService(AbstractBasicService):
|
||||
@classmethod
|
||||
def _to_int(cls, order: OrderDTO):
|
||||
new_order = OrderDTOInt(
|
||||
id=order["id"],
|
||||
order_date=order["order_date"],
|
||||
total_amount=order["total_amount"],
|
||||
status=order["status"].value,
|
||||
user_id=order["user_id"]
|
||||
)
|
||||
return new_order
|
||||
|
||||
@classmethod
|
||||
def add(cls, order: OrderDTO):
|
||||
order_dictionary = order.to_dictionary()
|
||||
new_order = OrderRepository.add(order_dictionary)
|
||||
return TypeAdapter(OrderDTO).dump_python(new_order)
|
||||
|
||||
@classmethod
|
||||
def get(cls, id: int):
|
||||
order = OrderRepository.get(id)
|
||||
order = cls._to_int(order)
|
||||
return TypeAdapter(OrderDTOInt).dump_python(order)
|
||||
|
||||
@classmethod
|
||||
def get_all(cls, user_id: int):
|
||||
orders = OrderRepository.get_all(user_id)
|
||||
new_orders = []
|
||||
for order in orders:
|
||||
new_orders += [cls._to_int(order)]
|
||||
return TypeAdapter(list[OrderDTOInt]).dump_python(new_orders)
|
||||
|
||||
@classmethod
|
||||
def delete(cls, id: int):
|
||||
order = OrderRepository.delete(id)
|
||||
return TypeAdapter(OrderDTO).dump_python(order)
|
||||
|
||||
@classmethod
|
||||
def add_order_items(cls, order: OrderDTO, items: List[OrderItemDTO]):
|
||||
new_order = cls.add(order)
|
||||
for item in items:
|
||||
new_item = {"count": item["count"], "price": item["price"],
|
||||
"part_id": item["part_id"], "order_id": new_order["id"]}
|
||||
OrderItemRepository.add(new_item)
|
||||
return new_order
|
@ -1,194 +0,0 @@
|
||||
from pydantic import TypeAdapter
|
||||
from application.config import es
|
||||
from application.dto.PartDTO import PartDTO, parts_mapping
|
||||
from application.repositories.PartRepository import PartRepository
|
||||
from application.services.ManufacturerService import ManufacturerService
|
||||
from application.services.AbstractService import AbstractService
|
||||
|
||||
|
||||
class PartService(AbstractService):
|
||||
@classmethod
|
||||
def add(cls, part: PartDTO):
|
||||
part_dictionary = part.to_dictionary()
|
||||
new_part = PartRepository.add(part_dictionary)
|
||||
new_part = TypeAdapter(PartDTO).dump_python(new_part)
|
||||
PartIndexManager.add(part=PartDTO(**new_part))
|
||||
return new_part
|
||||
|
||||
@classmethod
|
||||
def get(cls, id: int):
|
||||
part = PartRepository.get(id)
|
||||
return TypeAdapter(PartDTO).dump_python(part)
|
||||
|
||||
@classmethod
|
||||
def get_all(cls):
|
||||
parts = PartRepository.get_all()
|
||||
return TypeAdapter(list[PartDTO]).dump_python(parts)
|
||||
|
||||
@classmethod
|
||||
def update(cls, id: int, part: PartDTO):
|
||||
part_dictionary = part.to_dictionary()
|
||||
new_part = PartRepository.update(id, part_dictionary)
|
||||
PartIndexManager.update(id, part_dictionary)
|
||||
return TypeAdapter(PartDTO).dump_python(new_part)
|
||||
|
||||
@classmethod
|
||||
def delete(cls, id: int):
|
||||
part = PartRepository.delete(id)
|
||||
PartIndexManager.delete(id)
|
||||
return TypeAdapter(PartDTO).dump_python(part)
|
||||
|
||||
@classmethod
|
||||
def get_all_category_id(cls, category_id: int):
|
||||
parts = PartRepository.get_all_category_id(category_id)
|
||||
return TypeAdapter(list[PartDTO]).dump_python(parts)
|
||||
|
||||
@classmethod
|
||||
def get_part_manufacturer(cls, id: int):
|
||||
part = cls.get(id)
|
||||
manufacturer = ManufacturerService.get(part["manufacturer_id"])
|
||||
new_part = {"id": part["id"], "name": part["name"], "description": part["description"],
|
||||
"feature": part["feature"], "price": part["price"], "category_id": part["category_id"],
|
||||
"manufacturer": manufacturer}
|
||||
return new_part
|
||||
|
||||
|
||||
class PartIndexManager:
|
||||
@classmethod
|
||||
def get_part(cls, part: dict):
|
||||
return {
|
||||
'id': part["id"],
|
||||
'name': part["name"],
|
||||
'description': part["description"],
|
||||
'feature': part["feature"],
|
||||
'price': part["price"],
|
||||
'category_id': part["category_id"],
|
||||
'manufacturer_id': part["manufacturer_id"]
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def add(cls, part: PartDTO):
|
||||
doc = {
|
||||
'id': part.id,
|
||||
'name': part.name,
|
||||
'description': part.description,
|
||||
'feature': part.feature,
|
||||
'price': part.price,
|
||||
'category_id': part.category_id,
|
||||
'manufacturer_id': part.manufacturer_id
|
||||
}
|
||||
es.index(index='parts', id=part.id, body=doc)
|
||||
|
||||
@classmethod
|
||||
def search(cls, search_query: str):
|
||||
try:
|
||||
query = {
|
||||
"query": {
|
||||
"multi_match": {
|
||||
"query": search_query,
|
||||
"fields": ["feature", "description", "comment"],
|
||||
"type": "best_fields",
|
||||
"fuzziness": "AUTO"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
response = es.search(
|
||||
index="parts,reviews",
|
||||
body=query,
|
||||
size=1000
|
||||
)
|
||||
|
||||
search_results = []
|
||||
for hit in response['hits']['hits']:
|
||||
source = hit['_source']
|
||||
|
||||
if hit['_index'] == 'parts':
|
||||
item = {
|
||||
'id': source.get('id'),
|
||||
'name': source.get('name'),
|
||||
'description': source.get('description', ''),
|
||||
'feature': source.get('feature', {}),
|
||||
'price': source.get('price'),
|
||||
'category_id': source.get('category_id'),
|
||||
'manufacturer_id': source.get('manufacturer_id'),
|
||||
'type': 'part',
|
||||
'score': hit['_score']
|
||||
}
|
||||
else:
|
||||
item = {
|
||||
'id': source.get('id'),
|
||||
'rating': source.get('rating'),
|
||||
'comment': source.get('comment', ''),
|
||||
'created_at': source.get('created_at'),
|
||||
'updated_at': source.get('updated_at'),
|
||||
'user_id': source.get('user_id'),
|
||||
'part_id': source.get('part_id'),
|
||||
'type': 'review',
|
||||
'score': hit['_score']
|
||||
}
|
||||
|
||||
search_results.append(item)
|
||||
|
||||
return search_results
|
||||
|
||||
except Exception as e:
|
||||
raise Exception(f"Error in search_parts_and_reviews: {str(e)}")
|
||||
|
||||
@classmethod
|
||||
def get_parts(cls, result_searches: list):
|
||||
parts = []
|
||||
for result_search in result_searches:
|
||||
found = False
|
||||
if "part_id" in result_search:
|
||||
part = PartService.get(result_search["part_id"])
|
||||
new_part = cls.get_part(part)
|
||||
else:
|
||||
new_part = cls.get_part(result_search)
|
||||
|
||||
if len(parts) > 0:
|
||||
for part in parts:
|
||||
if part["id"] == new_part["id"]:
|
||||
found = True
|
||||
break
|
||||
if not found:
|
||||
parts += [new_part]
|
||||
return parts
|
||||
|
||||
@classmethod
|
||||
def update(cls, part_id: int, part):
|
||||
try:
|
||||
if not es.exists(index='parts', id=part_id):
|
||||
return {'error': f'Part with id {part_id} not found'}
|
||||
|
||||
update_doc = {
|
||||
'doc': {
|
||||
k: v for k, v in part.items()
|
||||
if k in parts_mapping['mappings']['properties']
|
||||
}
|
||||
}
|
||||
|
||||
response = es.update(
|
||||
index='parts',
|
||||
id=part_id,
|
||||
body=update_doc
|
||||
)
|
||||
|
||||
if response['result'] == 'updated':
|
||||
return {'message': f'Part with id {part_id} successfully updated'}
|
||||
else:
|
||||
return {'error': f'Failed to update part with id {part_id}'}
|
||||
|
||||
except Exception as e:
|
||||
return {'error': f'Error updating part: {str(e)}'}
|
||||
|
||||
@classmethod
|
||||
def delete(cls, part_id: int):
|
||||
try:
|
||||
response = es.delete(index='parts', id=part_id)
|
||||
if response['result'] == 'deleted':
|
||||
return {'message': f'Part with id {part_id} successfully deleted'}
|
||||
else:
|
||||
return {'error': f'Failed to delete part with id {part_id}'}
|
||||
except Exception as e:
|
||||
return {'error': f'Error deleting part: {str(e)}'}
|
@ -1,63 +0,0 @@
|
||||
from pydantic import TypeAdapter
|
||||
from application.config import es
|
||||
from application.dto.ReviewDTO import ReviewDTO
|
||||
from application.repositories.ReviewRepository import ReviewRepository
|
||||
from application.services.AbstractBasicService import AbstractBasicService
|
||||
from application.services.UserService import UserService
|
||||
|
||||
|
||||
class ReviewService(AbstractBasicService):
|
||||
@classmethod
|
||||
def add(cls, review: ReviewDTO):
|
||||
review_dictionary = review.to_dictionary()
|
||||
new_review = ReviewRepository.add(review_dictionary)
|
||||
new_review = TypeAdapter(ReviewDTO).dump_python(new_review)
|
||||
ReviewIndexManager.add(ReviewDTO(**new_review))
|
||||
return new_review
|
||||
|
||||
@classmethod
|
||||
def get(cls, id: int):
|
||||
review = ReviewRepository.get(id)
|
||||
return TypeAdapter(ReviewDTO).dump_python(review)
|
||||
|
||||
@classmethod
|
||||
def get_all(cls, part_id: int):
|
||||
reviews = ReviewRepository.get_all(part_id)
|
||||
new_reviews = []
|
||||
for review in reviews:
|
||||
user = UserService.get_user_id(review["user_id"])
|
||||
new_reviews += [{"id": review["id"], "rating": review["rating"], "comment": review["comment"],
|
||||
"email": user["email"], "user_name": user["name"], "part_id": review["part_id"]}]
|
||||
return new_reviews
|
||||
|
||||
@classmethod
|
||||
def delete(cls, id: int):
|
||||
review = ReviewRepository.delete(id)
|
||||
ReviewIndexManager.delete(id)
|
||||
return TypeAdapter(ReviewDTO).dump_python(review)
|
||||
|
||||
|
||||
class ReviewIndexManager:
|
||||
@classmethod
|
||||
def add(cls, review: ReviewDTO):
|
||||
doc = {
|
||||
'id': review.id,
|
||||
'rating': review.rating,
|
||||
'comment': review.comment,
|
||||
'created_at': review.created_at.isoformat(),
|
||||
'updated_at': review.updated_at.isoformat(),
|
||||
'user_id': review.user_id,
|
||||
'part_id': review.part_id
|
||||
}
|
||||
es.index(index='reviews', id=review.id, body=doc)
|
||||
|
||||
@classmethod
|
||||
def delete(cls, review_id: int):
|
||||
try:
|
||||
response = es.delete(index='reviews', id=review_id)
|
||||
if response['result'] == 'deleted':
|
||||
return {'message': f'Review with id {review_id} successfully deleted'}
|
||||
else:
|
||||
return {'error': f'Failed to delete review with id {review_id}'}
|
||||
except Exception as e:
|
||||
return {'error': f'Error deleting review: {str(e)}'}
|
@ -1,110 +0,0 @@
|
||||
from pydantic import TypeAdapter
|
||||
from flask_jwt_extended import create_access_token
|
||||
from datetime import timedelta
|
||||
from hashlib import sha256
|
||||
from application.dto.UserDTO import UserDTO, UserDTOInt, UserDTOToken
|
||||
from application.repositories.UserRepository import UserRepository
|
||||
|
||||
|
||||
class UserService:
|
||||
@classmethod
|
||||
def _to_int(cls, user: UserDTO):
|
||||
new_user = UserDTOInt(
|
||||
id=user["id"],
|
||||
email=user["email"],
|
||||
name=user["name"],
|
||||
phone=user["phone"],
|
||||
password_hash=user["password_hash"],
|
||||
address=user["address"],
|
||||
role=user["role"].value
|
||||
)
|
||||
return TypeAdapter(UserDTOInt).dump_python(new_user)
|
||||
|
||||
@classmethod
|
||||
def _to_user_token(cls, user: UserDTO, token: str):
|
||||
new_user = UserDTOToken(
|
||||
id=user["id"],
|
||||
email=user["email"],
|
||||
name=user["name"],
|
||||
role=user["role"].value,
|
||||
token=token
|
||||
)
|
||||
return TypeAdapter(UserDTOToken).dump_python(new_user)
|
||||
|
||||
@classmethod
|
||||
def add_user(cls, user: UserDTO):
|
||||
user.password_hash = cls.get_hash(user.password_hash)
|
||||
user_dictionary = user.to_dictionary()
|
||||
new_user = UserRepository.add_user(user_dictionary)
|
||||
new_user = TypeAdapter(UserDTOInt).dump_python(new_user)
|
||||
token = cls.get_token(new_user["id"])
|
||||
return cls._to_user_token(new_user, token)
|
||||
|
||||
@classmethod
|
||||
def add_default_user(cls, user: UserDTO):
|
||||
user.password_hash = cls.get_hash(user.password_hash)
|
||||
user_dictionary = user.to_dictionary()
|
||||
new_user = UserRepository.add_user(user_dictionary)
|
||||
return TypeAdapter(UserDTO).dump_python(new_user)
|
||||
|
||||
@classmethod
|
||||
def get_user(cls, email: str, password: str):
|
||||
new_password = cls.get_hash(password)
|
||||
user = UserRepository.get_user(email, new_password)
|
||||
if user is None:
|
||||
return None
|
||||
else:
|
||||
token = cls.get_token(user["id"])
|
||||
return cls._to_user_token(user, token)
|
||||
|
||||
@classmethod
|
||||
def get_user_id(cls, id: int, flag=False):
|
||||
user = UserRepository.get_user_id(id)
|
||||
if user is None:
|
||||
return None
|
||||
else:
|
||||
if flag:
|
||||
return cls._to_int(user)
|
||||
else:
|
||||
return user
|
||||
|
||||
@classmethod
|
||||
def get_all_users_role(cls, users=None):
|
||||
if users is None:
|
||||
users = UserRepository.get_all_users_role()
|
||||
users = TypeAdapter(list[UserDTO]).dump_python(users)
|
||||
new_users = []
|
||||
for user in users:
|
||||
new_users += [cls._to_int(user)]
|
||||
return TypeAdapter(list[UserDTOInt]).dump_python(new_users)
|
||||
|
||||
@classmethod
|
||||
def get_all_users(cls):
|
||||
users = UserRepository.get_all_users()
|
||||
return cls.get_all_users_role(users)
|
||||
|
||||
@classmethod
|
||||
def update_user(cls, id: int, user: UserDTO):
|
||||
user.password_hash = cls.get_hash(user.password_hash)
|
||||
user_dictionary = user.to_dictionary()
|
||||
new_user = UserRepository.update_user(id, user_dictionary)
|
||||
return cls._to_int(new_user)
|
||||
|
||||
@classmethod
|
||||
def get_hash(cls, string: str):
|
||||
hash = sha256()
|
||||
hash.update(str.encode(string))
|
||||
return hash.hexdigest()
|
||||
|
||||
@classmethod
|
||||
def get_token(cls, id, expire_time=24):
|
||||
expire_delta = timedelta(expire_time)
|
||||
additional_claims = {
|
||||
'sub': str(id)
|
||||
}
|
||||
|
||||
token = create_access_token(
|
||||
identity=id, expires_delta=expire_delta,
|
||||
additional_claims=additional_claims
|
||||
)
|
||||
return token
|
@ -1,47 +0,0 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
def create_directory(path):
|
||||
try:
|
||||
if not os.path.exists(path):
|
||||
os.mkdir(path)
|
||||
except OSError as e:
|
||||
print(f"Не удалось создать директорию {path}: {e}")
|
||||
else:
|
||||
print(f"Директория {path} успешно создана")
|
||||
|
||||
|
||||
def create_files(path, names, add_par_file):
|
||||
for name in names:
|
||||
new_name = f"{path}\\{name}.py" if add_par_file == "no" else f"{path}\\{name}{add_par_file}.py"
|
||||
file = open(new_name, "w")
|
||||
file.close()
|
||||
return 0
|
||||
|
||||
|
||||
def read_file(name_file):
|
||||
names = []
|
||||
with open(name_file, 'r') as file:
|
||||
for line in file:
|
||||
names += [line.strip()]
|
||||
file.close()
|
||||
return names
|
||||
|
||||
|
||||
def main(name_directory, name_file, add_par_file):
|
||||
names = read_file(name_file)
|
||||
path = f"{os.getcwd()}\\application\\{name_directory}"
|
||||
create_directory(path)
|
||||
create_files(path, names, add_par_file)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) != 4:
|
||||
print("Usage: python script.py <name_directory> <input_file> <additional_parameter_file>")
|
||||
sys.exit(1)
|
||||
|
||||
name_directory = sys.argv[1]
|
||||
input_file = sys.argv[2]
|
||||
add_par_file = sys.argv[3]
|
||||
main(name_directory, input_file, add_par_file)
|
@ -1,47 +0,0 @@
|
||||
version: '3.7'
|
||||
|
||||
services:
|
||||
elasticsearch:
|
||||
image: elasticsearch:7.17.10
|
||||
build:
|
||||
context: ./docker-files
|
||||
dockerfile: Dockerfile.elasticsearch
|
||||
container_name: elasticsearch
|
||||
environment:
|
||||
- discovery.type=single-node
|
||||
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
|
||||
- xpack.security.enabled=false
|
||||
- bootstrap.memory_lock=true
|
||||
ulimits:
|
||||
memlock:
|
||||
soft: -1
|
||||
hard: -1
|
||||
volumes:
|
||||
- elasticsearch-data:/usr/share/elasticsearch/data
|
||||
ports:
|
||||
- "9200:9200"
|
||||
networks:
|
||||
- elastic
|
||||
|
||||
kibana:
|
||||
image: kibana:7.17.10
|
||||
build:
|
||||
context: ./docker-files
|
||||
dockerfile: Dockerfile.kibana
|
||||
container_name: kibana
|
||||
environment:
|
||||
- ELASTICSEARCH_HOSTS=http://elasticsearch:9200
|
||||
ports:
|
||||
- "5601:5601"
|
||||
depends_on:
|
||||
- elasticsearch
|
||||
networks:
|
||||
- elastic
|
||||
|
||||
volumes:
|
||||
elasticsearch-data:
|
||||
driver: local
|
||||
|
||||
networks:
|
||||
elastic:
|
||||
driver: bridge
|
@ -1,8 +0,0 @@
|
||||
FROM elasticsearch:7.17.10
|
||||
|
||||
COPY elasticsearch.yml /usr/share/elasticsearch/config/
|
||||
COPY jvm.options /usr/share/elasticsearch/config/
|
||||
|
||||
USER root
|
||||
RUN elasticsearch-plugin install analysis-icu
|
||||
USER elasticsearch
|
@ -1,7 +0,0 @@
|
||||
FROM kibana:7.17.10
|
||||
|
||||
COPY kibana.yml /usr/share/kibana/config/
|
||||
|
||||
USER root
|
||||
RUN kibana-plugin install <plugin-name>
|
||||
USER kibana
|
@ -1,12 +0,0 @@
|
||||
cluster.name: "docker-cluster"
|
||||
network.host: 0.0.0.0
|
||||
discovery.type: single-node
|
||||
xpack.security.enabled: false
|
||||
|
||||
bootstrap.memory_lock: true
|
||||
|
||||
path.data: /usr/share/elasticsearch/data
|
||||
path.logs: /usr/share/elasticsearch/logs
|
||||
|
||||
http.cors.enabled: true
|
||||
http.cors.allow-origin: "*"
|
@ -1,49 +0,0 @@
|
||||
## JVM configuration
|
||||
|
||||
################################################################
|
||||
## IMPORTANT: JVM heap size
|
||||
################################################################
|
||||
-Xms512m
|
||||
-Xmx512m
|
||||
|
||||
################################################################
|
||||
## Expert settings
|
||||
################################################################
|
||||
## GC configuration
|
||||
8-13:-XX:+UseConcMarkSweepGC
|
||||
8-13:-XX:CMSInitiatingOccupancyFraction=75
|
||||
8-13:-XX:+UseCMSInitiatingOccupancyOnly
|
||||
|
||||
## G1GC Configuration
|
||||
14-:-XX:+UseG1GC
|
||||
14-:-XX:G1ReservePercent=25
|
||||
14-:-XX:InitiatingHeapOccupancyPercent=30
|
||||
|
||||
## JVM temporary directory
|
||||
-Djava.io.tmpdir=${ES_TMPDIR}
|
||||
|
||||
## heap dumps
|
||||
|
||||
# generate a heap dump when an allocation from the Java heap fails
|
||||
# heap dumps are created in the working directory of the JVM
|
||||
-XX:+HeapDumpOnOutOfMemoryError
|
||||
|
||||
# specify an alternative path for heap dumps
|
||||
# ensure the directory exists and has sufficient space
|
||||
#-XX:HeapDumpPath=/var/lib/elasticsearch
|
||||
|
||||
# specify an alternative path for JVM fatal error logs
|
||||
#-XX:ErrorFile=/var/log/elasticsearch/hs_err_pid%p.log
|
||||
|
||||
## JDK 8 GC logging
|
||||
8:-XX:+PrintGCDetails
|
||||
8:-XX:+PrintGCDateStamps
|
||||
8:-XX:+PrintTenuringDistribution
|
||||
8:-XX:+PrintGCApplicationStoppedTime
|
||||
8:-Xloggc:/var/log/elasticsearch/gc.log
|
||||
8:-XX:+UseGCLogFileRotation
|
||||
8:-XX:NumberOfGCLogFiles=32
|
||||
8:-XX:GCLogFileSize=64m
|
||||
|
||||
# JDK 9+ GC logging
|
||||
9-:-Xlog:gc*,gc+age=trace,safepoint:file=/var/log/elasticsearch/gc.log:utctime,pid,tags:filecount=32,filesize=64m
|
@ -1,4 +0,0 @@
|
||||
server.name: kibana
|
||||
server.host: "0"
|
||||
elasticsearch.hosts: [ "http://elasticsearch:9200" ]
|
||||
monitoring.ui.container.elasticsearch.enabled: true
|
26
front/.gitignore
vendored
26
front/.gitignore
vendored
@ -1,26 +0,0 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
.parcel-cache
|
@ -1,17 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport"
|
||||
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<script type="module" src="./node_modules/bootstrap/dist/js/bootstrap.min.js"></script>
|
||||
<link rel="stylesheet" href="/node_modules/bootstrap/dist/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="/node_modules/@fortawesome/fontawesome-free/css/all.min.css">
|
||||
<title>Интернет-магазин автозапчастей</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
</body>
|
||||
</html>
|
1365
front/package-lock.json
generated
1365
front/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,22 +0,0 @@
|
||||
{
|
||||
"name": "internet-auto-parts-store",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"start": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-free": "^6.2.1",
|
||||
"bootstrap": "^5.2.2",
|
||||
"maska": "^3.0.4",
|
||||
"vue": "^3.2.41",
|
||||
"vue-router": "^4.1.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^3.2.0",
|
||||
"vite": "^3.2.3"
|
||||
}
|
||||
}
|
Binary file not shown.
Before Width: | Height: | Size: 609 KiB |
@ -1,19 +0,0 @@
|
||||
<script>
|
||||
import Header from './components/Header.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Header
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Header></Header>
|
||||
<div class="container">
|
||||
<router-view></router-view>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
@ -1,199 +0,0 @@
|
||||
<script>
|
||||
import DataService from '../services/DataService';
|
||||
import CatalogMixins from '../mixins/CatalogMixins.js';
|
||||
export default {
|
||||
mixins: [
|
||||
CatalogMixins
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
getAllUrl: 'cart',
|
||||
dataUrl: 'cart/',
|
||||
newItems: [],
|
||||
path: '/public/Part.jpg',
|
||||
order_date: ""
|
||||
}
|
||||
},
|
||||
beforeCreate() {
|
||||
if(localStorage.getItem("token") == null) {
|
||||
this.$router.push("/signupin");
|
||||
}
|
||||
else
|
||||
if(document.getElementById("user").innerText == "Войти" && localStorage.getItem("token") != null)
|
||||
document.getElementById("user").innerText = "Выход (" + localStorage.getItem("user").substr(0,5) +"...)";
|
||||
},
|
||||
computed: {
|
||||
totalPrice() {
|
||||
return this.newItems.reduce((total, item) => {
|
||||
const price = item.commonPrice;
|
||||
return total + price;
|
||||
}, 0);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getItems() {
|
||||
if (localStorage.getItem("token") != null) {
|
||||
DataService.getData(this.getAllUrl).then(data => {
|
||||
this.items = data;
|
||||
var i, varDict, count = 0;
|
||||
for (i = 0; i < this.items.length; i++){
|
||||
varDict = { id: 0, name: "", commonPrice: "", count: "" };
|
||||
count = this.items[i]["count"];
|
||||
varDict["id"] = this.items[i]["id"];
|
||||
varDict["name"] = this.items[i]["part"]["name"];
|
||||
varDict["commonPrice"]= this.items[i]["part"]["price"] * count;
|
||||
varDict["count"] = count;
|
||||
this.newItems.push(varDict);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
removeItem(id){
|
||||
if (confirm('Удалить выбранный элемент?')) {
|
||||
DataService.delete(this.dataUrl + id).then((data) => {
|
||||
this.getItems();
|
||||
//this.$router.go(0);
|
||||
});
|
||||
}
|
||||
},
|
||||
addOrderItems(){
|
||||
var i, orderItem, orderItems = [];
|
||||
for (i = 0; i < this.items.length; i++){
|
||||
orderItem = { count: this.items[i]["count"],
|
||||
price: this.items[i]["part"]["price"],
|
||||
part_id: this.items[i]["part"]["id"]};
|
||||
orderItems.push(orderItem);
|
||||
}
|
||||
DataService.create(`orders/orderwithitems?order_date=${this.order_date}&total_amount=${this.totalPrice}&status=0&items=${encodeURIComponent(JSON.stringify(orderItems))}`)
|
||||
.then(() => {
|
||||
this.getItems();
|
||||
this.$router.push("/orders");
|
||||
});
|
||||
},
|
||||
showOrderItems(){
|
||||
this.modal.header = 'Добавление заказа';
|
||||
this.modal.confirm = 'Добавить';
|
||||
this.modalShow = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="container cart-container" v-if="newItems.length > 0">
|
||||
<h2 class="mb-4">Корзина запчастей</h2>
|
||||
|
||||
<div v-for="(item, index) in newItems" :key="item.id" class="cart-item">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-md-2">
|
||||
<div class="product-image">
|
||||
<img :src="this.path">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<h5>{{ item.name }}</h5>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<span class="quantity">Количество: {{ item.count }} шт.</span>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<p class="price mb-0">Сумма: {{ item.commonPrice }} ₽</p>
|
||||
</div>
|
||||
<div class="col-md-2 mt-2">
|
||||
<button class="btn btn-outline-danger btn-sm" @click="removeItem(item.id)">Удалить</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="total-section">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<h4>Итого:</h4>
|
||||
</div>
|
||||
<div class="col-md-6 text-end">
|
||||
<h4>{{ totalPrice }} ₽</h4>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn btn-danger order-btn" @click="showOrderItems">Заказать запчасти</button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="cart-empty">
|
||||
<p>Корзина пуста</p>
|
||||
</div>
|
||||
<Modal
|
||||
:header="this.modal.header"
|
||||
:confirm="this.modal.confirm"
|
||||
v-model:visible="this.modalShow"
|
||||
@done="addOrderItems">
|
||||
<div class="col-mb-3">
|
||||
<label for="Order_date" class="form-label">Дата</label>
|
||||
<input type="date" class="form-control" id="Order_date" required v-model="this.order_date">
|
||||
</div>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.cart-container {
|
||||
max-width: 800px;
|
||||
margin: 30px auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.cart-item {
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 15px;
|
||||
padding: 15px;
|
||||
background-color: #fff;
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
.cart-item:hover {
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.total-section {
|
||||
margin-top: 30px;
|
||||
padding: 20px;
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.order-btn {
|
||||
width: 100%;
|
||||
padding: 15px;
|
||||
font-size: 1.2rem;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.quantity {
|
||||
font-weight: 500;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.price {
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.cart-empty {
|
||||
text-align: center;
|
||||
color: #6c757d;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.product-image {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.product-image img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
</style>
|
@ -1,129 +0,0 @@
|
||||
<script>
|
||||
import DataService from '../services/DataService';
|
||||
import CatalogMixins from '../mixins/CatalogMixins.js';
|
||||
import PartList from '../components/PartList.vue'
|
||||
export default {
|
||||
mixins: [
|
||||
CatalogMixins
|
||||
],
|
||||
components: {
|
||||
PartList
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
getAllUrl: 'categories',
|
||||
path: '/public/Part.jpg',
|
||||
newItems: [],
|
||||
parts: [],
|
||||
query: ""
|
||||
}
|
||||
},
|
||||
beforeCreate() {
|
||||
if(document.getElementById("user").innerText == "Войти" && localStorage.getItem("token") != null)
|
||||
document.getElementById("user").innerText = "Выход (" + localStorage.getItem("user").substr(0,5) +"...)";
|
||||
},
|
||||
methods: {
|
||||
getItems() {
|
||||
DataService.getData(this.getAllUrl).then(data => {
|
||||
this.items = data;
|
||||
var i;
|
||||
this.newItems = [];
|
||||
for (i = 0; i < this.items.length; i++){
|
||||
var varDict = { id: 0, name: "", link: ""};
|
||||
varDict["id"] = this.items[i]["id"];
|
||||
varDict["name"] = this.items[i]["name"];
|
||||
varDict["link"] = `parts/${varDict["id"]}`;
|
||||
this.newItems.push(varDict);
|
||||
}
|
||||
});
|
||||
},
|
||||
search() {
|
||||
DataService.getData(`parts/get_part_review?search_query=${this.query}`).then(data => {
|
||||
this.parts = data;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="search-container">
|
||||
<input type="text" class="search-input" placeholder="Введите текст для поиска..." v-model="this.query">
|
||||
<button class="search-button" @click="search">Найти</button>
|
||||
<div class="names-container" v-for="(item, index) in this.newItems">
|
||||
<div class="name-item">
|
||||
<p><a :href="item['link']">{{ item["name"] }}</a></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<PartList
|
||||
:parts="this.parts" v-if="this.parts.length > 0">
|
||||
</PartList>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.search-container {
|
||||
width: 500px;
|
||||
padding: 20px;
|
||||
border: 2px solid #ccc;
|
||||
border-radius: 5px;
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
margin-bottom: 10px;
|
||||
border: 1px solid #999;
|
||||
border-radius: 4px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.search-button {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
background-color: #4CAF50;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.search-button:hover {
|
||||
background-color: #45a049;
|
||||
}
|
||||
|
||||
.names-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.name-item {
|
||||
flex: 0 0 calc(33.33% - 10px);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.name-item p {
|
||||
margin: 0;
|
||||
padding: 5px;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.name-item a {
|
||||
text-decoration: none;
|
||||
color: #333;
|
||||
display: block;
|
||||
padding: 5px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 3px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.name-item a:hover {
|
||||
background-color: #e9e9e9;
|
||||
border-color: #999;
|
||||
}
|
||||
</style>
|
@ -1,90 +0,0 @@
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
headers: Array,
|
||||
items: Array,
|
||||
selectedItems: Array,
|
||||
imagePath: String
|
||||
},
|
||||
emits: {
|
||||
dblclick: null
|
||||
},
|
||||
methods: {
|
||||
cardClick(id) {
|
||||
if (this.isSelected(id)) {
|
||||
var index = this.selectedItems.indexOf(id);
|
||||
if (index !== -1) {
|
||||
this.selectedItems.splice(index, 1);
|
||||
}
|
||||
} else {
|
||||
this.selectedItems.push(id);
|
||||
}
|
||||
},
|
||||
cardDblClick(item) {
|
||||
this.$emit('dblclick', item);
|
||||
},
|
||||
isSelected(id) {
|
||||
return this.selectedItems.includes(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div id="div-items" class = "row" style="justify-content: center;">
|
||||
<div align="justify" class = "card" v-for="(item, index) in this.items" :id = "'item - ' + item.id"
|
||||
@click="cardClick(item.id)"
|
||||
@dblclick="cardDblClick(item)"
|
||||
:class="{selected: isSelected(item.id)}">
|
||||
<img class="card-img" :src="imagePath" v-if="typeof(imagePath) === 'string'">
|
||||
<img class="card-img" :src="imagePath[index]" v-else>
|
||||
<p v-for="header in this.headers"><b>{{ header.label }}:</b> {{ item[header.name] }} <span v-if="header.name === 'commonPrice'">₽</span></p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.card {
|
||||
width: 20%;
|
||||
height: 340px;
|
||||
margin: 10px;
|
||||
float: left;
|
||||
text-align: center;
|
||||
box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2);
|
||||
transition: 0.3s;
|
||||
border-radius: 20px;
|
||||
overflow: hidden;
|
||||
-moz-hyphens: auto;
|
||||
-webkit-hyphens: auto;
|
||||
-ms-hyphens: auto;
|
||||
}
|
||||
|
||||
.card-img{
|
||||
margin-top: 4px;
|
||||
border-radius: 20px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
box-shadow: 0 8px 16px 0 rgba(0,0,0,0.2);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
p {
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
font-size: 18px;
|
||||
text-align: center;
|
||||
font-family: sans-serif;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.selected {
|
||||
background-color: color-mix(in srgb, #c41e3a 50%, #383838);
|
||||
opacity: 80%;
|
||||
}
|
||||
div {
|
||||
user-select: none;
|
||||
}
|
||||
</style>
|
@ -1,72 +0,0 @@
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
headers: Array,
|
||||
items: Array,
|
||||
selectedItems: Array
|
||||
},
|
||||
emits: {
|
||||
dblclick: null
|
||||
},
|
||||
methods: {
|
||||
rowClick(id) {
|
||||
if (this.isSelected(id)) {
|
||||
var index = this.selectedItems.indexOf(id);
|
||||
if (index !== -1) {
|
||||
this.selectedItems.splice(index, 1);
|
||||
}
|
||||
} else {
|
||||
this.selectedItems.push(id);
|
||||
}
|
||||
},
|
||||
rowDblClick(id) {
|
||||
this.$emit('dblclick', id);
|
||||
},
|
||||
isSelected(id) {
|
||||
return this.selectedItems.includes(id);
|
||||
},
|
||||
dataConvert(data) {
|
||||
return data;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">#</th>
|
||||
<th v-for="header in this.headers"
|
||||
:id="header.name"
|
||||
scope="col">{{ header.label }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(item, index) in this.items"
|
||||
@click="rowClick(item.id)"
|
||||
@dblclick="rowDblClick(item.id)"
|
||||
:class="{selected: isSelected(item.id)}">
|
||||
<th scope="row">{{ index + 1 }}</th>
|
||||
<td v-for="header in this.headers">
|
||||
{{ dataConvert(item[header.name]) }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
tbody tr:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
tr.selected {
|
||||
background-color: #0000fd;
|
||||
opacity: 80%;
|
||||
}
|
||||
|
||||
tbody tr {
|
||||
user-select: none;
|
||||
}
|
||||
</style>
|
@ -1,51 +0,0 @@
|
||||
<script>
|
||||
export default {
|
||||
methods: {
|
||||
getRoutes() {
|
||||
var menuList;
|
||||
menuList = this.$router.options.routes.filter(route => route.meta?.hasOwnProperty('label'));
|
||||
if (localStorage.getItem("token") == null)
|
||||
menuList = menuList.filter(item => item.path !== "/orders" && item.path !== "/cart")
|
||||
return menuList;
|
||||
},
|
||||
logout() {
|
||||
if (document.getElementById("user").innerText == "Войти")
|
||||
this.$router.push('/signupin');
|
||||
else {
|
||||
localStorage.clear();
|
||||
this.$router.push('/signupin');
|
||||
document.getElementById("user").innerText = "Войти";
|
||||
location.reload();
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
token: null
|
||||
}
|
||||
},
|
||||
created(){
|
||||
this.token = localStorage.getItem("token");
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<nav class="navbar navbar-expand-lg navbar-light bg-light">
|
||||
<div class="container">
|
||||
<a class="navbar-brand" href="/categories">Internet shop parts</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
|
||||
<li class="nav-item"
|
||||
v-for="route in this.getRoutes()">
|
||||
<router-link class="nav-link" :to="route.path">{{ route.meta.label }}</router-link>
|
||||
</li>
|
||||
</ul>
|
||||
<a id="user" class="btn btn-auth text-white" @click.prevent="logout">Войти</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</template>
|
@ -1,63 +0,0 @@
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
header: String,
|
||||
confirm: String,
|
||||
visible: Boolean
|
||||
},
|
||||
emits: {
|
||||
done: null,
|
||||
'update:visible': (value) => {
|
||||
if (typeof value !== 'boolean') {
|
||||
throw 'Value is not a boolean';
|
||||
}
|
||||
return true;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
hide() {
|
||||
this.$emit('update:visible', false);
|
||||
},
|
||||
done() {
|
||||
if (this.$refs.form.checkValidity()) {
|
||||
this.$emit('done');
|
||||
this.hide();
|
||||
} else {
|
||||
this.$refs.form.reportValidity();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="modal fade" tabindex="-1" aria-hidden="true"
|
||||
:class="{ 'modal-show': this.visible, 'show': this.visible }">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h1 class="modal-title fs-5" id="exampleModalLabel">{{ header }}</h1>
|
||||
<button type="button" class="btn-close" aria-label="Close"
|
||||
@click.prevent="hide"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form @submit.prevent="done" ref="form">
|
||||
<slot></slot>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary"
|
||||
@click.prevent="hide">Закрыть</button>
|
||||
<button type="button" class="btn btn-danger"
|
||||
@click.prevent="done">{{ confirm }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.modal-show {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
@ -1,158 +0,0 @@
|
||||
<script>
|
||||
import DataService from '../services/DataService';
|
||||
import CatalogMixins from '../mixins/CatalogMixins.js';
|
||||
export default {
|
||||
mixins: [
|
||||
CatalogMixins
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
getAllUrl: 'orders',
|
||||
path: '/public/Part.jpg',
|
||||
statuses: [
|
||||
{name: 'in_processing', label: 'собирается'},
|
||||
{name: 'shipped', label: 'в пути'},
|
||||
{name: 'delivered', label: 'доставлен'}
|
||||
]
|
||||
}
|
||||
},
|
||||
beforeCreate() {
|
||||
if(document.getElementById("user").innerText == "Войти" && localStorage.getItem("token") != null)
|
||||
document.getElementById("user").innerText = "Выход (" + localStorage.getItem("user").substr(0,5) +"...)";
|
||||
},
|
||||
methods: {
|
||||
getItems() {
|
||||
DataService.getData(this.getAllUrl).then(data => {
|
||||
this.items = data;
|
||||
for(var i = 0; i < this.items.length; i++){
|
||||
this.items[i].order_date = this.ChangeFormat(this.items[i].order_date);
|
||||
this.items[i].status = this.statuses[this.items[i].status]["label"];
|
||||
}
|
||||
});
|
||||
},
|
||||
ChangeFormat(date){
|
||||
var vde = date.replace(/(\d*)-(\d*)-(\d*)/, '$3.$2.$1').split(".");
|
||||
var i, de;
|
||||
const options = {year: 'numeric', month: 'long', day: 'numeric'};
|
||||
for(i = 0; i < vde.length; i++)
|
||||
vde[i] = parseInt(vde[i]);
|
||||
de = new Date(vde[2], vde[1] - 1, vde[0]);
|
||||
return de.toLocaleString("ru", options);
|
||||
},
|
||||
editOrderDblClick(item){
|
||||
var id = -1;
|
||||
if(typeof(item) === "number")
|
||||
id = item;
|
||||
else
|
||||
id = item.id;
|
||||
|
||||
this.$router.push('/orderitems/' + id);
|
||||
},
|
||||
editOrderClick(id){
|
||||
if (this.isSelected(id)) {
|
||||
var index = this.selectedItems.indexOf(id);
|
||||
if (index !== -1) {
|
||||
this.selectedItems.splice(index, 1);
|
||||
}
|
||||
} else {
|
||||
this.selectedItems.push(id);
|
||||
}
|
||||
},
|
||||
isSelected(id) {
|
||||
return this.selectedItems.includes(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="container mt-4">
|
||||
|
||||
<div class="order-list" v-if="items.length > 0">
|
||||
<h3 class="mb-4">Заказы</h3>
|
||||
<div class="card mb-3 order-card">
|
||||
<div class="card-body" v-for="(item, index) in this.items" :id = "'item - ' + item.id"
|
||||
@click="editOrderClick(item.id)"
|
||||
@dblclick="editOrderDblClick(item)"
|
||||
:class="{selected: isSelected(item.id)}">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-md-3">
|
||||
<div class="order-date">
|
||||
<i class="far fa-calendar-alt me-2"></i>
|
||||
<span>{{ item.order_date }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="order-total">
|
||||
<strong>Сумма:</strong>
|
||||
<span>{{ item.total_amount }} ₽</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="order-status">
|
||||
<span class="badge bg-success">{{ item.status }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<button class="btn btn-outline-primary btn-sm" @click="editOrderDblClick(item.id)">Подробнее</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="order-empty">
|
||||
<p>Нет активных заказов</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.order-card {
|
||||
transition: all 0.3s ease;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.order-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 8px rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
.order-date {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.order-total {
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
.order-status .badge {
|
||||
padding: 8px 12px;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.selected {
|
||||
background-color: color-mix(in srgb, #c41e3a 50%, #383838);
|
||||
opacity: 80%;
|
||||
}
|
||||
|
||||
.selected * {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.selected .btn-outline-primary {
|
||||
color: white;
|
||||
border-color: white;
|
||||
}
|
||||
|
||||
.selected .btn-outline-primary:hover {
|
||||
background-color: white;
|
||||
color: #c41e3a;
|
||||
}
|
||||
|
||||
.order-empty {
|
||||
text-align: center;
|
||||
color: #6c757d;
|
||||
font-style: italic;
|
||||
}
|
||||
</style>
|
@ -1,153 +0,0 @@
|
||||
<script>
|
||||
import DataService from '../services/DataService';
|
||||
import CatalogMixins from '../mixins/CatalogMixins.js';
|
||||
export default {
|
||||
mixins: [
|
||||
CatalogMixins
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
getAllUrl: 'order_items/get_all_order_items_parts',
|
||||
headers: [
|
||||
{ name: 'count', label: 'Количество' },
|
||||
{ name: 'price', label: 'Цена' },
|
||||
{ name: 'name_part', label: 'Название детали' }
|
||||
],
|
||||
path: '/public/Part.jpg'
|
||||
}
|
||||
},
|
||||
beforeCreate() {
|
||||
if(document.getElementById("user").innerText == "Войти" && localStorage.getItem("token") != null)
|
||||
document.getElementById("user").innerText = "Выход (" + localStorage.getItem("user").substr(0,5) +"...)";
|
||||
},
|
||||
methods: {
|
||||
getItems() {
|
||||
DataService.getData(`orders/${this.getId()}`).then(data => {
|
||||
this.data = data;
|
||||
this.data.order_date = this.ChangeFormat(this.data.order_date);
|
||||
});
|
||||
DataService.getData(`${this.getAllUrl}/${this.getId()}`).then(data => {
|
||||
this.items = data;
|
||||
});
|
||||
},
|
||||
ChangeFormat(date){
|
||||
var vde = date.replace(/(\d*)-(\d*)-(\d*)/, '$3.$2.$1').split(".");
|
||||
var i, de;
|
||||
const options = {year: 'numeric', month: 'long', day: 'numeric'};
|
||||
for(i = 0; i < vde.length; i++)
|
||||
vde[i] = parseInt(vde[i]);
|
||||
de = new Date(vde[2], vde[1] - 1, vde[0]);
|
||||
return de.toLocaleString("ru", options);
|
||||
},
|
||||
getId(){
|
||||
var ph, id;
|
||||
ph = this.$route.path.replace('/orderitems/', '');
|
||||
if(!isNaN(parseInt(ph)))
|
||||
id = parseInt(ph);
|
||||
else
|
||||
throw "Неверный id!";
|
||||
return id;
|
||||
},
|
||||
deleteOrder(){
|
||||
if (confirm('Вы действительно хотите отменить заказ?')) {
|
||||
DataService.delete(`orders/${this.getId()}`).then((data) => {
|
||||
this.getItems();
|
||||
this.$router.push("/orders");
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div style="display: flex; justify-content: space-between; gap: 20px;">
|
||||
<div style="flex: 1;">
|
||||
<div class="container">
|
||||
<div class="info-row">
|
||||
<span class="label">Дата:</span>
|
||||
<span class="value">{{ data.order_date }}</span>
|
||||
</div>
|
||||
|
||||
<div class="info-row">
|
||||
<span class="label">Сумма:</span>
|
||||
<span class="value">{{ data.total_amount }} ₽</span>
|
||||
</div>
|
||||
|
||||
<div class="info-row">
|
||||
<span class="label">Статус:</span>
|
||||
<span class="status status-completed" v-if="data.status === '2'">Доставлен</span>
|
||||
<span class="status status-pending" v-if="data.status === '1'">В обработке</span>
|
||||
<span class="status status-cancelled" v-if="data.status === '0'">Собирается</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display: flex; align-items: center;">
|
||||
<button class="btn btn-danger order-btn" @click="deleteOrder">Отменить заказ</button>
|
||||
</div>
|
||||
</div>
|
||||
<h1 align="center"> Запчасти </h1>
|
||||
<DataCard
|
||||
:headers="this.headers"
|
||||
:items="this.items"
|
||||
:selectedItems="this.selectedItems"
|
||||
:imagePath="this.path">
|
||||
</DataCard>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.container {
|
||||
font-family: Arial, sans-serif;
|
||||
max-width: 400px;
|
||||
margin: 20px;
|
||||
padding: 15px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 10px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.label {
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.value {
|
||||
font-weight: bold;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.status {
|
||||
padding: 5px 10px;
|
||||
border-radius: 15px;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.status-collecting {
|
||||
background-color: #fff3cd;
|
||||
color: #856404;
|
||||
}
|
||||
|
||||
.status-in-transit {
|
||||
background-color: #cce5ff;
|
||||
color: #004085;
|
||||
}
|
||||
|
||||
.status-delivered {
|
||||
background-color: #d4edda;
|
||||
color: #155724;
|
||||
}
|
||||
|
||||
.order-btn {
|
||||
width: 100%;
|
||||
padding: 15px;
|
||||
font-size: 1.2rem;
|
||||
margin-top: 20px;
|
||||
}
|
||||
</style>
|
@ -1,61 +0,0 @@
|
||||
<script>
|
||||
import DataService from '../services/DataService';
|
||||
import CatalogMixins from '../mixins/CatalogMixins.js';
|
||||
export default {
|
||||
mixins: [
|
||||
CatalogMixins
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
getAllUrl: 'parts/get_all_parts/',
|
||||
headers: [
|
||||
{ name: 'name', label: 'Имя запчасти'},
|
||||
{ name: 'price', label: 'Цена'}
|
||||
],
|
||||
path: '/public/Part.jpg'
|
||||
}
|
||||
},
|
||||
beforeCreate() {
|
||||
if(document.getElementById("user").innerText == "Войти" && localStorage.getItem("token") != null)
|
||||
document.getElementById("user").innerText = "Выход (" + localStorage.getItem("user").substr(0,5) +"...)";
|
||||
},
|
||||
methods: {
|
||||
getItems() {
|
||||
DataService.getData(`${this.getAllUrl}${this.getId()}`).then(data => {
|
||||
this.items = data;
|
||||
});
|
||||
},
|
||||
getId(){
|
||||
var ph, id;
|
||||
ph = this.$route.path.replace('/parts/', '');
|
||||
if(!isNaN(parseInt(ph)))
|
||||
id = parseInt(ph);
|
||||
else
|
||||
throw "Неверный id!";
|
||||
return id;
|
||||
},
|
||||
editPartDblClick(item){
|
||||
var id = -1;
|
||||
if(typeof(item) === "number")
|
||||
id = item;
|
||||
else
|
||||
id = item.id;
|
||||
|
||||
this.$router.push('/part/' + id);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="container">
|
||||
<h1 align="center"> Запчасти </h1>
|
||||
<DataCard
|
||||
:headers="this.headers"
|
||||
:items="this.items"
|
||||
:selectedItems="this.selectedItems"
|
||||
:imagePath="this.path"
|
||||
@dblclick="editPartDblClick">
|
||||
</DataCard>
|
||||
</div>
|
||||
</template>
|
@ -1,216 +0,0 @@
|
||||
<script>
|
||||
import DataService from '../services/DataService';
|
||||
import CatalogMixins from '../mixins/CatalogMixins.js';
|
||||
import Review from './Review.vue'
|
||||
export default {
|
||||
components: {
|
||||
Review
|
||||
},
|
||||
mixins: [
|
||||
CatalogMixins
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
getAllUrl: 'parts/get_part_manufacturer/',
|
||||
dataUrl: 'parts/',
|
||||
headers: [
|
||||
{ name: 'name', label: 'Название характеристики' },
|
||||
{ name: 'description', label: 'Описание' }
|
||||
],
|
||||
path: '/public/Part.jpg',
|
||||
features: [],
|
||||
part_ids: [],
|
||||
cart_ids: [],
|
||||
manufacturerName: "",
|
||||
country: "",
|
||||
ifUser: false,
|
||||
count: 0
|
||||
}
|
||||
},
|
||||
beforeCreate() {
|
||||
if(document.getElementById("user").innerText == "Войти" && localStorage.getItem("token") != null)
|
||||
document.getElementById("user").innerText = "Выход (" + localStorage.getItem("user").substr(0,5) +"...)";
|
||||
},
|
||||
methods: {
|
||||
getItems() {
|
||||
if (localStorage.getItem("token") != null)
|
||||
this.ifUser = true;
|
||||
DataService.getData(`${this.getAllUrl}${this.getId()}`).then(data => {
|
||||
this.data = data;
|
||||
this.features = this.data["feature"]["features"];
|
||||
this.manufacturerName = this.data["manufacturer"]["name"];
|
||||
this.country = this.data["manufacturer"]["country"];
|
||||
});
|
||||
DataService.getData("parts").then(data => {
|
||||
var items = data, i;
|
||||
for (i = 0; i < items.length; i++)
|
||||
this.part_ids.push(items[i]["id"]);
|
||||
});
|
||||
DataService.getData("cart").then(data => {
|
||||
var items = data, i;
|
||||
for (i = 0; i < items.length; i++)
|
||||
this.cart_ids.push(items[i]["id"]);
|
||||
});
|
||||
},
|
||||
getId(){
|
||||
var ph, id;
|
||||
ph = this.$route.path.replace('/part/', '');
|
||||
if(!isNaN(parseInt(ph)))
|
||||
id = parseInt(ph);
|
||||
else
|
||||
throw "Неверный id!";
|
||||
return id;
|
||||
},
|
||||
getMaxId(){
|
||||
var i, maxId = -1, cartMaxId = -1;
|
||||
if (this.part_ids.length > 0)
|
||||
for (i = 0; i < this.part_ids.length; i++)
|
||||
if (maxId < this.part_ids[i])
|
||||
maxId = this.part_ids[i];
|
||||
if (this.cart_ids.length > 0)
|
||||
for (i = 0; i < this.cart_ids.length; i++)
|
||||
if (cartMaxId < this.cart_ids[i])
|
||||
cartMaxId = this.cart_ids[i];
|
||||
maxId = maxId > cartMaxId ? maxId : cartMaxId;
|
||||
return maxId;
|
||||
},
|
||||
addCart() {
|
||||
var part = {id: this.data.id, name: this.data.name, description: this.data.description,
|
||||
price: this.data.price, category_id: this.getId(), manufacturer_id: this.data["manufacturer"]["id"]};
|
||||
DataService.create(`cart?id=${this.getMaxId() + 1}&count=${this.count}&feature={"part": ${JSON.stringify(part)}}`)
|
||||
.then((data) => {
|
||||
this.$router.push("/cart");
|
||||
});
|
||||
},
|
||||
showEditCart() {
|
||||
this.modal.header = 'Добавить запчасть в корзину';
|
||||
this.modal.confirm = 'Добавить';
|
||||
this.modalShow = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="container">
|
||||
<div class="image-container">
|
||||
<img :src="path" alt="Part Image" class="product-image">
|
||||
</div>
|
||||
|
||||
<div class="form-container">
|
||||
<div class="form-group">
|
||||
<label for="Name" class="form-label">Название запчасти</label>
|
||||
<input type="text" class="form-control" id="Name" readonly v-model="data.name">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="Description" class="form-label">Описание</label>
|
||||
<textarea class="form-control" id="Description" readonly v-model="data.description"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="Price" class="form-label">Цена</label>
|
||||
<input type="number" class="form-control" id="Price" readonly v-model="data.price">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="Manufacturer" class="form-label">Имя производителя</label>
|
||||
<input type="text" class="form-control" id="Manufacturer" readonly v-model="this.manufacturerName">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="Country" class="form-label">Страна</label>
|
||||
<input type="text" class="form-control" id="Country" readonly v-model="this.country">
|
||||
</div>
|
||||
|
||||
<button class="add-to-cart-btn" @click.prevent="showEditCart" v-if="this.ifUser">Добавить в корзину</button>
|
||||
</div>
|
||||
</div>
|
||||
<h2 align="center"> Характеристики </h2>
|
||||
<DataTable
|
||||
:headers="this.headers"
|
||||
:items="this.features"
|
||||
:selectedItems="this.selectedItems">
|
||||
</DataTable>
|
||||
<Review></Review>
|
||||
<Modal
|
||||
:header="this.modal.header"
|
||||
:confirm="this.modal.confirm"
|
||||
v-model:visible="this.modalShow"
|
||||
@done="addCart">
|
||||
<div class="col-mb-3">
|
||||
<label for="Count" class="form-label">Количество</label>
|
||||
<input type="number" class="form-control" id="Count" step="1" min="1" required v-model="this.count">
|
||||
</div>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.container {
|
||||
display: flex;
|
||||
padding: 20px;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
gap: 30px;
|
||||
}
|
||||
|
||||
.image-container {
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.product-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.form-container {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.form-control {
|
||||
width: 100%;
|
||||
padding: 8px 12px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.form-control:focus {
|
||||
outline: none;
|
||||
border-color: #80bdff;
|
||||
box-shadow: 0 0 0 0.2rem rgba(0,123,255,.25);
|
||||
}
|
||||
|
||||
.add-to-cart-btn {
|
||||
background-color: #dc3545;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 12px 24px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
.add-to-cart-btn:hover {
|
||||
background-color: #c82333;
|
||||
}
|
||||
</style>
|
@ -1,49 +0,0 @@
|
||||
<script>
|
||||
import CatalogMixins from '../mixins/CatalogMixins.js';
|
||||
export default {
|
||||
mixins: [
|
||||
CatalogMixins
|
||||
],
|
||||
props: {
|
||||
parts: Array
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
headers: [
|
||||
{ name: 'name', label: 'Имя запчасти'},
|
||||
{ name: 'price', label: 'Цена'}
|
||||
],
|
||||
path: '/public/Part.jpg'
|
||||
}
|
||||
},
|
||||
beforeCreate() {
|
||||
if(document.getElementById("user").innerText == "Войти" && localStorage.getItem("token") != null)
|
||||
document.getElementById("user").innerText = "Выход (" + localStorage.getItem("user").substr(0,5) +"...)";
|
||||
},
|
||||
methods: {
|
||||
getItems(){},
|
||||
editPartDblClick(item){
|
||||
var id = -1;
|
||||
if(typeof(item) === "number")
|
||||
id = item;
|
||||
else
|
||||
id = item.id;
|
||||
|
||||
this.$router.push('/part/' + id);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="container">
|
||||
<h1 align="center"> Запчасти </h1>
|
||||
<DataCard
|
||||
:headers="this.headers"
|
||||
:items="this.parts"
|
||||
:selectedItems="this.selectedItems"
|
||||
:imagePath="this.path"
|
||||
@dblclick="editPartDblClick">
|
||||
</DataCard>
|
||||
</div>
|
||||
</template>
|
@ -1,120 +0,0 @@
|
||||
<script>
|
||||
import DataService from '../services/DataService';
|
||||
import CatalogMixins from '../mixins/CatalogMixins.js';
|
||||
import ReviewCard from './ReviewCard.vue'
|
||||
export default {
|
||||
components: {
|
||||
ReviewCard
|
||||
},
|
||||
mixins: [
|
||||
CatalogMixins
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
getAllUrl: 'reviews/get_all_reviews/',
|
||||
dataUrl: 'reviews/',
|
||||
path: '/public/Part.jpg',
|
||||
ifUser: false
|
||||
}
|
||||
},
|
||||
beforeCreate() {
|
||||
if(document.getElementById("user").innerText == "Войти" && localStorage.getItem("token") != null)
|
||||
document.getElementById("user").innerText = "Выход (" + localStorage.getItem("user").substr(0,5) +"...)";
|
||||
},
|
||||
methods: {
|
||||
getItems() {
|
||||
if (localStorage.getItem("token") != null)
|
||||
this.ifUser = true;
|
||||
DataService.getData(`${this.getAllUrl}${this.getId()}`).then(data => {
|
||||
this.items = data;
|
||||
});
|
||||
},
|
||||
getId(){
|
||||
var ph, id;
|
||||
ph = this.$route.path.replace('/part/', '');
|
||||
if(!isNaN(parseInt(ph)))
|
||||
id = parseInt(ph);
|
||||
else
|
||||
throw "Неверный id!";
|
||||
return id;
|
||||
},
|
||||
addReview(){
|
||||
this.isEdit = false;
|
||||
this.data = this.items;
|
||||
this.modal.header = 'Добавить отзыв';
|
||||
this.modal.confirm = 'Добавить';
|
||||
this.modalShow = true;
|
||||
},
|
||||
Save(data, md){
|
||||
var partId = this.getId();
|
||||
if(md == "create")
|
||||
return `reviews?comment=${data.comment}&rating=${data.rating}&part_id=${partId}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h2 align="center"> Отзывы </h2>
|
||||
<div class="container">
|
||||
<div class="button-group mb-4">
|
||||
<button class="btn btn-success me-2" @click="addReview" v-if="this.ifUser">
|
||||
<i class="bi bi-plus-circle me-2"></i>
|
||||
Добавить отзыв
|
||||
</button>
|
||||
<button class="btn btn-danger" @click="removeSelectedItems" v-if="this.ifUser">
|
||||
<i class="bi bi-trash me-2"></i>
|
||||
Удалить отзыв
|
||||
</button>
|
||||
</div>
|
||||
<ReviewCard
|
||||
:items="this.items"
|
||||
:selectedItems="this.selectedItems"
|
||||
:imagePath="this.path">
|
||||
</ReviewCard>
|
||||
<Modal
|
||||
:header="this.modal.header"
|
||||
:confirm="this.modal.confirm"
|
||||
v-model:visible="this.modalShow"
|
||||
@done="saveItem">
|
||||
<div class="col-mb-3">
|
||||
<label for="comment" class="form-label">Комментарий</label>
|
||||
<textarea class="form-control" id="comment" rows="3" required v-model="data.comment"></textarea>
|
||||
</div>
|
||||
<div class="col-mb-3">
|
||||
<label for="Rating" class="form-label">Рейтинг</label>
|
||||
<input type="number" class="form-control" id="Rating" step="1" min="1" max="10" required v-model="data.rating">
|
||||
</div>
|
||||
</Modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.button-group {
|
||||
max-width: 800px;
|
||||
margin: 20px auto;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 10px 20px;
|
||||
font-weight: 500;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.btn-success {
|
||||
background-color: #28a745;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
background-color: #dc3545;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
|
||||
}
|
||||
</style>
|
@ -1,133 +0,0 @@
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
items: Array,
|
||||
selectedItems: Array,
|
||||
imagePath: String
|
||||
},
|
||||
emits: {
|
||||
dblclick: null
|
||||
},
|
||||
methods: {
|
||||
cardClick(id) {
|
||||
if (this.isSelected(id)) {
|
||||
var index = this.selectedItems.indexOf(id);
|
||||
if (index !== -1) {
|
||||
this.selectedItems.splice(index, 1);
|
||||
}
|
||||
} else {
|
||||
this.selectedItems.push(id);
|
||||
}
|
||||
},
|
||||
isSelected(id) {
|
||||
return this.selectedItems.includes(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="reviews-container">
|
||||
<div v-for="(item, index) in this.items" class="review-card" :id = "'item - ' + item.id"
|
||||
@click="cardClick(item.id)"
|
||||
:class="{selected: isSelected(item.id)}">
|
||||
<div class="review-left">
|
||||
<img :src="imagePath" alt="Avatar" class="user-avatar">
|
||||
</div>
|
||||
<div class="review-right">
|
||||
<div class="rating">
|
||||
<span class="rating-number">{{ item["rating"] }}</span>
|
||||
</div>
|
||||
<div class="comment">
|
||||
{{ item["comment"] }}
|
||||
</div>
|
||||
<div class="user-info">
|
||||
<span class="username">{{ item["user_name"] }}</span>
|
||||
<span class="email">{{ item["email"] }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.reviews-container {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.review-card {
|
||||
display: flex;
|
||||
margin-bottom: 20px;
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.selected {
|
||||
background-color: color-mix(in srgb, #ffffff 90%, #808080);
|
||||
opacity: 80%;
|
||||
}
|
||||
|
||||
.review-left {
|
||||
flex: 0 0 150px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.user-avatar {
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.review-right {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.rating {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.rating-number {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 3px solid #333;
|
||||
border-radius: 50%;
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.comment {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
margin: 15px 0;
|
||||
font-size: 16px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.user-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.username {
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.email {
|
||||
color: #666;
|
||||
}
|
||||
</style>
|
@ -1,348 +0,0 @@
|
||||
<script>
|
||||
import DataService from '../services/DataService';
|
||||
export default {
|
||||
methods: {
|
||||
formatPhoneNumber(e) {
|
||||
let phoneNumber = e.target.value.replace(/\D/g, '');
|
||||
|
||||
if (phoneNumber.length > 0) {
|
||||
if (phoneNumber[0] === '8') {
|
||||
phoneNumber = '7' + phoneNumber.slice(1);
|
||||
} else if (phoneNumber[0] !== '7') {
|
||||
phoneNumber = '7' + phoneNumber;
|
||||
}
|
||||
}
|
||||
|
||||
phoneNumber = phoneNumber.substring(0, 11);
|
||||
|
||||
let formattedPhone = '';
|
||||
if (phoneNumber.length > 0) {
|
||||
formattedPhone = '+7';
|
||||
if (phoneNumber.length > 1) {
|
||||
formattedPhone += ' (' + phoneNumber.substring(1, 4);
|
||||
}
|
||||
if (phoneNumber.length > 4) {
|
||||
formattedPhone += ') ' + phoneNumber.substring(4, 7);
|
||||
}
|
||||
if (phoneNumber.length > 7) {
|
||||
formattedPhone += '-' + phoneNumber.substring(7, 9);
|
||||
}
|
||||
if (phoneNumber.length > 9) {
|
||||
formattedPhone += '-' + phoneNumber.substring(9, 11);
|
||||
}
|
||||
}
|
||||
|
||||
this.data.phone = formattedPhone;
|
||||
},
|
||||
getUserInfo () {
|
||||
if (!localStorage.getItem("token")){
|
||||
localStorage.removeItem("user");
|
||||
}
|
||||
},
|
||||
async login (login, password) {
|
||||
const requestParams = {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
};
|
||||
var str = `?email=${login}&password=${password}`;
|
||||
const response = await fetch(this.hostURL + "/users/getuser" + str, requestParams);
|
||||
var result = await response.text();
|
||||
if (response.status === 200) {
|
||||
result = JSON.parse(result);
|
||||
if (result["id"] == 1)
|
||||
localStorage.setItem("id", result["id"]);
|
||||
localStorage.setItem("token", result["token"]);
|
||||
localStorage.setItem("user", login);
|
||||
this.$router.push("/categories");
|
||||
location.reload();
|
||||
window.location.href = "/categories";
|
||||
} else {
|
||||
if (localStorage.getItem("id") != null)
|
||||
localStorage.removeItem("id");
|
||||
localStorage.removeItem("token");
|
||||
localStorage.removeItem("user");
|
||||
document.getElementById('errorSignIn').hidden = false;
|
||||
alert(result);
|
||||
}
|
||||
},
|
||||
loginForm () {
|
||||
this.login(this.loginInput.value, this.passwordInput.value).then(() => {
|
||||
this.loginInput.value = "";
|
||||
this.passwordInput.value = "";
|
||||
this.getUserInfo();
|
||||
});
|
||||
},
|
||||
async createUser() {
|
||||
const requestParams = {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(this.data)
|
||||
};
|
||||
var data = this.data; var response_json = "";
|
||||
var str = `?email=${data.email}&name=${data.name}&phone=${data.phone}&password_hash=${data.password_hash}&address=${data.address}&role=1`;
|
||||
const response = await fetch(this.hostURL + "/users" + str, requestParams);
|
||||
if (response.text !== "error") {
|
||||
var result = await response.text();
|
||||
response_json = JSON.parse(result);
|
||||
localStorage.setItem("token", response_json["token"]);
|
||||
document.getElementById('tab-1').checked = true;
|
||||
} else {
|
||||
document.getElementById('error').hidden = false;
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
hostURL: "http://localhost:5001",
|
||||
loginInput: undefined,
|
||||
passwordInput: undefined,
|
||||
loginButton: undefined,
|
||||
data: {}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.loginInput = document.getElementById("email");
|
||||
this.passwordInput = document.getElementById("password");
|
||||
this.loginButton = document.getElementById("loginBtn");
|
||||
this.getUserInfo();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="login-wrap">
|
||||
<div class="login-html">
|
||||
<input id="tab-1" type="radio" name="tab" class="sign-in" checked><label for="tab-1" class="tab">Вход</label>
|
||||
<input id="tab-2" type="radio" name="tab" class="sign-up"><label for="tab-2" class="tab">Регистрация</label>
|
||||
<div class="login-form">
|
||||
<form id="loginForm" @submit.prevent="loginForm" ref="form">
|
||||
<div class="sign-in-htm">
|
||||
<div class="group form-label">
|
||||
<label for="email" class="label">Электронная почта</label>
|
||||
<p><span class="fontawesome-user badge badge-secondary"></span><input id="email" type="text" class="input form-control" required></p>
|
||||
</div>
|
||||
<div class="group">
|
||||
<label for="password" class="label form-label">Пароль</label>
|
||||
<p><span class="fontawesome-lock badge badge-secondary"></span><input id="password" type="password" class="input form-control" required></p>
|
||||
</div>
|
||||
<div class="group">
|
||||
<input id="loginBtn" type="submit" class="btn button" value="Войти">
|
||||
</div>
|
||||
<div id="errorSignIn" hidden class="alert alert-danger" role="alert">
|
||||
Пользователь не найден!
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<form @submit.prevent="createUser" ref="form">
|
||||
<div class="sign-up-htm">
|
||||
<div class="group">
|
||||
<label for="userEmail" class="label form-label">Электронная почта</label>
|
||||
<p><span class="fontawesome-user badge badge-secondary"></span><input id="userEmail" type="text" class="input form-control" required autofocus maxlength="64" v-model="data.email"></p>
|
||||
</div>
|
||||
<div class="group">
|
||||
<label for="userName" class="label form-label">ФИО</label>
|
||||
<p><span class="fontawesome-user badge badge-secondary"></span><input id="userName" type="text" class="input form-control" required autofocus maxlength="64" v-model="data.name"></p>
|
||||
</div>
|
||||
<div class="group">
|
||||
<label for="password_hash" class="label form-label">Пароль</label>
|
||||
<p><span class="fontawesome-lock badge badge-secondary"></span><input id="password_hash" type="password" class="input form-control" required minlength="6" maxlength="64" v-model="data.password_hash"></p>
|
||||
</div>
|
||||
<div class="group">
|
||||
<label for="userPhone" class="label form-label">Телефон</label>
|
||||
<p><span class="fontawesome-user badge badge-secondary"></span><input id="userPhone" type="text" class="input form-control" required autofocus maxlength="20" v-model="data.phone" @input="formatPhoneNumber"></p>
|
||||
</div>
|
||||
<div class="group">
|
||||
<label for="userAddress" class="label form-label">Адрес</label>
|
||||
<p><span class="fontawesome-lock badge badge-secondary"></span><input id="userAddress" type="text" class="input form-control" required minlength="6" maxlength="64" v-model="data.address"></p>
|
||||
</div>
|
||||
<div class="group">
|
||||
<input type="submit" class="btn button" value="Зарегистрироваться">
|
||||
</div>
|
||||
<div class="hr"></div>
|
||||
<div class="foot-lnk">
|
||||
<label for="tab-1">Уже зарегистрирован?</label>
|
||||
</div>
|
||||
<div id="errorSignUp" hidden class="alert alert-danger" role="alert">
|
||||
Error!
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
[class*="fontawesome-"]:before {
|
||||
font-family: 'FontAwesome', sans-serif;
|
||||
}
|
||||
|
||||
input[type="tel"] {
|
||||
padding: 8px;
|
||||
font-size: 16px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
input[type="tel"]:focus {
|
||||
outline: none;
|
||||
border-color: #007bff;
|
||||
box-shadow: 0 0 0 2px rgba(0,123,255,.25);
|
||||
}
|
||||
|
||||
input[type="tel"]::placeholder {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.login-wrap{
|
||||
width:100%;
|
||||
margin:auto;
|
||||
max-width:800px;
|
||||
min-height:696px;
|
||||
position:relative;
|
||||
box-shadow:0 12px 15px 0 rgba(0,0,0,.24),0 17px 50px 0 rgba(0,0,0,.19);
|
||||
}
|
||||
|
||||
.login-html{
|
||||
width:100%;
|
||||
height:100%;
|
||||
position:absolute;
|
||||
padding:90px 70px 50px 70px;
|
||||
background-color: #2c3338;
|
||||
color: #606468;
|
||||
}
|
||||
|
||||
.login-html .sign-in-htm,
|
||||
.login-html .sign-up-htm{
|
||||
top:0;
|
||||
left:0;
|
||||
right:0;
|
||||
bottom:0;
|
||||
position:absolute;
|
||||
transform:rotateY(180deg);
|
||||
backface-visibility:hidden;
|
||||
transition:all .4s linear;
|
||||
}
|
||||
|
||||
.login-html .sign-in,
|
||||
.login-html .sign-up,
|
||||
.login-form .group .check{
|
||||
display:none;
|
||||
}
|
||||
.login-html .tab,
|
||||
.login-form .group .label,
|
||||
.login-form .group .button{
|
||||
text-transform:uppercase;
|
||||
}
|
||||
.login-html .tab{
|
||||
font-size:22px;
|
||||
margin-right:15px;
|
||||
padding-bottom:5px;
|
||||
margin:0 15px 10px 0;
|
||||
display:inline-block;
|
||||
border-bottom:2px solid transparent;
|
||||
}
|
||||
.login-html .sign-in:checked + .tab,
|
||||
.login-html .sign-up:checked + .tab{
|
||||
color:#fff;
|
||||
border-color:#ea4c88;
|
||||
}
|
||||
.login-form{
|
||||
min-height:345px;
|
||||
position:relative;
|
||||
perspective:1000px;
|
||||
transform-style:preserve-3d;
|
||||
}
|
||||
.login-form .group{
|
||||
margin-bottom:15px;
|
||||
}
|
||||
.login-form .group .label{
|
||||
width:100%;
|
||||
color:#fff;
|
||||
display:block;
|
||||
}
|
||||
|
||||
.login-form .group .button{
|
||||
border:none;
|
||||
padding:15px 20px;
|
||||
border-radius:25px;
|
||||
}
|
||||
|
||||
.login-form .group .label{
|
||||
color:#aaa;
|
||||
font-size:12px;
|
||||
}
|
||||
|
||||
.login-html .sign-in:checked + .tab + .sign-up + .tab + .login-form .sign-in-htm{
|
||||
transform:rotate(0);
|
||||
}
|
||||
.login-html .sign-up:checked + .tab + .login-form .sign-up-htm{
|
||||
transform:rotate(0);
|
||||
}
|
||||
|
||||
.form-control:focus {
|
||||
box-shadow: 0 0 0 0.2rem rgba(234, 76, 136, 0.25);
|
||||
}
|
||||
|
||||
.login-form .group span {
|
||||
background-color: #363b41;
|
||||
border-radius: 3px 0px 0px 3px;
|
||||
-moz-border-radius: 3px 0px 0px 3px;
|
||||
-webkit-border-radius: 3px 0px 0px 3px;
|
||||
color: #606468;
|
||||
display: block;
|
||||
float: left;
|
||||
height: 50px;
|
||||
line-height: 50px;
|
||||
text-align: center;
|
||||
width: 8%;
|
||||
}
|
||||
|
||||
.login-form .group .input {
|
||||
border: none;
|
||||
font-family: 'Open Sans', Arial, sans-serif;
|
||||
font-size: 14px;
|
||||
line-height: 1.5em;
|
||||
padding: 0;
|
||||
-webkit-appearance: none;
|
||||
height: 50px;
|
||||
outline:none;
|
||||
}
|
||||
|
||||
.login-form .group .input {
|
||||
background-color: #3b4148;
|
||||
border-radius: 0px 3px 3px 0px;
|
||||
-moz-border-radius: 0px 3px 3px 0px;
|
||||
-webkit-border-radius: 0px 3px 3px 0px;
|
||||
color: #606468;
|
||||
margin-bottom: 1em;
|
||||
padding: 0 16px;
|
||||
width: 92%;
|
||||
}
|
||||
|
||||
.login-form .group .input:focus {
|
||||
color:#fff;
|
||||
}
|
||||
|
||||
.login-form .group .button {
|
||||
border-radius: 3px;
|
||||
-moz-border-radius: 3px;
|
||||
-webkit-border-radius: 3px;
|
||||
background-color: #ea4c88;
|
||||
color: #eee;
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
cursor:pointer;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.login-form .group .button:hover {
|
||||
background-color: #d44179;
|
||||
}
|
||||
</style>
|
@ -1,95 +0,0 @@
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
|
||||
},
|
||||
emits: {
|
||||
add: null,
|
||||
edit: null,
|
||||
remove: null
|
||||
},
|
||||
methods: {
|
||||
add() {
|
||||
this.$emit('add');
|
||||
},
|
||||
edit() {
|
||||
this.$emit('edit');
|
||||
},
|
||||
remove() {
|
||||
this.$emit('remove');
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="btn-group mt-3" role="group">
|
||||
<button type="button" class="btn btn-custom btn-gray" @click.prevent="add">
|
||||
<i class="fas fa-plus-circle me-2"></i>Добавить
|
||||
</button>
|
||||
<button type="button" class="btn btn-custom btn-blue" @click.prevent="edit">
|
||||
<i class="fas fa-edit me-2"></i>Изменить
|
||||
</button>
|
||||
<button type="button" class="btn btn-custom btn-red" @click.prevent="remove">
|
||||
<i class="fas fa-trash-alt me-2"></i>Удалить
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.btn {
|
||||
min-width: 140px;
|
||||
}
|
||||
|
||||
.btn-group {
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.btn-custom {
|
||||
padding: 10px 20px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
transition: all 0.3s ease;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.btn-red {
|
||||
background-color: #8B0000;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-blue {
|
||||
background-color: #00008B;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-gray {
|
||||
background-color: #4A4A4A;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-custom:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.btn-red:hover {
|
||||
background-color: #A52A2A;
|
||||
}
|
||||
|
||||
.btn-blue:hover {
|
||||
background-color: #0000CD;
|
||||
}
|
||||
|
||||
.btn-gray:hover {
|
||||
background-color: #696969;
|
||||
}
|
||||
|
||||
.btn-custom:active {
|
||||
transform: translateY(0);
|
||||
box-shadow: none;
|
||||
}
|
||||
</style>
|
@ -1,30 +0,0 @@
|
||||
import { createApp } from 'vue'
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
import './style.css'
|
||||
import App from './App.vue'
|
||||
import SignUpIn from "./components/SignUpIn.vue"
|
||||
import Category from "./components/Category.vue"
|
||||
import Part from "./components/Part.vue"
|
||||
import PartDefine from "./components/PartDefine.vue"
|
||||
import Cart from "./components/Cart.vue"
|
||||
import Order from "./components/Order.vue"
|
||||
import OrderItems from "./components/OrderItems.vue"
|
||||
|
||||
const routes = [
|
||||
{ path: '/', redirect: '/categories' },
|
||||
{ path: '/categories', component: Category, meta: { label: 'Категории' } },
|
||||
{ path: '/orders', component: Order, meta: { label: 'Заказы' } },
|
||||
{ path: '/cart', component: Cart, meta: { label: 'Корзина' } },
|
||||
{ path: '/orderitems/:orderId(\\d+)', component: OrderItems },
|
||||
{ path: '/parts/:categoryId(\\d+)', component: Part },
|
||||
{ path: '/part/:partId(\\d+)', component: PartDefine },
|
||||
{ path: '/signupin', component: SignUpIn}
|
||||
]
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
linkActiveClass: 'active',
|
||||
routes
|
||||
})
|
||||
|
||||
createApp(App).use(router).mount('#app')
|
@ -1,101 +0,0 @@
|
||||
import ToolBar from '../components/ToolBar.vue';
|
||||
import DataCard from '../components/DataCard.vue';
|
||||
import DataTable from '../components/DataTable.vue';
|
||||
import Modal from '../components/Modal.vue';
|
||||
import DataService from '../services/DataService';
|
||||
|
||||
const CatalogMixin = {
|
||||
components: {
|
||||
ToolBar, Modal, DataCard, DataTable
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
getAllUrl: undefined,
|
||||
dataUrl: undefined,
|
||||
headers: [],
|
||||
items: [],
|
||||
selectedItems: [],
|
||||
modal: {
|
||||
header: undefined,
|
||||
confirm: undefined,
|
||||
},
|
||||
modalShow: false,
|
||||
data: undefined,
|
||||
isEdit: false
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getItems();
|
||||
this.data = this.items;
|
||||
},
|
||||
methods: {
|
||||
showAddModal() {
|
||||
this.isEdit = false;
|
||||
this.data = this.items;
|
||||
this.modal.header = 'Добавление элемента';
|
||||
this.modal.confirm = 'Добавить';
|
||||
this.modalShow = true;
|
||||
},
|
||||
showEditModal() {
|
||||
if (this.selectedItems.length === 0) {
|
||||
return;
|
||||
}
|
||||
this.showEditModalDblClick(this.selectedItems[0]);
|
||||
},
|
||||
showEditModalDblClick(editId) {
|
||||
if(typeof(editId) != 'number')
|
||||
editId = editId.id;
|
||||
DataService.getData(this.dataUrl + editId)
|
||||
.then(data => {
|
||||
this.data = data;
|
||||
this.isEdit = true;
|
||||
this.modal.header = 'Редактирование элемента';
|
||||
this.modal.confirm = 'Сохранить';
|
||||
this.modalShow = true;
|
||||
});
|
||||
},
|
||||
saveItem() {
|
||||
var dataUrlSave = '';
|
||||
if (!this.isEdit) {
|
||||
dataUrlSave = this.Save(this.data, "create");
|
||||
DataService.create(dataUrlSave)
|
||||
.then(() => {
|
||||
this.getItems();
|
||||
});
|
||||
} else {
|
||||
dataUrlSave = this.Save(this.data, "update");
|
||||
DataService.update(this.dataUrl + this.data.id, dataUrlSave)
|
||||
.then(() => {
|
||||
this.getItems();
|
||||
document.querySelectorAll("div").forEach(div => {
|
||||
div.classList.remove("selected");
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
removeSelectedItems() {
|
||||
if (this.selectedItems.length === 0) {
|
||||
return;
|
||||
}
|
||||
if (confirm('Удалить выбранные элементы?')) {
|
||||
const promises = [];
|
||||
const self = this;
|
||||
this.selectedItems.forEach(item => {
|
||||
promises.push(DataService.delete(this.dataUrl + item));
|
||||
});
|
||||
Promise.all(promises).then((results) => {
|
||||
results.forEach(function (id) {
|
||||
const index = self.selectedItems.indexOf(id);
|
||||
if (index === - 1) {
|
||||
return;
|
||||
}
|
||||
self.selectedItems.splice(index, 1);
|
||||
});
|
||||
this.getItems();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default CatalogMixin;
|
@ -1,58 +0,0 @@
|
||||
export default class DataService {
|
||||
static host = "http://localhost:5001/";
|
||||
|
||||
static async getData(str){
|
||||
var response, data, requestParams;
|
||||
if (localStorage.getItem("token") != null)
|
||||
requestParams = {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Authorization": "Bearer " + localStorage.getItem("token")
|
||||
}
|
||||
};
|
||||
else
|
||||
requestParams = {
|
||||
method: "GET"
|
||||
};
|
||||
response = await fetch(this.host + str, requestParams);
|
||||
data = await response.json();
|
||||
return data;
|
||||
}
|
||||
|
||||
static async create(str) {
|
||||
const requestParams = {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": "Bearer " + localStorage.getItem("token")
|
||||
}
|
||||
};
|
||||
const response = await fetch(this.host + str, requestParams);
|
||||
return await response.json();
|
||||
}
|
||||
|
||||
static async update(str, values) {
|
||||
const requestParams = {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
"Authorization": "Bearer " + localStorage.getItem("token")
|
||||
},
|
||||
body:JSON.stringify(values)
|
||||
};
|
||||
const response = await fetch(this.host + str, requestParams);
|
||||
return await response.json();
|
||||
}
|
||||
|
||||
static async delete(str) {
|
||||
var response;
|
||||
const requestParams = {
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
"Authorization": "Bearer " + localStorage.getItem("token")
|
||||
}
|
||||
};
|
||||
response = await fetch(this.host + str, requestParams);
|
||||
return await response.json();
|
||||
}
|
||||
}
|
@ -1,58 +0,0 @@
|
||||
header nav {
|
||||
background-color: #3c3c3c;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
header nav {
|
||||
height: 64px;
|
||||
}
|
||||
}
|
||||
|
||||
header nav a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
footer {
|
||||
background-color: #9c9c9c;
|
||||
height: 32px;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 1.5em;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.25em;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
.navbar-brand {
|
||||
font-weight: bold;
|
||||
font-size: 1.5rem;
|
||||
color: grey !important;
|
||||
background-color: #f8f9fa;
|
||||
padding: 5px 15px;
|
||||
border-radius: 20px;
|
||||
border: 2px solid red;
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.btn-auth {
|
||||
background-color: #FF0000;
|
||||
border-color: #FF0000;
|
||||
transition: all 0.3s ease;
|
||||
border-radius: 25px;
|
||||
padding: 8px 20px;
|
||||
}
|
||||
|
||||
.btn-auth:hover {
|
||||
background-color: #003d82;
|
||||
border-color: #003d82;
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [vue()]
|
||||
})
|
26
front_admin/.gitignore
vendored
26
front_admin/.gitignore
vendored
@ -1,26 +0,0 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
.parcel-cache
|
@ -1,17 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport"
|
||||
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<script type="module" src="./node_modules/bootstrap/dist/js/bootstrap.min.js"></script>
|
||||
<link rel="stylesheet" href="/node_modules/bootstrap/dist/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="/node_modules/@fortawesome/fontawesome-free/css/all.min.css">
|
||||
<title>Интернет-магазин автозапчастей</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
</body>
|
||||
</html>
|
1354
front_admin/package-lock.json
generated
1354
front_admin/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,21 +0,0 @@
|
||||
{
|
||||
"name": "internet-auto-parts-store",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"start": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-free": "^6.2.1",
|
||||
"bootstrap": "^5.2.2",
|
||||
"vue": "^3.2.41",
|
||||
"vue-router": "^4.1.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vite": "^3.2.3",
|
||||
"@vitejs/plugin-vue": "^3.2.0"
|
||||
}
|
||||
}
|
Binary file not shown.
Before Width: | Height: | Size: 609 KiB |
@ -1,19 +0,0 @@
|
||||
<script>
|
||||
import Header from './components/Header.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Header
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Header></Header>
|
||||
<div class="container">
|
||||
<router-view></router-view>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
@ -1,73 +0,0 @@
|
||||
<script>
|
||||
import DataService from '../services/DataService';
|
||||
import CatalogMixins from '../mixins/CatalogMixins.js';
|
||||
export default {
|
||||
mixins: [
|
||||
CatalogMixins
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
getAllUrl: 'categories',
|
||||
dataUrl: 'categories/',
|
||||
headers: [
|
||||
{ name: 'name', label: 'Имя категории'},
|
||||
{ name: 'description', label: 'Описание'}
|
||||
],
|
||||
path: '/public/Part.jpg'
|
||||
}
|
||||
},
|
||||
beforeCreate() {
|
||||
if(localStorage.getItem("token") == null) {
|
||||
this.$router.push("/signin");
|
||||
}
|
||||
else
|
||||
if(document.getElementById("user") != null)
|
||||
document.getElementById("user").innerText = "Выход (" + localStorage.getItem("user").substr(0,5) +"...)";
|
||||
},
|
||||
methods: {
|
||||
getItems() {
|
||||
if(localStorage.getItem("token") != null){
|
||||
DataService.getData(this.getAllUrl).then(data => {
|
||||
this.items = data;
|
||||
});
|
||||
}
|
||||
},
|
||||
addCategory(){
|
||||
this.$router.push("/createcategory");
|
||||
},
|
||||
editCategory(){
|
||||
if (this.selectedItems.length === 0) {
|
||||
return;
|
||||
}
|
||||
this.editCategoryDblClick(this.selectedItems[0]);
|
||||
},
|
||||
editCategoryDblClick(item){
|
||||
var id = -1;
|
||||
if(typeof(item) === "number")
|
||||
id = item;
|
||||
else
|
||||
id = item.id;
|
||||
|
||||
this.$router.push('updatecategory/' + id);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="container">
|
||||
<h1 align="center"> Каталог категорий </h1>
|
||||
<DataCard
|
||||
:headers="this.headers"
|
||||
:items="this.items"
|
||||
:selectedItems="this.selectedItems"
|
||||
:imagePath="this.path"
|
||||
@dblclick="editCategoryDblClick">
|
||||
</DataCard>
|
||||
</div>
|
||||
<ToolBar
|
||||
@add="addCategory"
|
||||
@edit="editCategory"
|
||||
@remove="removeSelectedItems">
|
||||
</ToolBar>
|
||||
</template>
|
@ -1,207 +0,0 @@
|
||||
<script>
|
||||
import DataService from '../services/DataService';
|
||||
import CatalogMixins from '../mixins/CatalogMixins.js';
|
||||
export default {
|
||||
mixins: [
|
||||
CatalogMixins
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
getAllUrl: 'categories',
|
||||
dataUrl: 'categories/',
|
||||
headers: [
|
||||
{ name: 'name', label: 'Название характеристики' },
|
||||
{ name: 'description', label: 'Описание' }
|
||||
],
|
||||
path: '/public/Part.jpg',
|
||||
isEditCategory: false,
|
||||
features: [],
|
||||
name_feature: "",
|
||||
description_feature: "",
|
||||
id: 1
|
||||
}
|
||||
},
|
||||
beforeCreate() {
|
||||
if(localStorage.getItem("token") == null) {
|
||||
this.$router.push("/signin");
|
||||
}
|
||||
else
|
||||
if(document.getElementById("user") != null)
|
||||
document.getElementById("user").innerText = "Выход (" + localStorage.getItem("user").substr(0,5) +"...)";
|
||||
},
|
||||
methods: {
|
||||
getItems(){
|
||||
var ph = this.$route.path;
|
||||
if(ph.indexOf("updatecategory") > 0){
|
||||
this.nameButton = "Сохранить";
|
||||
DataService.getData(this.dataUrl + this.getId())
|
||||
.then(data => {
|
||||
this.data = data;
|
||||
this.isEditCategory = true;
|
||||
if (this.data.parent_category_id == null)
|
||||
this.data.parent_category_id = 0;
|
||||
this.features = this.data["feature"]["features"];
|
||||
});
|
||||
}
|
||||
else
|
||||
this.nameButton = "Добавить";
|
||||
},
|
||||
Save(data, md){
|
||||
if(md == "create")
|
||||
return `categories?name=${data.name}&description=${data.description}&feature={"features": ${JSON.stringify(this.features)}}&parent_category_id=${data.parent_category_id}`;
|
||||
const dataUp = {name: `${data.name}`,
|
||||
description: `${data.description}`,
|
||||
feature: `{"features": ${JSON.stringify(this.features)}}`,
|
||||
parent_category_id: data.parent_category_id
|
||||
};
|
||||
return dataUp;
|
||||
},
|
||||
getId(){
|
||||
var ph, id;
|
||||
ph = this.$route.path.replace('/updatecategory/', '');
|
||||
if(!isNaN(parseInt(ph)))
|
||||
id = parseInt(ph);
|
||||
else
|
||||
throw "Неверный id!";
|
||||
return id;
|
||||
},
|
||||
async saveCategory(){
|
||||
var dataUrlSave = '';
|
||||
if (!this.isEditCategory) {
|
||||
dataUrlSave = this.Save(this.data, "create");
|
||||
DataService.create(dataUrlSave)
|
||||
.then(() => {
|
||||
this.$router.push("/categories");
|
||||
});
|
||||
}
|
||||
else {
|
||||
dataUrlSave = this.Save(this.data, "update");
|
||||
DataService.update(this.dataUrl + this.data.id, dataUrlSave)
|
||||
.then(() => {
|
||||
this.$router.push("/categories");
|
||||
});
|
||||
}
|
||||
},
|
||||
AddFeature(){
|
||||
this.name_feature = "";
|
||||
this.description_feature = "";
|
||||
this.isEdit = false;
|
||||
this.modal.header = 'Добавление характеристики';
|
||||
this.modal.confirm = 'Добавить';
|
||||
this.modalShow = true;
|
||||
},
|
||||
UpdateFeature(){
|
||||
if (this.selectedItems.length === 0) {
|
||||
return;
|
||||
}
|
||||
this.UpdateFeatureDblClick(this.selectedItems[0]);
|
||||
},
|
||||
UpdateFeatureDblClick(editId){
|
||||
var feature = this.getFeatureId(editId);
|
||||
if(Object.keys(feature).length > 0){
|
||||
this.id = feature["id"];
|
||||
this.name_feature = feature["name"];
|
||||
this.description_feature = feature["description"];
|
||||
}
|
||||
this.isEdit = true;
|
||||
this.modal.header = 'Редактирование элемента';
|
||||
this.modal.confirm = 'Сохранить';
|
||||
this.modalShow = true;
|
||||
},
|
||||
DeleteFeatures(){
|
||||
var i, j;
|
||||
for (i = 0; i <= this.selectedItems.length; i++){
|
||||
j = 0;
|
||||
while (j < this.features.length && this.features[j]["id"] != this.selectedItems[i])
|
||||
j++;
|
||||
if (j < this.features.length){
|
||||
this.features.splice(j, 1);
|
||||
this.selectedItems.splice(i, 1);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
},
|
||||
saveFeature(){
|
||||
var id, maxId, i = 0;
|
||||
if (this.isEdit){
|
||||
while (i < this.features.length && this.features[i]["id"] != this.id)
|
||||
i++;
|
||||
if (i < this.features.length)
|
||||
this.features[i] = {id: this.id, name: this.name_feature, description: this.description_feature};
|
||||
}
|
||||
else {
|
||||
maxId = this.getMaxId();
|
||||
id = maxId > this.id ? maxId + 1 : this.id;
|
||||
this.features.push({id: id, name: this.name_feature, description: this.description_feature});
|
||||
this.id++;
|
||||
}
|
||||
this.name_feature = "";
|
||||
this.description_feature = "";
|
||||
},
|
||||
getMaxId(){
|
||||
var i, maxId = -1;
|
||||
if (this.features.length > 0)
|
||||
for (i = 0; i < this.features.length; i++)
|
||||
if (maxId < this.features[i]["id"])
|
||||
maxId = this.features[i]["id"];
|
||||
return maxId;
|
||||
},
|
||||
getFeatureId(featureId){
|
||||
var i = 0;
|
||||
while (i < this.features.length && this.features[i]["id"] != featureId) i++;
|
||||
if (i < this.features.length)
|
||||
return this.features[i];
|
||||
return {};
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<form @submit.prevent="saveCategory" ref="form">
|
||||
<div class="col-mb-3" style="margin: 0 0 15px 15px;">
|
||||
<label for="Name" class="form-label">Имя категории</label>
|
||||
<input type="text" class="form-control" id="Name" required v-model="data.name">
|
||||
</div>
|
||||
<div class="col-mb-3" style="margin: 0 0 15px 15px;">
|
||||
<label for="Description" class="form-label">Описание</label>
|
||||
<textarea class="form-control" id="Description" rows="3" required v-model="data.description"></textarea>
|
||||
</div>
|
||||
<div class="col-mb-3" style="margin: 0 0 15px 15px;">
|
||||
<label for="Parent_category_id" class="form-label">Идентификатор категории</label>
|
||||
<input type="number" class="form-control" id="Parent_category_id" step="1" min="0" required v-model="data.parent_category_id">
|
||||
</div>
|
||||
<div class="d-grid gap-2 col-mb-3" style="margin: 0 0 15px 15px;">
|
||||
<button type="submit" id="save" class="btn btn-danger btn-lg">
|
||||
{{ this.nameButton }}
|
||||
</button>
|
||||
</div>
|
||||
<ToolBar
|
||||
@add="AddFeature"
|
||||
@edit="UpdateFeature"
|
||||
@remove="DeleteFeatures">
|
||||
</ToolBar>
|
||||
<DataTable
|
||||
:headers="this.headers"
|
||||
:items="this.features"
|
||||
:selectedItems="this.selectedItems"
|
||||
@dblclick="UpdateFeatureDblClick">
|
||||
</DataTable>
|
||||
</form>
|
||||
</div>
|
||||
<Modal
|
||||
:header="this.modal.header"
|
||||
:confirm="this.modal.confirm"
|
||||
v-model:visible="this.modalShow"
|
||||
@done="saveFeature">
|
||||
<div class="col-mb-3">
|
||||
<label for="Name_feature" class="form-label">Название характеристики</label>
|
||||
<input type="text" class="form-control" id="Name_feature" required v-model="this.name_feature">
|
||||
</div>
|
||||
<div class="col-mb-3">
|
||||
<label for="Description_feature" class="form-label">Описание</label>
|
||||
<input type="text" class="form-control" id="Description_feature" required v-model="this.description_feature">
|
||||
</div>
|
||||
</Modal>
|
||||
</template>
|
@ -1,90 +0,0 @@
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
headers: Array,
|
||||
items: Array,
|
||||
selectedItems: Array,
|
||||
imagePath: String
|
||||
},
|
||||
emits: {
|
||||
dblclick: null
|
||||
},
|
||||
methods: {
|
||||
cardClick(id) {
|
||||
if (this.isSelected(id)) {
|
||||
var index = this.selectedItems.indexOf(id);
|
||||
if (index !== -1) {
|
||||
this.selectedItems.splice(index, 1);
|
||||
}
|
||||
} else {
|
||||
this.selectedItems.push(id);
|
||||
}
|
||||
},
|
||||
cardDblClick(item) {
|
||||
this.$emit('dblclick', item);
|
||||
},
|
||||
isSelected(id) {
|
||||
return this.selectedItems.includes(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div id="div-items" class = "row" style="justify-content: center;">
|
||||
<div align="justify" class = "card" v-for="(item, index) in this.items" :id = "'item - ' + item.id"
|
||||
@click="cardClick(item.id)"
|
||||
@dblclick="cardDblClick(item)"
|
||||
:class="{selected: isSelected(item.id)}">
|
||||
<img class="card-img" :src="imagePath" v-if="typeof(imagePath) === 'string'">
|
||||
<img class="card-img" :src="imagePath[index]" v-else>
|
||||
<p v-for="header in this.headers"><b>{{ header.label}}:</b> {{item[header.name]}} <span v-if="header.name === 'commonPrice'">₽</span></p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.card {
|
||||
width: 20%;
|
||||
height: 340px;
|
||||
margin: 10px;
|
||||
float: left;
|
||||
text-align: center;
|
||||
box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2);
|
||||
transition: 0.3s;
|
||||
border-radius: 20px;
|
||||
overflow: hidden;
|
||||
-moz-hyphens: auto;
|
||||
-webkit-hyphens: auto;
|
||||
-ms-hyphens: auto;
|
||||
}
|
||||
|
||||
.card-img{
|
||||
margin-top: 4px;
|
||||
border-radius: 20px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
box-shadow: 0 8px 16px 0 rgba(0,0,0,0.2);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
p {
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
font-size: 18px;
|
||||
text-align: center;
|
||||
font-family: sans-serif;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.selected {
|
||||
background-color: color-mix(in srgb, #c41e3a 50%, #383838);
|
||||
opacity: 80%;
|
||||
}
|
||||
div {
|
||||
user-select: none;
|
||||
}
|
||||
</style>
|
@ -1,72 +0,0 @@
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
headers: Array,
|
||||
items: Array,
|
||||
selectedItems: Array
|
||||
},
|
||||
emits: {
|
||||
dblclick: null
|
||||
},
|
||||
methods: {
|
||||
rowClick(id) {
|
||||
if (this.isSelected(id)) {
|
||||
var index = this.selectedItems.indexOf(id);
|
||||
if (index !== -1) {
|
||||
this.selectedItems.splice(index, 1);
|
||||
}
|
||||
} else {
|
||||
this.selectedItems.push(id);
|
||||
}
|
||||
},
|
||||
rowDblClick(id) {
|
||||
this.$emit('dblclick', id);
|
||||
},
|
||||
isSelected(id) {
|
||||
return this.selectedItems.includes(id);
|
||||
},
|
||||
dataConvert(data) {
|
||||
return data;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">#</th>
|
||||
<th v-for="header in this.headers"
|
||||
:id="header.name"
|
||||
scope="col">{{ header.label }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(item, index) in this.items"
|
||||
@click="rowClick(item.id)"
|
||||
@dblclick="rowDblClick(item.id)"
|
||||
:class="{selected: isSelected(item.id)}">
|
||||
<th scope="row">{{ index + 1 }}</th>
|
||||
<td v-for="header in this.headers">
|
||||
{{ dataConvert(item[header.name]) }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
tbody tr:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
tr.selected {
|
||||
background-color: #0000fd;
|
||||
opacity: 80%;
|
||||
}
|
||||
|
||||
tbody tr {
|
||||
user-select: none;
|
||||
}
|
||||
</style>
|
@ -1,43 +0,0 @@
|
||||
<script>
|
||||
export default {
|
||||
methods: {
|
||||
getRoutes() {
|
||||
return this.$router.options.routes.filter(route => route.meta?.hasOwnProperty('label'));
|
||||
},
|
||||
logout() {
|
||||
localStorage.clear();
|
||||
this.$router.push('/signin');
|
||||
document.getElementById("user").innerText = "Выход ()";
|
||||
location.reload();
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
token: null
|
||||
}
|
||||
},
|
||||
created(){
|
||||
this.token = localStorage.getItem("token");
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<nav class="navbar navbar-expand-lg navbar-light bg-light" v-if="this.token !== null">
|
||||
<div class="container">
|
||||
<a class="navbar-brand" href="/categories">Internet shop parts</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
|
||||
<li class="nav-item"
|
||||
v-for="route in this.getRoutes()">
|
||||
<router-link class="nav-link" :to="route.path">{{ route.meta.label }}</router-link>
|
||||
</li>
|
||||
</ul>
|
||||
<a id="user" class="btn btn-auth text-white" @click.prevent="logout">Войти</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</template>
|
@ -1,76 +0,0 @@
|
||||
<script>
|
||||
import DataService from '../services/DataService';
|
||||
import CatalogMixins from '../mixins/CatalogMixins.js';
|
||||
export default {
|
||||
mixins: [
|
||||
CatalogMixins
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
getAllUrl: 'manufacturers',
|
||||
dataUrl: 'manufacturers/',
|
||||
headers: [
|
||||
{ name: 'name', label: 'Название производителя'},
|
||||
{ name: 'country', label: 'Страна'}
|
||||
],
|
||||
path: '/public/Part.jpg'
|
||||
}
|
||||
},
|
||||
beforeCreate() {
|
||||
if(localStorage.getItem("token") == null) {
|
||||
this.$router.push("/signin");
|
||||
}
|
||||
else
|
||||
if(document.getElementById("user") != null)
|
||||
document.getElementById("user").innerText = "Выход (" + localStorage.getItem("user").substr(0,5) +"...)";
|
||||
},
|
||||
methods: {
|
||||
getItems() {
|
||||
if(localStorage.getItem("token") != null)
|
||||
DataService.getData(this.getAllUrl).then(data => {
|
||||
this.items = data;
|
||||
});
|
||||
},
|
||||
Save(data, md){
|
||||
if(md == "create")
|
||||
return `manufacturers?name=${data.name}&country=${data.country}`;
|
||||
const dataUp = {name: `${data.name}`,
|
||||
country: `${data.country}`
|
||||
};
|
||||
return dataUp;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="container">
|
||||
<h1 align="center"> Каталог производителей </h1>
|
||||
<DataCard
|
||||
:headers="this.headers"
|
||||
:items="this.items"
|
||||
:selectedItems="this.selectedItems"
|
||||
:imagePath="this.path"
|
||||
@dblclick="showEditModalDblClick">
|
||||
</DataCard>
|
||||
</div>
|
||||
<ToolBar
|
||||
@add="showAddModal"
|
||||
@edit="showEditModal"
|
||||
@remove="removeSelectedItems">
|
||||
</ToolBar>
|
||||
<Modal
|
||||
:header="this.modal.header"
|
||||
:confirm="this.modal.confirm"
|
||||
v-model:visible="this.modalShow"
|
||||
@done="saveItem">
|
||||
<div class="col-mb-3">
|
||||
<label for="Name" class="form-label">Имя производителя</label>
|
||||
<input type="text" class="form-control" id="Name" required v-model="data.name">
|
||||
</div>
|
||||
<div class="col-mb-3">
|
||||
<label for="Country" class="form-label">Страна</label>
|
||||
<input type="text" class="form-control" id="Country" required v-model="data.country">
|
||||
</div>
|
||||
</Modal>
|
||||
</template>
|
@ -1,63 +0,0 @@
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
header: String,
|
||||
confirm: String,
|
||||
visible: Boolean
|
||||
},
|
||||
emits: {
|
||||
done: null,
|
||||
'update:visible': (value) => {
|
||||
if (typeof value !== 'boolean') {
|
||||
throw 'Value is not a boolean';
|
||||
}
|
||||
return true;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
hide() {
|
||||
this.$emit('update:visible', false);
|
||||
},
|
||||
done() {
|
||||
if (this.$refs.form.checkValidity()) {
|
||||
this.$emit('done');
|
||||
this.hide();
|
||||
} else {
|
||||
this.$refs.form.reportValidity();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="modal fade" tabindex="-1" aria-hidden="true"
|
||||
:class="{ 'modal-show': this.visible, 'show': this.visible }">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h1 class="modal-title fs-5" id="exampleModalLabel">{{ header }}</h1>
|
||||
<button type="button" class="btn-close" aria-label="Close"
|
||||
@click.prevent="hide"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form @submit.prevent="done" ref="form">
|
||||
<slot></slot>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary"
|
||||
@click.prevent="hide">Закрыть</button>
|
||||
<button type="button" class="btn btn-danger"
|
||||
@click.prevent="done">{{ confirm }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.modal-show {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
@ -1,74 +0,0 @@
|
||||
<script>
|
||||
import DataService from '../services/DataService';
|
||||
import CatalogMixins from '../mixins/CatalogMixins.js';
|
||||
export default {
|
||||
mixins: [
|
||||
CatalogMixins
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
getAllUrl: 'parts',
|
||||
dataUrl: 'parts/',
|
||||
headers: [
|
||||
{ name: 'name', label: 'Имя запчасти'},
|
||||
{ name: 'description', label: 'Описание'},
|
||||
{ name: 'price', label: 'Цена'}
|
||||
],
|
||||
path: '/public/Part.jpg'
|
||||
}
|
||||
},
|
||||
beforeCreate() {
|
||||
if(localStorage.getItem("token") == null) {
|
||||
this.$router.push("/signin");
|
||||
}
|
||||
else
|
||||
if(document.getElementById("user") != null)
|
||||
document.getElementById("user").innerText = "Выход (" + localStorage.getItem("user").substr(0,5) +"...)";
|
||||
},
|
||||
methods: {
|
||||
getItems() {
|
||||
if(localStorage.getItem("token") != null){
|
||||
DataService.getData(this.getAllUrl).then(data => {
|
||||
this.items = data;
|
||||
});
|
||||
}
|
||||
},
|
||||
addPart(){
|
||||
this.$router.push("/createpart");
|
||||
},
|
||||
editPart(){
|
||||
if (this.selectedItems.length === 0) {
|
||||
return;
|
||||
}
|
||||
this.editPartDblClick(this.selectedItems[0]);
|
||||
},
|
||||
editPartDblClick(item){
|
||||
var id = -1;
|
||||
if(typeof(item) === "number")
|
||||
id = item;
|
||||
else
|
||||
id = item.id;
|
||||
|
||||
this.$router.push('updatepart/' + id);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="container">
|
||||
<h1 align="center"> Каталог запчастей </h1>
|
||||
<DataCard
|
||||
:headers="this.headers"
|
||||
:items="this.items"
|
||||
:selectedItems="this.selectedItems"
|
||||
:imagePath="this.path"
|
||||
@dblclick="editPartDblClick">
|
||||
</DataCard>
|
||||
</div>
|
||||
<ToolBar
|
||||
@add="addPart"
|
||||
@edit="editPart"
|
||||
@remove="removeSelectedItems">
|
||||
</ToolBar>
|
||||
</template>
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user