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

View File

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

View File

@ -22,7 +22,7 @@ export const Userpic = (props: Props) => {
const letters = () => {
if (!props.name) return
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(() => {

View File

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

View File

@ -34,6 +34,8 @@ export const ForgotPasswordForm = () => {
const authFormRef: { current: HTMLFormElement } = { current: null }
const [message, setMessage] = createSignal<string>('')
const handleSubmit = async (event: Event) => {
event.preventDefault()
@ -61,11 +63,14 @@ export const ForgotPasswordForm = () => {
}
setIsSubmitting(true)
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) {
console.debug(response)
if (response.message) setMessage(response.message)
}
} catch (error) {
if (error instanceof ApiError && error.code === 'user_not_found') {
@ -86,7 +91,9 @@ export const ForgotPasswordForm = () => {
>
<div>
<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
class={clsx('pretty-form__item', {
@ -116,7 +123,6 @@ export const ForgotPasswordForm = () => {
<Show when={isUserNotFount()}>
<div class={styles.authSubtitle}>
{/*TODO: text*/}
{t("We can't find you, check email or")}{' '}
<a
href="#"

View File

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

View File

@ -1,7 +1,15 @@
import type { ParentComponent } from 'solid-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'
export type AuthorizerState = {
@ -21,7 +29,7 @@ type AuthorizerContextActions = {
}
const config: ConfigType = {
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?
}
@ -56,15 +64,16 @@ export const AuthorizerProvider: ParentComponent<AuthorizerProviderProps> = (pro
loading: true,
config: {
authorizerURL: props.authorizerURL,
redirectURL: props.redirectURL,
clientID: props.clientID,
} as ConfigType,
})
const [redirect, setRedirect] = createSignal<string>()
const authorizer = createMemo(
() =>
new Authorizer({
authorizerURL: props.authorizerURL,
redirectURL: props.redirectURL,
redirectURL: redirect(),
clientID: props.clientID,
}),
)
@ -148,6 +157,7 @@ export const AuthorizerProvider: ParentComponent<AuthorizerProviderProps> = (pro
}
onMount(() => {
setRedirect(window.location.origin)
!state.token && getToken()
})

View File

@ -3,9 +3,8 @@ import type { Accessor, JSX } from 'solid-js'
import { fetchEventSource } from '@microsoft/fetch-event-source'
import { createContext, useContext, createSignal, createEffect } from 'solid-js'
import { getToken } from '../graphql/privateGraphQLClient'
import { useSession } from './session'
import { useAuthorizer } from './authorizer'
export interface SSEMessage {
id: string
@ -30,20 +29,22 @@ export const ConnectProvider = (props: { children: JSX.Element }) => {
// const [messages, setMessages] = createSignal<Array<SSEMessage>>([]);
const [connected, setConnected] = createSignal(false)
const { isAuthenticated } = useSession()
const {
isAuthenticated,
actions: { getToken },
} = useSession()
const addHandler = (handler: MessageHandler) => {
setHandlers((hhh) => [...hhh, handler])
}
const listen = () => {
const token = getToken()
if (token) {
if (isAuthenticated()) {
fetchEventSource('https://connect.discours.io', {
method: 'GET',
headers: {
'Content-Type': 'application/json',
Authorization: token,
Authorization: getToken(),
},
onmessage(event) {
const m: SSEMessage = JSON.parse(event.data)
@ -54,9 +55,11 @@ export const ConnectProvider = (props: { children: JSX.Element }) => {
},
onclose() {
console.log('[context.connect] sse connection closed by server')
setConnected(false)
},
onerror(err) {
console.error('[context.connect] sse connection closed by error', err)
setConnected(false)
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 { 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 { apiClient } from '../graphql/client/core'
import { apiClient as coreClient } from '../graphql/client/core'
import { ShoutVisibility, Topic, TopicInput } from '../graphql/schema/core.gen'
import { router, useRouter } from '../stores/router'
import { slugify } from '../utils/slugify'
@ -84,7 +84,10 @@ export const EditorProvider = (props: { children: JSX.Element }) => {
const { t } = useLocalize()
const { page } = useRouter()
const apiClient = createMemo(() => {
if (!coreClient.private) coreClient.connect()
return coreClient
})
const {
actions: { showSnackbar },
} = useSnackbar()
@ -126,7 +129,7 @@ export const EditorProvider = (props: { children: JSX.Element }) => {
}
const updateShout = async (formToUpdate: ShoutForm, { publish }: { publish: boolean }) => {
return apiClient.updateArticle({
return apiClient().updateArticle({
shoutId: formToUpdate.shoutId,
shoutInput: {
body: formToUpdate.body,
@ -211,7 +214,7 @@ export const EditorProvider = (props: { children: JSX.Element }) => {
const publishShoutById = async (shoutId: number) => {
try {
await apiClient.updateArticle({
await apiClient().updateArticle({
shoutId,
publish: true,
})
@ -225,7 +228,7 @@ export const EditorProvider = (props: { children: JSX.Element }) => {
const deleteShout = async (shoutId: number) => {
try {
await apiClient.deleteShout({
await apiClient().deleteShout({
shoutId,
})
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 { createContext, createSignal, useContext } from 'solid-js'
import { createContext, createMemo, createSignal, useContext } from 'solid-js'
import { inboxClient } from '../graphql/client/chat'
import { loadMessages } from '../stores/inbox'
@ -15,7 +15,7 @@ type InboxContextType = {
createChat: (members: number[], title: string) => Promise<{ chat: Chat }>
loadChats: () => 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])
}
}
const apiClient = createMemo(() => {
if (!inboxClient.private) inboxClient.connect()
return inboxClient
})
const { addHandler } = useConnect()
addHandler(handleMessage)
const loadChats = async () => {
try {
const newChats = await inboxClient.loadChats({ limit: 50, offset: 0 })
const newChats = await apiClient().loadChats({ limit: 50, offset: 0 })
setChats(newChats)
} catch (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 {
const message = await inboxClient.createMessage(args)
const message = await apiClient().createMessage(args)
setMessages((prev) => [...prev, message])
const currentChat = chats().find((chat) => chat.id === args.chat_id)
setChats((prev) => [

View File

@ -6,7 +6,7 @@ import { Portal } from 'solid-js/web'
import { ShowIfAuthenticated } from '../components/_shared/ShowIfAuthenticated'
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 { SSEMessage, useConnect } from './connect'
@ -38,9 +38,13 @@ export const NotificationsProvider = (props: { children: JSX.Element }) => {
const [unreadNotificationsCount, setUnreadNotificationsCount] = createSignal(0)
const [totalNotificationsCount, setTotalNotificationsCount] = createSignal(0)
const [notificationEntities, setNotificationEntities] = createStore<Record<number, Notification>>({})
const apiClient = createMemo(() => {
if (!notifierClient.private) notifierClient.connect()
return notifierClient
})
const { addHandler } = useConnect()
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) => {
acc[notification.id] = notification
return acc
@ -69,13 +73,13 @@ export const NotificationsProvider = (props: { children: JSX.Element }) => {
})
const markNotificationAsRead = async (notification: Notification) => {
await apiClient.markNotificationAsRead(notification.id)
await apiClient().markNotificationAsRead(notification.id)
const nnn = new Set([...notification.seen, notification.id])
setNotificationEntities(notification.id, 'seen', [...nnn])
setUnreadNotificationsCount((oldCount) => oldCount - 1)
}
const markAllNotificationsAsRead = async () => {
await apiClient.markAllNotificationsAsRead()
await apiClient().markAllNotificationsAsRead()
loadNotifications({ limit: loadedNotificationsCount() })
}

View File

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

View File

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

View File

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

View File

@ -1,4 +1,5 @@
// inbox
import { createGraphQLClient } from '../createGraphQLClient'
import createChat from '../mutation/chat/chat-create'
import deleteChat from '../mutation/chat/chat-delete'
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 updateChatMessage from '../mutation/chat/chat-message-update'
import updateChat from '../mutation/chat/chat-update'
import { getPrivateClient } from '../privateGraphQLClient'
import chatMessagesLoadBy from '../query/chat/chat-messages-load-by'
import loadRecipients from '../query/chat/chat-recipients'
import myChats from '../query/chat/chats-load'
@ -24,55 +24,56 @@ import {
QueryLoad_RecipientsArgs,
} from '../schema/chat.gen'
const privateInboxGraphQLClient = getPrivateClient('chat')
export const inboxClient = {
private: null,
connect: () => (inboxClient.private = createGraphQLClient('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
},
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
},
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
},
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
},
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
},
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
},
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
},
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
},
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
},
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
},
}

View File

@ -13,6 +13,7 @@ import type {
QueryLoad_Shouts_SearchArgs,
} from '../schema/core.gen'
import { createGraphQLClient } from '../createGraphQLClient'
import createArticle from '../mutation/core/article-create'
import deleteShout from '../mutation/core/article-delete'
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 unfollowMutation from '../mutation/core/unfollow'
import updateProfile from '../mutation/core/update-profile'
import { getPrivateClient } from '../privateGraphQLClient'
import { getPublicClient } from '../publicGraphQLClient'
import shoutLoad from '../query/core/article-load'
import shoutsLoadBy from '../query/core/articles-load-by'
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 topicsRandomQuery from '../query/core/topics-random'
export const privateGraphQLClient = getPublicClient('core')
export const publicGraphQLClient = getPrivateClient('core')
const publicGraphQLClient = createGraphQLClient('core')
export const apiClient = {
private: null,
connect: () => (apiClient.private = createGraphQLClient('core')), // NOTE: use it after token appears
getRandomTopics: async ({ amount }: { amount: number }) => {
const response = await publicGraphQLClient.query(topicsRandomQuery, { amount }).toPromise()
@ -56,11 +57,11 @@ export const apiClient = {
},
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
},
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
},
@ -95,7 +96,7 @@ export const apiClient = {
return response.data.userFollowedTopics
},
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
},
getTopic: async ({ slug }: { slug: string }): Promise<Topic> => {
@ -103,7 +104,7 @@ export const apiClient = {
return response.data.get_topic
},
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
},
updateArticle: async ({
@ -115,33 +116,33 @@ export const apiClient = {
shoutInput?: ShoutInput
publish: boolean
}): Promise<Shout> => {
const response = await privateGraphQLClient
const response = await apiClient.private
.mutation(updateArticle, { shoutId, shoutInput, publish })
.toPromise()
console.debug('[graphql.client.core] updateArticle:', response.data)
return response.data.update_shout.shout
},
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)
},
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)
return response.data.load_shouts_drafts
},
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)
return response.data.create_reaction.reaction
},
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)
return response.data.delete_reaction.reaction
},
updateReaction: async (id: number, input: ReactionInput) => {
const response = await privateGraphQLClient
const response = await apiClient.private
.mutation(reactionUpdate, { id: id, reaction: input })
.toPromise()
console.debug('[graphql.client.core] updateReaction:', response)
@ -177,7 +178,7 @@ export const apiClient = {
},
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)
return resp.data.load_shouts_feed
@ -190,7 +191,7 @@ export const apiClient = {
return resp.data.load_reactions_by
},
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
},

View File

@ -1,18 +1,19 @@
import markAllNotificationsAsRead from '../mutation/notifier/mark-all-notifications-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 { NotificationsResult, QueryLoad_NotificationsArgs } from '../schema/notifier.gen'
export const notifierPrivateGraphqlClient = getPrivateClient('notifier')
export const notifierClient = {
private: null,
connect: () => (notifierClient.private = createGraphQLClient('notifier')),
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
},
markNotificationAsRead: async (notification_id: number): Promise<void> => {
await notifierPrivateGraphqlClient
await notifierClient.private
.mutation(markNotificationAsRead, {
notification_id,
})
@ -20,6 +21,6 @@ export const notifierClient = {
},
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')