This commit is contained in:
DmitryGantimurov 2023-07-17 23:36:46 +03:00
commit 91842dcc51
9 changed files with 115 additions and 100 deletions

View File

@ -1,4 +1,7 @@
<<<<<<< HEAD
# <<<<<<< HEAD # <<<<<<< HEAD
=======
>>>>>>> de8a1abcbfea61d8d4898c18e133b8b0feaf87e8
#подключение библиотек #подключение библиотек
from fastapi import FastAPI, Response, Path, Depends, Body, Form, Query, status, HTTPException, APIRouter, UploadFile, File from fastapi import FastAPI, Response, Path, Depends, Body, Form, Query, status, HTTPException, APIRouter, UploadFile, File
from fastapi.responses import HTMLResponse, FileResponse, JSONResponse, RedirectResponse from fastapi.responses import HTMLResponse, FileResponse, JSONResponse, RedirectResponse
@ -102,7 +105,7 @@ def put_in_db(name: Annotated[str, Form()], category: Annotated[str, Form()], be
uploaded_name = "/uploads/"+destination.name uploaded_name = "/uploads/"+destination.name
temp_ancmt = Announcement(user_id=userId, name=name, category=category, best_by=bestBy, address=address, longtitude=longtitude, latitude=latitude, description=description, src=uploaded_name, metro=metro, trashId=trashId) temp_ancmt = Announcement(user_id=userId, name=name, category=category, best_by=bestBy, address=address, longtitude=longtitude, latitude=latitude, description=description, src=uploaded_name, metro=metro, trashId=trashId, booked_by=-1)
db.add(temp_ancmt) # добавляем в бд db.add(temp_ancmt) # добавляем в бд
db.commit() # сохраняем изменения db.commit() # сохраняем изменения
db.refresh(temp_ancmt) # обновляем состояние объекта db.refresh(temp_ancmt) # обновляем состояние объекта
@ -185,7 +188,7 @@ async def read_own_items(
@app.get("/api/trashbox") @app.get("/api/trashbox")
def get_trashboxes(lat:float, lng:float):#крутая функция для работы с api def get_trashboxes(lat:float, lng:float):#крутая функция для работы с api
BASE_URL='https://geointelect2.gate.petersburg.ru'#адрес сайта и мой токин BASE_URL='https://geointelect2.gate.petersburg.ru'#адрес сайта и мой токин
my_token='eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJhU1RaZm42bHpTdURYcUttRkg1SzN5UDFhT0FxUkhTNm9OendMUExaTXhFIn0.eyJleHAiOjE3Nzg2NTk4MjEsImlhdCI6MTY4Mzk2NTQyMSwianRpIjoiOTI2ZGMyNmEtMGYyZi00OTZiLWI0NTUtMWQyYWM5YmRlMTZkIiwiaXNzIjoiaHR0cHM6Ly9rYy5wZXRlcnNidXJnLnJ1L3JlYWxtcy9lZ3MtYXBpIiwiYXVkIjoiYWNjb3VudCIsInN1YiI6ImJjYjQ2NzljLTU3ZGItNDU5ZC1iNWUxLWRlOGI4Yzg5MTMwMyIsInR5cCI6IkJlYXJlciIsImF6cCI6ImFkbWluLXJlc3QtY2xpZW50Iiwic2Vzc2lvbl9zdGF0ZSI6IjE2MGU1ZGVkLWFmMjMtNDkyNS05OTc1LTRhMzM0ZjVmNTkyOSIsImFjciI6IjEiLCJhbGxvd2VkLW9yaWdpbnMiOlsiLyoiXSwicmVhbG1fYWNjZXNzIjp7InJvbGVzIjpbImRlZmF1bHQtcm9sZXMtZWdzLWFwaSIsIm9mZmxpbmVfYWNjZXNzIiwidW1hX2F1dGhvcml6YXRpb24iXX0sInJlc291cmNlX2FjY2VzcyI6eyJhY2NvdW50Ijp7InJvbGVzIjpbIm1hbmFnZS1hY2NvdW50IiwibWFuYWdlLWFjY291bnQtbGlua3MiLCJ2aWV3LXByb2ZpbGUiXX19LCJzY29wZSI6ImVtYWlsIHByb2ZpbGUiLCJzaWQiOiIxNjBlNWRlZC1hZjIzLTQ5MjUtOTk3NS00YTMzNGY1ZjU5MjkiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsIm5hbWUiOiLQktC70LDQtNC40LzQuNGAINCv0LrQvtCy0LvQtdCyIiwicHJlZmVycmVkX3VzZXJuYW1lIjoiZTBmYzc2OGRhOTA4MjNiODgwZGQzOGVhMDJjMmQ5NTciLCJnaXZlbl9uYW1lIjoi0JLQu9Cw0LTQuNC80LjRgCIsImZhbWlseV9uYW1lIjoi0K_QutC-0LLQu9C10LIifQ.BRyUIyY-KKnZ9xqTNa9vIsfKF0UN2VoA9h4NN4y7IgBVLiiS-j43QbeE6qgjIQo0pV3J8jtCAIPvJbO-Ex-GNkw_flgMiGHhKEpsHPW3WK-YZ-XsZJzVQ_pOmLte-Kql4z97WJvolqiXT0nMo2dlX2BGvNs6JNbupvcuGwL4YYpekYAaFNYMQrxi8bSN-R7FIqxP-gzZDAuQSWRRSUqVBLvmgRhphTM-FAx1sX833oXL9tR7ze3eDR_obSV0y6cKVIr4eIlKxFd82qiMrN6A6CTUFDeFjeAGERqeBPnJVXU36MHu7Ut7eOVav9OUARARWRkrZRkqzTfZ1iqEBq5Tsg' my_token='eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJhU1RaZm42bHpTdURYcUttRkg1SzN5UDFhT0FxUkhTNm9OendMUExaTXhFIn0.eyJleHAiOjE3ODM3ODk4NjgsImlhdCI6MTY4OTA5NTQ2OCwianRpIjoiNDUzNjQzZTgtYTkyMi00NTI4LWIzYmMtYWJiYTNmYjkyNTkxIiwiaXNzIjoiaHR0cHM6Ly9rYy5wZXRlcnNidXJnLnJ1L3JlYWxtcy9lZ3MtYXBpIiwiYXVkIjoiYWNjb3VudCIsInN1YiI6ImJjYjQ2NzljLTU3ZGItNDU5ZC1iNWUxLWRlOGI4Yzg5MTMwMyIsInR5cCI6IkJlYXJlciIsImF6cCI6ImFkbWluLXJlc3QtY2xpZW50Iiwic2Vzc2lvbl9zdGF0ZSI6ImM2ZDJiOTZhLWMxNjMtNDAxZS05ZjMzLTI0MmE0NDcxMDY5OCIsImFjciI6IjEiLCJhbGxvd2VkLW9yaWdpbnMiOlsiLyoiXSwicmVhbG1fYWNjZXNzIjp7InJvbGVzIjpbImRlZmF1bHQtcm9sZXMtZWdzLWFwaSIsIm9mZmxpbmVfYWNjZXNzIiwidW1hX2F1dGhvcml6YXRpb24iXX0sInJlc291cmNlX2FjY2VzcyI6eyJhY2NvdW50Ijp7InJvbGVzIjpbIm1hbmFnZS1hY2NvdW50IiwibWFuYWdlLWFjY291bnQtbGlua3MiLCJ2aWV3LXByb2ZpbGUiXX19LCJzY29wZSI6ImVtYWlsIHByb2ZpbGUiLCJzaWQiOiJjNmQyYjk2YS1jMTYzLTQwMWUtOWYzMy0yNDJhNDQ3MTA2OTgiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsIm5hbWUiOiLQktC70LDQtNC40LzQuNGAINCv0LrQvtCy0LvQtdCyIiwicHJlZmVycmVkX3VzZXJuYW1lIjoiZTBmYzc2OGRhOTA4MjNiODgwZGQzOGVhMDJjMmQ5NTciLCJnaXZlbl9uYW1lIjoi0JLQu9Cw0LTQuNC80LjRgCIsImZhbWlseV9uYW1lIjoi0K_QutC-0LLQu9C10LIifQ.E2bW0B-c6W5Lj63eP_G8eI453NlDMnW05l11TZT0GSsAtGayXGaolHtWrmI90D5Yxz7v9FGkkCmcUZYy1ywAdO9dDt_XrtFEJWFpG-3csavuMjXmqfQQ9SmPwDw-3toO64NuZVv6qVqoUlPPj57sLx4bLtVbB4pdqgyJYcrDHg7sgwz4d1Z3tAeUfSpum9s5ZfELequfpLoZMXn6CaYZhePaoK-CxeU3KPBPTPOVPKZZ19s7QY10VdkxLULknqf9opdvLs4j8NMimtwoIiHNBFlgQz10Cr7bhDKWugfvSRsICouniIiBJo76wrj5T92s-ztf1FShJuqnQcKE_QLd2A'
head = {'Authorization': 'Bearer {}'.format(my_token)} head = {'Authorization': 'Bearer {}'.format(my_token)}
my_data={ my_data={
@ -217,6 +220,7 @@ def get_trashboxes(lat:float, lng:float):#крутая функция для р
@app.get("/{rest_of_path:path}") @app.get("/{rest_of_path:path}")
async def react_app(req: Request, rest_of_path: str): async def react_app(req: Request, rest_of_path: str):
return templates.TemplateResponse('index.html', { 'request': req }) return templates.TemplateResponse('index.html', { 'request': req })
<<<<<<< HEAD
# ======= # =======
# #подключение библиотек # #подключение библиотек
# from fastapi import FastAPI, Response, Path, Depends, Body, Form, Query, status, HTTPException, APIRouter, UploadFile, File # from fastapi import FastAPI, Response, Path, Depends, Body, Form, Query, status, HTTPException, APIRouter, UploadFile, File
@ -437,3 +441,5 @@ async def react_app(req: Request, rest_of_path: str):
# async def react_app(req: Request, rest_of_path: str): # async def react_app(req: Request, rest_of_path: str):
# return templates.TemplateResponse('index.html', { 'request': req }) # return templates.TemplateResponse('index.html', { 'request': req })
# >>>>>>> 3668e8c33f71b7a79a0c83d41a106d9b55e2df71 # >>>>>>> 3668e8c33f71b7a79a0c83d41a106d9b55e2df71
=======
>>>>>>> de8a1abcbfea61d8d4898c18e133b8b0feaf87e8

View File

@ -0,0 +1,12 @@
import { API_URL } from '../../config'
import { PutAnnouncement, PutAnnouncementResponse } from './types'
const composePutAnnouncementURL = () => (
API_URL + '/announcement?'
)
const processPutAnnouncement = (data: PutAnnouncementResponse): PutAnnouncement => {
return data.Answer
}
export { composePutAnnouncementURL, processPutAnnouncement }

View File

@ -0,0 +1,17 @@
import { isObject } from '../../utils/types'
type PutAnnouncementResponse = {
Answer: boolean
}
const isPutAnnouncementResponse = (obj: unknown): obj is PutAnnouncementResponse => (
isObject(obj, {
'Answer': 'boolean'
})
)
type PutAnnouncement = boolean
export type { PutAnnouncementResponse, PutAnnouncement }
export { isPutAnnouncementResponse }

View File

@ -1,6 +1,7 @@
import { LatLng } from 'leaflet' import { LatLng } from 'leaflet'
import { API_URL } from '../../config' import { API_URL } from '../../config'
import { Trashbox, TrashboxResponse } from './types'
const composeTrashboxURL = (position: LatLng) => ( const composeTrashboxURL = (position: LatLng) => (
API_URL + '/trashbox?' + new URLSearchParams({ API_URL + '/trashbox?' + new URLSearchParams({
@ -9,4 +10,7 @@ const composeTrashboxURL = (position: LatLng) => (
}).toString() }).toString()
) )
export { composeTrashboxURL } const processTrashbox = (data: TrashboxResponse): Trashbox[] =>
data
export { composeTrashboxURL, processTrashbox }

View File

@ -1,79 +1,31 @@
import { useEffect, useRef, useState } from 'react' import { useCallback } from 'react'
import { API_URL } from '../../config' import { useSend } from '..'
import { isLiteralUnion } from '../../utils/types' import { composePutAnnouncementURL, processPutAnnouncement } from '../../api/putAnnouncement'
import { handleHTTPErrors } from '../../utils' import { isPutAnnouncementResponse } from '../../api/putAnnouncement/types'
import useSendButtonCaption from '../useSendButtonCaption'
const addErrors = ['Не удалось опубликовать объявление', 'Неверный ответ от сервера', 'Неизвестная ошибка'] as const
type AddError = typeof addErrors[number]
const isAddError = (obj: unknown): obj is AddError => (
isLiteralUnion(obj, addErrors)
)
const buttonStates = ['Опубликовать', 'Загрузка...', 'Опубликовано', 'Отменено'] as const
type ButtonState = typeof buttonStates[number] | AddError
type AddResponse = {
Answer: boolean
}
const isAddResponse = (obj: unknown): obj is AddResponse => (
typeof obj === 'object' && obj !== null && typeof Reflect.get(obj, 'Answer') === 'boolean'
)
const useAddAnnouncement = () => { const useAddAnnouncement = () => {
const [status, setStatus] = useState<ButtonState>('Опубликовать') const { doSend, loading, error } = useSend(
composePutAnnouncementURL(),
'PUT',
true,
isPutAnnouncementResponse,
processPutAnnouncement,
)
const timerIdRef = useRef<number>() const { update, ...button } = useSendButtonCaption('Опубликовать', loading, error, 'Опубликовано')
const abortControllerRef = useRef<AbortController>()
const doAdd = async (formData: FormData) => { const doSendWithButton = useCallback(async (formData: FormData) => {
if (status === 'Загрузка...') { const data = await doSend({}, {
abortControllerRef.current?.abort() body: formData
setStatus('Отменено') })
timerIdRef.current = setTimeout(() => setStatus('Опубликовать'), 3000) update(data)
return
}
setStatus('Загрузка...') return data
}, [doSend, update])
const abortController = new AbortController() return { doSend: doSendWithButton, button }
abortControllerRef.current = abortController
try {
const res = await fetch(API_URL + '/announcement', {
method: 'PUT',
body: formData,
signal: abortController.signal
})
handleHTTPErrors(res)
const data: unknown = await res.json()
if (!isAddResponse(data)) throw new Error('Неверный ответ от сервера')
if (!data.Answer) {
throw new Error('Не удалось опубликовать объявление')
}
setStatus('Опубликовано')
} catch (err) {
setStatus(isAddError(err) ? err : 'Неизвестная ошибка')
timerIdRef.current = setTimeout(() => setStatus('Опубликовать'), 10000)
}
}
useEffect(() => {
const abortController = abortControllerRef.current
return () => {
clearTimeout(timerIdRef.current)
abortController?.abort()
}
})
return { doAdd, status }
} }
export default useAddAnnouncement export default useAddAnnouncement

View File

@ -1,7 +1,7 @@
import { LatLng } from 'leaflet' import { LatLng } from 'leaflet'
import { useFetch } from '../' import { useFetch } from '../'
import { composeTrashboxURL } from '../../api/trashbox' import { composeTrashboxURL, processTrashbox } from '../../api/trashbox'
import { isTrashboxResponse } from '../../api/trashbox/types' import { isTrashboxResponse } from '../../api/trashbox/types'
const useTrashboxes = (position: LatLng) => ( const useTrashboxes = (position: LatLng) => (
@ -10,7 +10,7 @@ const useTrashboxes = (position: LatLng) => (
'GET', 'GET',
true, true,
isTrashboxResponse, isTrashboxResponse,
(data) => data, processTrashbox,
[] []
) )
) )

View File

@ -24,6 +24,7 @@ function useSend<R, T>(
/** Don't use in useEffect. If you need request result, go with useFetch instead */ /** Don't use in useEffect. If you need request result, go with useFetch instead */
const doSend = useCallback(async (urlProps?: Record<string, string>, params?: Omit<RequestInit, 'method'>) => { const doSend = useCallback(async (urlProps?: Record<string, string>, params?: Omit<RequestInit, 'method'>) => {
setLoading(true) setLoading(true)
setError(null)
if (abortControllerRef.current) { if (abortControllerRef.current) {
abortControllerRef.current.abort() abortControllerRef.current.abort()

View File

@ -0,0 +1,44 @@
import { useCallback, useEffect, useState } from 'react'
function useSendButtonCaption(
initial: string,
loading: boolean,
error: string | null,
result = initial,
singular = true
) {
const [caption, setCaption] = useState(initial)
const [disabled, setDisabled] = useState(false)
const [title, setTitle] = useState(initial)
const update = useCallback(<T extends NonNullable<unknown>>(data: T | undefined) => {
if (data !== undefined) {
setCaption(result)
setTitle('Отправить ещё раз')
if (singular) {
setDisabled(true)
setTitle('')
}
}
}, [result, singular])
useEffect(() => {
if (loading) {
setCaption('Загрузка...')
setTitle('Отменить и отправить ещё раз')
}
}, [loading])
useEffect(() => {
if (!loading && error !== null) {
setCaption(error + ', нажмите, чтобы попробовать ещё раз')
setTitle('')
}
}, [error, loading])
return { update, children: caption, disabled, title }
}
export default useSendButtonCaption

View File

@ -1,11 +1,10 @@
import { CSSProperties, FormEventHandler, useEffect, useState } from 'react' import { CSSProperties, FormEventHandler, useState } from 'react'
import { Form, Button, Card } from 'react-bootstrap' import { Form, Button, Card } from 'react-bootstrap'
import { MapContainer, TileLayer } from 'react-leaflet' import { MapContainer, TileLayer } from 'react-leaflet'
import { latLng } from 'leaflet' import { latLng } from 'leaflet'
import { ClickHandler, LocationMarker, TrashboxMarkers } from '../components' import { ClickHandler, LocationMarker, TrashboxMarkers } from '../components'
import { useAddAnnouncement, useTrashboxes } from '../hooks/api' import { useAddAnnouncement, useTrashboxes } from '../hooks/api'
import { handleHTTPErrors } from '../utils'
import { categories, categoryNames } from '../assets/category' import { categories, categoryNames } from '../assets/category'
import { stations, lines, lineNames } from '../assets/metro' import { stations, lines, lineNames } from '../assets/metro'
import { fallbackError, gotError } from '../hooks/useFetch' import { fallbackError, gotError } from '../hooks/useFetch'
@ -32,25 +31,7 @@ function AddPage() {
const address = useOsmAddresses(addressPosition) const address = useOsmAddresses(addressPosition)
useEffect(() => { const { doSend, button } = useAddAnnouncement()
if (!gotError(address))
void (async () => {
try {
const res = await fetch(location.protocol + '//nominatim.openstreetmap.org/search?format=json&q=' + encodeURIComponent(address.data))
handleHTTPErrors(res)
const fetchData: unknown = await res.json()
console.log('f', fetchData)
} catch (err) {
console.error(err)
}
})()
}, [address])
const { doAdd, status } = useAddAnnouncement()
const handleSubmit: FormEventHandler<HTMLFormElement> = (event) => { const handleSubmit: FormEventHandler<HTMLFormElement> = (event) => {
event.preventDefault() event.preventDefault()
@ -63,7 +44,7 @@ function AddPage() {
formData.append('address', address.data || '') // if address.error formData.append('address', address.data || '') // if address.error
formData.set('bestBy', new Date((formData.get('bestBy') as number | null) || 0).getTime().toString()) formData.set('bestBy', new Date((formData.get('bestBy') as number | null) || 0).getTime().toString())
void doAdd(formData) void doSend(formData)
} }
return ( return (
@ -151,7 +132,7 @@ function AddPage() {
</Form.Select> </Form.Select>
</Form.Group> </Form.Group>
<Form.Group className='mb-3' controlId='password'> <Form.Group className='mb-3' controlId='trashbox'>
<Form.Label>Пункт сбора мусора</Form.Label> <Form.Label>Пункт сбора мусора</Form.Label>
<div className='mb-3'> <div className='mb-3'>
{trashboxes.loading {trashboxes.loading
@ -195,9 +176,7 @@ function AddPage() {
)} )}
</Form.Group> </Form.Group>
<Button variant='success' type='submit'> <Button variant='success' type='submit' {...button} />
{status}
</Button>
</Form> </Form>
</Card.Body> </Card.Body>
</Card> </Card>