redux shop response

main
Stepan Pilipenko 1 month ago
parent d76e86ca32
commit ebe207ec83

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

@ -1,15 +1,14 @@
import uuid
import psycopg2
import psycopg2.extras
import json
from datetime import timedelta
from psycopg2 import IntegrityError
from collections import Counter
from decimal import Decimal, InvalidOperation
from decimal import InvalidOperation
from ..utils import *
from ..type import *
from ..consts import *
from utils import *
from type import *
from consts import *
def update_token_expiry_date(token: str, expiry_date: date) -> None:
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)
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()
if res is None or res[0] is None:

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

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

@ -56,3 +56,6 @@ def decimal_to_float(data: list[dict]):
for item in 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 {
getProducts: () => Product[]
getStringURL: (suffix: string) => string
}
class Network implements NetworkApi {
getProducts(): Product[] {
return [
{
id: 2,
name: 'Серебрянный паровоз',
count: 23,
reserved: 0,
picture_url:
'https://git.softwarrior.ru/stepik104/game_shop/raw/branch/main/images/silver_train.png',
description:
'Серебро - благородный метал, такой паровоз не стыдно выкатить на рельсы',
type: 'skin',
cost: '50.00',
},
{
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',
},
]
private baseUrl: string
constructor() {
if (import.meta.env.DEV) {
this.baseUrl = DEV_URL
} else {
this.baseUrl = PROD_URL
}
}
getStringURL(suffix: string): string {
const urlString = `${this.baseUrl}/${suffix.replace(/^\//, '')}`
return urlString
}
}

@ -7,14 +7,21 @@ interface AddBasketAction {
count: number
}
type ResponseData = {
error?: string
success?: Array<object>
}
interface ShopState {
products: Product[]
loading: boolean
error?: string
}
const initialState: ShopState = {
products: [],
loading: false,
error: undefined,
}
export const shopSlice = createSlice({
@ -31,10 +38,19 @@ export const shopSlice = createSlice({
builder
.addCase(fetchProducts.pending, state => {
state.loading = true
state.error = undefined
})
.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
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 fetchProducts = createAsyncThunk<Product[], void, ThunkApi>(
'shop/fetchProducts',
export const fetchProducts = createAsyncThunk<ResponseData, void, ThunkApi>(
'products/fetchProducts',
async (_, { extra: networkApi }) => {
const response = await networkApi.getProducts()
return response
try {
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 styles from './shop.module.css'
import { Box, Button } from '@mui/material'
import { Box, Button, Typography } from '@mui/material'
import { useAppDispatch, useAppSelector } from '../../hooks'
import { addToBasket, fetchProducts, selectProducts } from './shop-slice'
@ -18,11 +17,10 @@ export const Shop = memo(() => {
return (
<Box>
<span className={styles.shop}>{'Магазин'}</span>
<Button variant="contained" onClick={handleClick}>
{'Добавить в корзину'}
</Button>
<span className={styles.shop}>{JSON.stringify(products)}</span>
<Typography variant="body1">{JSON.stringify(products)}</Typography>
</Box>
)
})

@ -4,4 +4,30 @@ import react from '@vitejs/plugin-react'
// https://vite.dev/config/
export default defineConfig({
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