forget-fix-graphql-client-fix
Some checks failed
deploy / test (push) Failing after 55s
deploy / deploy (push) Has been skipped

This commit is contained in:
Untone 2023-12-03 13:22:42 +03:00
parent 3353004f48
commit fce7ffb972
21 changed files with 167 additions and 179 deletions

View File

@ -259,6 +259,7 @@
"Pin": "Закрепить", "Pin": "Закрепить",
"Platform Guide": "Гид по дискурсу", "Platform Guide": "Гид по дискурсу",
"Please check your email address": "Пожалуйста, проверьте введенный адрес почты", "Please check your email address": "Пожалуйста, проверьте введенный адрес почты",
"Please check your inbox! We have sent a password reset link.": "Пожалуйста, проверьте ваш адрес почты, мы отправили ссылку для сброса пароля",
"Please confirm your email to finish": "Подтвердите почту и действие совершится", "Please confirm your email to finish": "Подтвердите почту и действие совершится",
"Please enter a name to sign your comments and publication": "Пожалуйста, введите имя, которое будет отображаться на сайте", "Please enter a name to sign your comments and publication": "Пожалуйста, введите имя, которое будет отображаться на сайте",
"Please enter email": "Пожалуйста, введите почту", "Please enter email": "Пожалуйста, введите почту",

View File

@ -33,6 +33,7 @@ export const AuthorCard = (props: Props) => {
const { t, lang } = useLocalize() const { t, lang } = useLocalize()
const { const {
session, session,
author,
subscriptions, subscriptions,
isSessionLoaded, isSessionLoaded,
actions: { loadSubscriptions, requireAuthentication }, actions: { loadSubscriptions, requireAuthentication },
@ -57,7 +58,7 @@ export const AuthorCard = (props: Props) => {
setIsSubscribing(false) setIsSubscribing(false)
} }
const isProfileOwner = createMemo(() => session()?.user?.slug === props.author.slug) const isProfileOwner = createMemo(() => author()?.slug === props.author.slug)
const name = createMemo(() => { const name = createMemo(() => {
if (lang() !== 'ru') { if (lang() !== 'ru') {
@ -123,7 +124,7 @@ export const AuthorCard = (props: Props) => {
<Userpic <Userpic
size={'XL'} size={'XL'}
name={props.author.name} name={props.author.name}
userpic={props.author.userpic} userpic={props.author.pic}
slug={props.author.slug} slug={props.author.slug}
class={styles.circlewrap} class={styles.circlewrap}
/> />
@ -143,12 +144,7 @@ export const AuthorCard = (props: Props) => {
<a href="?modal=followers" class={styles.subscribers}> <a href="?modal=followers" class={styles.subscribers}>
<For each={props.followers.slice(0, 3)}> <For each={props.followers.slice(0, 3)}>
{(f) => ( {(f) => (
<Userpic <Userpic size={'XS'} name={f.name} userpic={f.pic} class={styles.subscribersItem} />
size={'XS'}
name={f.name}
userpic={f.userpic}
class={styles.subscribersItem}
/>
)} )}
</For> </For>
<div class={styles.subscribersCounter}> <div class={styles.subscribersCounter}>
@ -166,7 +162,7 @@ export const AuthorCard = (props: Props) => {
<Userpic <Userpic
size={'XS'} size={'XS'}
name={f.name} name={f.name}
userpic={f.userpic} userpic={f.pic}
class={styles.subscribersItem} class={styles.subscribersItem}
/> />
) )
@ -242,7 +238,7 @@ export const AuthorCard = (props: Props) => {
<SharePopup <SharePopup
title={props.author.name} title={props.author.name}
description={props.author.bio} description={props.author.bio}
imageUrl={props.author.userpic} imageUrl={props.author.pic}
shareUrl={getShareUrl({ pathname: `/author/${props.author.slug}` })} shareUrl={getShareUrl({ pathname: `/author/${props.author.slug}` })}
trigger={<Button variant="secondary" value={t('Share')} />} trigger={<Button variant="secondary" value={t('Share')} />}
/> />

View File

@ -22,7 +22,7 @@ export const Userpic = (props: Props) => {
const letters = () => { const letters = () => {
if (!props.name) return if (!props.name) return
const names = props.name ? props.name.split(' ') : [] const names = props.name ? props.name.split(' ') : []
return names[0][0] + '.' + (names.length > 1 ? names[1][0] : '') + '.' return names[0][0 ?? names[0][0]] + '.' + (names.length > 1 ? names[1][0] + '.' : '')
} }
const avatarSize = createMemo(() => { const avatarSize = createMemo(() => {

View File

@ -28,7 +28,7 @@ export const EmailConfirm = () => {
onMount(async () => { onMount(async () => {
const token = searchParams().token const token = searchParams().token
try { try {
await confirmEmail(token) await confirmEmail({ token })
} catch (error) { } catch (error) {
if (error instanceof ApiError) { if (error instanceof ApiError) {
if (error.code === 'token_expired') { if (error.code === 'token_expired') {

View File

@ -34,6 +34,8 @@ export const ForgotPasswordForm = () => {
const authFormRef: { current: HTMLFormElement } = { current: null } const authFormRef: { current: HTMLFormElement } = { current: null }
const [message, setMessage] = createSignal<string>('')
const handleSubmit = async (event: Event) => { const handleSubmit = async (event: Event) => {
event.preventDefault() event.preventDefault()
@ -61,11 +63,14 @@ export const ForgotPasswordForm = () => {
} }
setIsSubmitting(true) setIsSubmitting(true)
try { try {
const response = await authorizer().forgotPassword({ email: email() }) const response = await authorizer().forgotPassword({
email: email(),
redirect_uri: window.location.href + '&success=1', // FIXME: redirect to success page accepting confirmation code
})
if (response) { if (response) {
console.debug(response) console.debug(response)
if (response.message) setMessage(response.message)
} }
} catch (error) { } catch (error) {
if (error instanceof ApiError && error.code === 'user_not_found') { if (error instanceof ApiError && error.code === 'user_not_found') {
@ -86,7 +91,9 @@ export const ForgotPasswordForm = () => {
> >
<div> <div>
<h4>{t('Forgot password?')}</h4> <h4>{t('Forgot password?')}</h4>
<div class={styles.authSubtitle}>{t('Everything is ok, please give us your email address')}</div> <div class={styles.authSubtitle}>
{t(message()) || t('Everything is ok, please give us your email address')}
</div>
<div <div
class={clsx('pretty-form__item', { class={clsx('pretty-form__item', {
@ -116,7 +123,6 @@ export const ForgotPasswordForm = () => {
<Show when={isUserNotFount()}> <Show when={isUserNotFount()}>
<div class={styles.authSubtitle}> <div class={styles.authSubtitle}>
{/*TODO: text*/}
{t("We can't find you, check email or")}{' '} {t("We can't find you, check email or")}{' '}
<a <a
href="#" href="#"

View File

@ -132,6 +132,7 @@ export const RegisterForm = () => {
email: cleanEmail, email: cleanEmail,
password: password(), password: password(),
confirm_password: password(), confirm_password: password(),
redirect_uri: window.location.origin,
}) })
setIsSuccess(true) setIsSuccess(true)

View File

@ -1,7 +1,15 @@
import type { ParentComponent } from 'solid-js' import type { ParentComponent } from 'solid-js'
import { Authorizer, User, AuthToken, ConfigType } from '@authorizerdev/authorizer-js' import { Authorizer, User, AuthToken, ConfigType } from '@authorizerdev/authorizer-js'
import { createContext, createEffect, createMemo, onCleanup, onMount, useContext } from 'solid-js' import {
createContext,
createEffect,
createMemo,
createSignal,
onCleanup,
onMount,
useContext,
} from 'solid-js'
import { createStore } from 'solid-js/store' import { createStore } from 'solid-js/store'
export type AuthorizerState = { export type AuthorizerState = {
@ -21,7 +29,7 @@ type AuthorizerContextActions = {
} }
const config: ConfigType = { const config: ConfigType = {
authorizerURL: 'https://auth.discours.io', authorizerURL: 'https://auth.discours.io',
redirectURL: 'https://auth.discours.io', redirectURL: 'https://discoursio-webapp.vercel.app',
clientID: '9c113377-5eea-4c89-98e1-69302462fc08', // FIXME: use env? clientID: '9c113377-5eea-4c89-98e1-69302462fc08', // FIXME: use env?
} }
@ -56,15 +64,16 @@ export const AuthorizerProvider: ParentComponent<AuthorizerProviderProps> = (pro
loading: true, loading: true,
config: { config: {
authorizerURL: props.authorizerURL, authorizerURL: props.authorizerURL,
redirectURL: props.redirectURL,
clientID: props.clientID, clientID: props.clientID,
} as ConfigType, } as ConfigType,
}) })
const [redirect, setRedirect] = createSignal<string>()
const authorizer = createMemo( const authorizer = createMemo(
() => () =>
new Authorizer({ new Authorizer({
authorizerURL: props.authorizerURL, authorizerURL: props.authorizerURL,
redirectURL: props.redirectURL, redirectURL: redirect(),
clientID: props.clientID, clientID: props.clientID,
}), }),
) )
@ -148,6 +157,7 @@ export const AuthorizerProvider: ParentComponent<AuthorizerProviderProps> = (pro
} }
onMount(() => { onMount(() => {
setRedirect(window.location.origin)
!state.token && getToken() !state.token && getToken()
}) })

View File

@ -3,9 +3,8 @@ import type { Accessor, JSX } from 'solid-js'
import { fetchEventSource } from '@microsoft/fetch-event-source' import { fetchEventSource } from '@microsoft/fetch-event-source'
import { createContext, useContext, createSignal, createEffect } from 'solid-js' import { createContext, useContext, createSignal, createEffect } from 'solid-js'
import { getToken } from '../graphql/privateGraphQLClient'
import { useSession } from './session' import { useSession } from './session'
import { useAuthorizer } from './authorizer'
export interface SSEMessage { export interface SSEMessage {
id: string id: string
@ -30,20 +29,22 @@ export const ConnectProvider = (props: { children: JSX.Element }) => {
// const [messages, setMessages] = createSignal<Array<SSEMessage>>([]); // const [messages, setMessages] = createSignal<Array<SSEMessage>>([]);
const [connected, setConnected] = createSignal(false) const [connected, setConnected] = createSignal(false)
const { isAuthenticated } = useSession() const {
isAuthenticated,
actions: { getToken },
} = useSession()
const addHandler = (handler: MessageHandler) => { const addHandler = (handler: MessageHandler) => {
setHandlers((hhh) => [...hhh, handler]) setHandlers((hhh) => [...hhh, handler])
} }
const listen = () => { const listen = () => {
const token = getToken() if (isAuthenticated()) {
if (token) {
fetchEventSource('https://connect.discours.io', { fetchEventSource('https://connect.discours.io', {
method: 'GET', method: 'GET',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
Authorization: token, Authorization: getToken(),
}, },
onmessage(event) { onmessage(event) {
const m: SSEMessage = JSON.parse(event.data) const m: SSEMessage = JSON.parse(event.data)
@ -54,9 +55,11 @@ export const ConnectProvider = (props: { children: JSX.Element }) => {
}, },
onclose() { onclose() {
console.log('[context.connect] sse connection closed by server') console.log('[context.connect] sse connection closed by server')
setConnected(false)
}, },
onerror(err) { onerror(err) {
console.error('[context.connect] sse connection closed by error', err) console.error('[context.connect] sse connection closed by error', err)
setConnected(false)
throw new Error(err) // NOTE: simple hack to close the connection throw new Error(err) // NOTE: simple hack to close the connection
}, },
}) })

View File

@ -2,10 +2,10 @@ import type { JSX } from 'solid-js'
import { openPage } from '@nanostores/router' import { openPage } from '@nanostores/router'
import { Editor } from '@tiptap/core' import { Editor } from '@tiptap/core'
import { Accessor, createContext, createSignal, useContext } from 'solid-js' import { Accessor, createContext, createMemo, createSignal, useContext } from 'solid-js'
import { createStore, SetStoreFunction } from 'solid-js/store' import { createStore, SetStoreFunction } from 'solid-js/store'
import { apiClient } from '../graphql/client/core' import { apiClient as coreClient } from '../graphql/client/core'
import { ShoutVisibility, Topic, TopicInput } from '../graphql/schema/core.gen' import { ShoutVisibility, Topic, TopicInput } from '../graphql/schema/core.gen'
import { router, useRouter } from '../stores/router' import { router, useRouter } from '../stores/router'
import { slugify } from '../utils/slugify' import { slugify } from '../utils/slugify'
@ -84,7 +84,10 @@ export const EditorProvider = (props: { children: JSX.Element }) => {
const { t } = useLocalize() const { t } = useLocalize()
const { page } = useRouter() const { page } = useRouter()
const apiClient = createMemo(() => {
if (!coreClient.private) coreClient.connect()
return coreClient
})
const { const {
actions: { showSnackbar }, actions: { showSnackbar },
} = useSnackbar() } = useSnackbar()
@ -126,7 +129,7 @@ export const EditorProvider = (props: { children: JSX.Element }) => {
} }
const updateShout = async (formToUpdate: ShoutForm, { publish }: { publish: boolean }) => { const updateShout = async (formToUpdate: ShoutForm, { publish }: { publish: boolean }) => {
return apiClient.updateArticle({ return apiClient().updateArticle({
shoutId: formToUpdate.shoutId, shoutId: formToUpdate.shoutId,
shoutInput: { shoutInput: {
body: formToUpdate.body, body: formToUpdate.body,
@ -211,7 +214,7 @@ export const EditorProvider = (props: { children: JSX.Element }) => {
const publishShoutById = async (shoutId: number) => { const publishShoutById = async (shoutId: number) => {
try { try {
await apiClient.updateArticle({ await apiClient().updateArticle({
shoutId, shoutId,
publish: true, publish: true,
}) })
@ -225,7 +228,7 @@ export const EditorProvider = (props: { children: JSX.Element }) => {
const deleteShout = async (shoutId: number) => { const deleteShout = async (shoutId: number) => {
try { try {
await apiClient.deleteShout({ await apiClient().deleteShout({
shoutId, shoutId,
}) })
return true return true

View File

@ -1,7 +1,7 @@
import type { Chat, Message, MutationCreateMessageArgs } from '../graphql/schema/chat.gen' import type { Chat, Message, MutationCreate_MessageArgs } from '../graphql/schema/chat.gen'
import type { Accessor, JSX } from 'solid-js' import type { Accessor, JSX } from 'solid-js'
import { createContext, createSignal, useContext } from 'solid-js' import { createContext, createMemo, createSignal, useContext } from 'solid-js'
import { inboxClient } from '../graphql/client/chat' import { inboxClient } from '../graphql/client/chat'
import { loadMessages } from '../stores/inbox' import { loadMessages } from '../stores/inbox'
@ -15,7 +15,7 @@ type InboxContextType = {
createChat: (members: number[], title: string) => Promise<{ chat: Chat }> createChat: (members: number[], title: string) => Promise<{ chat: Chat }>
loadChats: () => Promise<void> loadChats: () => Promise<void>
getMessages?: (chatId: string) => Promise<void> getMessages?: (chatId: string) => Promise<void>
sendMessage?: (args: MutationCreateMessageArgs) => void sendMessage?: (args: MutationCreate_MessageArgs) => void
} }
} }
@ -39,13 +39,16 @@ export const InboxProvider = (props: { children: JSX.Element }) => {
setChats((prev) => [...prev, relivedChat]) setChats((prev) => [...prev, relivedChat])
} }
} }
const apiClient = createMemo(() => {
if (!inboxClient.private) inboxClient.connect()
return inboxClient
})
const { addHandler } = useConnect() const { addHandler } = useConnect()
addHandler(handleMessage) addHandler(handleMessage)
const loadChats = async () => { const loadChats = async () => {
try { try {
const newChats = await inboxClient.loadChats({ limit: 50, offset: 0 }) const newChats = await apiClient().loadChats({ limit: 50, offset: 0 })
setChats(newChats) setChats(newChats)
} catch (error) { } catch (error) {
console.log('[loadChats]', error) console.log('[loadChats]', error)
@ -62,9 +65,9 @@ export const InboxProvider = (props: { children: JSX.Element }) => {
} }
} }
const sendMessage = async (args: MutationCreateMessageArgs) => { const sendMessage = async (args: MutationCreate_MessageArgs) => {
try { try {
const message = await inboxClient.createMessage(args) const message = await apiClient().createMessage(args)
setMessages((prev) => [...prev, message]) setMessages((prev) => [...prev, message])
const currentChat = chats().find((chat) => chat.id === args.chat_id) const currentChat = chats().find((chat) => chat.id === args.chat_id)
setChats((prev) => [ setChats((prev) => [

View File

@ -6,7 +6,7 @@ import { Portal } from 'solid-js/web'
import { ShowIfAuthenticated } from '../components/_shared/ShowIfAuthenticated' import { ShowIfAuthenticated } from '../components/_shared/ShowIfAuthenticated'
import { NotificationsPanel } from '../components/NotificationsPanel' import { NotificationsPanel } from '../components/NotificationsPanel'
import { notifierClient as apiClient } from '../graphql/client/notifier' import { notifierClient } from '../graphql/client/notifier'
import { Notification } from '../graphql/schema/notifier.gen' import { Notification } from '../graphql/schema/notifier.gen'
import { SSEMessage, useConnect } from './connect' import { SSEMessage, useConnect } from './connect'
@ -38,9 +38,13 @@ export const NotificationsProvider = (props: { children: JSX.Element }) => {
const [unreadNotificationsCount, setUnreadNotificationsCount] = createSignal(0) const [unreadNotificationsCount, setUnreadNotificationsCount] = createSignal(0)
const [totalNotificationsCount, setTotalNotificationsCount] = createSignal(0) const [totalNotificationsCount, setTotalNotificationsCount] = createSignal(0)
const [notificationEntities, setNotificationEntities] = createStore<Record<number, Notification>>({}) const [notificationEntities, setNotificationEntities] = createStore<Record<number, Notification>>({})
const apiClient = createMemo(() => {
if (!notifierClient.private) notifierClient.connect()
return notifierClient
})
const { addHandler } = useConnect() const { addHandler } = useConnect()
const loadNotifications = async (options: { limit: number; offset?: number }) => { const loadNotifications = async (options: { limit: number; offset?: number }) => {
const { notifications, unread, total } = await apiClient.getNotifications(options) const { notifications, unread, total } = await apiClient().getNotifications(options)
const newNotificationEntities = notifications.reduce((acc, notification) => { const newNotificationEntities = notifications.reduce((acc, notification) => {
acc[notification.id] = notification acc[notification.id] = notification
return acc return acc
@ -69,13 +73,13 @@ export const NotificationsProvider = (props: { children: JSX.Element }) => {
}) })
const markNotificationAsRead = async (notification: Notification) => { const markNotificationAsRead = async (notification: Notification) => {
await apiClient.markNotificationAsRead(notification.id) await apiClient().markNotificationAsRead(notification.id)
const nnn = new Set([...notification.seen, notification.id]) const nnn = new Set([...notification.seen, notification.id])
setNotificationEntities(notification.id, 'seen', [...nnn]) setNotificationEntities(notification.id, 'seen', [...nnn])
setUnreadNotificationsCount((oldCount) => oldCount - 1) setUnreadNotificationsCount((oldCount) => oldCount - 1)
} }
const markAllNotificationsAsRead = async () => { const markAllNotificationsAsRead = async () => {
await apiClient.markAllNotificationsAsRead() await apiClient().markAllNotificationsAsRead()
loadNotifications({ limit: loadedNotificationsCount() }) loadNotifications({ limit: loadedNotificationsCount() })
} }

View File

@ -1,9 +1,9 @@
import type { ProfileInput } from '../graphql/schema/core.gen' import type { ProfileInput } from '../graphql/schema/core.gen'
import { createEffect, createSignal } from 'solid-js' import { createEffect, createMemo, createSignal } from 'solid-js'
import { createStore } from 'solid-js/store' import { createStore } from 'solid-js/store'
import { apiClient } from '../graphql/client/core' import { apiClient as coreClient } from '../graphql/client/core'
import { loadAuthor } from '../stores/zine/authors' import { loadAuthor } from '../stores/zine/authors'
import { useSession } from './session' import { useSession } from './session'
@ -18,8 +18,13 @@ const useProfileForm = () => {
const { author: currentAuthor } = useSession() const { author: currentAuthor } = useSession()
const [slugError, setSlugError] = createSignal<string>() const [slugError, setSlugError] = createSignal<string>()
const apiClient = createMemo(() => {
if (!coreClient.private) coreClient.connect()
return coreClient
})
const submit = async (profile: ProfileInput) => { const submit = async (profile: ProfileInput) => {
const response = await apiClient.updateProfile(profile) const response = await apiClient().updateProfile(profile)
if (response.error) { if (response.error) {
setSlugError(response.error) setSlugError(response.error)
return response.error return response.error

View File

@ -1,9 +1,9 @@
import type { JSX } from 'solid-js' import type { JSX } from 'solid-js'
import { createContext, onCleanup, useContext } from 'solid-js' import { createContext, createMemo, onCleanup, useContext } from 'solid-js'
import { createStore, reconcile } from 'solid-js/store' import { createStore, reconcile } from 'solid-js/store'
import { apiClient } from '../graphql/client/core' import { apiClient as coreClient } from '../graphql/client/core'
import { Reaction, ReactionBy, ReactionInput, ReactionKind } from '../graphql/schema/core.gen' import { Reaction, ReactionBy, ReactionInput, ReactionKind } from '../graphql/schema/core.gen'
type ReactionsContextType = { type ReactionsContextType = {
@ -33,6 +33,11 @@ export function useReactions() {
export const ReactionsProvider = (props: { children: JSX.Element }) => { export const ReactionsProvider = (props: { children: JSX.Element }) => {
const [reactionEntities, setReactionEntities] = createStore<Record<number, Reaction>>({}) const [reactionEntities, setReactionEntities] = createStore<Record<number, Reaction>>({})
const apiClient = createMemo(() => {
if (!coreClient.private) coreClient.connect()
return coreClient
})
const loadReactionsBy = async ({ const loadReactionsBy = async ({
by, by,
limit, limit,
@ -42,7 +47,7 @@ export const ReactionsProvider = (props: { children: JSX.Element }) => {
limit?: number limit?: number
offset?: number offset?: number
}): Promise<Reaction[]> => { }): Promise<Reaction[]> => {
const reactions = await apiClient.getReactionsBy({ by, limit, offset }) const reactions = await coreClient.getReactionsBy({ by, limit, offset })
const newReactionEntities = reactions.reduce((acc, reaction) => { const newReactionEntities = reactions.reduce((acc, reaction) => {
acc[reaction.id] = reaction acc[reaction.id] = reaction
return acc return acc
@ -52,7 +57,7 @@ export const ReactionsProvider = (props: { children: JSX.Element }) => {
} }
const createReaction = async (input: ReactionInput): Promise<void> => { const createReaction = async (input: ReactionInput): Promise<void> => {
const reaction = await apiClient.createReaction(input) const reaction = await apiClient().createReaction(input)
const changes = { const changes = {
[reaction.id]: reaction, [reaction.id]: reaction,
@ -79,14 +84,14 @@ export const ReactionsProvider = (props: { children: JSX.Element }) => {
} }
const deleteReaction = async (id: number): Promise<void> => { const deleteReaction = async (id: number): Promise<void> => {
const reaction = await apiClient.destroyReaction(id) const reaction = await apiClient().destroyReaction(id)
setReactionEntities({ setReactionEntities({
[reaction.id]: undefined, [reaction.id]: undefined,
}) })
} }
const updateReaction = async (id: number, input: ReactionInput): Promise<void> => { const updateReaction = async (id: number, input: ReactionInput): Promise<void> => {
const reaction = await apiClient.updateReaction(id, input) const reaction = await apiClient().updateReaction(id, input)
setReactionEntities(reaction.id, reaction) setReactionEntities(reaction.id, reaction)
} }

View File

@ -12,6 +12,7 @@ import { showModal } from '../stores/ui'
import { useAuthorizer } from './authorizer' import { useAuthorizer } from './authorizer'
import { useLocalize } from './localize' import { useLocalize } from './localize'
import { useSnackbar } from './snackbar' import { useSnackbar } from './snackbar'
import { getToken, resetToken, setToken } from '../stores/token'
export type SessionContextType = { export type SessionContextType = {
session: Resource<AuthToken> session: Resource<AuthToken>
@ -21,6 +22,7 @@ export type SessionContextType = {
author: Resource<Author | null> author: Resource<Author | null>
isAuthenticated: Accessor<boolean> isAuthenticated: Accessor<boolean>
actions: { actions: {
getToken: () => string
loadSession: () => AuthToken | Promise<AuthToken> loadSession: () => AuthToken | Promise<AuthToken>
loadSubscriptions: () => Promise<void> loadSubscriptions: () => Promise<void>
requireAuthentication: ( requireAuthentication: (
@ -52,15 +54,6 @@ export const SessionProvider = (props: { children: JSX.Element }) => {
actions: { showSnackbar }, actions: { showSnackbar },
} = useSnackbar() } = useSnackbar()
const [, { authorizer }] = useAuthorizer() const [, { authorizer }] = useAuthorizer()
// const [getToken, setToken] = createSignal<string>('')
// https://start.solidjs.com/api/createCookieSessionStorage
const [store, setStore, { remove, clear, toJSON }] = createStorage({
api: cookieStorage,
prefix: 'discoursio',
})
const getToken = () => store.token
const setToken = (value) => setStore('token', value)
const resetToken = () => remove('token')
const loadSubscriptions = async (): Promise<void> => { const loadSubscriptions = async (): Promise<void> => {
const result = await apiClient.getMySubscriptions() const result = await apiClient.getMySubscriptions()
@ -73,13 +66,13 @@ export const SessionProvider = (props: { children: JSX.Element }) => {
const getSession = async (): Promise<AuthToken> => { const getSession = async (): Promise<AuthToken> => {
try { try {
const token = getToken() // FIXME: token in localStorage? const token = getToken()
const authResult = await authorizer().getSession({ const authResult = await authorizer().getSession({
Authorization: token, // authToken() Authorization: token,
}) })
if (authResult) { if (authResult) {
console.log(authResult) console.log(authResult)
setToken(authResult.access_token || authResult.id_token) setToken(authResult.access_token)
loadSubscriptions() loadSubscriptions()
return authResult return authResult
} else { } else {
@ -122,7 +115,7 @@ export const SessionProvider = (props: { children: JSX.Element }) => {
const signIn = async (params: LoginInput) => { const signIn = async (params: LoginInput) => {
const authResult = await authorizer().login(params) const authResult = await authorizer().login(params)
if (authResult) { if (authResult) {
setToken(authResult.access_token || authResult.id_token) setToken(authResult.access_token)
mutate(authResult) mutate(authResult)
} }
loadSubscriptions() loadSubscriptions()
@ -170,6 +163,7 @@ export const SessionProvider = (props: { children: JSX.Element }) => {
signOut, signOut,
confirmEmail, confirmEmail,
loadSubscriptions, loadSubscriptions,
getToken,
} }
const value: SessionContextType = { const value: SessionContextType = {
session, session,

View File

@ -1,4 +1,5 @@
// inbox // inbox
import { createGraphQLClient } from '../createGraphQLClient'
import createChat from '../mutation/chat/chat-create' import createChat from '../mutation/chat/chat-create'
import deleteChat from '../mutation/chat/chat-delete' import deleteChat from '../mutation/chat/chat-delete'
import markAsRead from '../mutation/chat/chat-mark-as-read' import markAsRead from '../mutation/chat/chat-mark-as-read'
@ -6,7 +7,6 @@ import createChatMessage from '../mutation/chat/chat-message-create'
import deleteChatMessage from '../mutation/chat/chat-message-delete' import deleteChatMessage from '../mutation/chat/chat-message-delete'
import updateChatMessage from '../mutation/chat/chat-message-update' import updateChatMessage from '../mutation/chat/chat-message-update'
import updateChat from '../mutation/chat/chat-update' import updateChat from '../mutation/chat/chat-update'
import { getPrivateClient } from '../privateGraphQLClient'
import chatMessagesLoadBy from '../query/chat/chat-messages-load-by' import chatMessagesLoadBy from '../query/chat/chat-messages-load-by'
import loadRecipients from '../query/chat/chat-recipients' import loadRecipients from '../query/chat/chat-recipients'
import myChats from '../query/chat/chats-load' import myChats from '../query/chat/chats-load'
@ -24,55 +24,56 @@ import {
QueryLoad_RecipientsArgs, QueryLoad_RecipientsArgs,
} from '../schema/chat.gen' } from '../schema/chat.gen'
const privateInboxGraphQLClient = getPrivateClient('chat')
export const inboxClient = { export const inboxClient = {
private: null,
connect: () => (inboxClient.private = createGraphQLClient('chat')),
loadChats: async (options: QueryLoad_ChatsArgs): Promise<Chat[]> => { loadChats: async (options: QueryLoad_ChatsArgs): Promise<Chat[]> => {
const resp = await privateInboxGraphQLClient.query(myChats, options).toPromise() const resp = await inboxClient.private.query(myChats, options).toPromise()
return resp.data.load_chats.chats return resp.data.load_chats.chats
}, },
createChat: async (options: MutationCreate_ChatArgs) => { createChat: async (options: MutationCreate_ChatArgs) => {
const resp = await privateInboxGraphQLClient.mutation(createChat, options).toPromise() const resp = await inboxClient.private.mutation(createChat, options).toPromise()
return resp.data.create_chat return resp.data.create_chat
}, },
markAsRead: async (options: MutationMark_As_ReadArgs) => { markAsRead: async (options: MutationMark_As_ReadArgs) => {
const resp = await privateInboxGraphQLClient.mutation(markAsRead, options).toPromise() const resp = await inboxClient.private.mutation(markAsRead, options).toPromise()
return resp.data.mark_as_read return resp.data.mark_as_read
}, },
updateChat: async (options: MutationUpdate_ChatArgs) => { updateChat: async (options: MutationUpdate_ChatArgs) => {
const resp = await privateInboxGraphQLClient.mutation(updateChat, options).toPromise() const resp = await inboxClient.private.mutation(updateChat, options).toPromise()
return resp.data.update_chat return resp.data.update_chat
}, },
deleteChat: async (options: MutationDelete_ChatArgs) => { deleteChat: async (options: MutationDelete_ChatArgs) => {
const resp = await privateInboxGraphQLClient.mutation(deleteChat, options).toPromise() const resp = await inboxClient.private.mutation(deleteChat, options).toPromise()
return resp.data.delete_chat return resp.data.delete_chat
}, },
createMessage: async (options: MutationCreate_MessageArgs) => { createMessage: async (options: MutationCreate_MessageArgs) => {
const resp = await privateInboxGraphQLClient.mutation(createChatMessage, options).toPromise() const resp = await inboxClient.private.mutation(createChatMessage, options).toPromise()
return resp.data.create_message.message return resp.data.create_message.message
}, },
updateMessage: async (options: MutationUpdate_MessageArgs) => { updateMessage: async (options: MutationUpdate_MessageArgs) => {
const resp = await privateInboxGraphQLClient.mutation(updateChatMessage, options).toPromise() const resp = await inboxClient.private.mutation(updateChatMessage, options).toPromise()
return resp.data.update_message.message return resp.data.update_message.message
}, },
deleteMessage: async (options: MutationDelete_MessageArgs) => { deleteMessage: async (options: MutationDelete_MessageArgs) => {
const resp = await privateInboxGraphQLClient.mutation(deleteChatMessage, options).toPromise() const resp = await inboxClient.private.mutation(deleteChatMessage, options).toPromise()
return resp.data.delete_message return resp.data.delete_message
}, },
loadChatMessages: async (options: QueryLoad_Messages_ByArgs) => { loadChatMessages: async (options: QueryLoad_Messages_ByArgs) => {
const resp = await privateInboxGraphQLClient.query(chatMessagesLoadBy, options).toPromise() const resp = await inboxClient.private.query(chatMessagesLoadBy, options).toPromise()
return resp.data.load_messages_by.messages return resp.data.load_messages_by.messages
}, },
loadRecipients: async (options: QueryLoad_RecipientsArgs) => { loadRecipients: async (options: QueryLoad_RecipientsArgs) => {
const resp = await privateInboxGraphQLClient.query(loadRecipients, options).toPromise() const resp = await inboxClient.private.query(loadRecipients, options).toPromise()
return resp.data.load_recipients.members return resp.data.load_recipients.members
}, },
} }

View File

@ -13,6 +13,7 @@ import type {
QueryLoad_Shouts_SearchArgs, QueryLoad_Shouts_SearchArgs,
} from '../schema/core.gen' } from '../schema/core.gen'
import { createGraphQLClient } from '../createGraphQLClient'
import createArticle from '../mutation/core/article-create' import createArticle from '../mutation/core/article-create'
import deleteShout from '../mutation/core/article-delete' import deleteShout from '../mutation/core/article-delete'
import updateArticle from '../mutation/core/article-update' import updateArticle from '../mutation/core/article-update'
@ -22,8 +23,6 @@ import reactionDestroy from '../mutation/core/reaction-destroy'
import reactionUpdate from '../mutation/core/reaction-update' import reactionUpdate from '../mutation/core/reaction-update'
import unfollowMutation from '../mutation/core/unfollow' import unfollowMutation from '../mutation/core/unfollow'
import updateProfile from '../mutation/core/update-profile' import updateProfile from '../mutation/core/update-profile'
import { getPrivateClient } from '../privateGraphQLClient'
import { getPublicClient } from '../publicGraphQLClient'
import shoutLoad from '../query/core/article-load' import shoutLoad from '../query/core/article-load'
import shoutsLoadBy from '../query/core/articles-load-by' import shoutsLoadBy from '../query/core/articles-load-by'
import draftsLoad from '../query/core/articles-load-drafts' import draftsLoad from '../query/core/articles-load-drafts'
@ -41,10 +40,12 @@ import topicsAll from '../query/core/topics-all'
import userFollowedTopics from '../query/core/topics-by-author' import userFollowedTopics from '../query/core/topics-by-author'
import topicsRandomQuery from '../query/core/topics-random' import topicsRandomQuery from '../query/core/topics-random'
export const privateGraphQLClient = getPublicClient('core') const publicGraphQLClient = createGraphQLClient('core')
export const publicGraphQLClient = getPrivateClient('core')
export const apiClient = { export const apiClient = {
private: null,
connect: () => (apiClient.private = createGraphQLClient('core')), // NOTE: use it after token appears
getRandomTopics: async ({ amount }: { amount: number }) => { getRandomTopics: async ({ amount }: { amount: number }) => {
const response = await publicGraphQLClient.query(topicsRandomQuery, { amount }).toPromise() const response = await publicGraphQLClient.query(topicsRandomQuery, { amount }).toPromise()
@ -56,11 +57,11 @@ export const apiClient = {
}, },
follow: async ({ what, slug }: { what: FollowingEntity; slug: string }) => { follow: async ({ what, slug }: { what: FollowingEntity; slug: string }) => {
const response = await privateGraphQLClient.mutation(followMutation, { what, slug }).toPromise() const response = await apiClient.private.mutation(followMutation, { what, slug }).toPromise()
return response.data.follow return response.data.follow
}, },
unfollow: async ({ what, slug }: { what: FollowingEntity; slug: string }) => { unfollow: async ({ what, slug }: { what: FollowingEntity; slug: string }) => {
const response = await privateGraphQLClient.mutation(unfollowMutation, { what, slug }).toPromise() const response = await apiClient.private.mutation(unfollowMutation, { what, slug }).toPromise()
return response.data.unfollow return response.data.unfollow
}, },
@ -95,7 +96,7 @@ export const apiClient = {
return response.data.userFollowedTopics return response.data.userFollowedTopics
}, },
updateProfile: async (input: ProfileInput) => { updateProfile: async (input: ProfileInput) => {
const response = await privateGraphQLClient.mutation(updateProfile, { profile: input }).toPromise() const response = await apiClient.private.mutation(updateProfile, { profile: input }).toPromise()
return response.data.update_profile return response.data.update_profile
}, },
getTopic: async ({ slug }: { slug: string }): Promise<Topic> => { getTopic: async ({ slug }: { slug: string }): Promise<Topic> => {
@ -103,7 +104,7 @@ export const apiClient = {
return response.data.get_topic return response.data.get_topic
}, },
createArticle: async ({ article }: { article: ShoutInput }): Promise<Shout> => { createArticle: async ({ article }: { article: ShoutInput }): Promise<Shout> => {
const response = await privateGraphQLClient.mutation(createArticle, { shout: article }).toPromise() const response = await apiClient.private.mutation(createArticle, { shout: article }).toPromise()
return response.data.create_shout.shout return response.data.create_shout.shout
}, },
updateArticle: async ({ updateArticle: async ({
@ -115,33 +116,33 @@ export const apiClient = {
shoutInput?: ShoutInput shoutInput?: ShoutInput
publish: boolean publish: boolean
}): Promise<Shout> => { }): Promise<Shout> => {
const response = await privateGraphQLClient const response = await apiClient.private
.mutation(updateArticle, { shoutId, shoutInput, publish }) .mutation(updateArticle, { shoutId, shoutInput, publish })
.toPromise() .toPromise()
console.debug('[graphql.client.core] updateArticle:', response.data) console.debug('[graphql.client.core] updateArticle:', response.data)
return response.data.update_shout.shout return response.data.update_shout.shout
}, },
deleteShout: async ({ shoutId }: { shoutId: number }): Promise<void> => { deleteShout: async ({ shoutId }: { shoutId: number }): Promise<void> => {
const response = await privateGraphQLClient.mutation(deleteShout, { shout_id: shoutId }).toPromise() const response = await apiClient.private.mutation(deleteShout, { shout_id: shoutId }).toPromise()
console.debug('[graphql.client.core] deleteShout:', response) console.debug('[graphql.client.core] deleteShout:', response)
}, },
getDrafts: async (): Promise<Shout[]> => { getDrafts: async (): Promise<Shout[]> => {
const response = await privateGraphQLClient.query(draftsLoad, {}).toPromise() const response = await apiClient.private.query(draftsLoad, {}).toPromise()
console.debug('[graphql.client.core] getDrafts:', response) console.debug('[graphql.client.core] getDrafts:', response)
return response.data.load_shouts_drafts return response.data.load_shouts_drafts
}, },
createReaction: async (input: ReactionInput) => { createReaction: async (input: ReactionInput) => {
const response = await privateGraphQLClient.mutation(reactionCreate, { reaction: input }).toPromise() const response = await apiClient.private.mutation(reactionCreate, { reaction: input }).toPromise()
console.debug('[graphql.client.core] createReaction:', response) console.debug('[graphql.client.core] createReaction:', response)
return response.data.create_reaction.reaction return response.data.create_reaction.reaction
}, },
destroyReaction: async (id: number) => { destroyReaction: async (id: number) => {
const response = await privateGraphQLClient.mutation(reactionDestroy, { id: id }).toPromise() const response = await apiClient.private.mutation(reactionDestroy, { id: id }).toPromise()
console.debug('[graphql.client.core] destroyReaction:', response) console.debug('[graphql.client.core] destroyReaction:', response)
return response.data.delete_reaction.reaction return response.data.delete_reaction.reaction
}, },
updateReaction: async (id: number, input: ReactionInput) => { updateReaction: async (id: number, input: ReactionInput) => {
const response = await privateGraphQLClient const response = await apiClient.private
.mutation(reactionUpdate, { id: id, reaction: input }) .mutation(reactionUpdate, { id: id, reaction: input })
.toPromise() .toPromise()
console.debug('[graphql.client.core] updateReaction:', response) console.debug('[graphql.client.core] updateReaction:', response)
@ -177,7 +178,7 @@ export const apiClient = {
}, },
getMyFeed: async (options: LoadShoutsOptions) => { getMyFeed: async (options: LoadShoutsOptions) => {
const resp = await privateGraphQLClient.query(myFeed, { options }).toPromise() const resp = await apiClient.private.query(myFeed, { options }).toPromise()
if (resp.error) console.error(resp) if (resp.error) console.error(resp)
return resp.data.load_shouts_feed return resp.data.load_shouts_feed
@ -190,7 +191,7 @@ export const apiClient = {
return resp.data.load_reactions_by return resp.data.load_reactions_by
}, },
getMySubscriptions: async (): Promise<Result> => { getMySubscriptions: async (): Promise<Result> => {
const resp = await privateGraphQLClient.query(mySubscriptions, {}).toPromise() const resp = await apiClient.private.query(mySubscriptions, {}).toPromise()
return resp.data.get_my_followed return resp.data.get_my_followed
}, },

View File

@ -1,18 +1,19 @@
import markAllNotificationsAsRead from '../mutation/notifier/mark-all-notifications-as-read' import markAllNotificationsAsRead from '../mutation/notifier/mark-all-notifications-as-read'
import markNotificationAsRead from '../mutation/notifier/mark-notification-as-read' import markNotificationAsRead from '../mutation/notifier/mark-notification-as-read'
import { getPrivateClient } from '../privateGraphQLClient' import { createGraphQLClient } from '../createGraphQLClient'
import loadNotifications from '../query/notifier/notifications-load' import loadNotifications from '../query/notifier/notifications-load'
import { NotificationsResult, QueryLoad_NotificationsArgs } from '../schema/notifier.gen' import { NotificationsResult, QueryLoad_NotificationsArgs } from '../schema/notifier.gen'
export const notifierPrivateGraphqlClient = getPrivateClient('notifier')
export const notifierClient = { export const notifierClient = {
private: null,
connect: () => (notifierClient.private = createGraphQLClient('notifier')),
getNotifications: async (params: QueryLoad_NotificationsArgs): Promise<NotificationsResult> => { getNotifications: async (params: QueryLoad_NotificationsArgs): Promise<NotificationsResult> => {
const resp = await notifierPrivateGraphqlClient.query(loadNotifications, params).toPromise() const resp = await notifierClient.private.query(loadNotifications, params).toPromise()
return resp.data.load_notifications return resp.data.load_notifications
}, },
markNotificationAsRead: async (notification_id: number): Promise<void> => { markNotificationAsRead: async (notification_id: number): Promise<void> => {
await notifierPrivateGraphqlClient await notifierClient.private
.mutation(markNotificationAsRead, { .mutation(markNotificationAsRead, {
notification_id, notification_id,
}) })
@ -20,6 +21,6 @@ export const notifierClient = {
}, },
markAllNotificationsAsRead: async (): Promise<void> => { markAllNotificationsAsRead: async (): Promise<void> => {
await notifierPrivateGraphqlClient.mutation(markAllNotificationsAsRead, {}).toPromise() await notifierClient.private.mutation(markAllNotificationsAsRead, {}).toPromise()
}, },
} }

View File

@ -0,0 +1,23 @@
import { ClientOptions, dedupExchange, fetchExchange, Exchange, createClient } from '@urql/core'
import { devtoolsExchange } from '@urql/devtools'
import { isDev } from '../utils/config'
import { getToken } from '../stores/token'
const exchanges: Exchange[] = [dedupExchange, fetchExchange]
if (isDev) {
exchanges.unshift(devtoolsExchange)
}
export const createGraphQLClient = (serviceName: string) => {
const token = getToken()
const options: ClientOptions = {
url: `https://${serviceName}.discours.io`,
maskTypename: true,
requestPolicy: 'cache-and-network',
fetchOptions: () => (token ? { headers: { Authorization: token } } : {}),
exchanges,
}
return createClient(options)
}

View File

@ -1,55 +0,0 @@
import { ClientOptions, dedupExchange, fetchExchange, Exchange, createClient } from '@urql/core'
import { devtoolsExchange } from '@urql/devtools'
import { isDev } from '../utils/config'
const TOKEN_LOCAL_STORAGE_KEY = 'token'
const exchanges: Exchange[] = [dedupExchange, fetchExchange]
if (isDev) {
exchanges.unshift(devtoolsExchange)
}
export const getToken = (): string => {
return localStorage.getItem(TOKEN_LOCAL_STORAGE_KEY)
}
export const setToken = (token: string) => {
if (!token) {
console.error('[privateGraphQLClient] setToken: token is null!')
}
localStorage.setItem(TOKEN_LOCAL_STORAGE_KEY, token)
}
export const resetToken = () => {
localStorage.removeItem(TOKEN_LOCAL_STORAGE_KEY)
}
const options: ClientOptions = {
url: '',
maskTypename: true,
requestPolicy: 'cache-and-network',
fetchOptions: () => {
try {
// localStorage is the source of truth for now
// to change token call setToken, for example after login
const token = localStorage.getItem(TOKEN_LOCAL_STORAGE_KEY)
if (!token) {
console.error('[privateGraphQLClient] fetchOptions: token is null!')
}
const headers = { Authorization: token }
return { headers }
} catch {
return {}
}
},
exchanges,
}
export const getPrivateClient = (name: string) =>
createClient({
...options,
url: `https://${name}.discours.io`,
})

View File

@ -1,24 +0,0 @@
import { ClientOptions, dedupExchange, fetchExchange, Exchange, createClient } from '@urql/core'
import { devtoolsExchange } from '@urql/devtools'
import { isDev } from '../utils/config'
// import { cache } from './cache'
const exchanges: Exchange[] = [dedupExchange, fetchExchange] //, cache]
if (isDev) {
exchanges.unshift(devtoolsExchange)
}
const options: ClientOptions = {
url: '',
maskTypename: true,
requestPolicy: 'cache-and-network',
exchanges,
}
export const getPublicClient = (name: string) =>
createClient({
...options,
url: `https://${name}.discours.io`,
})

10
src/stores/token.ts Normal file
View File

@ -0,0 +1,10 @@
import { cookieStorage, createStorage } from '@solid-primitives/storage'
// https://start.solidjs.com/api/createCookieSessionStorage
export const [store, setStore, { remove }] = createStorage({
api: cookieStorage,
prefix: 'discoursio',
})
export const getToken = () => store.token
export const setToken = (value) => setStore('token', value)
export const resetToken = () => remove('token')