Merge branch 'api-refactor' into shigusegubu-themes3

This commit is contained in:
Henry Jameson 2026-06-17 19:10:07 +03:00
commit cfefb70b1f
14 changed files with 337 additions and 433 deletions

45
src/api/mfa.js Normal file
View file

@ -0,0 +1,45 @@
import { promisedRequest } from './helpers.js'
export const verifyOTPCode = ({
clientId,
clientSecret,
instance,
mfaToken,
code,
}) => {
const formData = new window.FormData()
formData.append('client_id', clientId)
formData.append('client_secret', clientSecret)
formData.append('mfa_token', mfaToken)
formData.append('code', code)
formData.append('challenge_type', 'totp')
return promisedRequest({
url: '/oauth/mfa/challenge',
method: 'POST',
formData,
})
}
export const verifyRecoveryCode = ({
clientId,
clientSecret,
instance,
mfaToken,
code,
}) => {
const formData = new window.FormData()
formData.append('client_id', clientId)
formData.append('client_secret', clientSecret)
formData.append('mfa_token', mfaToken)
formData.append('code', code)
formData.append('challenge_type', 'recovery')
return promisedRequest({
url: `${instance}/oauth/mfa/challenge`,
method: 'POST',
formData,
})
}

138
src/api/oauth.js Normal file
View file

@ -0,0 +1,138 @@
import { reduce } from 'lodash'
import { paramsString, promisedRequest } from './helpers.js'
import { StatusCodeError } from 'src/services/errors/errors.js'
const REDIRECT_URI = `${window.location.origin}/oauth-callback`
export const createApp = ({ instance }) => {
const formData = new window.FormData()
formData.append('client_name', 'PleromaFE')
formData.append('website', 'https://pleroma.social')
formData.append('redirect_uris', REDIRECT_URI)
formData.append('scopes', 'read write follow push admin')
return promisedRequest({
url: `${instance}/api/v1/apps`,
method: 'POST',
formData,
}).then(({ data, ...rest }) => ({
...rest,
data: {
...data,
clientId: data.client_id,
clientSecret: data.client_secret,
},
}))
}
export const getLoginUrl = ({ instance, clientId }) => {
const data = {
responseType: 'code',
clientId,
redirectUri: REDIRECT_URI,
scope: 'read write follow push admin',
}
return `${instance}/oauth/authorize${paramsString(data)}`
}
export const getTokenWithCredentials = ({
clientId,
clientSecret,
instance,
username,
password,
}) => {
const formData = new window.FormData()
formData.append('client_id', clientId)
formData.append('client_secret', clientSecret)
formData.append('grant_type', 'password')
formData.append('username', username)
formData.append('password', password)
return promisedRequest({
url: `${instance}/oauth/token`,
method: 'POST',
formData,
})
}
export const getToken = ({ clientId, clientSecret, instance, code }) => {
const formData = new window.FormData()
formData.append('client_id', clientId)
formData.append('client_secret', clientSecret)
formData.append('grant_type', 'authorization_code')
formData.append('code', code)
formData.append('redirect_uri', `${window.location.origin}/oauth-callback`)
return promisedRequest({
url: `${instance}/oauth/token`,
method: 'POST',
formData,
})
}
export const getClientToken = ({ clientId, clientSecret, instance }) => {
const formData = new window.FormData()
formData.append('client_id', clientId)
formData.append('client_secret', clientSecret)
formData.append('grant_type', 'client_credentials')
formData.append('redirect_uri', `${window.location.origin}/oauth-callback`)
return promisedRequest({
url: `${instance}/oauth/token`,
method: 'POST',
formData,
})
}
export const verifyOTPCode = ({ app, instance, mfaToken, code }) => {
const formData = new window.FormData()
formData.append('client_id', app.client_id)
formData.append('client_secret', app.client_secret)
formData.append('mfa_token', mfaToken)
formData.append('code', code)
formData.append('challenge_type', 'totp')
return promisedRequest({
url: `${instance}/oauth/mfa/challenge`,
method: 'POST',
formData,
})
}
export const verifyRecoveryCode = ({ app, instance, mfaToken, code }) => {
const formData = new window.FormData()
formData.append('client_id', app.client_id)
formData.append('client_secret', app.client_secret)
formData.append('mfa_token', mfaToken)
formData.append('code', code)
formData.append('challenge_type', 'recovery')
return promisedRequest({
url: `${instance}/oauth/mfa/challenge`,
method: 'POST',
formData,
})
}
export const revokeToken = ({ app, instance, token }) => {
const formData = new window.FormData()
formData.append('client_id', app.clientId)
formData.append('client_secret', app.clientSecret)
formData.append('token', token)
return promisedRequest({
url: `${instance}/oauth/revoke`,
method: 'POST',
formData,
})
}

View file

@ -14,9 +14,11 @@ import {
import { RegistrationError, StatusCodeError } from 'src/services/errors/errors' import { RegistrationError, StatusCodeError } from 'src/services/errors/errors'
const SUGGESTIONS_URL = '/api/v1/suggestions' const SUGGESTIONS_URL = '/api/v1/suggestions'
/* eslint-env browser */
const MASTODON_LOGIN_URL = '/api/v1/accounts/verify_credentials' const MASTODON_LOGIN_URL = '/api/v1/accounts/verify_credentials'
const MASTODON_REGISTRATION_URL = '/api/v1/accounts' const MASTODON_REGISTRATION_URL = '/api/v1/accounts'
const MASTODON_PASSWORD_RESET_URL = ({ email }) =>
`/auth/password${paramsString({ email })}`
const MASTODON_USER_FAVORITES_TIMELINE_URL = '/api/v1/favourites' const MASTODON_USER_FAVORITES_TIMELINE_URL = '/api/v1/favourites'
const MASTODON_USER_NOTIFICATIONS_URL = '/api/v1/notifications' const MASTODON_USER_NOTIFICATIONS_URL = '/api/v1/notifications'
const MASTODON_FOLLOWING_URL = ( const MASTODON_FOLLOWING_URL = (
@ -110,7 +112,7 @@ export const fetchUserByName = ({ name, credentials }) =>
credentials, credentials,
params: { acct: name }, params: { acct: name },
}) })
.then((data) => data.id) .then(({ data }) => data.id)
.catch((error) => { .catch((error) => {
if (error && error.statusCode === 404) { if (error && error.statusCode === 404) {
// Either the backend does not support lookup endpoint, // Either the backend does not support lookup endpoint,
@ -313,6 +315,13 @@ export const verifyCredentials = ({ credentials }) =>
credentials, credentials,
}).then(({ data, ...rest }) => ({ ...rest, data: parseUser(data) })) }).then(({ data, ...rest }) => ({ ...rest, data: parseUser(data) }))
export const resetPassword = ({ instance, email }) => {
return promisedRequest({
url: MASTODON_PASSWORD_RESET_URL({ email }),
method: 'POST',
})
}
export const suggestions = ({ credentials }) => export const suggestions = ({ credentials }) =>
promisedRequest({ promisedRequest({
url: SUGGESTIONS_URL, url: SUGGESTIONS_URL,

View file

@ -1,12 +1,12 @@
import { mapActions, mapState as mapPiniaState, mapStores } from 'pinia' import { mapActions, mapState as mapPiniaState, mapStores } from 'pinia'
import { mapState } from 'vuex' import { mapState } from 'vuex'
import oauthApi from '../../services/new_api/oauth.js'
import { useAuthFlowStore } from 'src/stores/auth_flow.js' import { useAuthFlowStore } from 'src/stores/auth_flow.js'
import { useInstanceStore } from 'src/stores/instance.js' import { useInstanceStore } from 'src/stores/instance.js'
import { useOAuthStore } from 'src/stores/oauth.js' import { useOAuthStore } from 'src/stores/oauth.js'
import { getLoginUrl, getTokenWithCredentials } from 'src/api/oauth.js'
import { library } from '@fortawesome/fontawesome-svg-core' import { library } from '@fortawesome/fontawesome-svg-core'
import { faTimes } from '@fortawesome/free-solid-svg-icons' import { faTimes } from '@fortawesome/free-solid-svg-icons'
@ -35,19 +35,13 @@ const LoginForm = {
this.isTokenAuth ? this.submitToken() : this.submitPassword() this.isTokenAuth ? this.submitToken() : this.submitPassword()
}, },
submitToken() { submitToken() {
const data = {
instance: this.server,
commit: this.$store.commit,
}
// NOTE: we do not really need the app token, but obtaining a token and // NOTE: we do not really need the app token, but obtaining a token and
// calling verify_credentials is the only way to ensure the app still works. // calling verify_credentials is the only way to ensure the app still works.
this.ensureAppToken().then(() => { this.ensureAppToken().then(() => {
const app = { window.location.href = getLoginUrl({
clientId: this.clientId, clientId: this.clientId,
clientSecret: this.clientSecret, instance: this.server,
} })
oauthApi.login({ ...app, ...data })
}) })
}, },
submitPassword() { submitPassword() {
@ -56,37 +50,31 @@ const LoginForm = {
// NOTE: we do not really need the app token, but obtaining a token and // NOTE: we do not really need the app token, but obtaining a token and
// calling verify_credentials is the only way to ensure the app still works. // calling verify_credentials is the only way to ensure the app still works.
this.ensureAppToken().then(() => { this.ensureAppToken().then(() => {
const app = { getTokenWithCredentials({
clientId: this.clientId, clientId: this.clientId,
clientSecret: this.clientSecret, clientSecret: this.clientSecret,
} instance: this.server,
username: this.user.username,
oauthApi password: this.user.password,
.getTokenWithCredentials({ }).then((result) => {
...app, if (result.error) {
instance: this.server, if (result.error === 'mfa_required') {
username: this.user.username, this.requireMFA({ settings: result })
password: this.user.password, } else if (result.identifier === 'password_reset_required') {
}) this.$router.push({
.then((result) => { name: 'password-reset',
if (result.error) { params: { passwordResetRequested: true },
if (result.error === 'mfa_required') { })
this.requireMFA({ settings: result }) } else {
} else if (result.identifier === 'password_reset_required') { this.error = result.error
this.$router.push({ this.focusOnPasswordInput()
name: 'password-reset',
params: { passwordResetRequested: true },
})
} else {
this.error = result.error
this.focusOnPasswordInput()
}
return
} }
this.login(result).then(() => { return
this.$router.push({ name: 'friends' }) }
}) this.login(result).then(() => {
this.$router.push({ name: 'friends' })
}) })
})
}) })
}, },
clearError() { clearError() {

View file

@ -1,11 +1,11 @@
import { mapActions, mapState, mapStores } from 'pinia' import { mapActions, mapState, mapStores } from 'pinia'
import mfaApi from '../../services/new_api/mfa.js'
import { useAuthFlowStore } from 'src/stores/auth_flow.js' import { useAuthFlowStore } from 'src/stores/auth_flow.js'
import { useInstanceStore } from 'src/stores/instance.js' import { useInstanceStore } from 'src/stores/instance.js'
import { useOAuthStore } from 'src/stores/oauth.js' import { useOAuthStore } from 'src/stores/oauth.js'
import { verifyRecoveryCode } from 'src/api/mfa.js'
import { library } from '@fortawesome/fontawesome-svg-core' import { library } from '@fortawesome/fontawesome-svg-core'
import { faTimes } from '@fortawesome/free-solid-svg-icons' import { faTimes } from '@fortawesome/free-solid-svg-icons'
@ -43,18 +43,18 @@ export default {
code: this.code, code: this.code,
} }
mfaApi.verifyRecoveryCode(data).then((result) => { verifyRecoveryCode(data)
if (result.error) { .then((result) => {
this.error = result.error this.login(result).then(() => {
this.$router.push({ name: 'friends' })
})
})
.catch((error) => {
this.error = error
this.code = null this.code = null
this.focusOnCodeInput() this.focusOnCodeInput()
return return
}
this.login(result).then(() => {
this.$router.push({ name: 'friends' })
}) })
})
}, },
}, },
} }

View file

@ -1,11 +1,11 @@
import { mapActions, mapState, mapStores } from 'pinia' import { mapActions, mapState, mapStores } from 'pinia'
import mfaApi from '../../services/new_api/mfa.js'
import { useAuthFlowStore } from 'src/stores/auth_flow.js' import { useAuthFlowStore } from 'src/stores/auth_flow.js'
import { useInstanceStore } from 'src/stores/instance.js' import { useInstanceStore } from 'src/stores/instance.js'
import { useOAuthStore } from 'src/stores/oauth.js' import { useOAuthStore } from 'src/stores/oauth.js'
import { verifyOTPCode } from 'src/api/mfa.js'
import { library } from '@fortawesome/fontawesome-svg-core' import { library } from '@fortawesome/fontawesome-svg-core'
import { faTimes } from '@fortawesome/free-solid-svg-icons' import { faTimes } from '@fortawesome/free-solid-svg-icons'
@ -46,18 +46,18 @@ export default {
code: this.code, code: this.code,
} }
mfaApi.verifyOTPCode(data).then((result) => { verifyOTPCode(data)
if (result.error) { .then(({ data: result }) => {
this.error = result.error this.login(result).then(() => {
this.$router.push({ name: 'friends' })
})
})
.catch((error) => {
this.error = error
this.code = null this.code = null
this.focusOnCodeInput() this.focusOnCodeInput()
return return
}
this.login(result).then(() => {
this.$router.push({ name: 'friends' })
}) })
})
}, },
}, },
} }

View file

@ -1,8 +1,8 @@
import oauth from '../../services/new_api/oauth.js'
import { useInstanceStore } from 'src/stores/instance.js' import { useInstanceStore } from 'src/stores/instance.js'
import { useOAuthStore } from 'src/stores/oauth.js' import { useOAuthStore } from 'src/stores/oauth.js'
import { getToken } from 'src/api/oauth.js'
const oac = { const oac = {
props: ['code'], props: ['code'],
mounted() { mounted() {
@ -10,18 +10,16 @@ const oac = {
const oauthStore = useOAuthStore() const oauthStore = useOAuthStore()
const { clientId, clientSecret } = oauthStore const { clientId, clientSecret } = oauthStore
oauth getToken({
.getToken({ clientId,
clientId, clientSecret,
clientSecret, instance: useInstanceStore().server,
instance: useInstanceStore().server, code: this.code,
code: this.code, }).then(({ data: result }) => {
}) oauthStore.setToken(result.access_token)
.then((result) => { this.$store.dispatch('loginUser', result.access_token)
oauthStore.setToken(result.access_token) this.$router.push({ name: 'friends' })
this.$store.dispatch('loginUser', result.access_token) })
this.$router.push({ name: 'friends' })
})
} }
}, },
} }

View file

@ -9,7 +9,6 @@ import {
uniq, uniq,
} from 'lodash' } from 'lodash'
import oauthApi from '../services/new_api/oauth.js'
import { import {
registerPushNotifications, registerPushNotifications,
unregisterPushNotifications, unregisterPushNotifications,
@ -30,6 +29,7 @@ import { useOAuthStore } from 'src/stores/oauth.js'
import { useSyncConfigStore } from 'src/stores/sync_config.js' import { useSyncConfigStore } from 'src/stores/sync_config.js'
import { useUserHighlightStore } from 'src/stores/user_highlight.js' import { useUserHighlightStore } from 'src/stores/user_highlight.js'
import { revokeToken } from 'src/api/oauth.js'
import { import {
fetchFollowers, fetchFollowers,
fetchFriends, fetchFriends,
@ -711,7 +711,7 @@ const users = {
token: oauth.userToken, token: oauth.userToken,
} }
return oauthApi.revokeToken(params) return revokeToken(params)
}) })
.then(() => { .then(() => {
store.commit('clearCurrentUser') store.commit('clearCurrentUser')

View file

@ -13,7 +13,7 @@ const fetchRelationship = (attempt, userId, store) =>
id: userId, id: userId,
credentials: useOAuthStore().token, credentials: useOAuthStore().token,
}) })
.then((relationship) => { .then(({ data: relationship }) => {
store.commit('updateUserRelationship', [relationship]) store.commit('updateUserRelationship', [relationship])
return relationship return relationship
}) })
@ -36,7 +36,7 @@ const fetchRelationship = (attempt, userId, store) =>
}) })
export const requestFollow = async (userId, store) => { export const requestFollow = async (userId, store) => {
const updated = await followUser({ const { data: updated } = await followUser({
id: userId, id: userId,
credentials: useOAuthStore().token, credentials: useOAuthStore().token,
}) })
@ -58,7 +58,7 @@ export const requestFollow = async (userId, store) => {
} }
export const requestUnfollow = async (userId, store) => { export const requestUnfollow = async (userId, store) => {
const updated = await unfollowUser({ const { data: updated } = await unfollowUser({
id: userId, id: userId,
credentials: useOAuthStore().token, credentials: useOAuthStore().token,
}) })

View file

@ -1,54 +0,0 @@
const verifyOTPCode = ({
clientId,
clientSecret,
instance,
mfaToken,
code,
}) => {
const url = `${instance}/oauth/mfa/challenge`
const form = new window.FormData()
form.append('client_id', clientId)
form.append('client_secret', clientSecret)
form.append('mfa_token', mfaToken)
form.append('code', code)
form.append('challenge_type', 'totp')
return window
.fetch(url, {
method: 'POST',
body: form,
})
.then((data) => data.json())
}
const verifyRecoveryCode = ({
clientId,
clientSecret,
instance,
mfaToken,
code,
}) => {
const url = `${instance}/oauth/mfa/challenge`
const form = new window.FormData()
form.append('client_id', clientId)
form.append('client_secret', clientSecret)
form.append('mfa_token', mfaToken)
form.append('code', code)
form.append('challenge_type', 'recovery')
return window
.fetch(url, {
method: 'POST',
body: form,
})
.then((data) => data.json())
}
const mfa = {
verifyOTPCode,
verifyRecoveryCode,
}
export default mfa

View file

@ -1,198 +0,0 @@
import { reduce } from 'lodash'
import { StatusCodeError } from 'src/services/errors/errors.js'
const REDIRECT_URI = `${window.location.origin}/oauth-callback`
export const getJsonOrError = async (response) => {
if (response.ok) {
return response.json().catch((error) => {
throw new StatusCodeError(response.status, error, {}, response)
})
} else {
throw new StatusCodeError(
response.status,
await response.text(),
{},
response,
)
}
}
export const createApp = (instance) => {
const url = `${instance}/api/v1/apps`
const form = new window.FormData()
form.append('client_name', 'PleromaFE')
form.append('website', 'https://pleroma.social')
form.append('redirect_uris', REDIRECT_URI)
form.append('scopes', 'read write follow push admin')
return window
.fetch(url, {
method: 'POST',
body: form,
})
.then(getJsonOrError)
.then((app) => ({
clientId: app.client_id,
clientSecret: app.client_secret,
}))
}
export const verifyAppToken = ({ instance, appToken }) => {
return window
.fetch(`${instance}/api/v1/apps/verify_credentials`, {
method: 'GET',
headers: { Authorization: `Bearer ${appToken}` },
})
.then(getJsonOrError)
}
const login = ({ instance, clientId }) => {
const data = {
response_type: 'code',
client_id: clientId,
redirect_uri: REDIRECT_URI,
scope: 'read write follow push admin',
}
const dataString = reduce(
data,
(acc, v, k) => {
const encoded = `${k}=${encodeURIComponent(v)}`
if (!acc) {
return encoded
} else {
return `${acc}&${encoded}`
}
},
false,
)
// Do the redirect...
const url = `${instance}/oauth/authorize?${dataString}`
window.location.href = url
}
const getTokenWithCredentials = ({
clientId,
clientSecret,
instance,
username,
password,
}) => {
const url = `${instance}/oauth/token`
const form = new window.FormData()
form.append('client_id', clientId)
form.append('client_secret', clientSecret)
form.append('grant_type', 'password')
form.append('username', username)
form.append('password', password)
return window
.fetch(url, {
method: 'POST',
body: form,
})
.then((data) => data.json())
}
const getToken = ({ clientId, clientSecret, instance, code }) => {
const url = `${instance}/oauth/token`
const form = new window.FormData()
form.append('client_id', clientId)
form.append('client_secret', clientSecret)
form.append('grant_type', 'authorization_code')
form.append('code', code)
form.append('redirect_uri', `${window.location.origin}/oauth-callback`)
return window
.fetch(url, {
method: 'POST',
body: form,
})
.then((data) => data.json())
}
export const getClientToken = ({ clientId, clientSecret, instance }) => {
const url = `${instance}/oauth/token`
const form = new window.FormData()
form.append('client_id', clientId)
form.append('client_secret', clientSecret)
form.append('grant_type', 'client_credentials')
form.append('redirect_uri', `${window.location.origin}/oauth-callback`)
return window
.fetch(url, {
method: 'POST',
body: form,
})
.then(getJsonOrError)
}
const verifyOTPCode = ({ app, instance, mfaToken, code }) => {
const url = `${instance}/oauth/mfa/challenge`
const form = new window.FormData()
form.append('client_id', app.client_id)
form.append('client_secret', app.client_secret)
form.append('mfa_token', mfaToken)
form.append('code', code)
form.append('challenge_type', 'totp')
return window
.fetch(url, {
method: 'POST',
body: form,
})
.then((data) => data.json())
}
const verifyRecoveryCode = ({ app, instance, mfaToken, code }) => {
const url = `${instance}/oauth/mfa/challenge`
const form = new window.FormData()
form.append('client_id', app.client_id)
form.append('client_secret', app.client_secret)
form.append('mfa_token', mfaToken)
form.append('code', code)
form.append('challenge_type', 'recovery')
return window
.fetch(url, {
method: 'POST',
body: form,
})
.then((data) => data.json())
}
const revokeToken = ({ app, instance, token }) => {
const url = `${instance}/oauth/revoke`
const form = new window.FormData()
form.append('client_id', app.clientId)
form.append('client_secret', app.clientSecret)
form.append('token', token)
return window
.fetch(url, {
method: 'POST',
body: form,
})
.then((data) => data.json())
}
const oauth = {
login,
getToken,
getTokenWithCredentials,
verifyOTPCode,
verifyRecoveryCode,
revokeToken,
}
export default oauth

View file

@ -1,22 +0,0 @@
import { reduce } from 'lodash'
const MASTODON_PASSWORD_RESET_URL = '/auth/password'
const resetPassword = ({ instance, email }) => {
const params = { email }
const query = reduce(
params,
(acc, v, k) => {
const encoded = `${k}=${encodeURIComponent(v)}`
return `${acc}&${encoded}`
},
'',
)
const url = `${instance}${MASTODON_PASSWORD_RESET_URL}?${query}`
return window.fetch(url, {
method: 'POST',
})
}
export default resetPassword

View file

@ -2,14 +2,7 @@ import { defineStore } from 'pinia'
import { useOAuthStore } from 'src/stores/oauth.js' import { useOAuthStore } from 'src/stores/oauth.js'
import {
getAnnouncements as adminGetAnnouncements,
deleteAnnouncement,
editAnnouncement,
postAnnouncement,
} from 'src/api/admin.js'
import { getAnnouncements } from 'src/api/public.js' import { getAnnouncements } from 'src/api/public.js'
import { dismissAnnouncement } from 'src/api/user.js'
const FETCH_ANNOUNCEMENT_INTERVAL_MS = 1000 * 60 * 5 const FETCH_ANNOUNCEMENT_INTERVAL_MS = 1000 * 60 * 5
@ -18,6 +11,8 @@ export const useAnnouncementsStore = defineStore('announcements', {
announcements: [], announcements: [],
supportsAnnouncements: true, supportsAnnouncements: true,
fetchAnnouncementsTimer: undefined, fetchAnnouncementsTimer: undefined,
adminActions: {},
userActions: {},
}), }),
getters: { getters: {
unreadAnnouncementCount() { unreadAnnouncementCount() {
@ -32,34 +27,41 @@ export const useAnnouncementsStore = defineStore('announcements', {
}, },
}, },
actions: { actions: {
fetchAnnouncements() { async fetchAnnouncements() {
if (!this.supportsAnnouncements) { if (!this.supportsAnnouncements) return
return Promise.resolve()
}
const currentUser = window.vuex.state.users.currentUser const currentUser = window.vuex.state.users.currentUser
const isAdmin = const isAdmin =
currentUser && currentUser &&
currentUser.privileges.has('announcements_manage_announcements') currentUser.privileges.has('announcements_manage_announcements')
const fetchAnnouncements = async () => { try {
if (!isAdmin) { if (currentUser) {
const result = await getAnnouncements({ this.userActions = await import('src/api/user.js')
credentials: useOAuthStore().token,
})
return result.data
} }
const { data: all } = await adminGetAnnouncements({ if (isAdmin) {
this.adminActions = await import('src/api/admin.js')
} else {
const all = await getAnnouncements({
credentials: useOAuthStore().token,
})
return all.data
}
const { data: all } = await this.adminActions.getAnnouncements({
credentials: useOAuthStore().token, credentials: useOAuthStore().token,
}) })
const { data: visible } = await getAnnouncements({ const { data: visible } = await getAnnouncements({
credentials: useOAuthStore().token, credentials: useOAuthStore().token,
}) })
const visibleObject = visible.reduce((a, c) => { const visibleObject = visible.reduce((a, c) => {
a[c.id] = c a[c.id] = c
return a return a
}, {}) }, {})
const getWithinVisible = (announcement) => const getWithinVisible = (announcement) =>
visibleObject[announcement.id] visibleObject[announcement.id]
@ -72,36 +74,32 @@ export const useAnnouncementsStore = defineStore('announcements', {
} }
}) })
return all this.announcements = all
} catch (error) {
// If and only if backend does not support announcements, it would return 404.
// In this case, silently ignores it.
if (error && error.statusCode === 404) {
this.supportsAnnouncements = false
} else {
throw error
}
} }
return fetchAnnouncements()
.then((announcements) => {
this.announcements = announcements
})
.catch((error) => {
// If and only if backend does not support announcements, it would return 404.
// In this case, silently ignores it.
if (error && error.statusCode === 404) {
this.supportsAnnouncements = false
} else {
throw error
}
})
}, },
markAnnouncementAsRead(id) { markAnnouncementAsRead(id) {
return dismissAnnouncement({ return this.userActions
id, .dismissAnnouncement({
credentials: useOAuthStore().token, id,
}).then(() => { credentials: useOAuthStore().token,
const index = this.announcements.findIndex((a) => a.id === id) })
.then(() => {
const index = this.announcements.findIndex((a) => a.id === id)
if (index < 0) { if (index < 0) {
return return
} }
this.announcements[index].read = true this.announcements[index].read = true
}) })
}, },
startFetchingAnnouncements() { startFetchingAnnouncements() {
if (this.fetchAnnouncementsTimer) { if (this.fetchAnnouncementsTimer) {
@ -122,35 +120,41 @@ export const useAnnouncementsStore = defineStore('announcements', {
clearInterval(interval) clearInterval(interval)
}, },
postAnnouncement({ content, startsAt, endsAt, allDay }) { postAnnouncement({ content, startsAt, endsAt, allDay }) {
return postAnnouncement({ return this.adminActions
credentials: useOAuthStore().token, .postAnnouncement({
content, credentials: useOAuthStore().token,
startsAt, content,
endsAt, startsAt,
allDay, endsAt,
}).then(() => { allDay,
return this.fetchAnnouncements() })
}) .then(() => {
return this.fetchAnnouncements()
})
}, },
editAnnouncement({ id, content, startsAt, endsAt, allDay }) { editAnnouncement({ id, content, startsAt, endsAt, allDay }) {
return editAnnouncement({ return this.adminActions
id, .editAnnouncement({
content, id,
startsAt, content,
endsAt, startsAt,
allDay, endsAt,
credentials: useOAuthStore().token, allDay,
}).then(() => { credentials: useOAuthStore().token,
return this.fetchAnnouncements() })
}) .then(() => {
return this.fetchAnnouncements()
})
}, },
deleteAnnouncement(id) { deleteAnnouncement(id) {
return deleteAnnouncement({ return this.adminActions
id, .deleteAnnouncement({
credentials: useOAuthStore().token, id,
}).then(() => { credentials: useOAuthStore().token,
return this.fetchAnnouncements() })
}) .then(() => {
return this.fetchAnnouncements()
})
}, },
}, },
}) })

View file

@ -2,11 +2,8 @@ import { defineStore } from 'pinia'
import { useInstanceStore } from 'src/stores/instance.js' import { useInstanceStore } from 'src/stores/instance.js'
import { import { createApp, getClientToken } from 'src/api/oauth.js'
createApp, import { verifyCredentials } from 'src/api/public.js'
getClientToken,
verifyAppToken,
} from 'src/services/new_api/oauth.js'
// status codes about verifyAppToken (GET /api/v1/apps/verify_credentials) // status codes about verifyAppToken (GET /api/v1/apps/verify_credentials)
const isAppTokenRejected = (error) => const isAppTokenRejected = (error) =>
@ -61,9 +58,9 @@ export const useOAuthStore = defineStore('oauth', {
}, },
async createApp() { async createApp() {
const instance = useInstanceStore().server const instance = useInstanceStore().server
const app = await createApp(instance) const app = await createApp({ instance })
this.setClientData(app) this.setClientData(app.data)
return app return app.data
}, },
/// Use this if you want to get the client id and secret but are not interested /// Use this if you want to get the client id and secret but are not interested
/// in whether they are valid. /// in whether they are valid.
@ -85,7 +82,7 @@ export const useOAuthStore = defineStore('oauth', {
clientSecret: this.clientSecret, clientSecret: this.clientSecret,
instance, instance,
}) })
this.setAppToken(res.access_token) this.setAppToken(res.data.access_token)
return res.access_token return res.access_token
}, },
/// Use this if you want to ensure the app is still valid to use. /// Use this if you want to ensure the app is still valid to use.
@ -93,9 +90,8 @@ export const useOAuthStore = defineStore('oauth', {
async ensureAppToken() { async ensureAppToken() {
if (this.appToken) { if (this.appToken) {
try { try {
await verifyAppToken({ await verifyCredentials({
instance: useInstanceStore().server, credentials: this.appToken,
appToken: this.appToken,
}) })
return this.appToken return this.appToken
} catch (e) { } catch (e) {