feat: coming soon

This commit is contained in:
Divlo
2021-10-24 05:48:06 +02:00
parent 21123c4477
commit 33bd2bb6bf
176 changed files with 36858 additions and 22133 deletions

View File

@ -1,10 +0,0 @@
import axios from 'axios'
export const API_URL = process.env.NEXT_PUBLIC_API_URL as string
export const api = axios.create({
baseURL: API_URL,
headers: {
'Content-Type': 'application/json'
}
})

View File

@ -1,105 +0,0 @@
import axios, { AxiosInstance } from 'axios'
import { io, Socket } from 'socket.io-client'
import { API_URL } from 'utils/api'
import { cookies } from 'utils/cookies'
import { Tokens } from '.'
import { fetchRefreshToken } from './authenticationFromServerSide'
export class Authentication {
public tokens: Tokens
public accessTokenAge: number
public socket: Socket
public api: AxiosInstance
constructor (tokens: Tokens) {
this.tokens = tokens
this.accessTokenAge = Date.now()
this.socket = io(API_URL, {
auth: { token: `Bearer ${tokens.accessToken}` }
})
this.socket.on('connect_error', (error) => {
if (error.message.startsWith('Unauthorized')) {
fetchRefreshToken(this.tokens.refreshToken)
.then(({ accessToken }) => {
this.setAccessToken(accessToken)
})
.catch(async () => {
this.signout()
return await Promise.reject(error)
})
}
})
this.api = axios.create({
baseURL: API_URL,
headers: {
'Content-Type': 'application/json'
}
})
this.api.interceptors.request.use(
async (config) => {
const isValidAccessToken =
this.accessTokenAge + tokens.expiresIn > Date.now()
if (!isValidAccessToken) {
const { accessToken } = await fetchRefreshToken(
this.tokens.refreshToken
)
this.setAccessToken(accessToken)
}
config.headers.Authorization = `${this.tokens.type} ${this.tokens.accessToken}`
return config
},
async (error) => {
this.signout()
return await Promise.reject(error)
}
)
this.api.interceptors.response.use(
(response) => {
return response
},
async (error) => {
if (error.response.status !== 403 || (error.config._retry as boolean)) {
return await Promise.reject(error)
}
error.config._retry = true
try {
const { accessToken } = await fetchRefreshToken(
this.tokens.refreshToken
)
this.setAccessToken(accessToken)
error.response.config.headers.Authorization = `${this.tokens.type} ${this.tokens.accessToken}`
return await this.api.request(error.response.config)
} catch {
this.signout()
return await Promise.reject(error)
}
}
)
}
public setAccessToken (accessToken: string): void {
this.tokens.accessToken = accessToken
this.accessTokenAge = Date.now()
const token = `${this.tokens.type} ${this.tokens.accessToken}`
if (typeof this.socket.auth !== 'function') {
this.socket.auth.token = token
}
}
public signout (): void {
cookies.remove('refreshToken')
window.location.href = '/authentication/signin'
}
public async signoutServerSide (): Promise<void> {
await this.api.post('/users/signout', {
refreshToken: this.tokens.refreshToken
})
this.signout()
}
public signin (): void {
cookies.set('refreshToken', this.tokens.refreshToken)
}
}

View File

@ -1,60 +0,0 @@
import { createContext, useState, useEffect, useMemo, useContext } from 'react'
import useTranslation from 'next-translate/useTranslation'
import setLanguage from 'next-translate/setLanguage'
import {
Authentication,
PagePropsWithAuthentication,
User,
UserSettings
} from '.'
import { useTheme } from 'contexts/Theme'
export interface AuthenticationValue {
authentication: Authentication
user: User
settings: UserSettings
}
const defaultAnthenticationContext: AuthenticationValue = {} as any
const AuthenticationContext = createContext<AuthenticationValue>(
defaultAnthenticationContext
)
export const AuthenticationProvider: React.FC<PagePropsWithAuthentication> = (
props
) => {
const { lang } = useTranslation()
const { theme, setTheme } = useTheme()
const [user] = useState<User>(props.authentication.user)
const [settings] = useState<UserSettings>(props.authentication.settings)
const authentication = useMemo(() => {
return new Authentication(props.authentication.tokens)
}, [props.authentication.tokens])
useEffect(() => {
setLanguage(settings.language).catch(() => {})
setTheme(settings.theme)
}, [])
useEffect(() => {
authentication.api.put('/users/current/settings', { theme }).catch(() => {})
}, [theme])
useEffect(() => {
authentication.api
.put('/users/current/settings', { language: lang })
.catch(() => {})
}, [lang])
return (
<AuthenticationContext.Provider value={{ authentication, user, settings }}>
{props.children}
</AuthenticationContext.Provider>
)
}
export const useAuthentication = (): AuthenticationValue => {
return useContext(AuthenticationContext)
}

View File

@ -1,75 +0,0 @@
import { AxiosInstance } from 'axios'
import { GetServerSideProps, GetServerSidePropsContext } from 'next'
import { api } from 'utils/api'
import { Cookies } from 'utils/cookies'
import { RefreshTokenResponse, Tokens } from '.'
import { Authentication } from './Authentication'
export const fetchRefreshToken = async (
refreshToken: string
): Promise<Tokens> => {
const { data } = await api.post<RefreshTokenResponse>('/users/refreshToken', {
refreshToken
})
return { ...data, refreshToken }
}
interface AuthenticationFromServerSideOptions {
shouldBeAuthenticated: boolean
/** allows to fetch data server side with the authenticated user, the callback should returns the data that will be transfer to the component as props */
fetchData?: (api: AxiosInstance, context: GetServerSidePropsContext) => Promise<{ [key: string]: any }>
}
export const authenticationFromServerSide = (
options: AuthenticationFromServerSideOptions
): GetServerSideProps => {
const { shouldBeAuthenticated, fetchData } = options
return async (context) => {
const cookies = new Cookies(context.req.headers.cookie)
const refreshToken = cookies.get('refreshToken')
let tokens: Tokens | null = null
if (refreshToken != null) {
try {
tokens = await fetchRefreshToken(refreshToken)
} catch {
cookies.remove('refreshToken')
}
}
if (!shouldBeAuthenticated) {
if (tokens != null) {
return {
redirect: {
destination: '/application',
permanent: false
}
}
} else {
return { props: {} }
}
} else {
if (tokens == null) {
return {
redirect: {
destination: '/authentication/signin',
permanent: false
}
}
} else {
let data = {}
const authentication = new Authentication(tokens)
const { data: currentUser } = await authentication.api.get(
'/users/current'
)
if (fetchData != null) {
data = await fetchData(authentication.api, context)
}
return {
props: { authentication: { tokens, ...currentUser }, ...data }
}
}
}
}
}

View File

@ -1,53 +0,0 @@
import { Theme } from 'contexts/Theme'
export interface RefreshTokenResponse {
accessToken: string
/** how long, in milliseconds, until the accessToken expires */
expiresIn: number
type: 'Bearer'
}
export interface Tokens extends RefreshTokenResponse {
refreshToken: string
}
export interface User {
id: number
name: string
email: string
status: string
biography: string
logo: string
isConfirmed: boolean
createdAt: string
updatedAt: string
}
export const languages = ['en', 'fr'] as const
export type Language = typeof languages[number]
export interface UserSettings {
language: Language
theme: Theme
isPublicEmail: boolean
}
export const providers = ['google', 'github', 'discord'] as const
export const strategies = [...providers, 'local'] as const
export type AuthenticationStrategy = typeof strategies[number]
export interface PagePropsWithAuthentication {
authentication: {
tokens: Tokens
user: User
settings: UserSettings
currentStrategy: AuthenticationStrategy
strategies: AuthenticationStrategy[]
}
}
export * from './Authentication'
export * from './authenticationFromServerSide'
export * from './AuthenticationContext'

View File

@ -1,29 +0,0 @@
import UniversalCookie from 'universal-cookie'
export class Cookies {
/** how long in seconds, until the cookie expires (10 years) */
static readonly COOKIE_MAX_AGE = 10 * 365.25 * 24 * 60 * 60
public universalCookie: UniversalCookie
constructor (cookies?: string | object | null) {
this.universalCookie = new UniversalCookie(cookies)
}
set (name: string, value: string): void {
this.universalCookie.set(name, value, {
path: '/',
maxAge: Cookies.COOKIE_MAX_AGE
})
}
remove (name: string): void {
this.universalCookie.remove(name, { path: '/' })
}
get (name: string): string {
return this.universalCookie.get(name)
}
}
export const cookies = new Cookies()

View File

@ -1,54 +0,0 @@
import { SetData } from 'hooks/usePagination'
export interface Item {
id: number
[key: string]: any
}
export interface SocketData {
action: 'create' | 'update' | 'delete'
item: Item
}
export interface HandleSocketDataOptions<T> {
setData: SetData<T>
}
export type SocketListener = (data: SocketData) => void
export const handleSocketData = <T extends Item>(
options: HandleSocketDataOptions<T>
): SocketListener => {
const { setData } = options
const socketListener: SocketListener = (data) => {
setData((oldItems) => {
const newItems = { ...oldItems }
switch (data.action) {
case 'create': {
newItems.rows.push(data.item as T)
break
}
case 'delete': {
const itemIndex = newItems.rows.findIndex(
(item) => item.id === data.item.id
)
if (itemIndex !== -1) {
newItems.rows.splice(itemIndex, 1)
}
break
}
case 'update': {
const itemIndex = newItems.rows.findIndex(
(item) => item.id === data.item.id
)
if (itemIndex !== -1) {
newItems.rows[itemIndex] = data.item as T
}
break
}
}
return newItems
})
}
return socketListener
}