From d1eeea08006947d211df42a0e94155ed42111347 Mon Sep 17 00:00:00 2001
From: tylen
Date: Sun, 2 Nov 2025 14:55:49 +0200
Subject: [PATCH] add reserve unreserve endpoints
---
backend/src/db_client.py | 24 +++++++
backend/src/hosting.py | 101 ++++++++++++++++++++++++++++
backend/src/server.py | 2 +
frontend/src/App.tsx | 2 +-
frontend/src/components/Hosting.tsx | 76 ++++++++++++---------
frontend/src/types/index.d.ts | 9 +--
frontend/src/utils/fetchHosting.tsx | 74 +++++++++++++-------
7 files changed, 228 insertions(+), 60 deletions(-)
create mode 100644 backend/src/hosting.py
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 (
<>
-
- {loading && Loading...
}
- {error && Error
}
- {data && (
-
-
-
-
- | Размещение |
- Спальных мест |
- Бронирование |
-
-
-
- {Object.entries(data).map(([id, item]) => (
-
- | {item.name} |
- {item.capacity} |
- {} |
+ {loading && Loading...
}
+ {error && Error
}
+ {data && (
+
+
+
+
+ | Размещение |
+ Спальных мест |
+ Бронирование |
- ))}
-
-
-
Таблицу можно скроллить
-
- )}
+
+
+ {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;