Merge pull request #386 from Discours/hotfix/posting

Hotfix/posting
This commit is contained in:
Tony 2024-02-03 17:56:25 +03:00 committed by GitHub
commit ab48f173b3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 86 additions and 61 deletions

View File

@ -1,9 +1,11 @@
name: 'deploy' name: 'deploy'
on: on:
push: push:
branches: branches:
- main - main
- feature/sse-connect - dev
- feature/email-templates
jobs: jobs:
test: test:
@ -26,24 +28,40 @@ jobs:
update_mailgun_template: update_mailgun_template:
runs-on: ubuntu-latest runs-on: ubuntu-latest
name: Update templates on Mailgun name: Update templates on Mailgun
if: github.event_name == 'push' && github.ref == 'refs/heads/feature/email-templates'
continue-on-error: true continue-on-error: true
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v2
- name: update authorizer_password_reset
uses: jlepocher/mailgun-create-template-version-action@v1.3
with:
mailgun-host: 'api.eu.mailgun.net'
mailgun-api-key: ${{ secrets.MAILGUN_API_KEY }}
mailgun-domain-name: 'discours.io'
mailgun-template-name: 'authorizer_password_reset'
html-file-path: './templates/authorizer_password_reset.html'
- name: update authorizer_password_reset - name: "Email confirmation template"
uses: jlepocher/mailgun-create-template-version-action@v1.3 uses: gyto/mailgun-template-action@v2
with: with:
mailgun-host: 'api.eu.mailgun.net' html-file: "./templates/authorizer_email_confirmation.html"
mailgun-api-key: ${{ secrets.MAILGUN_API_KEY }} mailgun-api-key: ${{ secrets.MAILGUN_API_KEY }}
mailgun-domain-name: 'discours.io' mailgun-domain: "discours.io"
mailgun-template-name: 'authorizer_email_confirm' mailgun-template: "authorizer_email_confirmation"
html-file-path: './templates/authorizer_email_confirm.html'
- name: "Password reset template"
uses: gyto/mailgun-template-action@v2
with:
html-file: "./templates/authorizer_password_reset.html"
mailgun-api-key: ${{ secrets.MAILGUN_API_KEY }}
mailgun-domain: "discours.io"
mailgun-template: "authorizer_password_reset"
- name: "First publication notification"
uses: gyto/mailgun-template-action@v2
with:
html-file: "./templates/first_publication_notification.html"
mailgun-api-key: ${{ secrets.MAILGUN_API_KEY }}
mailgun-domain: "discours.io"
mailgun-template: "first_publication_notification"
- name: "New comment notification template"
uses: gyto/mailgun-template-action@v2
with:
html-file: "./templates/new_comment_notification.html"
mailgun-api-key: ${{ secrets.MAILGUN_API_KEY }}
mailgun-domain: "discours.io"
mailgun-template: "new_comment_notification"

View File

@ -8,7 +8,6 @@ import { useMediaQuery } from '../../../context/mediaQuery'
import { useSession } from '../../../context/session' import { useSession } from '../../../context/session'
import { Author, FollowingEntity } from '../../../graphql/schema/core.gen' import { Author, FollowingEntity } from '../../../graphql/schema/core.gen'
import { router, useRouter } from '../../../stores/router' import { router, useRouter } from '../../../stores/router'
import { resetSortedArticles } from '../../../stores/zine/articles'
import { isCyrillic } from '../../../utils/cyrillic' import { isCyrillic } from '../../../utils/cyrillic'
import { translit } from '../../../utils/ru2en' import { translit } from '../../../utils/ru2en'
import { Button } from '../../_shared/Button' import { Button } from '../../_shared/Button'

View File

@ -1,6 +1,5 @@
import type { AuthModalSearchParams } from './types' import type { AuthModalSearchParams } from './types'
import { ApiResponse, ForgotPasswordResponse } from '@authorizerdev/authorizer-js'
import { clsx } from 'clsx' import { clsx } from 'clsx'
import { createSignal, JSX, Show } from 'solid-js' import { createSignal, JSX, Show } from 'solid-js'
@ -67,6 +66,7 @@ export const ForgotPasswordForm = () => {
if (errors && errors.some((error) => error.message.includes('bad user credentials'))) { if (errors && errors.some((error) => error.message.includes('bad user credentials'))) {
setIsUserNotFound(true) setIsUserNotFound(true)
} }
if (data.message) setMessage(data.message)
} catch (error) { } catch (error) {
console.error(error) console.error(error)
} finally { } finally {

View File

@ -1,12 +1,11 @@
import type { AuthModalSearchParams } from './types' import type { AuthModalSearchParams } from './types'
import { clsx } from 'clsx' import { clsx } from 'clsx'
import { createEffect, createSignal, Show } from 'solid-js' import { createSignal, Show } from 'solid-js'
import { useLocalize } from '../../../context/localize' import { useLocalize } from '../../../context/localize'
import { useSession } from '../../../context/session' import { useSession } from '../../../context/session'
import { useSnackbar } from '../../../context/snackbar' import { useSnackbar } from '../../../context/snackbar'
import { ApiError } from '../../../graphql/error'
import { useRouter } from '../../../stores/router' import { useRouter } from '../../../stores/router'
import { hideModal } from '../../../stores/ui' import { hideModal } from '../../../stores/ui'
import { validateEmail } from '../../../utils/validateEmail' import { validateEmail } from '../../../utils/validateEmail'

View File

@ -55,6 +55,7 @@ export const SearchModal = () => {
>( >(
async () => { async () => {
setIsLoading(true) setIsLoading(true)
saveScrollPosition()
const { hasMore, newShouts } = await loadShoutsSearch({ const { hasMore, newShouts } = await loadShoutsSearch({
limit: FEED_PAGE_SIZE, limit: FEED_PAGE_SIZE,
text: inputValue(), text: inputValue(),

View File

@ -128,7 +128,7 @@ export const AuthorView = (props: Props) => {
const data = await apiClient.getReactionsBy({ const data = await apiClient.getReactionsBy({
by: { comment: false, created_by: commenter.id }, by: { comment: false, created_by: commenter.id },
}) })
console.debug(`[components.Author] fetched ${data.length} comments`) console.debug(`[components.Author] fetched comments`, data)
setCommented(data) setCommented(data)
} }

View File

@ -22,8 +22,8 @@ export const DraftsView = () => {
} }
} }
createEffect(async () => { createEffect(() => {
if (isSessionLoaded()) await loadDrafts() if (isSessionLoaded()) loadDrafts()
}) })
const { const {
@ -32,7 +32,9 @@ export const DraftsView = () => {
const handleDraftDelete = async (shout: Shout) => { const handleDraftDelete = async (shout: Shout) => {
const result = deleteShout(shout.id) const result = deleteShout(shout.id)
if (result) await loadDrafts() if (result) {
setDrafts((ddd) => ddd.filter((d) => d.id !== shout.id))
}
} }
const handleDraftPublish = (shout: Shout) => { const handleDraftPublish = (shout: Shout) => {

View File

@ -21,6 +21,7 @@ import { Editor, Panel } from '../Editor'
import { AudioUploader } from '../Editor/AudioUploader' import { AudioUploader } from '../Editor/AudioUploader'
import { AutoSaveNotice } from '../Editor/AutoSaveNotice' import { AutoSaveNotice } from '../Editor/AutoSaveNotice'
import { VideoUploader } from '../Editor/VideoUploader' import { VideoUploader } from '../Editor/VideoUploader'
import { Modal } from '../Nav/Modal'
import { TableOfContents } from '../TableOfContents' import { TableOfContents } from '../TableOfContents'
import { PublishSettings } from './PublishSettings' import { PublishSettings } from './PublishSettings'
@ -413,7 +414,10 @@ export const EditView = (props: Props) => {
<PublishSettings shoutId={props.shout.id} form={form} /> <PublishSettings shoutId={props.shout.id} form={form} />
</Show> </Show>
<Panel shoutId={props.shout.id} /> <Panel shoutId={props.shout.id} />
<Modal variant="medium" name="inviteCoauthors">
<InviteMembers variant={'coauthors'} title={t('Invite experts')} /> <InviteMembers variant={'coauthors'} title={t('Invite experts')} />
</Modal>
</> </>
) )
} }

View File

@ -30,6 +30,7 @@ import { AuthorBadge } from '../../Author/AuthorBadge'
import { AuthorLink } from '../../Author/AuthorLink' import { AuthorLink } from '../../Author/AuthorLink'
import { ArticleCard } from '../../Feed/ArticleCard' import { ArticleCard } from '../../Feed/ArticleCard'
import { Sidebar } from '../../Feed/Sidebar' import { Sidebar } from '../../Feed/Sidebar'
import { Modal } from '../../Nav/Modal'
import styles from './Feed.module.scss' import styles from './Feed.module.scss'
import stylesBeside from '../../Feed/Beside.module.scss' import stylesBeside from '../../Feed/Beside.module.scss'
@ -439,7 +440,10 @@ export const FeedView = (props: Props) => {
shareUrl={getShareUrl({ pathname: `/${shareData().slug}` })} shareUrl={getShareUrl({ pathname: `/${shareData().slug}` })}
/> />
</Show> </Show>
<InviteMembers title={t('Invite experts')} variant={'coauthors'} />
<Modal variant="medium" name="inviteCoauthors">
<InviteMembers variant={'coauthors'} title={t('Invite experts')} />
</Modal>
</div> </div>
) )
} }

View File

@ -1,7 +1,6 @@
import { getPagePath } from '@nanostores/router' import { getPagePath } from '@nanostores/router'
import { batch, createEffect, createMemo, createSignal, For, onMount, Show } from 'solid-js' import { batch, createMemo, createSignal, For, onMount, Show } from 'solid-js'
import { useFollowing } from '../../context/following'
import { useLocalize } from '../../context/localize' import { useLocalize } from '../../context/localize'
import { apiClient } from '../../graphql/client/core' import { apiClient } from '../../graphql/client/core'
import { Shout, Topic } from '../../graphql/schema/core.gen' import { Shout, Topic } from '../../graphql/schema/core.gen'

View File

@ -19,6 +19,7 @@ import DialogHeader from '../../Inbox/DialogHeader'
import { Message } from '../../Inbox/Message' import { Message } from '../../Inbox/Message'
import MessagesFallback from '../../Inbox/MessagesFallback' import MessagesFallback from '../../Inbox/MessagesFallback'
import Search from '../../Inbox/Search' import Search from '../../Inbox/Search'
import { Modal } from '../../Nav/Modal'
import styles from './Inbox.module.scss' import styles from './Inbox.module.scss'
@ -181,7 +182,9 @@ export const InboxView = (props: Props) => {
return ( return (
<div class={clsx('container', styles.Inbox)}> <div class={clsx('container', styles.Inbox)}>
<Modal variant="medium" name="inviteMembers">
<InviteMembers title={t('Create Chat')} variant={'recipients'} /> <InviteMembers title={t('Create Chat')} variant={'recipients'} />
</Modal>
{/*<CreateModalContent users={recipients()} />*/} {/*<CreateModalContent users={recipients()} />*/}
<div class={clsx('row', styles.row)}> <div class={clsx('row', styles.row)}>
<div class={clsx(styles.chatList, 'col-md-8')}> <div class={clsx(styles.chatList, 'col-md-8')}>

View File

@ -8,7 +8,6 @@ import { Author } from '../../../graphql/schema/core.gen'
import { hideModal } from '../../../stores/ui' import { hideModal } from '../../../stores/ui'
import { useAuthorsStore } from '../../../stores/zine/authors' import { useAuthorsStore } from '../../../stores/zine/authors'
import { AuthorBadge } from '../../Author/AuthorBadge' import { AuthorBadge } from '../../Author/AuthorBadge'
import { Modal } from '../../Nav/Modal'
import { Button } from '../Button' import { Button } from '../Button'
import { DropdownSelect } from '../DropdownSelect' import { DropdownSelect } from '../DropdownSelect'
import { Loading } from '../Loading' import { Loading } from '../Loading'

View File

@ -8,6 +8,7 @@ import { createStore, SetStoreFunction } from 'solid-js/store'
import { apiClient } from '../graphql/client/core' import { apiClient } from '../graphql/client/core'
import { Topic, TopicInput } from '../graphql/schema/core.gen' import { Topic, TopicInput } from '../graphql/schema/core.gen'
import { router, useRouter } from '../stores/router' import { router, useRouter } from '../stores/router'
import { addArticles } from '../stores/zine/articles'
import { slugify } from '../utils/slugify' import { slugify } from '../utils/slugify'
import { useLocalize } from './localize' import { useLocalize } from './localize'
@ -122,20 +123,11 @@ export const EditorProvider = (props: { children: JSX.Element }) => {
const updateShout = async (formToUpdate: ShoutForm, { publish }: { publish: boolean }) => { const updateShout = async (formToUpdate: ShoutForm, { publish }: { publish: boolean }) => {
return await apiClient.updateArticle({ return await apiClient.updateArticle({
shoutId: formToUpdate.shoutId, shout_id: formToUpdate.shoutId,
shoutInput: { shout_input: {
body: formToUpdate.body, ...formToUpdate,
topics: formToUpdate.selectedTopics.map((topic) => topic2topicInput(topic)), // NOTE: first is main topics: formToUpdate.selectedTopics.map((topic) => topic2topicInput(topic)), // NOTE: first is main
// authors?: InputMaybe<Array<InputMaybe<Scalars['String']>>>
// community?: InputMaybe<Scalars['Int']>
// mainTopic: topic2topicInput(formToUpdate.mainTopic),
slug: formToUpdate.slug,
subtitle: formToUpdate.subtitle,
title: formToUpdate.title,
lead: formToUpdate.lead,
description: formToUpdate.description,
cover: formToUpdate.coverImageUrl, cover: formToUpdate.coverImageUrl,
media: formToUpdate.media,
}, },
publish, publish,
}) })
@ -204,13 +196,13 @@ export const EditorProvider = (props: { children: JSX.Element }) => {
} }
} }
const publishShoutById = async (shoutId: number) => { const publishShoutById = async (shout_id: number) => {
try { try {
await apiClient.updateArticle({ const newShout = await apiClient.updateArticle({
shoutId, shout_id,
publish: true, publish: true,
}) })
addArticles([newShout])
openPage(router, 'feed') openPage(router, 'feed')
} catch (error) { } catch (error) {
console.error('[publishShoutById]', error) console.error('[publishShoutById]', error)
@ -218,10 +210,10 @@ export const EditorProvider = (props: { children: JSX.Element }) => {
} }
} }
const deleteShout = async (shoutId: number) => { const deleteShout = async (shout_id: number) => {
try { try {
await apiClient.deleteShout({ await apiClient.deleteShout({
shoutId, shout_id,
}) })
return true return true
} catch { } catch {

View File

@ -13,6 +13,7 @@ import type {
QueryLoad_Shouts_SearchArgs, QueryLoad_Shouts_SearchArgs,
QueryLoad_Shouts_Random_TopArgs, QueryLoad_Shouts_Random_TopArgs,
Community, Community,
MutationDelete_ShoutArgs,
} from '../schema/core.gen' } from '../schema/core.gen'
import { createGraphQLClient } from '../createGraphQLClient' import { createGraphQLClient } from '../createGraphQLClient'
@ -149,22 +150,22 @@ export const apiClient = {
return response.data.create_shout.shout return response.data.create_shout.shout
}, },
updateArticle: async ({ updateArticle: async ({
shoutId, shout_id,
shoutInput, shout_input,
publish, publish,
}: { }: {
shoutId: number shout_id: number
shoutInput?: ShoutInput shout_input?: ShoutInput
publish: boolean publish: boolean
}): Promise<Shout> => { }): Promise<Shout> => {
const response = await apiClient.private const response = await apiClient.private
.mutation(updateArticle, { shoutId, shoutInput, publish }) .mutation(updateArticle, { shout_id, shout_input, 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 (params: MutationDelete_ShoutArgs): Promise<void> => {
const response = await apiClient.private.mutation(deleteShout, { shout_id: shoutId }).toPromise() const response = await apiClient.private.mutation(deleteShout, params).toPromise()
console.debug('[graphql.client.core] deleteShout:', response) console.debug('[graphql.client.core] deleteShout:', response)
}, },
getDrafts: async (): Promise<Shout[]> => { getDrafts: async (): Promise<Shout[]> => {

View File

@ -1,8 +1,8 @@
import { gql } from '@urql/core' import { gql } from '@urql/core'
export default gql` export default gql`
mutation DeleteShoutMutation($shoutId: Int!) { mutation DeleteShoutMutation($shout_id: Int!) {
delete_shout(shout_id: $shoutId) { delete_shout(shout_id: $shout_id) {
error error
} }
} }

View File

@ -1,8 +1,8 @@
import { gql } from '@urql/core' import { gql } from '@urql/core'
export default gql` export default gql`
mutation UpdateShoutMutation($shoutId: Int!, $shoutInput: ShoutInput, $publish: Boolean) { mutation UpdateShoutMutation($shout_id: Int!, $shout_input: ShoutInput, $publish: Boolean) {
update_shout(shout_id: $shoutId, shout_input: $shoutInput, publish: $publish) { update_shout(shout_id: $shout_id, shout_input: $shout_input, publish: $publish) {
error error
shout { shout {
id id
@ -12,7 +12,10 @@ export default gql`
lead lead
description description
body body
visibility created_at
updated_at
published_at
featured_at
} }
} }
} }

View File

@ -6,7 +6,6 @@ export default gql`
id id
kind kind
body body
range
reply_to reply_to
shout { shout {
id id

View File

@ -64,12 +64,14 @@ const topCommentedArticles = createLazyMemo(() => {
}) })
// eslint-disable-next-line sonarjs/cognitive-complexity // eslint-disable-next-line sonarjs/cognitive-complexity
const addArticles = (...args: Shout[][]) => { export const addArticles = (...args: Shout[][]) => {
const allArticles = args.flatMap((articles) => articles || []) const allArticles = args.flatMap((articles) => articles || [])
const newArticleEntities = allArticles.reduce( const newArticleEntities = allArticles.reduce(
(acc, article) => { (acc, article) => {
if (!acc[article.slug]) {
acc[article.slug] = article acc[article.slug] = article
}
return acc return acc
}, },
{} as { [articleSLug: string]: Shout }, {} as { [articleSLug: string]: Shout },