Author: Krum Yakimov
This project was developed for my final exam in the Web Applications with Flask course at SoftUni. It aims to showcase the principles of structuring Flask RESTful applications while ensuring clean, maintainable, and reusable code. The project includes models, authentication and authorization mechanisms, schemas, and various access levels, along with several API endpoints.
The Online Appointment Booking System is designed to provide a robust backend solution for managing appointments in various service industries (e.g., salons, clinics, tutors). This system allows service providers to automate appointment bookings and manage schedules, while customers can easily book time slots for different services online.
-
User Management:
- Clients can register, log in, and manage their profiles.
- Service providers can register and manage their profiles.
-
Appointment Management:
- Clients can book, edit, and cancel appointments.
- Service providers can confirm, reject, and manage appointments.
-
Inquiry Management:
- Service providers can submit inquiries to apply for inclusion in the system.
- Inquiries can be reviewed and approved or rejected by administrators.
-
Service Management:
- Service providers can manage the services they offer, including categories and subcategories.
-
Integration with Third-Party Services:
-
AWS S3 to allow service providers to upload photos showcasing their businesses (e.g., salons, offices). This enables service providers to maintain an online presence and helps clients make informed decisions based on visual representations of services.
-
AWS SES for sending notifications via email. This functionality facilitates smooth communication between clients and service providers regarding appointment confirmations, cancellations, and updates.
-
- Automated reminders and follow-up emails.
- Integration with external calendar services (Google Calendar, etc.)
- Secure payment integration with Stripe or Square to handle deposits or full payments at booking.
- Integration with Facebook, Instagram.
- Video integrations (Teleport for virtual appointments).
- Enhanced reporting and analytics features for service providers.
- Inventory management (for salons selling products).
- Mobile application support for better accessibility.
- Python 3.11 or higher
- PostgreSQL or SQLite (depending on your configuration)
- AWS account for SES and S3 services
-
Clone the repository:
git clone https://github.com/KrumYakimov/FlaskAppointmentBookingAPI.git
or, if using SSH:
git clone git@github.com:KrumYakimov/FlaskAppointmentBookingAPI.git
-
Create a virtual environment and activate it:
- For macOS/Linux:
python3 -m venv venv source venv/bin/activate - For Windows:
python -m venv venv venv\Scripts\activate
- For macOS/Linux:
-
Install the dependencies:
pip install -r requirements.txt
-
Set up PostgreSQL:
- Make sure PostgreSQL is installed and running.
- Create a database for Development environment:
CREATE DATABASE appointment_booking_db;
- Create a database for Testing environment:
CREATE DATABASE appointment_booking_test;
-
Set up environment variables in a
.envfile:DB_USER=<your_database_username> # Database username DB_PASSWORD=<your_database_password> # Database password DB_HOST=<your_database_host> # Database host (e.g., localhost) DB_PORT=<your_database_port> # Database port (e.g., 5432 for PostgreSQL) DB_NAME=appointment_booking_db # Main database name TEST_DB_NAME=appointment_booking_test # Test database name SECRET_KEY=<your_secret_key> # Secret key for session management CONFIG_ENV="config.<your_environment>" # Configuration environment (e.g., DevelopmentConfig) TOKEN_EXPIRATION_HOURS=24 # JWT token expiration time in hours AWS_ACCESS_KEY=<your_aws_access_key> # AWS access key AWS_SECRET=<your_aws_secret_key> # AWS secret key AWS_BUCKET=<your_aws_bucket> # S3 bucket name for uploads AWS_REGION=<your_aws_region> # AWS region (e.g., us-east-1) EMAIL_SENDER=<your_aws_email_sender> # Email sender address for notifications
-
Initialize the database:
flask db init flask db migrate flask db upgrade
-
Start the Flask server:
flask run
- To run the unit tests:
pytest tests/
This project includes an interactive API documentation interface using Swagger UI. Swagger UI provides a visual representation of the API endpoints, making it easier for developers to understand how to interact with the API.
Once your Flask application is running, you can access the Swagger UI at the following URL:
http://127.0.0.1:5000/static/swagger-ui/index.html
The API's Swagger JSON specification can be accessed directly at:
http://127.0.0.1:5000/swagger.json
For more details on how to configure the Swagger UI or to modify the Swagger JSON specification, refer to the Swagger UI documentation. ``
- Endpoint:
POST /clients - Description: Register a new client with email, password, first name, last name, and phone number.
- Request Body:
{ "email": "string", "password": "string", "first_name": "string", "last_name": "string", "phone": "string" } - Responses:
201 Created: Successfully registered.409 Conflict: The provided information doesn't meet our data management policy. Please verify and try again.400 Bad Request: Possible error messages include:
{ "message": "Invalid payload: {'email': ['Not a valid email address.', 'Invalid email format.'], 'first_name': ['First name must be between 2 and 50 characters.'], 'phone': ['Phone number must start with 0 and contain exactly 10 to 15 digits.'], 'password': ['Your password needs to be at least 8 characters long.', 'Your password should include at least one special character (e.g., !, @, #, $, etc.).', 'Too common!']}" }{ "message": "Invalid payload: {'email': ['Email is required.'], 'first_name': ['First name is required.'], 'last_name': ['Last name is required.'], 'phone': ['Phone number is required.'], 'password': ['Missing data for required field.']}" }{ "message": "The provided information doesn't meet our data management policy. Please verify and try again." }
- Endpoint:
POST /login - Description: Authenticate a user by email and password.
- Request Body:
{ "email": "string", "password": "string" } - Responses:
200 OK: Returns a token for authenticated access.401 Unauthorized: Possible error messages include:
{ "message": "Invalid payload: {'email': ['Not a valid email address.', 'Invalid email format.']}" }{ "message": "Invalid username or password" }
- Endpoint:
POST /change-password - Description: Change the user's password.
- Request Body:
{ "old_password": "string", "new_password": "string" } - Responses:
200 OK: Password changed successfully.400 Bad Request: Possible error messages include:
{ "message": "Invalid payload: {'_schema': ['New password cannot be the same as the old password.']" }
- Endpoint:
GET /clients/profile - Description: Retrieve the authenticated client's profile.
- Responses:
200 OK: Returns client profile data.401 Unauthorized: User not authenticated.
- Endpoint:
PUT /clients/profile/edit - Description: Update the client's profile.
- Request Body:
{ "email": "string", "first_name": "string", "last_name": "string", "phone": "string" } - Responses:
200 OK: Profile updated successfully.401 Unauthorized: User not authenticated.409 Conflict: The provided information doesn't meet our data management policy. Please verify and try again.400 Bad Request: Possible error messages include:
{ "message": "Invalid payload: {'email': ['Not a valid email address.', 'Invalid email format.'], 'first_name': ['First name must be between 2 and 50 characters.'], 'phone': ['Phone number must start with 0 and contain exactly 10 to 15 digits.']}" }
- Endpoint:
PUT /clients/profile/deactivate - Description: Deactivate the authenticated client's profile.
- Responses:
200 OK: Client profile deactivated successfully.401 Unauthorized: User not authenticated.
- Endpoint:
/users - Method:
POST - Description: Registers a new user in the system.
- Request Body:
{ "email": "example@mail.com", "password": "examplePassword!", "first_name": "First", "last_name": "Last", "phone": "1234567890" } - Responses:
201 Created: User successfully registered.400 Bad Request: {"message": "string"}409 Conflict: The provided information doesn't meet our data management policy. Please verify and try again.
- Endpoint:
"/users/profile", "/users/profile/{status}", "/users/profile/{id}" - Method:
GET - Description: Retrieves the profile information of the user.
- Responses:
200 OK: Returns user profile data.401 Unauthorized: User not authorized.404 Not Found: User does not exist.
- Endpoint:
/users/{id}/edit/ - Method:
PUT - Description: Updates the specified user's profile information.
- Request Body:
{ "email": "string", "first_name": "string", "last_name": "string", "phone": "string" } - Responses:
200 OK: User account successfully updated.404 Not Found: User does not exist.401 Unauthorized: User not authorized.400 Bad Request: {"message": "string"}
- Endpoint:
/users/{id}/deactivate - Method:
PUT - Description: Deactivates a user account, preventing future logins.
- Responses:
200 OK: User account successfully deactivated.404 Not Found: User does not exist.401 Unauthorized: User not authorized.
- Endpoint:
POST /inquiries - Description: Register a new inquiry for a salon.
- Request Body:
{ "salon_name": "sting", "city": "sting", "email": "sting", "first_name": "sting", "last_name": "sting", "phone": "sting" } - Responses:
201 Created: Inquiry created successfully.400 Bad Request: {"message": "string"}
- Endpoint:
GET "/approver/inquiries", "/approver/inquiries/{status}" - Description: Retrieve pending inquiries.
- Responses:
200 OK: Returns list of pending inquiries.401 Unauthorized: User not authorized.
- Endpoint:
PUT /approver/inquiries/{id}/approval - Description: Approve an inquiry.
- Responses:
200 OK: Inquiry approved successfully.404 Not Found: Inquiry not found.401 Unauthorized: User not authorized.
- Endpoint:
PUT /approver/inquiries/{id}/rejection - Description: Reject an inquiry.
- Responses:
200 OK: Inquiry rejected successfully.404 Not Found: Inquiry not found.401 Unauthorized: User not authorized.
- Endpoint:
PUT /approver/inquiries/{id}/no-show - Description: Mark an inquiry as no-show.
- Responses:
200 OK: Inquiry marked as no-show.404 Not Found: Inquiry not found.401 Unauthorized: User not authorized.
- Endpoint:
POST /provider - Description: Register a new service provider.
- Request Body:
{ "company_name": "string", "trade_name": "string", "uic": "string", "photo": "string", "photo_extension": "string", "inquiry_id": 15, "country": "string", "district": "string", "city": "string", "neighborhood": "string", "street": "string", "street_number": "string", "block_number": "string", "apartment": "string", "floor": "string", "postal_code": "string", "latitude": "number", "longitude": "number" } - Responses:
201 Created: Provider registered successfully.400 Bad Request: {"message": "string"}.401 Unauthorized: User not authorized.
- Endpoint:
PUT /provider/{id}/edit - Description: Update the provider's information.
- Request Body:
{ "company_name": "string", "trade_name": "string", "country": "string", "district": "string", "city": "string", "neighborhood": "string", "street": "string", "street_number": "string", "block_number": "string", "apartment": "string", "floor": "string", "postal_code": "string", "latitude": "number", "longitude": "number" } - Responses:
200 OK: Provider updated successfully.404 Not Found: Provider not found.400 Bad Request: {"message": "string"}.401 Unauthorized: User not authorized.
- Endpoint:
GET "/providers/profile", "/providers/profile/{status}", "/providers/profile/{id}" - Description: Retrieve provider profile.
- Responses:
200 OK: Returns provider profile data.404 Not Found: Provider not found.401 Unauthorized: User not authorized.
- Endpoint:
PUT /provider/{id}/deactivate - Description: Deactivate a provider.
- Responses:
200 OK: Provider deactivated successfully.404 Not Found: Provider not found.401 Unauthorized: User not authorized.
- Endpoint:
POST /services - Description: Register a new service.
- Request Body:
{ "name": "test_1", "price": "decimal", "duration": "integer", "service_subcategory_id": "integer", "service_provider_id": "integer", "staff_id": "integer" } - Responses:
201 Created: Service registered successfully.400 Bad Request: {"message": "string"}401 Unauthorized: User not authorized.
- Endpoint:
GET "/services/profile", "/services/profile/{status}", "/services/profile/{id}" - Description: Retrieve the list of services.
- Responses:
200 OK: Returns the list of services.401 Unauthorized: User not authenticated.404 Not Found: No services found.
- Endpoint:
PUT /services/{id}/edit - Description: Update service information.
- Request Body:
{ "name": "string", "price": "decimal", "duration": "integer", "staff_id": "integer" } - Responses:
200 OK: Service updated successfully.401 Unauthorized: User not authenticated.404 Not Found: Service not found.400 Bad Request: {"message": "string"}
- Endpoint:
PUT /services/{id}/deactivate - Description: Deactivate a service.
- Responses:
200 OK: Service deactivated successfully.401 Unauthorized: User not authenticated.404 Not Found: Service not found.
- Endpoint:
POST /categories - Description: Register a new service category.
- Request Body:
{ "name": "string" } - Responses:
201 Created: Category registered successfully.401 Unauthorized: User not authenticated.400 Bad Request: Invalid input data.
{ "message": "string" }
- Endpoint:
GET "/categories/profile","/categories/profile/{status}", "/categories/profile/{id}" - Description: Retrieve the list of categories.
- Responses:
200 OK: Returns the list of categories.401 Unauthorized: User not authenticated.404 Not Found: No categories found.
- Endpoint:
PUT /categories/{id}/edit
Description: Update a category's information.
- Request Body:
{ "name": "string" } - Responses:
200 OK: Category updated successfully.401 Unauthorized: User not authenticated.404 Not Found: Category not found.400 Bad Request: Invalid input data.
- Endpoint:
PUT /categories/{id}/deactivate - Description: Deactivate a category.
- Responses:
200 OK: Category deactivated successfully.404 Not Found: Category not found.
- Endpoint:
POST /subcategories - Description: Register a new service subcategory.
- Request Body:
{ "name": "string", "category_id": "integer" } - Responses:
201 Created: Subcategory registered successfully.401 Unauthorized: User not authenticated.400 Bad Request: {"message": "string"}
- Endpoint:
GET "/subcategories/profile", "/subcategories/profile/{status}>","/subcategories/profile/{id}>" - Description: Retrieve the list of subcategories.
- Responses:
200 OK: Returns the list of subcategories.401 Unauthorized: User not authenticated.404 Not Found: No subcategories found.
- Endpoint:
PUT /subcategories/{id}/edit - Description: Update a subcategory's information.
- Request Body:
{ "name": "string" } - Responses:
200 OK: Subcategory updated successfully.404 Not Found: Subcategory not found.401 Unauthorized: User not authenticated.400 Bad Request: {"message": "string"}
- Endpoint:
PUT /subcategories/{id}/deactivate - Description: Deactivate a subcategory.
- Responses:
200 OK: Subcategory deactivated successfully.401 Unauthorized: User not authenticated.404 Not Found: Subcategory not found.
- Endpoint:
POST /working_hours/register - Description: Register working hours for providers and staff.
- Request Body:
{ "provider_id": "integer", "employees": [ { "employee_id": "integer", "working_hours": [ { "day_of_week": "integer", "start_time": "HH:MM", "end_time": "HH:MM", "provider_id": "integer", "employee_id": "integer" }, ... ] }, ... ] } - Responses:
201 Created: Working hours registered successfully.400 Bad Request: Invalid input data.400 Bad Request: {"message": "string"}
{ "message": "string" }
- Endpoint:
GET "/working_hours/profile", "/working_hours/profile/provider/{provider_id}", "/working_hours/profile/employee/{employee_id}" - Description: Retrieve working hours for a specific employee.
- Responses:
200 OK: Returns the employee's working hours.401 Unauthorized: User not authenticated.404 Not Found: Employee not found.
- Endpoint:
PUT /working_hours/{id}/edit - Description: Update a specific working hour entry.
- Request Body:
{ "day_of_week": "integer", "start_time": "HH:MM", "end_time": "HH:MM", "provider_id": "integer", "employee_id": "integer" } - Responses:
200 OK: Working hour updated successfully.401 Unauthorized: User not authenticated.404 Not Found: Working hour not found.400 Bad Request: {"message": "string"}
- Endpoint:
PUT /working_hours/{id}/deactivate - Description: Deactivate a specific working hour entry.
- Responses:
200 OK: Working hour deactivated successfully.401 Unauthorized: User not authenticated.404 Not Found: Working hour not found.
- Endpoint:
GET /appointments/available_slots/{staff_id}/{service_id}/{date} - Description: Retrieve available appointment slots for a specific staff member and service on a given date.
- Responses:
200 OK: Returns available slots.401 Unauthorized: User not authenticated.404 Not Found: No slots available.
- Endpoint:
POST /appointments - Description: Book an appointment for a specific service.
- Request Body:
{ "service_id": "integer", "staff_id": "integer", "appointment_time": "YYYY-MM-DDTHH:MM:SS", } - Responses:
201 Created: Appointment booked successfully.401 Unauthorized: User not authenticated.400 Bad Request: Invalid input data.
{ "message": "string" }
- Endpoint:
GET /appointments/info - Description: Retrieve all appointments for the authenticated user.
- Responses:
200 OK: Returns the list of appointments.401 Unauthorized: User not authenticated.404 Not Found: No appointments found.
- Endpoint:
PUT /appointments/{id}/edit - Description: Edit an existing appointment.
- Request Body:
{ "appointment_time": "YYYY-MM-DDTHH:MM:SS" } - Responses:
200 OK: Appointment updated successfully.401 Unauthorized: User not authenticated.404 Not Found: Appointment not found.
- Endpoint:
DELETE /appointments/{id}/cancel - Description: Cancel an existing appointment.
- Responses:
200 OK: Appointment cancelled successfully.401 Unauthorized: User not authenticated.404 Not Found: Appointment not found.
- Endpoint:
PUT /appointments/{id}/confirm - Description: Confirm an appointment as staff.
- Responses:
200 OK: Appointment confirmed.401 Unauthorized: User not authenticated.404 Not Found: Appointment not found.
- Endpoint:
PUT /appointments/{id}/reject - Description: Reject an appointment as staff.
- Responses:
200 OK: Appointment rejected.401 Unauthorized: User not authenticated.404 Not Found: Appointment not found.
- Endpoint:
PUT /appointments/{id}/no_show - Description: Mark an appointment as no-show.
- Responses:
200 OK: Appointment marked as no-show.401 Unauthorized: User not authenticated.404 Not Found: Appointment not found.
- Endpoint:
PUT /appointments/{id}/cancel - Description: Mark an appointment as cancel.
- Responses:
200 OK: Appointment marked as cancel.401 Unauthorized: User not authenticated.404 Not Found: Appointment not found.
- Endpoint:
PUT /appointments/{id}/complete - Description: Mark an appointment as completed.
- Responses:
200 OK: Appointment marked as completed.401 Unauthorized: User not authenticated.404 Not Found: Appointment not found.
- Status Code:
400 Bad Request - Description: This status code indicates that the server could not understand the request due to invalid syntax or data. The response will typically include a message detailing the specific validation error(s) that occurred.
-
Invalid Input: When the provided data does not conform to the expected format or constraints.
- Example: "Invalid email format. Please provide a valid email address."
-
Missing Required Fields: When the request body is missing one or more required fields.
- Example: "The 'password' field is required."
-
Out of Range Values: When numeric values fall outside the expected range.
- Example: "Duration must be a positive integer."
-
Invalid Enumeration Values: When an invalid value is provided for an enumerated field.
- Example: "Role must be one of: 'CLIENT', 'ADMIN', 'SERVICE_PROVIDER'."
-
Format Errors: When the data format is incorrect (e.g., date format, number format).
- Example: "Start time must be in HH:MM format."
Feel free to open issues or submit pull requests if you have suggestions for improvements.
This project is licensed under the MIT License.