redux shop response

main
Stepan Pilipenko 1 month ago
parent d76e86ca32
commit ebe207ec83

@ -1,6 +1,6 @@
import psycopg2 import psycopg2
from ..consts import DB_CONFIG from consts import DB_CONFIG
def add_product_to_shop( def add_product_to_shop(
name: str, name: str,

@ -1,15 +1,14 @@
import uuid import uuid
import psycopg2 import psycopg2
import psycopg2.extras import psycopg2.extras
import json
from datetime import timedelta from datetime import timedelta
from psycopg2 import IntegrityError from psycopg2 import IntegrityError
from collections import Counter from collections import Counter
from decimal import Decimal, InvalidOperation from decimal import InvalidOperation
from ..utils import * from utils import *
from ..type import * from type import *
from ..consts import * from consts import *
def update_token_expiry_date(token: str, expiry_date: date) -> None: def update_token_expiry_date(token: str, expiry_date: date) -> None:
connection = psycopg2.connect(**DB_CONFIG) connection = psycopg2.connect(**DB_CONFIG)
@ -669,7 +668,10 @@ def get_products_id(token: str, table_name: str) -> list[int]:
user_id: int = get_user_id(cursor, token) user_id: int = get_user_id(cursor, token)
cursor.execute("SELECT products_id FROM %s WHERE user_id = %s", (table_name, user_id,)) query = sql.SQL("SELECT products_id FROM {} WHERE user_id = %s").format(
sql.Identifier(table_name)
)
cursor.execute(query, (user_id,))
res = cursor.fetchone() res = cursor.fetchone()
if res is None or res[0] is None: if res is None or res[0] is None:

@ -2,8 +2,8 @@ import json
from django.http import JsonResponse from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
from backend.api import api from api import api
from backend.utils import decimal_to_float from utils import decimal_to_float, format_token
@csrf_exempt @csrf_exempt
async def shop(request): async def shop(request):
@ -14,9 +14,9 @@ async def shop(request):
user_agent = request.headers.get('User-Agent') user_agent = request.headers.get('User-Agent')
products = decimal_to_float(products) products = decimal_to_float(products)
print("get_shop", user_agent) print("get_shop", user_agent)
return JsonResponse({"OK": products}, status=200) return JsonResponse({"success": products}, status=200)
except Exception as error: except Exception as error:
return JsonResponse({"ERROR": format(error)}, status=500) return JsonResponse({"error": format(error)}, status=500)
@csrf_exempt @csrf_exempt
async def user(request): async def user(request):
@ -33,22 +33,22 @@ async def user(request):
token = api.login(body["login"]["email"], token = api.login(body["login"]["email"],
body["login"]["password"]) body["login"]["password"])
elif body["unregister"]: elif body["unregister"]:
token = request.headers.get("Token") token = format_token(request.headers.get("Authorization"))
api.unregister(token) api.unregister(token)
elif body["logout"]: elif body["logout"]:
token = request.headers.get("Token") token = format_token(request.headers.get("Authorization"))
api.logout(token) api.logout(token)
elif body["add_money"]: elif body["add_money"]:
token = request.headers.get("Token") token = format_token(request.headers.get("Authorization"))
api.add_money(token, body["add_money"]["money"]) api.add_money(token, body["add_money"]["money"])
else: else:
token = request.headers.get("Token") token = format_token(request.headers.get("Authorization"))
user1 = api.get_user(token) user1 = api.get_user(token)
return JsonResponse({"OK": user1}, status=200) return JsonResponse({"success": list(user1)}, status=200)
except Exception as error: except Exception as error:
return JsonResponse({"ERROR": format(error)}, status=500) return JsonResponse({"error": format(error)}, status=500)
@csrf_exempt @csrf_exempt
@ -56,22 +56,23 @@ async def basket(request):
try: try:
basket1 = None basket1 = None
if request.method == 'POST': if request.method == 'POST':
body: dict = json.loads(request.body) token = format_token(request.headers.get("Authorization"))
token = request.headers.get("Token")
if request.body:
if body["add_product"]: body: dict = json.loads(request.body)
api.add_product_to_basket(token, body["add_product"]["product_id"]) if body["add_product"]:
elif body["delete_product"]: api.add_product_to_basket(token, body["add_product"]["product_id"])
api.delete_product_from_basket(token, body["delete_product"]["product_id"]) elif body["delete_product"]:
elif body["clear"]: api.delete_product_from_basket(token, body["delete_product"]["product_id"])
api.clear_basket(token) elif body["clear"]:
elif body["buy_products"]: api.clear_basket(token)
api.buy_products(token) elif body["buy_products"]:
api.buy_products(token)
products_id = api.get_products_id(token, "basket") products_id = api.get_products_id(token, "basket")
basket1 = api.get_products_by_id(products_id) basket1 = api.get_products_by_id(products_id)
return JsonResponse({"OK": basket1}, status=200) return JsonResponse({"success": basket1}, status=200)
except Exception as error: except Exception as error:
return JsonResponse({"error": format(error)}, status=500) return JsonResponse({"error": format(error)}, status=500)
@ -80,9 +81,9 @@ async def history(request):
try: try:
histories = None histories = None
if request.method == 'POST': if request.method == 'POST':
token = request.headers.get("Token") token = format_token(request.headers.get("Authorization"))
basket1 = api.get_histories_with_products(token) histories = api.get_histories_with_products(token)
return JsonResponse({"OK": histories}, status=200) return JsonResponse({"success": histories}, status=200)
except Exception as error: except Exception as error:
return JsonResponse({"error": format(error)}, status=500) return JsonResponse({"error": format(error)}, status=500)
@ -93,7 +94,7 @@ async def login(request):
if request.method == 'POST': if request.method == 'POST':
body: dict = json.loads(request.body) body: dict = json.loads(request.body)
token = api.login(body["email"], body["password"]) token = api.login(body["email"], body["password"])
return JsonResponse({"OK": token}, status=200) return JsonResponse({"success": token}, status=200)
except Exception as error: except Exception as error:
return JsonResponse({"error": format(error)}, status=500) return JsonResponse({"error": format(error)}, status=500)
@ -102,9 +103,9 @@ async def logout(request):
try: try:
token = None token = None
if request.method == 'POST': if request.method == 'POST':
token = request.headers.get("Token") token = format_token(request.headers.get("Authorization"))
api.logout(token) api.logout(token)
return JsonResponse({"OK": token}, status=200) return JsonResponse({"success": token}, status=200)
except Exception as error: except Exception as error:
return JsonResponse({"error": format(error)}, status=500) return JsonResponse({"error": format(error)}, status=500)
@ -115,7 +116,7 @@ async def register(request):
if request.method == 'POST': if request.method == 'POST':
body: dict = json.loads(request.body) body: dict = json.loads(request.body)
token = api.registration(body["nickname"], body["password"], body["email"]) token = api.registration(body["nickname"], body["password"], body["email"])
return JsonResponse({"OK": token}, status=200) return JsonResponse({"success": token}, status=200)
except Exception as error: except Exception as error:
return JsonResponse({"error": format(error)}, status=500) return JsonResponse({"error": format(error)}, status=500)
@ -124,9 +125,9 @@ async def unregister(request):
try: try:
token = None token = None
if request.method == 'POST': if request.method == 'POST':
token = request.headers.get("Token") token = format_token(request.headers.get("Authorization"))
api.unregister(token) api.unregister(token)
return JsonResponse({"OK": token}, status=200) return JsonResponse({"success": token}, status=200)
except Exception as error: except Exception as error:
return JsonResponse({"error": format(error)}, status=500) return JsonResponse({"error": format(error)}, status=500)
@ -136,9 +137,9 @@ async def add_product_to_basket(request):
token = None token = None
if request.method == 'POST': if request.method == 'POST':
body: dict = json.loads(request.body) body: dict = json.loads(request.body)
token = request.headers.get("Token") token = format_token(request.headers.get("Authorization"))
api.add_product_to_basket(token, body["product_id"]) api.add_product_to_basket(token, body["product_id"])
return JsonResponse({"OK": token}, status=200) return JsonResponse({"success": token}, status=200)
except Exception as error: except Exception as error:
return JsonResponse({"error": format(error)}, status=500) return JsonResponse({"error": format(error)}, status=500)
@ -148,9 +149,9 @@ async def delete_product_from_basket(request):
token = None token = None
if request.method == 'POST': if request.method == 'POST':
body: dict = json.loads(request.body) body: dict = json.loads(request.body)
token = request.headers.get("Token") token = format_token(request.headers.get("Authorization"))
api.delete_product_from_basket(token, body["product_id"]) api.delete_product_from_basket(token, body["product_id"])
return JsonResponse({"OK": token}, status=200) return JsonResponse({"success": token}, status=200)
except Exception as error: except Exception as error:
return JsonResponse({"error": format(error)}, status=500) return JsonResponse({"error": format(error)}, status=500)
@ -160,9 +161,9 @@ async def buy_products(request):
token = None token = None
if request.method == 'POST': if request.method == 'POST':
body: dict = json.loads(request.body) body: dict = json.loads(request.body)
token = request.headers.get("Token") token = format_token(request.headers.get("Authorization"))
api.buy_products(token) api.buy_products(token)
return JsonResponse({"OK": token}, status=200) return JsonResponse({"success": token}, status=200)
except Exception as error: except Exception as error:
return JsonResponse({"error": format(error)}, status=500) return JsonResponse({"error": format(error)}, status=500)
@ -172,9 +173,9 @@ async def clear_basket(request):
token = None token = None
if request.method == 'POST': if request.method == 'POST':
body: dict = json.loads(request.body) body: dict = json.loads(request.body)
token = request.headers.get("Token") token = format_token(request.headers.get("Authorization"))
api.clear_basket(token) api.clear_basket(token)
return JsonResponse({"OK": token}, status=200) return JsonResponse({"success": token}, status=200)
except Exception as error: except Exception as error:
return JsonResponse({"error": format(error)}, status=500) return JsonResponse({"error": format(error)}, status=500)

@ -39,9 +39,9 @@ class TestGameShopAPI(unittest.TestCase):
error_msg = get_error_message(response) error_msg = get_error_message(response)
raise RuntimeError(f"Registration failed ({response.status_code}): {error_msg}") raise RuntimeError(f"Registration failed ({response.status_code}): {error_msg}")
data = response.json() data = response.json()
cls.valid_token = data.get("OK") cls.valid_token = data.get("success")
if not cls.valid_token: if not cls.valid_token:
raise ValueError("Response missing 'OK' field with token") raise ValueError("Response missing 'success' field with token")
uuid.UUID(cls.valid_token) uuid.UUID(cls.valid_token)
except Exception as e: except Exception as e:
raise unittest.SkipTest(f"Не удалось зарегистрировать пользователя: {e}") raise unittest.SkipTest(f"Не удалось зарегистрировать пользователя: {e}")
@ -51,7 +51,7 @@ class TestGameShopAPI(unittest.TestCase):
"""Удаляем пользователя после всех тестов.""" """Удаляем пользователя после всех тестов."""
if cls.valid_token: if cls.valid_token:
try: try:
headers = {"Token": cls.valid_token} headers = {"Authorization": "Bearer " + cls.valid_token}
response = requests.post(f"{BASE_URL}/unregister/", headers=headers, timeout=10) response = requests.post(f"{BASE_URL}/unregister/", headers=headers, timeout=10)
if response.status_code != 200: if response.status_code != 200:
error_msg = get_error_message(response) error_msg = get_error_message(response)
@ -68,9 +68,9 @@ class TestGameShopAPI(unittest.TestCase):
self.fail(f"GET /shop/ failed ({response.status_code}): {error_msg}") self.fail(f"GET /shop/ failed ({response.status_code}): {error_msg}")
data = response.json() data = response.json()
self.assertIn("OK", data, f"Ответ не содержит 'OK'. Получено: {data}") self.assertIn("success", data, f"Ответ не содержит 'success'. Получено: {data}")
products = data["OK"] products = data["success"]
self.assertIsInstance(products, list, f"'OK' должен быть списком, получено: {type(products)}") self.assertIsInstance(products, list, f"'success' должен быть списком, получено: {type(products)}")
self.assertGreater(len(products), 0, "Список товаров пуст") self.assertGreater(len(products), 0, "Список товаров пуст")
product = products[0] product = products[0]
@ -89,31 +89,31 @@ class TestGameShopAPI(unittest.TestCase):
def test_3_basket_success(self): def test_3_basket_success(self):
"""POST /basket/ с валидным токеном → возвращает '[]'""" """POST /basket/ с валидным токеном → возвращает '[]'"""
headers = {"Token": self.valid_token} headers = {"Authorization": "Bearer " + self.valid_token}
response = requests.post(f"{BASE_URL}/basket/", headers=headers) response = requests.post(f"{BASE_URL}/basket/", headers=headers)
if response.status_code != 200: if response.status_code != 200:
error_msg = get_error_message(response) error_msg = get_error_message(response)
self.fail(f"POST /basket/ failed ({response.status_code}): {error_msg}") self.fail(f"POST /basket/ failed ({response.status_code}): {error_msg}")
data = response.json() data = response.json()
self.assertIn("OK", data, f"Ответ не содержит 'OK'. Получено: {data}") self.assertIn("success", data, f"Ответ не содержит 'success'. Получено: {data}")
self.assertEqual(data["OK"], '[]', f"Ожидалась пустая корзина ('[]'), получено: {data['OK']}") self.assertEqual(data["success"], [], f"Ожидалась пустая корзина ([]), получено: {data['success']}")
def test_4_history_success(self): def test_4_history_success(self):
"""POST /history/ с валидным токеном → возвращает '[]'""" """POST /history/ с валидным токеном → возвращает '[]'"""
headers = {"Token": self.valid_token} headers = {"Authorization": "Bearer " + self.valid_token}
response = requests.post(f"{BASE_URL}/history/", headers=headers) response = requests.post(f"{BASE_URL}/history/", headers=headers)
if response.status_code != 200: if response.status_code != 200:
error_msg = get_error_message(response) error_msg = get_error_message(response)
self.fail(f"POST /history/ failed ({response.status_code}): {error_msg}") self.fail(f"POST /history/ failed ({response.status_code}): {error_msg}")
data = response.json() data = response.json()
self.assertIn("OK", data, f"Ответ не содержит 'OK'. Получено: {data}") self.assertIn("success", data, f"Ответ не содержит 'success'. Получено: {data}")
self.assertEqual(data["OK"], '[]', f"Ожидалась пустая история ('[]'), получено: {data['OK']}") self.assertEqual(data["success"], [], f"Ожидалась пустая история ([]), получено: {data['success']}")
def test_5_basket_invalid_token(self): def test_5_basket_invalid_token(self):
"""POST /basket/ с невалидным токеном → ошибка 500""" """POST /basket/ с невалидным токеном → ошибка 500"""
headers = {"Token": "invalid-token-12345"} headers = {"Authorization": "Bearer " + "invalid-token-12345"}
response = requests.post(f"{BASE_URL}/basket/", headers=headers) response = requests.post(f"{BASE_URL}/basket/", headers=headers)
if response.status_code != 500: if response.status_code != 500:
error_msg = get_error_message(response) error_msg = get_error_message(response)
@ -126,7 +126,7 @@ class TestGameShopAPI(unittest.TestCase):
def test_6_history_invalid_token(self): def test_6_history_invalid_token(self):
"""POST /history/ с невалидным токеном → ошибка 500""" """POST /history/ с невалидным токеном → ошибка 500"""
headers = {"Token": str(uuid.uuid4())} headers = {"Authorization": "Bearer " + "invalid-token-12345"}
response = requests.post(f"{BASE_URL}/history/", headers=headers) response = requests.post(f"{BASE_URL}/history/", headers=headers)
if response.status_code != 500: if response.status_code != 500:
error_msg = get_error_message(response) error_msg = get_error_message(response)
@ -147,8 +147,8 @@ class TestGameShopAPI(unittest.TestCase):
self.fail(f"POST /login/ failed ({response.status_code}): {error_msg}") self.fail(f"POST /login/ failed ({response.status_code}): {error_msg}")
data = response.json() data = response.json()
self.assertIn("OK", data, f"Ответ не содержит 'OK'. Получено: {data}") self.assertIn("success", data, f"Ответ не содержит 'OK'. Получено: {data}")
login_token = data["OK"] login_token = data["success"]
try: try:
uuid.UUID(login_token) uuid.UUID(login_token)
@ -156,22 +156,22 @@ class TestGameShopAPI(unittest.TestCase):
self.fail(f"Токен от /login/ не является валидным UUID: {login_token}") self.fail(f"Токен от /login/ не является валидным UUID: {login_token}")
# Проверка, что токен работает # Проверка, что токен работает
basket_resp = requests.post(f"{BASE_URL}/basket/", headers={"Token": login_token}) basket_resp = requests.post(f"{BASE_URL}/basket/", headers={"Authorization": "Bearer " + login_token})
if basket_resp.status_code != 200: if basket_resp.status_code != 200:
error_msg = get_error_message(basket_resp) error_msg = get_error_message(basket_resp)
self.fail(f"Токен от /login/ не работает: {error_msg}") self.fail(f"Токен от /login/ не работает: {error_msg}")
def test_8_logout_success(self): def test_8_logout_success(self):
"""POST /logout/ с валидным токеном — успешный выход""" """POST /logout/ с валидным токеном — успешный выход"""
headers = {"Token": self.valid_token} headers = {"Authorization": "Bearer " + self.valid_token}
response = requests.post(f"{BASE_URL}/logout/", headers=headers) response = requests.post(f"{BASE_URL}/logout/", headers=headers)
if response.status_code != 200: if response.status_code != 200:
error_msg = get_error_message(response) error_msg = get_error_message(response)
self.fail(f"POST /logout/ failed ({response.status_code}): {error_msg}") self.fail(f"POST /logout/ failed ({response.status_code}): {error_msg}")
data = response.json() data = response.json()
self.assertIn("OK", data, f"Ответ не содержит 'OK'. Получено: {data}") self.assertIn("success", data, f"Ответ не содержит 'OK'. Получено: {data}")
self.assertEqual(data["OK"], self.valid_token, "В ответе должен быть тот же токен") self.assertEqual(data["success"], self.valid_token, "В ответе должен быть тот же токен")
def test_9_all_scenarios_completed(self): def test_9_all_scenarios_completed(self):
"""Финальная проверка: все тесты пройдены""" """Финальная проверка: все тесты пройдены"""

@ -56,3 +56,6 @@ def decimal_to_float(data: list[dict]):
for item in data for item in data
] ]
return clean_data return clean_data
def format_token(token: str) -> str:
return token[7:] # delete "Bearer "

@ -1,37 +1,24 @@
import type { Product } from './types' const DEV_URL = '/dev_api'
const PROD_URL = 'https://shop.softwarrior.ru:8090'
export interface NetworkApi { export interface NetworkApi {
getProducts: () => Product[] getStringURL: (suffix: string) => string
} }
class Network implements NetworkApi { class Network implements NetworkApi {
getProducts(): Product[] { private baseUrl: string
return [
{ constructor() {
id: 2, if (import.meta.env.DEV) {
name: 'Серебрянный паровоз', this.baseUrl = DEV_URL
count: 23, } else {
reserved: 0, this.baseUrl = PROD_URL
picture_url: }
'https://git.softwarrior.ru/stepik104/game_shop/raw/branch/main/images/silver_train.png', }
description:
'Серебро - благородный метал, такой паровоз не стыдно выкатить на рельсы', getStringURL(suffix: string): string {
type: 'skin', const urlString = `${this.baseUrl}/${suffix.replace(/^\//, '')}`
cost: '50.00', return urlString
},
{
id: 4,
name: 'Золотая башня',
count: 10,
reserved: 0,
picture_url:
'https://git.softwarrior.ru/stepik104/game_shop/raw/branch/main/images/golden_tower.png',
description:
'Целая башня из чистого золота, кто-то скажет - непрактично, но мы ответим - да',
type: 'skin',
cost: '100.00',
},
]
} }
} }

@ -7,14 +7,21 @@ interface AddBasketAction {
count: number count: number
} }
type ResponseData = {
error?: string
success?: Array<object>
}
interface ShopState { interface ShopState {
products: Product[] products: Product[]
loading: boolean loading: boolean
error?: string
} }
const initialState: ShopState = { const initialState: ShopState = {
products: [], products: [],
loading: false, loading: false,
error: undefined,
} }
export const shopSlice = createSlice({ export const shopSlice = createSlice({
@ -31,10 +38,19 @@ export const shopSlice = createSlice({
builder builder
.addCase(fetchProducts.pending, state => { .addCase(fetchProducts.pending, state => {
state.loading = true state.loading = true
state.error = undefined
}) })
.addCase(fetchProducts.fulfilled, (state, action) => { .addCase(fetchProducts.fulfilled, (state, action) => {
state.products = action.payload state.products = action.payload.success as Product[]
state.loading = false
})
.addCase(fetchProducts.rejected, (state, action) => {
state.loading = false state.loading = false
if (action.payload) {
state.error = (action.payload as ResponseData).error
} else {
state.error = action.error.message
}
}) })
}, },
}) })
@ -45,10 +61,28 @@ export const selectProducts = (state: RootState) => state.shop.products
export const shopReducer = shopSlice.reducer export const shopReducer = shopSlice.reducer
export const fetchProducts = createAsyncThunk<Product[], void, ThunkApi>( export const fetchProducts = createAsyncThunk<ResponseData, void, ThunkApi>(
'shop/fetchProducts', 'products/fetchProducts',
async (_, { extra: networkApi }) => { async (_, { extra: networkApi }) => {
const response = await networkApi.getProducts() try {
return response const response = await fetch(networkApi.getStringURL('shop/'), {
headers: {
//Authorization: `Bearer ${localStorage.getItem('token')}`,
//'Content-Type': 'application/json',
},
mode: 'cors',
})
if (!response.ok) {
throw new Error('Server error!')
}
const responseData: ResponseData = await response.json()
return responseData
} catch (error) {
return {
error: error instanceof Error ? error.message : 'Unknown error',
}
}
} }
) )

@ -1,6 +1,5 @@
import { memo, useCallback, useEffect } from 'react' import { memo, useCallback, useEffect } from 'react'
import styles from './shop.module.css' import { Box, Button, Typography } from '@mui/material'
import { Box, Button } from '@mui/material'
import { useAppDispatch, useAppSelector } from '../../hooks' import { useAppDispatch, useAppSelector } from '../../hooks'
import { addToBasket, fetchProducts, selectProducts } from './shop-slice' import { addToBasket, fetchProducts, selectProducts } from './shop-slice'
@ -18,11 +17,10 @@ export const Shop = memo(() => {
return ( return (
<Box> <Box>
<span className={styles.shop}>{'Магазин'}</span>
<Button variant="contained" onClick={handleClick}> <Button variant="contained" onClick={handleClick}>
{'Добавить в корзину'} {'Добавить в корзину'}
</Button> </Button>
<span className={styles.shop}>{JSON.stringify(products)}</span> <Typography variant="body1">{JSON.stringify(products)}</Typography>
</Box> </Box>
) )
}) })

@ -4,4 +4,30 @@ import react from '@vitejs/plugin-react'
// https://vite.dev/config/ // https://vite.dev/config/
export default defineConfig({ export default defineConfig({
plugins: [react()], plugins: [react()],
server: {
port: 5173,
proxy: {
'/dev_api': {
target: 'http://127.0.0.1:8090',
changeOrigin: true,
secure: false,
rewrite: path => path.replace(/^\/dev_api/, ''),
configure: (proxy, _options) => {
proxy.on('error', (err, _req, _res) => {
console.log('Proxy error:', err)
})
proxy.on('proxyReq', (proxyReq, req, _res) => {
console.log(
proxyReq,
'Proxying:',
req.method,
req.url,
'→',
'http://127.0.0.1:8090' + req?.url?.replace('/dev_api', '')
)
})
},
},
},
},
}) })

Loading…
Cancel
Save