diff --git a/backend/src/db_client.py b/backend/src/db_client.py index 21299dd..41d954f 100644 --- a/backend/src/db_client.py +++ b/backend/src/db_client.py @@ -13,6 +13,28 @@ STARTUP_TABLE_CREATION_QUERIES = { Token VARCHAR(2048), Name varchar(255) );""", + "hosting": """CREATE TABLE IF NOT EXISTS hosting ( + id SERIAL, + Name varchar(255), + Capacity INT, + reservedBy varchar(255) + );""", +} + +INJECT_TABLE_CREATION_QUERIES = { + "hosting": """ +INSERT INTO hosting (Name, Capacity, reservedBy) +SELECT name, capacity, reservedBy FROM ( + SELECT 'Матрац 160см' AS name, 2 AS capacity, '' AS reservedBy + UNION ALL + SELECT 'Кровать 120см', 2, '' + UNION ALL + SELECT 'Матрац 90см', 1, '' + UNION ALL + SELECT 'Диван', 1, '' +) AS temp +WHERE NOT EXISTS (SELECT 1 FROM hosting WHERE Name = temp.name); +""", } class Severity(Enum): @@ -53,6 +75,8 @@ class DBClient: def initialize_database(self): self.query(STARTUP_TABLE_CREATION_QUERIES['users']) self.query(STARTUP_TABLE_CREATION_QUERIES['sessions']) + self.query(STARTUP_TABLE_CREATION_QUERIES['hosting']) + self.query(INJECT_TABLE_CREATION_QUERIES['hosting']) def query(self, query_str, params=None): max_retries = 3 diff --git a/backend/src/hosting.py b/backend/src/hosting.py new file mode 100644 index 0000000..8f95d07 --- /dev/null +++ b/backend/src/hosting.py @@ -0,0 +1,101 @@ +#!/usr/bin/env python +# encoding: utf-8 + +''' +hosting.py is a source for all hosting endpoints. +''' + +from flask import request, jsonify +import mysql.connector + +def registerHostingEndpoints(app, database): + + @app.route('/hosting', methods=['GET']) + def get_hosting(): + try: + app.logger.info('Fetching hosting data') + query = "SELECT id, Name AS name, Capacity AS capacity, reservedBy FROM hosting" + result = database.query(query) # Adjust this depending on your database implementation + + # Transform the result into the required structure + furniture_list = [ + { + "id": row[0], + "name": row[1], + "capacity": row[2], + "reservedBy": row[3] + } for row in result + ] + + hosting_data = { + "furniture": furniture_list + } + + app.logger.info(f'Fetched data: {hosting_data}') + return jsonify(hosting_data), 200 + except mysql.connector.Error as err: + app.logger.error(f"Database error: {err}") + return jsonify({"error": "Database error occurred"}), 500 + except Exception as e: + app.logger.error(f"Unexpected error: {e}") + return jsonify({"error": "Internal server error"}), 500 + + + @app.route('/hosting/', methods=['POST']) + def update_hosting(id): + data = request.get_json() + reserved_by = data.get('reservedBy') + + if not reserved_by: + return jsonify({"error": "reservedBy field is required"}), 400 + + try: + app.logger.info(f'Updating hosting with ID {id} to reserved by {reserved_by}') + query = "UPDATE hosting SET reservedBy=%s WHERE id=%s" + params = (reserved_by, id) + database.query(query, params) # Adjust this depending on your database implementation + app.logger.info(f'Successfully updated hosting ID {id}') + return jsonify({"message": "Successfully updated"}), 200 + except mysql.connector.Error as err: + app.logger.error(f"Database error: {err}") + return jsonify({"error": "Database error occurred"}), 500 + except Exception as e: + app.logger.error(f"Unexpected error: {e}") + return jsonify({"error": "Internal server error"}), 500 + + @app.route('/hosting//unreserve', methods=['POST']) + def unreserve_item(id): + token = request.json.get('token') + + if not token: + return jsonify(success=False, message="Token is required"), 400 + + query_session = "SELECT * FROM sessions WHERE Token=%s" + + try: + result = database.query(query_session, params=(token,)) + if not result: + return jsonify(success=False, message="Token is invalid or expired"), 401 + + user_name = result[0][1] # Assuming user name is in the second column of session result + + # Check if the item is reserved by the user + reservation_check_query = """ + SELECT * FROM hosting WHERE id = %s AND reservedBy = %s + """ + reservation_check_result = database.query(reservation_check_query, params=(id, user_name)) + + if not reservation_check_result: + return jsonify(success=False, message="Item is not reserved by you or does not exist"), 404 + + # Proceed to unreserve the item + unreserve_query = """ + UPDATE hosting SET reservedBy = '' WHERE id = %s + """ + database.query(unreserve_query, params=(id,)) + + return jsonify(success=True, message="Item unreserved successfully"), 200 + + except Exception as e: + return jsonify(success=False, message=str(e)), 500 + diff --git a/backend/src/server.py b/backend/src/server.py index 01dd619..5c478e7 100644 --- a/backend/src/server.py +++ b/backend/src/server.py @@ -10,6 +10,7 @@ from flask_cors import CORS from dotenv import load_dotenv from db_client import DBClient from user import registerUserEndpoints +from hosting import registerHostingEndpoints import logging load_dotenv() @@ -23,6 +24,7 @@ allowed_origins = [ CORS(app, resources={r"*": {"origins": allowed_origins}}) # Only allow example.com database = DBClient(app) registerUserEndpoints(app=app, database=database) +registerHostingEndpoints(app=app, database=database) if __name__ == "__main__": app.run(debug=True) \ No newline at end of file diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 1917787..166ded3 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import { useState } from 'react'; import './App.css'; import Greeting from './components/Greeting'; import Hosting from './components/Hosting'; diff --git a/frontend/src/components/Hosting.tsx b/frontend/src/components/Hosting.tsx index 823c160..755efc4 100644 --- a/frontend/src/components/Hosting.tsx +++ b/frontend/src/components/Hosting.tsx @@ -6,38 +6,52 @@ import CenteredContainer from "./ChildrenContainer"; interface ReserveButtonProps { update: (name: string, id: number) => void, + unreserve: (token: string, id: number) => void, reservedBy: string, id: number, } const ReserveButton: React.FC = (props) => { - const { reservedBy, update, id } = props; + const { reservedBy, update, id, unreserve } = props; const [cookie] = useCookies(['userName']) + const [tokenCookie] = useCookies(['apiToken']) const userName = cookie.userName; const isReserved = reservedBy !== ''; const notify = useNotification(); - const handleReserve = async () => { + const handleReserve = async (name: string) => { try { - await update(userName, id); - notify(`Успешно забронировано для ${userName}`, 'success'); + await update(name, id); + notify(`Успешно забронировано для ${name}`, 'success'); } catch (error) { notify(`Не удалось забронировать: ${error instanceof Error ? error.message : 'Unknown error'}`, 'error'); } }; + const handleUnreserve = async () => { + try { + await unreserve(tokenCookie.apiToken, id); + notify(`Удалось разбронировать ${name}`, 'success'); + } catch (error) { + notify(`Не удалось разбронировать: ${error instanceof Error ? error.message : 'Unknown error'}`, 'error'); + } + } + return ( <> - + {(reservedBy == userName) && ( + + )} ); }; function Hosting() { - const { data, error, loading, update } = useFetchHosting(); + const { data, error, loading, update, unreserveHosting } = useFetchHosting(); return ( <> @@ -50,31 +64,33 @@ function Hosting() { (Лучше бронировать заранее если есть надобность. Оба в 1-1,5км от нашего дома). Спальные места:

- {loading &&
Loading...
} - {error &&
Error
} - {data && ( -
- - - - - - - - - - {Object.entries(data).map(([id, item]) => ( - - - - + {loading &&
Loading...
} + {error &&
Error
} + {data && ( +
+
РазмещениеСпальных местБронирование
{item.name}{item.capacity}{}
+ + + + + - ))} - -
РазмещениеСпальных местБронирование
-

Таблицу можно скроллить

-
- )} + + + {data.furniture && data.furniture.map((item) => ( + + {item.name} + {item.capacity} + + + + + ))} + + +

Таблицу можно скроллить

+ + )}

diff --git a/frontend/src/types/index.d.ts b/frontend/src/types/index.d.ts index 924532b..8b2f6cd 100644 --- a/frontend/src/types/index.d.ts +++ b/frontend/src/types/index.d.ts @@ -1,11 +1,12 @@ // src/types/index.d.ts export interface Furniture { - reservedBy: string; - name: string; - capacity: number; + id: number; // Unique identifier for the furniture + name: string; // Name of the furniture + capacity: number; // Capacity of the furniture + reservedBy: string; // Name of the person who reserved the furniture } export interface Hosting { - [key: number]: Furniture; + furniture: Furniture[]; // Array of furniture items } diff --git a/frontend/src/utils/fetchHosting.tsx b/frontend/src/utils/fetchHosting.tsx index 103d100..a373031 100644 --- a/frontend/src/utils/fetchHosting.tsx +++ b/frontend/src/utils/fetchHosting.tsx @@ -2,31 +2,31 @@ import { useState, useEffect } from 'react'; import type { Hosting } from '../types'; import { API_URL } from '../constants/constants'; -const mockData: Hosting = { - 1: { - reservedBy: "", - name: "Матрац 160см", - capacity: 2 - }, - 2: { - reservedBy: "Vasya", - name: "Кровать 120см", - capacity: 2 - }, - 3: { - reservedBy: "", - name: "Матрац 90см", - capacity: 1 - }, - 4: { - reservedBy: "", - name: "Диван", - capacity: 1 - }, -}; +// const mockData: Hosting = { +// 1: { +// reservedBy: "", +// name: "Матрац 160см", +// capacity: 2 +// }, +// 2: { +// reservedBy: "", +// name: "Кровать 120см", +// capacity: 2 +// }, +// 3: { +// reservedBy: "", +// name: "Матрац 90см", +// capacity: 1 +// }, +// 4: { +// reservedBy: "", +// name: "Диван", +// capacity: 1 +// }, +// }; const useFetchHosting = () => { - const [data, setData] = useState(mockData); + const [data, setData] = useState(null); const [error, setError] = useState(null); const [loading, setLoading] = useState(false); @@ -69,12 +69,36 @@ const useFetchHosting = () => { } }; + const unreserveHosting = async (token: string, id: number) => { + setLoading(true); + setError(null); + try { + const response = await fetch(`${API_URL}/hosting/${id}/unreserve`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ token }) + }); + + if (!response.ok) { // Check for non-200 responses + const errorText = await response.text(); // Capture the response text for further insights + throw new Error(`Error ${response.status}: ${errorText}`); + } + + // Optional: Fetch the updated data after reservation + await fetchData(); + } finally { + setLoading(false); + } + }; + useEffect(() => { - //fetchData(); // Initial fetch on mount + fetchData(); // Initial fetch on mount }, []); - return { data, error, loading, refetch: fetchData, update: updateData }; + return { data, error, loading, refetch: fetchData, update: updateData, unreserveHosting}; }; export default useFetchHosting;