mirror of
https://github.com/myfatemi04/wheelshare-frontend.git
synced 2025-04-21 11:20:17 -04:00
standardize api
This commit is contained in:
parent
179047d672
commit
7e27f35b0a
|
@ -1,43 +0,0 @@
|
|||
import axios from 'axios';
|
||||
import { APIError } from './error';
|
||||
import { makeAPIGetCall } from './utils';
|
||||
|
||||
// eslint-disable-next-line
|
||||
const dev = process.env.NODE_ENV === 'development';
|
||||
export const API_ENDPOINT = 'http://localhost:5000/api';
|
||||
export const ION_AUTHORIZATION_ENDPOINT = dev
|
||||
? 'https://ion.tjhsst.edu/oauth/authorize?response_type=code&client_id=rNa6n9YSg8ftINdyVPpUsaMuxNbHLo9dh1OsOktR&scope=read&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fauth%2Fion%2Fcallback'
|
||||
: 'https://ion.tjhsst.edu/oauth/authorize?response_type=code&client_id=rNa6n9YSg8ftINdyVPpUsaMuxNbHLo9dh1OsOktR&scope=read&redirect_uri=https%3A%2F%2Fwheelshare.space%2Fauth%2Fion%2Fcallback';
|
||||
|
||||
axios.defaults.baseURL = API_ENDPOINT;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param code The code sent by the OAuth provider
|
||||
* @param provider The provider that send the OAuth code
|
||||
*/
|
||||
export async function createSession(
|
||||
code: string,
|
||||
provider: string
|
||||
): Promise<string> {
|
||||
const response = await axios.post('/auth/create_session', {
|
||||
code,
|
||||
provider,
|
||||
});
|
||||
|
||||
if (response.status === 200) {
|
||||
return response.data.session_id;
|
||||
} else {
|
||||
throw new APIError('/auth/create_session', response.data.error);
|
||||
}
|
||||
}
|
||||
|
||||
export async function getMe(): Promise<Carpool.User> {
|
||||
let result = await makeAPIGetCall('/users/@me');
|
||||
return result.data.data;
|
||||
}
|
||||
|
||||
export async function getPublicUser(id: string): Promise<Carpool.PublicUser> {
|
||||
let result = await makeAPIGetCall(`/users/${id}`);
|
||||
return result.data.data;
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
export class APIError extends Error {
|
||||
constructor(route: string, message: string) {
|
||||
super('API Error @' + route + ': ' + message);
|
||||
}
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
export type PlaceDetails = {
|
||||
formattedAddress: string;
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
};
|
||||
|
||||
export async function getPlaceDetails(
|
||||
placeId: string
|
||||
): Promise<PlaceDetails | null> {
|
||||
if (placeId == null) {
|
||||
console.warn('placeId was null');
|
||||
return null;
|
||||
}
|
||||
|
||||
const result = await fetch('http://localhost:5000/api/place/' + placeId);
|
||||
const json = await result.json();
|
||||
|
||||
return json;
|
||||
}
|
100
src/api/utils.ts
100
src/api/utils.ts
|
@ -1,100 +0,0 @@
|
|||
import axios, { AxiosResponse } from 'axios';
|
||||
import getSessionId from '../lib/getSessionId';
|
||||
import { APIError } from './error';
|
||||
|
||||
export function createUrlWithGetParameters(
|
||||
url: string,
|
||||
params?: Record<string, string | undefined>
|
||||
) {
|
||||
if (params) {
|
||||
// Stringify the parameters
|
||||
let stringifiedParameters = '';
|
||||
for (let [name, value] of Object.entries(params)) {
|
||||
if (value) {
|
||||
stringifiedParameters +=
|
||||
encodeURIComponent(name) + '=' + encodeURIComponent(value);
|
||||
}
|
||||
}
|
||||
if (stringifiedParameters) {
|
||||
url += '?' + stringifiedParameters;
|
||||
}
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
export function graphql(query: string, variables?: any) {
|
||||
return new Promise<AxiosResponse>((resolve, reject) => {
|
||||
axios
|
||||
.post(
|
||||
'/graphql',
|
||||
{ query, variables },
|
||||
{ headers: { Authorization: 'Bearer ' + getSessionId() } }
|
||||
)
|
||||
.then((successfulResponse) => {
|
||||
resolve(successfulResponse.data.data);
|
||||
})
|
||||
.catch((error) => {
|
||||
if (error.response.status === 401) {
|
||||
localStorage.removeItem('session_token');
|
||||
window.location.pathname = '/';
|
||||
} else {
|
||||
reject(new APIError('/api/users/@me', error.response.data.error));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function makeAPIPostCall(url: string, data?: any) {
|
||||
return new Promise<AxiosResponse>((resolve, reject) => {
|
||||
axios
|
||||
.post(url, data, {
|
||||
headers: { Authorization: 'Bearer ' + getSessionId() },
|
||||
})
|
||||
.then((successfulResponse) => resolve(successfulResponse))
|
||||
.catch((error) => {
|
||||
if (error.response?.status === 401) {
|
||||
localStorage.removeItem('session_token');
|
||||
window.location.pathname = '/';
|
||||
} else {
|
||||
reject(new APIError(url, error.response?.data?.error));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function makeAPIGetCall(
|
||||
url: string,
|
||||
params?: Record<string, string | undefined>
|
||||
) {
|
||||
url = createUrlWithGetParameters(url, params);
|
||||
return new Promise<AxiosResponse>((resolve, reject) => {
|
||||
axios
|
||||
.get(url, {
|
||||
headers: { Authorization: 'Bearer ' + getSessionId() },
|
||||
})
|
||||
.then((successfulResponse) => resolve(successfulResponse))
|
||||
.catch((error) => {
|
||||
if (error.response?.status === 401) {
|
||||
localStorage.removeItem('session_token');
|
||||
window.location.pathname = '/';
|
||||
} else {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function makeAPIDeleteCall(url: string) {
|
||||
return new Promise<AxiosResponse>((resolve, reject) => {
|
||||
axios
|
||||
.delete(url, { headers: { Authorization: 'Bearer ' + getSessionId() } })
|
||||
.then((successfulResponse) => resolve(successfulResponse))
|
||||
.catch((error) => {
|
||||
if (error.response?.status === 401) {
|
||||
localStorage.removeItem('session_token');
|
||||
} else {
|
||||
reject(new APIError(url, error.response.data.error));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { getMe } from '../../api/api';
|
||||
import { getMe } from '../api';
|
||||
import AuthenticationContext, { AuthState } from './AuthenticationContext';
|
||||
|
||||
export default function AuthenticationWrapper({
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { useContext, useEffect, useState } from 'react';
|
||||
import { Redirect, useLocation, useParams } from 'react-router-dom';
|
||||
import AuthenticationContext from './AuthenticationContext';
|
||||
import { createSession } from './createSession';
|
||||
|
||||
export default function Authenticator() {
|
||||
const { provider } = useParams<{ provider: string }>();
|
||||
|
@ -11,14 +12,7 @@ export default function Authenticator() {
|
|||
useState<'pending' | 'errored' | 'authenticated'>('pending');
|
||||
|
||||
useEffect(() => {
|
||||
fetch('http://localhost:5000/create_session', {
|
||||
method: 'post',
|
||||
body: JSON.stringify({ code }),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
})
|
||||
.then((response) => response.json())
|
||||
createSession(code!)
|
||||
.then((data) => {
|
||||
if (data.status === 'success') {
|
||||
localStorage.setItem('session_token', data.token);
|
||||
|
|
16
src/components/Authentication/createSession.ts
Normal file
16
src/components/Authentication/createSession.ts
Normal file
|
@ -0,0 +1,16 @@
|
|||
export async function createSession(code: string) {
|
||||
const res = await fetch('http://localhost:5000/create_session', {
|
||||
method: 'post',
|
||||
body: JSON.stringify({ code }),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
const json = await res.json();
|
||||
|
||||
return {
|
||||
status: json.status,
|
||||
token: json.token,
|
||||
};
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
import { useEffect, useRef, useState } from 'react';
|
||||
import { post } from './api';
|
||||
import { post, removeEventSignup } from './api';
|
||||
import { green, lightgrey } from './colors';
|
||||
import latlongdist, { R_miles } from './latlongdist';
|
||||
import UIButton from './UIButton';
|
||||
|
@ -306,9 +306,7 @@ export default function Event({ event }: { event: IEvent }) {
|
|||
(prev.interested === true && interested === false) ||
|
||||
(interested === true && prev.placeId !== null && placeId === null)
|
||||
) {
|
||||
fetch(`http://localhost:5000/api/events/${event.id}/signup`, {
|
||||
method: 'delete',
|
||||
}).finally(() => setUpdating(false));
|
||||
removeEventSignup(event.id).finally(() => setUpdating(false));
|
||||
prev.interested = false;
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { useEffect, useState } from 'react';
|
||||
import { getEvents } from './api';
|
||||
import { IEvent } from './Event';
|
||||
import EventStream from './EventStream';
|
||||
|
||||
|
@ -6,9 +7,7 @@ export default function Events() {
|
|||
const [events, setEvents] = useState<IEvent[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
fetch('http://localhost:5000/api/events')
|
||||
.then((res) => res.json())
|
||||
.then(setEvents);
|
||||
getEvents().then(setEvents);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { useEffect, useState } from 'react';
|
||||
import { useParams } from 'react-router';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { getGroup, getGroupEvents } from './api';
|
||||
import { IEvent } from './Event';
|
||||
import EventCreatorLink from './EventCreatorLink';
|
||||
import EventStream from './EventStream';
|
||||
|
@ -21,14 +22,11 @@ export default function Group() {
|
|||
|
||||
useEffect(() => {
|
||||
setLoading(true);
|
||||
fetch('http://localhost:5000/api/groups/' + id)
|
||||
.then((response) => response.json())
|
||||
getGroup(+id)
|
||||
.then(setGroup)
|
||||
.finally(() => setLoading(false));
|
||||
|
||||
fetch('http://localhost:5000/api/groups/' + id + '/events')
|
||||
.then((response) => response.json())
|
||||
.then(setEvents);
|
||||
getGroupEvents(+id).then(setEvents);
|
||||
}, [id]);
|
||||
|
||||
if (!group && !loading) {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { useCallback, useState } from 'react';
|
||||
import { deleteGroup } from './api';
|
||||
import { IGroup } from './Group';
|
||||
import UILink from './UILink';
|
||||
import UIPressable from './UIPressable';
|
||||
|
@ -9,9 +10,8 @@ function GroupSettings({ group }: { group: IGroup }) {
|
|||
const [deletionSuccessful, setDeletionSuccessful] =
|
||||
useState<boolean | null>(null);
|
||||
|
||||
const deleteGroup = useCallback(() => {
|
||||
fetch('http://localhost:5000/api/groups/' + group.id, { method: 'delete' })
|
||||
.then((res) => res.json())
|
||||
const onClickedDelete = useCallback(() => {
|
||||
deleteGroup(group.id)
|
||||
.then(({ status }) => {
|
||||
setDeletionSuccessful(status === 'success');
|
||||
})
|
||||
|
@ -24,7 +24,7 @@ function GroupSettings({ group }: { group: IGroup }) {
|
|||
<UISecondaryBox>
|
||||
<h1>Settings</h1>
|
||||
{deletionSuccessful !== true && (
|
||||
<UIPressable onClick={deleteGroup}>Delete Group</UIPressable>
|
||||
<UIPressable onClick={onClickedDelete}>Delete Group</UIPressable>
|
||||
)}
|
||||
{deletionSuccessful !== null &&
|
||||
(deletionSuccessful ? (
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { useState, useEffect } from 'react';
|
||||
import { getGroups } from './api';
|
||||
import { IGroup } from './Group';
|
||||
import GroupCreatorLink from './GroupCreatorLink';
|
||||
import GroupJoinerLink from './GroupJoinerLink';
|
||||
|
@ -8,9 +9,7 @@ export default function Groups() {
|
|||
const [groups, setGroups] = useState<IGroup[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
fetch('http://localhost:5000/api/groups')
|
||||
.then((res) => res.json())
|
||||
.then(setGroups);
|
||||
getGroups().then(setGroups);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
|
|
|
@ -1,18 +1,70 @@
|
|||
export function post(path: string, data: any) {
|
||||
return fetch('http://localhost:5000/api' + path, {
|
||||
export async function post(path: string, data: any) {
|
||||
const res = await fetch('http://localhost:5000/api' + path, {
|
||||
method: 'post',
|
||||
body: JSON.stringify(data),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
return await res.json();
|
||||
}
|
||||
|
||||
export function delete$(path: string, data: any) {
|
||||
return fetch('http://localhost:5000/api' + path, {
|
||||
export async function delete$(path: string) {
|
||||
const res = await fetch('http://localhost:5000/api' + path, {
|
||||
method: 'delete',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
return await res.json();
|
||||
}
|
||||
|
||||
export async function get(path: string) {
|
||||
const res = await fetch('http://localhost:5000/api' + path);
|
||||
return await res.json();
|
||||
}
|
||||
|
||||
export type PlaceDetails = {
|
||||
formattedAddress: string;
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
};
|
||||
|
||||
export async function getPlaceDetails(
|
||||
placeId: string
|
||||
): Promise<PlaceDetails | null> {
|
||||
if (placeId == null) {
|
||||
console.warn('placeId was null');
|
||||
return null;
|
||||
}
|
||||
|
||||
return await get('/place/' + placeId);
|
||||
}
|
||||
|
||||
export async function removeEventSignup(eventId: number) {
|
||||
return await delete$(`/events/${eventId}/signup`);
|
||||
}
|
||||
|
||||
export async function getEvents() {
|
||||
return await get('/events');
|
||||
}
|
||||
|
||||
export async function getGroup(id: number) {
|
||||
return await get('/groups/' + id);
|
||||
}
|
||||
|
||||
export async function getGroupEvents(id: number) {
|
||||
return await get('/groups/' + id + '/events');
|
||||
}
|
||||
|
||||
export async function getGroups() {
|
||||
return await get('/groups');
|
||||
}
|
||||
|
||||
export async function deleteGroup(id: number) {
|
||||
return await delete$('/groups/' + id);
|
||||
}
|
||||
|
||||
export async function getMe() {
|
||||
return await get('/users/@me');
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { getPlaceDetails, PlaceDetails } from '../api/google';
|
||||
import { getPlaceDetails, PlaceDetails } from './api';
|
||||
import useThrottle from './useThrottle';
|
||||
|
||||
export default function usePlace(placeId: string | null) {
|
||||
|
|
88
src/store.ts
88
src/store.ts
|
@ -1,88 +0,0 @@
|
|||
import { useEffect, useState } from 'react';
|
||||
|
||||
export type UpdateListener<T> = (newValue: StoreValue<T> | null) => void;
|
||||
|
||||
export type ValueFetcher<T> = (key: string) => Promise<T | null>;
|
||||
|
||||
export type StoreValue<T> = { value: T } | { error: any };
|
||||
|
||||
/**
|
||||
* This is a general-purpose, subscribable key-value store for content like posts, groups, and spaces.
|
||||
*/
|
||||
export class Store<T> {
|
||||
/**
|
||||
* Stores the internal data. If the value is `null`, then we attempted to fetch the data, but it did not exist.
|
||||
*/
|
||||
private data = new Map<string, StoreValue<T> | null>();
|
||||
private listeners = new Map<string, Set<UpdateListener<T>>>();
|
||||
|
||||
constructor(private fetcher: ValueFetcher<T>) {}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param key The key to get the data for
|
||||
* @param forceRefresh If the data already exists, fetch it again anyway
|
||||
*/
|
||||
get(key: string, forceRefresh = false): StoreValue<T> | null {
|
||||
if (!this.data.has(key) || forceRefresh) {
|
||||
this.fetcher(key)
|
||||
.then((value) => {
|
||||
this.set(key, value ? { value } : null);
|
||||
})
|
||||
.catch((error) => {
|
||||
this.set(key, { error });
|
||||
});
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.data.get(key) ?? null;
|
||||
}
|
||||
|
||||
set(key: string, value: StoreValue<T> | null) {
|
||||
if (this.listeners.has(key)) {
|
||||
this.listeners.get(key)?.forEach((callback) => {
|
||||
callback(value);
|
||||
});
|
||||
}
|
||||
|
||||
return this.data.set(key, value);
|
||||
}
|
||||
|
||||
subscribe(key: string, listener: UpdateListener<T>) {
|
||||
if (!this.listeners.has(key)) {
|
||||
this.listeners.set(key, new Set());
|
||||
}
|
||||
|
||||
this.listeners.get(key)?.add(listener);
|
||||
}
|
||||
|
||||
unsubscribe(key: string, listener: UpdateListener<T>) {
|
||||
if (this.listeners.has(key)) {
|
||||
if (this.listeners.get(key)?.has(listener)) {
|
||||
this.listeners.get(key)?.delete(listener);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
console.warn(
|
||||
'Unsubscribed from',
|
||||
key,
|
||||
'but listener does not exist: ',
|
||||
listener
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function useStoredValue<T>(store: Store<T>, key: string) {
|
||||
const [value, setValue] = useState<StoreValue<T> | null>(store.get(key));
|
||||
|
||||
useEffect(() => {
|
||||
const callback = (value: StoreValue<T> | null) => setValue(value);
|
||||
store.subscribe(key, callback);
|
||||
|
||||
return () => store.unsubscribe(key, callback);
|
||||
}, [key, store]);
|
||||
|
||||
return value;
|
||||
}
|
Loading…
Reference in New Issue
Block a user