Initial commit
This commit is contained in:
commit
0f605c4f42
142
.dockerignore
Normal file
142
.dockerignore
Normal 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
160
.gitignore
vendored
Normal 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
12
Dockerfile
Normal 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
4
README.md
Normal 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
9
docker-compose.yaml
Normal 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
9
requirements.txt
Normal 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
13
src/app.py
Normal 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
6
src/config.py
Normal 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/')
|
65
src/controllers/reservation_controller.py
Normal file
65
src/controllers/reservation_controller.py
Normal 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)
|
29
src/controllers/user_controller.py
Normal file
29
src/controllers/user_controller.py
Normal 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
|
17
src/dtos/reservation_dtos.py
Normal file
17
src/dtos/reservation_dtos.py
Normal 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
15
src/dtos/user_dtos.py
Normal 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
|
17
src/middlewares/validate_request.py
Normal file
17
src/middlewares/validate_request.py
Normal 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
|
8
src/models/reservation_model.py
Normal file
8
src/models/reservation_model.py
Normal 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
4
src/models/room_model.py
Normal 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
9
src/models/user_model.py
Normal 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
|
40
src/repos/reservation_repo.py
Normal file
40
src/repos/reservation_repo.py
Normal 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
12
src/repos/user_repo.py
Normal 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})
|
12
src/singletons/app_singleton.py
Normal file
12
src/singletons/app_singleton.py
Normal 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]
|
11
src/singletons/database_singleton.py
Normal file
11
src/singletons/database_singleton.py
Normal 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]
|
Loading…
x
Reference in New Issue
Block a user