Merge branch 'update-frontend' into 'master'

Add subscribe/unsuscribe logic in browser-domain

See merge request adm3981141/fids!3
This commit is contained in:
Santiago Lo Coco 2023-12-01 23:15:26 +00:00
commit aebe3c3300
23 changed files with 341 additions and 61 deletions

View File

@ -14,6 +14,8 @@ default:
- api_failure - api_failure
before_script: before_script:
- docker login -u $CI_REGISTRY_USER --password $CI_JOB_TOKEN $CI_REGISTRY - docker login -u $CI_REGISTRY_USER --password $CI_JOB_TOKEN $CI_REGISTRY
artifacts:
expire_in: 1 week
stages: stages:
- prep - prep
@ -70,18 +72,8 @@ preparation:
dotenv: context.env dotenv: context.env
.build-and-push-script: &build-and-push-script .build-and-push-script: &build-and-push-script
- | - docker build ${FOLDER} -f ${FOLDER}/Dockerfile.prod --build-arg "${BUILD_ARG_PROD-x}" --build-arg "${BUILD_ARG_PROD_OTHER-x}" -t ${PROD_IMAGE}
if [[ -z "${BUILD_ARG_PROD}" ]]; then - docker build ${FOLDER} -f ${FOLDER}/Dockerfile.test -t ${TEST_IMAGE}
BUILD_ARG_PROD="x=x"
fi
if [[ -z "${BUILD_ARG_PROD_OTHER}" ]]; then
BUILD_ARG_PROD_OTHER="x=x"
fi
if [[ -z "${BUILD_ARG_TEST}" ]]; then
BUILD_ARG_TEST="x=x"
fi
- docker build ${FOLDER} -f ${FOLDER}/Dockerfile.prod --build-arg "${BUILD_ARG_PROD}" --build-arg "${BUILD_ARG_PROD_OTHER}" -t ${PROD_IMAGE}
- docker build ${FOLDER} -f ${FOLDER}/Dockerfile.test --build-arg "${BUILD_ARG_TEST}" -t ${TEST_IMAGE}
- docker push ${PROD_IMAGE} - docker push ${PROD_IMAGE}
- docker push ${TEST_IMAGE} - docker push ${TEST_IMAGE}

View File

@ -1,5 +1,5 @@
import { Axios, AxiosError } from "axios"; import { Axios, AxiosError } from "axios";
import { Credentials, Token, User, Flight, FlightCreate } from "./Types"; import { Credentials, Token, User, Flight, FlightCreate, SubscriptionsCreate } from "./Types";
const instance = new Axios({ const instance = new Axios({
baseURL: process.env.REACT_APP_ENDPOINT ? process.env.REACT_APP_ENDPOINT : "http://127.0.0.1:5000/", baseURL: process.env.REACT_APP_ENDPOINT ? process.env.REACT_APP_ENDPOINT : "http://127.0.0.1:5000/",
@ -24,6 +24,8 @@ instance.interceptors.response.use(
json["count"] = response.headers["x-count"] json["count"] = response.headers["x-count"]
console.log(json) console.log(json)
return json return json
} else if (response.status == 204) {
return response;
} }
return JSON.parse(response.data); return JSON.parse(response.data);
}, },
@ -75,4 +77,35 @@ export const createFlight = (
return instance.post("flights", flight_data, { return instance.post("flights", flight_data, {
headers: { Authorization: `Bearer ${token}` }, headers: { Authorization: `Bearer ${token}` },
}); });
};
export const subscribeToFlight = (subscription: SubscriptionsCreate, token: string): Promise<SubscriptionsCreate> => {
return instance.post("subscriptions", subscription, {
headers: { Authorization: `Bearer ${token}` },
});
};
export const getChatId = (user_id: number, token: string): Promise<Flight> => {
return instance.get("notifications?user_id=" + user_id, {
headers: { Authorization: `Bearer ${token}` },
});
};
export const getSubscription = (subscription: SubscriptionsCreate, token: string): Promise<SubscriptionsCreate> => {
return instance.get("subscriptions?user_id=" + subscription.user_id + "&flight_id=" +subscription.flight_id, {
headers: { Authorization: `Bearer ${token}` },
});
};
export const unsubscribeFromFlight = (subscription: SubscriptionsCreate, token: string): Promise<any> => {
return instance.delete("subscriptions", {
headers: { Authorization: `Bearer ${token}`},
data: subscription
});
};
export const fetchSubscriptions = (user_id: number, token: string): Promise<SubscriptionsCreate[]> => {
return instance.get("subscriptions?user_id=" + user_id, {
headers: { Authorization: `Bearer ${token}` },
});
}; };

View File

@ -45,4 +45,9 @@ export interface FlightCreate {
departure_time: string; departure_time: string;
arrival_time: string; arrival_time: string;
gate: string; gate: string;
}
export interface SubscriptionsCreate {
flight_id: number;
user_id: number;
} }

View File

@ -1,10 +1,14 @@
import React from "react"; import React, { useEffect, useState } from "react";
import { Avatar, Space, Typography, Tag } from "antd"; import { Link } from "react-router-dom";
import { Avatar, Space, Typography, Tag, Button, Modal } from "antd";
import { RightOutlined, ClockCircleOutlined, SwapOutlined, EnvironmentOutlined, CalendarOutlined } from "@ant-design/icons"; import { RightOutlined, ClockCircleOutlined, SwapOutlined, EnvironmentOutlined, CalendarOutlined } from "@ant-design/icons";
import "./Card.css"; import "./Card.css";
import { getChatId, getSubscription, subscribeToFlight, unsubscribeFromFlight } from "../../../Api";
import { User } from "../../../Types";
interface FlightProps { interface FlightProps {
id: number;
flight_code: string; flight_code: string;
status: string; status: string;
origin: string; origin: string;
@ -16,11 +20,74 @@ interface FlightProps {
interface CardProps { interface CardProps {
flight: FlightProps; flight: FlightProps;
user: User | undefined;
subscribed: boolean;
refresh: any;
} }
const { Text } = Typography; const { Text } = Typography;
export const Card: React.FC<CardProps> = ({ flight }) => { export const Card: React.FC<CardProps> = ({ flight, user, subscribed, refresh }) => {
const [modalVisible, setModalVisible] = useState<boolean>(false);
const handleSubscribe = async (event: React.FormEvent) => {
event.preventDefault();
const token = localStorage.getItem("token");
if (!token || !user) {
return;
}
const data = {
user_id: user.id,
flight_id: flight.id
}
console.log(data)
subscribeToFlight(data, token)
.then(() => {
refresh()
getChatId(user.id, token)
.then(() => {})
.catch((error) => {
setModalVisible(true);
})
})
.catch((error) => {
});
};
const handleModalClose = () => {
setModalVisible(false);
};
const handleUnsubscribe = async (event: React.FormEvent) => {
event.preventDefault();
const token = localStorage.getItem("token");
if (!token || !user) {
return;
}
const data = {
user_id: user.id,
flight_id: flight.id
}
console.log(data)
unsubscribeFromFlight(data, token)
.then(() => {
refresh()
})
.catch((error) => {
});
};
console.log(subscribed)
return ( return (
<div className="flight-card"> <div className="flight-card">
<Space size={8} align="center"> <Space size={8} align="center">
@ -32,7 +99,7 @@ export const Card: React.FC<CardProps> = ({ flight }) => {
</div> </div>
</Space> </Space>
<div className="flight-details"> <div className="flight-details">
<Space size={8} direction="vertical"> <Space size={8} direction="horizontal">
<Text strong>Status:</Text> <Text strong>Status:</Text>
<Tag color={flight.status === "Scheduled" ? "green" : "orange"}>{flight.status}</Tag> <Tag color={flight.status === "Scheduled" ? "green" : "orange"}>{flight.status}</Tag>
</Space> </Space>
@ -50,11 +117,41 @@ export const Card: React.FC<CardProps> = ({ flight }) => {
{flight.arrival_time} {flight.arrival_time}
</Space> </Space>
</Space> </Space>
<Space size={8} direction="vertical"> <Space size={8} direction="horizontal">
<Text strong>Gate:</Text> <Text strong>Gate:</Text>
<Text>{flight.gate}</Text> <Text>{flight.gate}</Text>
</Space> </Space>
<Space size={8} direction="horizontal">
<Text strong>ID:</Text>
<Text>{flight.id}</Text>
</Space>
</div> </div>
{!(subscribed) ?
<Button type="primary" onClick={handleSubscribe}>
Subscribe
</Button>
:
<Button type="primary" onClick={handleUnsubscribe}>
Unsubscribe
</Button>
}
<Modal
title="Error"
visible={modalVisible}
onCancel={handleModalClose}
footer={[
<Button key="ok" type="primary" onClick={handleModalClose}>
OK
</Button>
]}
>
<p>To start receiving messages open this link on your smartphone:
</p>
<Link to={`https://t.me/fids_system_bot?start=${user?.id}`} target="_blank">
{`https://t.me/fids_system_bot?start=${user?.id}`}
</Link>
</Modal>
</div> </div>
); );
}; };

View File

@ -4,6 +4,7 @@ import { useFetchFlights } from "../../hooks/useFetchFlights";
import { Flight } from "../../Types"; import { Flight } from "../../Types";
import { useNavigate } from "react-router"; import { useNavigate } from "react-router";
import useAuth from "../../useAuth"; import useAuth from "../../useAuth";
import { useFetchSubscriptions } from "../../hooks/useFetchSubscriptions";
interface Props { interface Props {
flights?: Flight[]; flights?: Flight[];
@ -17,7 +18,9 @@ export const Home: React.FC<Props> = (props) => {
const navigate = useNavigate() const navigate = useNavigate()
const [currentPage, setCurrentPage] = useState(initialPage); const [currentPage, setCurrentPage] = useState(initialPage);
const { loading, isAirline } = useAuth(); const { loading, isAirline, user, token } = useAuth();
const { subscriptions, loading: subsLoading, fetchData } = useFetchSubscriptions(user, token);
useEffect(() => { useEffect(() => {
const newParams = new URLSearchParams(window.location.search); const newParams = new URLSearchParams(window.location.search);
@ -52,8 +55,8 @@ export const Home: React.FC<Props> = (props) => {
{isAirline ? <button onClick={() => { navigate("/create-flight") }}>Create flight</button> : <></>} {isAirline ? <button onClick={() => { navigate("/create-flight") }}>Create flight</button> : <></>}
<h2>Flights</h2> <h2>Flights</h2>
<div className="Items"> <div className="Items">
{(props.flights ? props.flights : flights).map((u) => { {(props.flights ? props.flights : flights).map((f) => {
return <Card key={u.id} flight={u} />; return <Card key={f.id} flight={f} user={user} subscribed={subscriptions.some((i => i.flight_id === f.id))} refresh={fetchData} />;
})} })}
{error ? <div className="Disconnected">{error}</div> : <></>} {error ? <div className="Disconnected">{error}</div> : <></>}
</div> </div>

View File

@ -8,6 +8,7 @@ export const LogIn = () => {
const [email, setEmail] = useState(""); const [email, setEmail] = useState("");
const [password, setPassword] = useState(""); const [password, setPassword] = useState("");
const navigate = useNavigate(); const navigate = useNavigate();
console.log(error)
return ( return (
<div className="Box Small"> <div className="Box Small">
@ -39,7 +40,7 @@ export const LogIn = () => {
Sign up Sign up
</Button> </Button>
{error ? ( {error ? (
<div className="Disconnected">{error}</div> <div className="Disconnected">{error?.message}</div>
) : ( ) : (
<></> <></>
)} )}

View File

@ -0,0 +1,31 @@
import React, { useEffect, useCallback } from "react";
import { useState } from "react";
import { User, Flight, SubscriptionsCreate } from "../Types";
import { fetchSubscriptions } from "../Api";
export const useFetchSubscriptions = (user: User | undefined, token: string | undefined) => {
const [error, setError] = useState<string | null>(null);
const [subscriptions, setSubscriptions] = useState<SubscriptionsCreate[]>([]);
const [loading, setLoading] = useState<boolean>(true);
const fetchData = useCallback(async () => {
setError(null);
if (!user || !token || !loading) {
return;
}
fetchSubscriptions(user.id, token)
.then((data) => {
setSubscriptions(data);
setLoading(false)
})
.catch((error) => { });
}, [user, token]);
useEffect(() => {
fetchData()
}, [fetchData]);
return { subscriptions, error, loading, fetchData };
};

View File

@ -8,6 +8,7 @@ interface AuthContextType {
user?: User; user?: User;
loading: boolean; loading: boolean;
isAirline: boolean; isAirline: boolean;
token?: string;
error?: any; error?: any;
login: (credentials: Credentials) => void; login: (credentials: Credentials) => void;
signUp: (email: string, name: string, password: string) => void; signUp: (email: string, name: string, password: string) => void;
@ -25,6 +26,7 @@ export function AuthProvider({
}): JSX.Element { }): JSX.Element {
const [user, setUser] = useState<User>(); const [user, setUser] = useState<User>();
const [error, setError] = useState<any>(); const [error, setError] = useState<any>();
const [token, setToken] = useState<string>();
const [loading, setLoading] = useState<boolean>(false); const [loading, setLoading] = useState<boolean>(false);
const [loadingInitial, setLoadingInitial] = useState<boolean>(true); const [loadingInitial, setLoadingInitial] = useState<boolean>(true);
const [isAirline, setIsAirline] = useState(false); const [isAirline, setIsAirline] = useState(false);
@ -51,7 +53,10 @@ export function AuthProvider({
.then((res) => fetchUserById(res.id, existingToken) .then((res) => fetchUserById(res.id, existingToken)
.then((res) => setUser(res)) .then((res) => setUser(res))
.catch((_error) => { }) .catch((_error) => { })
.finally(() => setLoadingInitial(false)) .finally(() => {
setToken(existingToken)
setLoadingInitial(false)
})
) )
.catch((_error) => { .catch((_error) => {
setLoadingInitial(false) setLoadingInitial(false)
@ -73,6 +78,7 @@ export function AuthProvider({
const user = fetchUserById(x.user_id as number, x.access_token) const user = fetchUserById(x.user_id as number, x.access_token)
.then(y => { .then(y => {
setUser(y); setUser(y);
setToken(x.access_token)
navigate("/home") navigate("/home")
}) })
.catch((error) => setError(error)) .catch((error) => setError(error))
@ -87,6 +93,7 @@ export function AuthProvider({
function logout() { function logout() {
localStorage.removeItem("token"); localStorage.removeItem("token");
setUser(undefined); setUser(undefined);
setToken(undefined)
navigate("/login") navigate("/login")
} }
@ -95,6 +102,7 @@ export function AuthProvider({
user, user,
loading, loading,
isAirline, isAirline,
token,
error, error,
login, login,
signUp, signUp,

View File

@ -4,6 +4,5 @@ psycopg2-binary==2.9.5
pyjwt==2.6.0 pyjwt==2.6.0
gunicorn==20.1.0 gunicorn==20.1.0
sqlalchemy==2.0.22 sqlalchemy==2.0.22
asyncreq==0.0.5 asyncreq==0.0.6
logmiddleware==0.0.3 logmiddleware==0.0.4
async-timeout==4.0.3

View File

@ -3,6 +3,5 @@ fastapi[all]==0.103.2
pyjwt==2.6.0 pyjwt==2.6.0
gunicorn==20.1.0 gunicorn==20.1.0
requests==2.31.0 requests==2.31.0
asyncreq==0.0.5 asyncreq==0.0.6
logmiddleware==0.0.3 logmiddleware==0.0.4
async-timeout==4.0.3

View File

@ -49,9 +49,9 @@ async def update_flight(
req: Request, req: Request,
authorization: Annotated[str | None, Header()] = None, authorization: Annotated[str | None, Header()] = None,
): ):
id = await checkAuth(req, authorization, isAirline=True) user_id = await checkAuth(req, authorization, isAirline=True)
update = flight_update.model_dump() update = flight_update.model_dump()
update["user_id"] = id update["user_id"] = user_id
request_id = req.state.request_id request_id = req.state.request_id
header = {"x-api-request-id": request_id} header = {"x-api-request-id": request_id}
(response, status, _) = await request( (response, status, _) = await request(

View File

@ -1,7 +1,10 @@
from typing import Annotated
from asyncreq import request from asyncreq import request
from fastapi import APIRouter, HTTPException, Request from fastapi import APIRouter, Header, HTTPException, Request
from src.api.config import API_NOTIFICATIONS from src.api.config import API_NOTIFICATIONS
from src.api.routes.auth import checkAuth
from src.api.schemas.notification import Update as Message from src.api.schemas.notification import Update as Message
router = APIRouter() router = APIRouter()
@ -18,3 +21,22 @@ async def receive_message(message: Message, req: Request):
if status < 200 or status > 204: if status < 200 or status > 204:
raise HTTPException(status_code=status, detail=response) raise HTTPException(status_code=status, detail=response)
return response return response
@router.get("")
async def get_chat_by_user_id(
req: Request,
user_id: int,
authorization: Annotated[str | None, Header()] = None,
):
await checkAuth(req, authorization, userId=user_id)
query = {}
query["user_id"] = user_id
request_id = req.state.request_id
header = {"x-api-request-id": request_id}
(response, status, _) = await request(
f"{API_NOTIFICATIONS}", "GET", query=query, headers=header
)
if status < 200 or status > 204:
raise HTTPException(status_code=status, detail=response)
return response

View File

@ -1,10 +1,10 @@
from typing import Annotated from typing import Annotated, Optional
from asyncreq import request from asyncreq import request
from fastapi import APIRouter, Header, HTTPException, Request from fastapi import APIRouter, Header, HTTPException, Request
from src.api.config import API_SUBSCRIPTIONS from src.api.config import API_SUBSCRIPTIONS
from src.api.routes.auth import status as checkAuth from src.api.routes.auth import checkAuth
from src.api.schemas.subscriptions import Subscription from src.api.schemas.subscriptions import Subscription
router = APIRouter() router = APIRouter()
@ -16,7 +16,7 @@ async def create_subscription(
req: Request, req: Request,
authorization: Annotated[str | None, Header()] = None, authorization: Annotated[str | None, Header()] = None,
): ):
await checkAuth(req, authorization) await checkAuth(req, authorization, userId=subscription.user_id)
request_id = req.state.request_id request_id = req.state.request_id
header = {"x-api-request-id": request_id} header = {"x-api-request-id": request_id}
(response, status, _) = await request( (response, status, _) = await request(
@ -25,3 +25,42 @@ async def create_subscription(
if status < 200 or status > 204: if status < 200 or status > 204:
raise HTTPException(status_code=status, detail=response) raise HTTPException(status_code=status, detail=response)
return response return response
@router.delete("")
async def delete_subscription(
subscription: Subscription,
req: Request,
authorization: Annotated[str | None, Header()] = None,
):
await checkAuth(req, authorization, userId=subscription.user_id)
request_id = req.state.request_id
header = {"x-api-request-id": request_id}
(response, status, _) = await request(
f"{API_SUBSCRIPTIONS}", "DELETE", json=subscription.model_dump(), headers=header
)
if status < 200 or status > 204:
raise HTTPException(status_code=status, detail=response)
return response
@router.get("")
async def get_subscriptions(
req: Request,
user_id: int,
flight_id: Optional[int] = None,
authorization: Annotated[str | None, Header()] = None,
):
await checkAuth(req, authorization, userId=user_id)
query = {}
query["user_id"] = user_id
if flight_id:
query["flight_id"] = flight_id
request_id = req.state.request_id
header = {"x-api-request-id": request_id}
(response, status, _) = await request(
f"{API_SUBSCRIPTIONS}", "GET", query=query, headers=header
)
if status < 200 or status > 204:
raise HTTPException(status_code=status, detail=response)
return response

View File

@ -2,5 +2,5 @@ POSTGRES_USER=user
POSTGRES_PASS=password POSTGRES_PASS=password
POSTGRES_DB=api_dev POSTGRES_DB=api_dev
APP_SETTINGS=src.config.DevelopmentConfig APP_SETTINGS=src.config.DevelopmentConfig
TOKEN=3275588851:AT36AGy_BChQUuCq2M6d2UrY5CSWtZe45gV TG_TOKEN=3275588851:AT36AGy_BChQUuCq2M6d2UrY5CSWtZe45gV
API_DEBUG=true API_DEBUG=true

View File

@ -2,5 +2,5 @@ POSTGRES_USER=user
POSTGRES_PASS=password POSTGRES_PASS=password
POSTGRES_DB=api_prod POSTGRES_DB=api_prod
APP_SETTINGS=src.config.ProductionConfig APP_SETTINGS=src.config.ProductionConfig
TOKEN=3275588851:AT36AGy_BChQUuCq2M6d2UrY5CSWtZe45gV TG_TOKEN=3275588851:AT36AGy_BChQUuCq2M6d2UrY5CSWtZe45gV
API_DEBUG=false API_DEBUG=false

View File

@ -15,7 +15,7 @@ services:
- PORT=5000 - PORT=5000
- DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASS}@subscriptions-db/${POSTGRES_DB} - DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASS}@subscriptions-db/${POSTGRES_DB}
- APP_SETTINGS=${APP_SETTINGS} - APP_SETTINGS=${APP_SETTINGS}
- TOKEN=${TOKEN} - TG_TOKEN=${TG_TOKEN}
logging: logging:
driver: gelf driver: gelf
options: options:

View File

@ -4,6 +4,5 @@ psycopg2-binary==2.9.5
pyjwt==2.6.0 pyjwt==2.6.0
gunicorn==20.1.0 gunicorn==20.1.0
sqlalchemy==2.0.22 sqlalchemy==2.0.22
asyncreq==0.0.5 asyncreq==0.0.6
logmiddleware==0.0.3 logmiddleware==0.0.4
async-timeout==4.0.3

View File

@ -1,4 +1,9 @@
import os import os
API_FLIGHTS = "http://fids_flights_api:5000/flights" TEST_TARGET = os.getenv("TEST_TARGET")
API_DEBUG = os.getenv("API_DEBUG") API_DEBUG = os.getenv("API_DEBUG")
if TEST_TARGET == "INTEGRATION":
API_FLIGHTS = "http://fids-flights-dev_flights-api:5000/flights"
else:
API_FLIGHTS = "http://fids-flights_flights-api:5000/flights"

View File

@ -9,7 +9,20 @@ def get_subscriptions(db: Session, user_id: int):
return db.query(Subscription).filter(Subscription.user_id == user_id).all() return db.query(Subscription).filter(Subscription.user_id == user_id).all()
def get_subscription(db: Session, user_id: int, flight_id: int):
return (
db.query(Subscription)
.filter(Subscription.user_id == user_id, Subscription.flight_id == flight_id)
.first()
)
def create_subscription(db: Session, subscription: SubscriptionPydantic): def create_subscription(db: Session, subscription: SubscriptionPydantic):
if get_subscription(
db, user_id=subscription.user_id, flight_id=subscription.flight_id
):
raise ValueError
db_subscription = Subscription( db_subscription = Subscription(
user_id=subscription.user_id, user_id=subscription.user_id,
flight_id=subscription.flight_id, flight_id=subscription.flight_id,
@ -22,7 +35,7 @@ def create_subscription(db: Session, subscription: SubscriptionPydantic):
def remove_subscription(db: Session, user_id: int, flight_id: int): def remove_subscription(db: Session, user_id: int, flight_id: int):
db.query(Subscription).filter( db.query(Subscription).filter(
Subscription.user_id == user_id and Subscription.flight_id == flight_id Subscription.user_id == user_id, Subscription.flight_id == flight_id
).delete() ).delete()
db.commit() db.commit()

View File

@ -1,7 +1,7 @@
import re import re
from asyncreq import request from asyncreq import request
from fastapi import APIRouter, BackgroundTasks, Depends, Response from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException, Response
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from src.api.config import API_FLIGHTS from src.api.config import API_FLIGHTS
@ -10,11 +10,15 @@ from src.api.cruds import subscription as subs_crud
from src.api.db import get_db from src.api.db import get_db
from src.api.schemas.chat import Chat, Update from src.api.schemas.chat import Chat, Update
from src.api.utils import telegram from src.api.utils import telegram
from src.api.utils.messages import get_flight_message, get_invalid_message from src.api.utils.messages import (
get_flight_message,
get_invalid_message,
get_start_message,
)
router = APIRouter() router = APIRouter()
msg_options = re.compile(r"^/(flight \d+|stop|start)$") msg_options = re.compile(r"^/(flight \d+|stop|start \d+)$")
@router.post("") @router.post("")
@ -33,8 +37,11 @@ async def create_chat(
action = text.partition(" ")[0] action = text.partition(" ")[0]
if action == "/start": if action == "/start":
user_id = int(message["text"].partition(" ")[2]) user_id = int(message["text"].partition(" ")[2])
new_chat = Chat(chat_id=str(message["chat"]["id"]), user_id=user_id) chat_id = str(message["chat"]["id"])
new_chat = Chat(chat_id=chat_id, user_id=user_id)
notif_crud.create_chat(db=db, chat=new_chat) notif_crud.create_chat(db=db, chat=new_chat)
msg = get_start_message()
background_tasks.add_task(telegram.send_message, chat_id, msg)
elif action == "/stop": elif action == "/stop":
chat_id = str(message["chat"]["id"]) chat_id = str(message["chat"]["id"])
user_id = notif_crud.get_user_from_chat(db=db, chat_id=chat_id).user_id user_id = notif_crud.get_user_from_chat(db=db, chat_id=chat_id).user_id
@ -50,3 +57,11 @@ async def create_chat(
background_tasks.add_task(telegram.send_message, chat_id, msg) background_tasks.add_task(telegram.send_message, chat_id, msg)
return Response(status_code=204) return Response(status_code=204)
@router.get("")
def get_chat_by_user_id(user_id: int, db: Session = Depends(get_db)):
db_chat = notif_crud.get_chat_id(db=db, user_id=user_id)
if db_chat is None:
raise HTTPException(status_code=404, detail="Chat not found")
return db_chat

View File

@ -1,31 +1,42 @@
from typing import Optional
from fastapi import APIRouter, Depends, HTTPException, Response from fastapi import APIRouter, Depends, HTTPException, Response
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from src.api.cruds import subscription as sub_crud from src.api.cruds import subscription as sub_crud
from src.api.db import get_db from src.api.db import get_db
from src.api.schemas.subscription import Subscription, SubscriptionRemove from src.api.schemas.subscription import Subscription
router = APIRouter() router = APIRouter()
@router.post("") @router.post("")
def create_subscription(subscription: Subscription, db: Session = Depends(get_db)): def create_subscription(subscription: Subscription, db: Session = Depends(get_db)):
return sub_crud.create_subscription(db=db, subscription=subscription) try:
db_subscription = sub_crud.create_subscription(db=db, subscription=subscription)
except ValueError:
raise HTTPException(status_code=409, detail="User already suscribed")
return db_subscription
@router.get("/{user_id}", response_model=list[Subscription]) @router.get("")
def get_subscriptions(user_id: int, db: Session = Depends(get_db)): def get_subscription(
db_subscriptions = sub_crud.get_subscriptions(db=db, user_id=user_id) user_id: int, flight_id: Optional[int] = None, db: Session = Depends(get_db)
):
if flight_id:
db_subscriptions = sub_crud.get_subscription(
db=db, user_id=user_id, flight_id=flight_id
)
else:
db_subscriptions = sub_crud.get_subscriptions(db=db, user_id=user_id)
if db_subscriptions is None: if db_subscriptions is None:
raise HTTPException(status_code=404, detail="Subscription not found") raise HTTPException(status_code=404, detail="Subscription not found")
return db_subscriptions return db_subscriptions
@router.delete("/{user_id}") @router.delete("")
def delete_subscription( def delete_subscription(subscription: Subscription, db: Session = Depends(get_db)):
user_id: int, subscription: SubscriptionRemove, db: Session = Depends(get_db)
):
sub_crud.remove_subscription( sub_crud.remove_subscription(
db=db, user_id=user_id, flight_id=subscription.flight_id db=db, user_id=subscription.user_id, flight_id=subscription.flight_id
) )
return Response(status_code=204) return Response(status_code=204)

View File

@ -30,7 +30,15 @@ def get_flight_message(flight: dict):
def get_invalid_message(): def get_invalid_message():
return ( return (
"Invalid option!\nPlease use:\n" "Invalid option!\nPlease use:\n"
"\n/flights NUMBER (e.g., /flights 1) for flight details" "\n/flight NUMBER (e.g., /flight 1) for flight details"
"\n/start to start receiving messages" "\n/stop to stop receiving updates."
"\n/stop to manage updates." )
def get_start_message():
return (
"Thanks for using fids! You will now start getting updates from your subscriptions!\n"
"Meanwhile you can type:\n"
"\n/flight NUMBER (e.g., /flight 1) for flight details"
"\n/stop to stop receiving updates."
) )

View File

@ -2,7 +2,7 @@ import os
from asyncreq import request from asyncreq import request
TOKEN = os.getenv("TOKEN") TOKEN = os.getenv("TG_TOKEN")
async def send_message(chat_id, message): async def send_message(chat_id, message):