Initial commit

This commit is contained in:
Sviatoslav Tsariov Yurievich 2025-01-29 00:42:37 +03:00
commit 0f605c4f42
20 changed files with 594 additions and 0 deletions

142
.dockerignore Normal file
View File

@ -0,0 +1,142 @@
.git
Dockerfile
.DS_Store
.gitignore
.dockerignore
/credentials
/cache
/store
/node_modules
# https://github.com/github/gitignore/blob/master/Global/macOS.gitignore
# General
*.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
# https://raw.githubusercontent.com/github/gitignore/master/Python.gitignore
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
# 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/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# pyenv
.python-version
# celery beat schedule file
celerybeat-schedule
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/

160
.gitignore vendored Normal file
View File

@ -0,0 +1,160 @@
# 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/

12
Dockerfile Normal file
View File

@ -0,0 +1,12 @@
FROM python:3.10-slim
WORKDIR /app
COPY requirements.txt /app
RUN pip3 install -r requirements.txt
COPY . .
ENV FLASK_APP=src/main.py
CMD [ "python3", "-m" , "flask", "run", "--host=0.0.0.0", "--port=5000"]

4
README.md Normal file
View File

@ -0,0 +1,4 @@
# Talkpal - Backend
This is the backend service for a **Meeting Room Booking System**, providing RESTful API endpoints for managing rooms, events, and users.
It is built using **Python** and integrates with a database for persistent storage.

9
docker-compose.yaml Normal file
View File

@ -0,0 +1,9 @@
version: '2.1'
services:
talkpal-backend:
container_name: talkpal-backend
image: talkpal-backend
build: .
network_mode: host
volumes:
- "./src:/app/src"

9
requirements.txt Normal file
View File

@ -0,0 +1,9 @@
blinker==1.8.2
click==8.1.8
flask==3.0.3
importlib-metadata==8.5.0
itsdangerous==2.2.0
jinja2==3.1.5
MarkupSafe==2.1.5
werkzeug==3.0.6
zipp==3.20.2

13
src/app.py Normal file
View File

@ -0,0 +1,13 @@
from flask import Flask
from pymongo import MongoClient
from flask_jwt_extended import JWTManager
from singletons.database_singleton import DatabaseSingleton
app = Flask(__name__)
app.config['JWT_SECRET_KEY'] = Config.JWT_SECRET_KEY
jwt = JWTManager(app)
client = MongoClient(Config.MONGO_URI)
db = DatabaseSingleton.get_instance()
if __name__ == '__main__':
app.run(debug=True)

6
src/config.py Normal file
View File

@ -0,0 +1,6 @@
import os
class Config:
DB_NAME = os.getenv('DB_NAME', 'meeting_reservation')
JWT_SECRET_KEY = os.getenv('JWT_SECRET_KEY', 'your_jwt_secret_key')
MONGO_URI = os.getenv('MONGO_URI', 'mongodb://localhost:27017/')

View File

@ -0,0 +1,65 @@
from flask import Blueprint, request, jsonify
from flask_jwt_extended import jwt_required, get_jwt_identity
from middlewares.validate_request import validate_request
from dtos.reservation_dtos import CreateReservationDTO, UpdateReservationDTO
from models.reservation_model import Reservation
from repos.reservation_repo import ReservationRepository
reservation_blueprint = Blueprint('reservation', __name__)
@reservation_blueprint.route('/', methods=['POST'])
@validate_request(CreateReservationDTO)
@jwt_required()
def create_reservation():
current_user = get_jwt_identity()
reservation = Reservation(**request.json.dict(), creator=current_user)
ReservationRepository.insert(reservation)
return jsonify({"message": "Reservation created successfully"})
@reservation_blueprint.route('/<reservation_id>', methods=['DELETE'])
@jwt_required()
def cancel_reservation(reservation_id):
result = ReservationRepository.delete(reservation_id)
if not result or result.deleted_count == 0:
return jsonify({"error": "Reservation not found"}), 404
return jsonify({"message": "Reservation cancelled"})
@reservation_blueprint.route('/<reservation_id>', methods=['PUT'])
@validate_request(UpdateReservationDTO)
@jwt_required()
def change_reservation(reservation_id):
data = request.json
try:
result = ReservationRepository.update(reservation_id, data)
if not result or result.matched_count == 0:
return jsonify({"error": "Reservation not found"}), 404
except:
return jsonify({"error": "Invalid reservation ID"}), 400
return jsonify({"message": "Reservation updated"})
@reservation_blueprint.route('/<reservation_id>', methods=['GET'])
@jwt_required()
def get_reservation(reservation_id):
try:
reservation = ReservationRepository.get_by_id()
if not reservation:
return jsonify({"error": "Reservation not found"}), 404
except:
return jsonify({"error": "Invalid reservation ID"}), 400
return jsonify(reservation)
@reservation_blueprint.route('/', methods=['GET'])
@jwt_required()
def list_reservations():
filters = {
"start_date": request.args.get("start_date"),
"end_date": request.args.get("end_date"),
"room_id": request.args.get("room_id")
}
filters = {k: v for k, v in filters.items() if v is not None}
reservations = ReservationRepository.list_all(filters)
return jsonify(reservations)

View File

@ -0,0 +1,29 @@
from flask import Blueprint, request, jsonify
from flask_jwt_extended import create_access_token
from middlewares.validate_request import validate_request
from dtos.user_dtos import RegisterUserDTO, LoginUserDTO
from repos.user_repo import UserRepo
from models.user_model import User
user_blueprint = Blueprint('user', __name__)
@user_blueprint.route('/register', methods=['POST'])
@validate_request(RegisterUserDTO)
def register_user():
data = request.json
if UserRepo.find_by_username(data.username):
return jsonify({"error": "User already exists"}), 409
user = User(**data.dict())
UserRepo.insert(user)
return jsonify({"message": "User registered successfully"})
@user_blueprint.route('/login', methods=['POST'])
@validate_request(LoginUserDTO)
def login_user():
data = request.json
user = UserRepo.find_by_username(data['username'])
if user:
access_token = create_access_token(identity=user['username'])
return jsonify(access_token=access_token)
return jsonify({"error": "Invalid credentials"}), 401

View File

@ -0,0 +1,17 @@
from typing import Optional
from pydantic import BaseModel
from datetime import datetime
class CreateReservationDTO(BaseModel):
title: str
description: Optional[str] = None
room_id: str
start_date: datetime
finish_date: datetime
class UpdateReservationDTO(BaseModel):
title: Optional[str] = None
description: Optional[str] = None
room_id: Optional[str] = None
start_date: Optional[datetime] = None
finish_date: Optional[datetime] = None

15
src/dtos/user_dtos.py Normal file
View File

@ -0,0 +1,15 @@
from pydantic import BaseModel, EmailStr
class RegisterUserDTO(BaseModel):
username: str
password: str
email: EmailStr
first_name: str
last_name: str
department: str
position: str
is_admin: bool = False
class LoginUserDTO(BaseModel):
username: str
password: str

View File

@ -0,0 +1,17 @@
from functools import wraps
from flask import request, jsonify
from pydantic import ValidationError
def validate_request(dto_class):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
try:
data = request.get_json()
dto = dto_class(**data)
return func(*args, **kwargs)
except ValidationError as e:
print(e)
return jsonify({"error": e.errors()}), 400
return wrapper
return decorator

View File

@ -0,0 +1,8 @@
class Reservation:
def __init__(self, title, description, room_id, creator, start_date, finish_date):
self.title = title
self.description = description
self.room_id = room_id
self.creator = creator
self.start_date = start_date
self.finish_date = finish_date

4
src/models/room_model.py Normal file
View File

@ -0,0 +1,4 @@
class Room:
def __init__(self, title, office_id):
self.title = title
self.office_id = office_id

9
src/models/user_model.py Normal file
View File

@ -0,0 +1,9 @@
class User:
def __init__(self, username, email, first_name, last_name, department, position, is_admin=False):
self.username = username
self.email = email
self.first_name = first_name
self.last_name = last_name
self.department = department
self.position = position
self.is_admin = is_admin

View File

@ -0,0 +1,40 @@
from singletons.database_singleton import DatabaseSingleton
class ReservationRepository:
db = DatabaseSingleton.get_instance()
@classmethod
def insert(cls, reservation):
cls.db.reservation.insert_one(reservation.__dict__)
@classmethod
def delete(cls, reservation_id):
try:
result = cls.db.reservation.delete_one({"_id": ObjectId(reservation_id)})
return result
except:
return None
@classmethod
def update(cls, reservation_id, data):
try:
result = cls.db.reservation.update_one({"_id": ObjectId(reservation_id)}, {"$set": data})
return result
except:
return None
@classmethod
def get_by_id(cls):
return list(cls.db.reservation.find({"_id": ObjectId(reservation_id)}))
@classmethod
def list_all(cls, filters=None):
query = {}
if filters:
if "start_date" in filters:
query["start_date"] = {"$gte": filters["start_date"]}
if "end_date" in filters:
query["start_date"] = {"$lte": filters["end_date"]}
if "room_id" in filters:
query["room_id"] = filters["room_id"]
return list(cls.db.reservation.find(query))

12
src/repos/user_repo.py Normal file
View File

@ -0,0 +1,12 @@
from singletons.database_singleton import DatabaseSingleton
class UserRepo:
db = DatabaseSingleton.get_instance()
@classmethod
def insert(cls, user):
cls.db.user.insert_one(user.__dict__)
@classmethod
def find_by_username(cls, username):
return cls.db.user.find_one({"username": username})

View File

@ -0,0 +1,12 @@
from config import Config
from flask import Flask
class AppSingleton:
_instance = None
@staticmethod
def get_instance():
if AppSingleton._instance is None:
AppSingleton._instance = Flask(__name__)
AppSingleton._instance['JWT_SECRET_KEY'] = Config.JWT_SECRET_KEY
return AppSingleton._instance[Config.DB_NAME]

View File

@ -0,0 +1,11 @@
from config import Config
from pymongo import MongoClient
class DatabaseSingleton:
_instance = None
@staticmethod
def get_instance():
if DatabaseSingleton._instance is None:
DatabaseSingleton._instance = MongoClient(Config.MONGO_URI)
return DatabaseSingleton._instance[Config.DB_NAME]