kopia lustrzana https://github.com/cloudflare/wildebeest
commit
0580e089ab
|
@ -151,7 +151,8 @@ export async function createPerson(
|
||||||
db: Database,
|
db: Database,
|
||||||
userKEK: string,
|
userKEK: string,
|
||||||
email: string,
|
email: string,
|
||||||
properties: PersonProperties = {}
|
properties: PersonProperties = {},
|
||||||
|
admin: boolean = false
|
||||||
): Promise<Person> {
|
): Promise<Person> {
|
||||||
const userKeyPair = await generateUserKey(userKEK)
|
const userKeyPair = await generateUserKey(userKEK)
|
||||||
|
|
||||||
|
@ -199,12 +200,12 @@ export async function createPerson(
|
||||||
const row = await db
|
const row = await db
|
||||||
.prepare(
|
.prepare(
|
||||||
`
|
`
|
||||||
INSERT INTO actors(id, type, email, pubkey, privkey, privkey_salt, properties)
|
INSERT INTO actors(id, type, email, pubkey, privkey, privkey_salt, properties, is_admin)
|
||||||
VALUES(?, ?, ?, ?, ?, ?, ?)
|
VALUES(?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
RETURNING *
|
RETURNING *
|
||||||
`
|
`
|
||||||
)
|
)
|
||||||
.bind(id, PERSON, email, userKeyPair.pubKey, privkey, salt, JSON.stringify(properties))
|
.bind(id, PERSON, email, userKeyPair.pubKey, privkey, salt, JSON.stringify(properties), admin ? 1 : null)
|
||||||
.first()
|
.first()
|
||||||
|
|
||||||
return personFromRow(row)
|
return personFromRow(row)
|
||||||
|
|
|
@ -7,6 +7,8 @@ import { createReply as createReplyInBackend } from 'wildebeest/backend/test/sha
|
||||||
import { createStatus } from 'wildebeest/backend/src/mastodon/status'
|
import { createStatus } from 'wildebeest/backend/src/mastodon/status'
|
||||||
import type { APObject } from 'wildebeest/backend/src/activitypub/objects'
|
import type { APObject } from 'wildebeest/backend/src/activitypub/objects'
|
||||||
import { type Database } from 'wildebeest/backend/src/database'
|
import { type Database } from 'wildebeest/backend/src/database'
|
||||||
|
import { upsertRule } from 'wildebeest/functions/api/wb/settings/server/rules'
|
||||||
|
import { upsertServerSettings } from 'wildebeest/functions/api/wb/settings/server/server'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Run helper commands to initialize the database with actors, statuses, etc.
|
* Run helper commands to initialize the database with actors, statuses, etc.
|
||||||
|
@ -41,6 +43,17 @@ export async function init(domain: string, db: Database) {
|
||||||
for (const reply of replies) {
|
for (const reply of replies) {
|
||||||
await createReply(domain, db, reply, loadedStatuses)
|
await createReply(domain, db, reply, loadedStatuses)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await createServerData(db)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createServerData(db: Database) {
|
||||||
|
await upsertServerSettings(db, {
|
||||||
|
'extended description': 'this is a test wildebeest instance!',
|
||||||
|
})
|
||||||
|
await upsertRule(db, "don't be mean")
|
||||||
|
await upsertRule(db, "don't insult people")
|
||||||
|
await upsertRule(db, 'respect the rules')
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -74,12 +87,21 @@ async function getOrCreatePerson(
|
||||||
db: Database,
|
db: Database,
|
||||||
{ username, avatar, display_name }: Account
|
{ username, avatar, display_name }: Account
|
||||||
): Promise<Person> {
|
): Promise<Person> {
|
||||||
const person = await getPersonByEmail(db, username)
|
const isAdmin = username === 'george'
|
||||||
|
const email = `${username}@test.email`
|
||||||
|
const person = await getPersonByEmail(db, email)
|
||||||
if (person) return person
|
if (person) return person
|
||||||
const newPerson = await createPerson(domain, db, 'test-kek', username, {
|
const newPerson = await createPerson(
|
||||||
icon: { url: avatar },
|
domain,
|
||||||
name: display_name,
|
db,
|
||||||
})
|
'test-kek',
|
||||||
|
email,
|
||||||
|
{
|
||||||
|
icon: { url: avatar },
|
||||||
|
name: display_name,
|
||||||
|
},
|
||||||
|
isAdmin
|
||||||
|
)
|
||||||
if (!newPerson) {
|
if (!newPerson) {
|
||||||
throw new Error('Could not create Actor ' + username)
|
throw new Error('Could not create Actor ' + username)
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,11 +10,11 @@
|
||||||
"lint": "eslint src mock-db adaptors",
|
"lint": "eslint src mock-db adaptors",
|
||||||
"build": "vite build && vite build -c adaptors/cloudflare-pages/vite.config.ts",
|
"build": "vite build && vite build -c adaptors/cloudflare-pages/vite.config.ts",
|
||||||
"dev": "vite --mode ssr",
|
"dev": "vite --mode ssr",
|
||||||
"watch": "concurrently \"vite build -w\" \"vite build -w -c adaptors/cloudflare-pages/vite.config.ts\""
|
"watch": "nodemon -w ./src --ext tsx,ts --exec npm run build"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@builder.io/qwik": "0.18.1",
|
"@builder.io/qwik": "0.19.2",
|
||||||
"@builder.io/qwik-city": "0.2.1",
|
"@builder.io/qwik-city": "0.4.0",
|
||||||
"@types/eslint": "8.4.10",
|
"@types/eslint": "8.4.10",
|
||||||
"@types/jest": "^29.2.4",
|
"@types/jest": "^29.2.4",
|
||||||
"@types/node": "^18.11.16",
|
"@types/node": "^18.11.16",
|
||||||
|
@ -22,12 +22,12 @@
|
||||||
"@typescript-eslint/eslint-plugin": "5.46.1",
|
"@typescript-eslint/eslint-plugin": "5.46.1",
|
||||||
"@typescript-eslint/parser": "5.46.1",
|
"@typescript-eslint/parser": "5.46.1",
|
||||||
"autoprefixer": "10.4.11",
|
"autoprefixer": "10.4.11",
|
||||||
"concurrently": "^7.6.0",
|
|
||||||
"eslint": "8.30.0",
|
"eslint": "8.30.0",
|
||||||
"eslint-plugin-qwik": "0.16.1",
|
"eslint-plugin-qwik": "0.16.1",
|
||||||
"jest": "^29.3.1",
|
"jest": "^29.3.1",
|
||||||
"lorem-ipsum": "^2.0.8",
|
"lorem-ipsum": "^2.0.8",
|
||||||
"node-fetch": "3.3.0",
|
"node-fetch": "3.3.0",
|
||||||
|
"nodemon": "^2.0.20",
|
||||||
"postcss": "^8.4.16",
|
"postcss": "^8.4.16",
|
||||||
"prettier": "2.8.1",
|
"prettier": "2.8.1",
|
||||||
"sass": "^1.57.0",
|
"sass": "^1.57.0",
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
import { component$, useSignal } from '@builder.io/qwik'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
label: string
|
||||||
|
name?: string
|
||||||
|
description?: string
|
||||||
|
class?: string
|
||||||
|
invalid?: boolean
|
||||||
|
value?: string
|
||||||
|
required?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TextArea = component$<Props>(
|
||||||
|
({ class: className, label, name, description, invalid, value, required }) => {
|
||||||
|
const inputId = useSignal(`${label.replace(/\s+/g, '_')}___${crypto.randomUUID()}`).value
|
||||||
|
return (
|
||||||
|
<div class={`mb-6 ${className || ''}`}>
|
||||||
|
<label class="font-semibold block mb-1" for={inputId}>
|
||||||
|
{label}
|
||||||
|
{!!required && <span class="ml-1 text-red-500">*</span>}
|
||||||
|
</label>
|
||||||
|
{!!description && <div class="text-sm inline-block mb-2 text-wildebeest-400">{description}</div>}
|
||||||
|
<textarea
|
||||||
|
class={`bg-black text-white p-3 rounded outline-none border hover:border-wildebeest-vibrant-500 focus:border-wildebeest-vibrant-500 w-full ${
|
||||||
|
invalid ? 'border-red-500' : 'border-black'
|
||||||
|
}`}
|
||||||
|
id={inputId}
|
||||||
|
name={name}
|
||||||
|
value={value}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
|
@ -0,0 +1,36 @@
|
||||||
|
import { component$, useSignal } from '@builder.io/qwik'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
label: string
|
||||||
|
name?: string
|
||||||
|
description?: string
|
||||||
|
class?: string
|
||||||
|
invalid?: boolean
|
||||||
|
value?: string
|
||||||
|
required?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TextInput = component$<Props>(
|
||||||
|
({ class: className, label, name, description, invalid, value, required }) => {
|
||||||
|
const inputId = useSignal(`${label.replace(/\s+/g, '_')}___${crypto.randomUUID()}`).value
|
||||||
|
const includeDefaultMb = !/(^|\s)m[y,b]?-\S+(\s|$)/.test(className || '')
|
||||||
|
return (
|
||||||
|
<div class={`${className || ''} ${includeDefaultMb ? 'mb-6' : ''}`}>
|
||||||
|
<label class="font-semibold block mb-2" for={inputId}>
|
||||||
|
{label}
|
||||||
|
{!!required && <span class="ml-1 text-red-500">*</span>}
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
class={`bg-black text-white p-3 mb-1 rounded outline-none border hover:border-wildebeest-vibrant-500 focus:border-wildebeest-vibrant-500 w-full ${
|
||||||
|
invalid ? 'border-red-500' : 'border-black'
|
||||||
|
}`}
|
||||||
|
type="text"
|
||||||
|
id={inputId}
|
||||||
|
name={name}
|
||||||
|
value={value}
|
||||||
|
/>
|
||||||
|
{!!description && <div class="text-sm text-wildebeest-400">{description}</div>}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
|
@ -1,5 +1,4 @@
|
||||||
import { component$ } from '@builder.io/qwik'
|
import { component$ } from '@builder.io/qwik'
|
||||||
import * as access from 'wildebeest/backend/src/access'
|
|
||||||
import type { Client } from 'wildebeest/backend/src/mastodon/client'
|
import type { Client } from 'wildebeest/backend/src/mastodon/client'
|
||||||
import { getClientById } from 'wildebeest/backend/src/mastodon/client'
|
import { getClientById } from 'wildebeest/backend/src/mastodon/client'
|
||||||
import { DocumentHead, loader$ } from '@builder.io/qwik-city'
|
import { DocumentHead, loader$ } from '@builder.io/qwik-city'
|
||||||
|
@ -9,8 +8,9 @@ import { getPersonByEmail } from 'wildebeest/backend/src/activitypub/actors'
|
||||||
import { getErrorHtml } from '~/utils/getErrorHtml/getErrorHtml'
|
import { getErrorHtml } from '~/utils/getErrorHtml/getErrorHtml'
|
||||||
import { buildRedirect } from 'wildebeest/functions/oauth/authorize'
|
import { buildRedirect } from 'wildebeest/functions/oauth/authorize'
|
||||||
import { getDatabase } from 'wildebeest/backend/src/database'
|
import { getDatabase } from 'wildebeest/backend/src/database'
|
||||||
|
import { getJwtEmail } from '~/utils/getJwtEmail'
|
||||||
|
|
||||||
export const clientLoader = loader$<Promise<Client>, { DATABASE: D1Database }>(async ({ platform, query, html }) => {
|
export const clientLoader = loader$<Promise<Client>>(async ({ platform, query, html }) => {
|
||||||
const client_id = query.get('client_id') || ''
|
const client_id = query.get('client_id') || ''
|
||||||
let client: Client | null = null
|
let client: Client | null = null
|
||||||
try {
|
try {
|
||||||
|
@ -26,50 +26,41 @@ export const clientLoader = loader$<Promise<Client>, { DATABASE: D1Database }>(a
|
||||||
return client
|
return client
|
||||||
})
|
})
|
||||||
|
|
||||||
export const userLoader = loader$<
|
export const userLoader = loader$<Promise<{ email: string; avatar: URL; name: string; url: URL }>>(
|
||||||
Promise<{ email: string; avatar: URL; name: string; url: URL }>,
|
async ({ cookie, platform, html, request, redirect, text }) => {
|
||||||
{ DATABASE: D1Database; domain: string }
|
const jwt = cookie.get('CF_Authorization')
|
||||||
>(async ({ cookie, platform, html, request, redirect, text }) => {
|
let email = ''
|
||||||
const jwt = cookie.get('CF_Authorization')
|
try {
|
||||||
if (jwt === null) {
|
email = getJwtEmail(jwt?.value ?? '')
|
||||||
throw html(500, getErrorHtml('Missing Authorization'))
|
} catch (e) {
|
||||||
}
|
throw html(500, getErrorHtml((e as Error)?.message))
|
||||||
let payload: access.JWTPayload
|
|
||||||
try {
|
|
||||||
// TODO: eventually, verify the JWT with Access, however this
|
|
||||||
// is not critical.
|
|
||||||
payload = access.getPayload(jwt.value)
|
|
||||||
} catch (e: unknown) {
|
|
||||||
const error = e as { stack: string; cause: string }
|
|
||||||
console.warn(error.stack, error.cause)
|
|
||||||
throw html(500, getErrorHtml('Failed to validate Access JWT'))
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!payload.email) {
|
|
||||||
throw html(500, getErrorHtml("The Access JWT doesn't contain an email"))
|
|
||||||
}
|
|
||||||
|
|
||||||
const person = await getPersonByEmail(await getDatabase(platform), payload.email)
|
|
||||||
if (person === null) {
|
|
||||||
const isFirstLogin = true
|
|
||||||
const res = await buildRedirect(await getDatabase(platform), request as Request, isFirstLogin, jwt.value)
|
|
||||||
if (res.status === 302) {
|
|
||||||
throw redirect(302, res.headers.get('location') || '')
|
|
||||||
} else {
|
|
||||||
throw text(res.status, await res.text())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const person = await getPersonByEmail(await getDatabase(platform), email)
|
||||||
|
if (person === null) {
|
||||||
|
const isFirstLogin = true
|
||||||
|
/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
|
-- jwt is defined otherwise getJwtEmail would have thrown
|
||||||
|
*/
|
||||||
|
const res = await buildRedirect(await getDatabase(platform), request as Request, isFirstLogin, jwt!.value)
|
||||||
|
if (res.status === 302) {
|
||||||
|
throw redirect(302, res.headers.get('location') || '')
|
||||||
|
} else {
|
||||||
|
throw text(res.status, await res.text())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const name = person.name
|
||||||
|
const avatar = person.icon?.url
|
||||||
|
const url = person.url
|
||||||
|
|
||||||
|
if (!name || !avatar) {
|
||||||
|
throw html(500, getErrorHtml("The person associated with the Access JWT doesn't include a name or avatar"))
|
||||||
|
}
|
||||||
|
|
||||||
|
return { email, avatar, name, url }
|
||||||
}
|
}
|
||||||
|
)
|
||||||
const name = person.name
|
|
||||||
const avatar = person.icon?.url
|
|
||||||
const url = person.url
|
|
||||||
|
|
||||||
if (!name || !avatar) {
|
|
||||||
throw html(500, getErrorHtml("The person associated with the Access JWT doesn't include a name or avatar"))
|
|
||||||
}
|
|
||||||
|
|
||||||
return { email: payload.email, avatar, name, url }
|
|
||||||
})
|
|
||||||
|
|
||||||
export default component$(() => {
|
export default component$(() => {
|
||||||
const client = clientLoader().value
|
const client = clientLoader().value
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { loader$ } from '@builder.io/qwik-city'
|
||||||
import { WildebeestEnv } from '~/types'
|
import { WildebeestEnv } from '~/types'
|
||||||
import { checkAuth } from '~/utils/checkAuth'
|
import { checkAuth } from '~/utils/checkAuth'
|
||||||
|
|
||||||
export const loader = loader$<WildebeestEnv, void>(async ({ request, platform, redirect }) => {
|
export const loader = loader$<void, WildebeestEnv>(async ({ request, platform, redirect }) => {
|
||||||
const isAuthorized = await checkAuth(request, platform)
|
const isAuthorized = await checkAuth(request, platform)
|
||||||
|
|
||||||
if (!isAuthorized) {
|
if (!isAuthorized) {
|
||||||
|
|
|
@ -1,7 +1,16 @@
|
||||||
import { component$, Slot } from '@builder.io/qwik'
|
import { component$, Slot } from '@builder.io/qwik'
|
||||||
import { WildebeestLogo } from '~/components/MastodonLogo'
|
import { WildebeestLogo } from '~/components/MastodonLogo'
|
||||||
|
|
||||||
|
import { loader$ } from '@builder.io/qwik-city'
|
||||||
|
import { getNotFoundHtml } from '~/utils/getNotFoundHtml/getNotFoundHtml'
|
||||||
|
|
||||||
|
export const loader = loader$(({ html }) => {
|
||||||
|
html(404, getNotFoundHtml())
|
||||||
|
})
|
||||||
|
|
||||||
export default component$(() => {
|
export default component$(() => {
|
||||||
|
loader()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="flex w-screen min-h-screen justify-center">
|
<div class="flex w-screen min-h-screen justify-center">
|
||||||
<AccountSidebar />
|
<AccountSidebar />
|
||||||
|
|
|
@ -0,0 +1,69 @@
|
||||||
|
import { component$ } from '@builder.io/qwik'
|
||||||
|
import { action$, Form, Link, z, zod$ } from '@builder.io/qwik-city'
|
||||||
|
import { getDatabase } from 'wildebeest/backend/src/database'
|
||||||
|
import { handleRequestPost } from 'wildebeest/functions/api/wb/settings/server/server'
|
||||||
|
import { TextArea } from '~/components/Settings/TextArea'
|
||||||
|
import { serverSettingsLoader } from '../layout'
|
||||||
|
|
||||||
|
const zodSchema = zod$({
|
||||||
|
'extended description': z.string().min(1),
|
||||||
|
'privacy policy': z.string().optional(),
|
||||||
|
})
|
||||||
|
|
||||||
|
export type ServerAboutData = Awaited<typeof zodSchema>['_type']
|
||||||
|
|
||||||
|
export const action = action$(async (data, { request, platform }) => {
|
||||||
|
let success = false
|
||||||
|
try {
|
||||||
|
const response = await handleRequestPost(
|
||||||
|
getDatabase(platform),
|
||||||
|
new Request(request, { body: JSON.stringify(data) })
|
||||||
|
)
|
||||||
|
success = response.ok
|
||||||
|
} catch (e: unknown) {
|
||||||
|
success = false
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
success,
|
||||||
|
}
|
||||||
|
}, zodSchema)
|
||||||
|
|
||||||
|
export default component$(() => {
|
||||||
|
const existingSettings = serverSettingsLoader()
|
||||||
|
const saveAction = action()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Form action={saveAction} spaReset>
|
||||||
|
<p class="mt-12 mb-9">Provide in-depth information about how the server is operated, moderated, funded.</p>
|
||||||
|
|
||||||
|
<div class="mb-12">
|
||||||
|
<TextArea
|
||||||
|
class="mb-1"
|
||||||
|
label="Extended description"
|
||||||
|
name="extended description"
|
||||||
|
description="Any additional information that may be useful to visitors and your users. Can be structured with Markdown syntax."
|
||||||
|
value={existingSettings.value['extended description']}
|
||||||
|
/>
|
||||||
|
<div class="text-sm text-wildebeest-400">
|
||||||
|
There is a dedicated area for rules that your users are expected to adhere to{' '}
|
||||||
|
<Link href="/settings/server-settings/rules">Manage server rules</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<TextArea
|
||||||
|
label="Privacy Policy"
|
||||||
|
description="Use your own privacy policy or leave blank to use the default. Can be structured with Markdown syntax."
|
||||||
|
name="privacy policy"
|
||||||
|
value={existingSettings.value['privacy policy']}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
class="w-full my-10 bg-wildebeest-vibrant-600 hover:bg-wildebeest-vibrant-500 p-2 text-white text-uppercase border-wildebeest-vibrant-600 text-lg text-semi outline-none border rounded hover:border-wildebeest-vibrant-500 focus:border-wildebeest-vibrant-500"
|
||||||
|
>
|
||||||
|
Save Changes
|
||||||
|
</button>
|
||||||
|
</Form>
|
||||||
|
)
|
||||||
|
})
|
|
@ -0,0 +1,71 @@
|
||||||
|
import { component$ } from '@builder.io/qwik'
|
||||||
|
import { action$, Form, zod$, z } from '@builder.io/qwik-city'
|
||||||
|
import { getDatabase } from 'wildebeest/backend/src/database'
|
||||||
|
import { handleRequestPost } from 'wildebeest/functions/api/wb/settings/server/server'
|
||||||
|
import { TextArea } from '~/components/Settings/TextArea'
|
||||||
|
import { TextInput } from '~/components/Settings/TextInput'
|
||||||
|
import { serverSettingsLoader } from '../layout'
|
||||||
|
|
||||||
|
const zodSchema = zod$({
|
||||||
|
'server name': z.string().min(1),
|
||||||
|
'server description': z.string().min(1),
|
||||||
|
})
|
||||||
|
|
||||||
|
export type ServerBrandingData = Awaited<typeof zodSchema>['_type']
|
||||||
|
|
||||||
|
export const action = action$(async (data, { request, platform }) => {
|
||||||
|
let success = false
|
||||||
|
try {
|
||||||
|
const response = await handleRequestPost(
|
||||||
|
await getDatabase(platform),
|
||||||
|
new Request(request, { body: JSON.stringify(data) })
|
||||||
|
)
|
||||||
|
success = response.ok
|
||||||
|
} catch (e: unknown) {
|
||||||
|
success = false
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
success,
|
||||||
|
}
|
||||||
|
}, zodSchema)
|
||||||
|
|
||||||
|
export default component$(() => {
|
||||||
|
const existingSettings = serverSettingsLoader()
|
||||||
|
const saveAction = action()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Form action={saveAction} spaReset>
|
||||||
|
<p class="mt-12 mb-9">
|
||||||
|
Your server's branding differentiates it from other servers in the network. This information may be displayed
|
||||||
|
across a variety of environments, such as Mastodon's web interface, native applications, in link previews on
|
||||||
|
other websites and within messaging apps, and so on. For this reason, it is best to keep this information clear,
|
||||||
|
short and concise.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<TextInput
|
||||||
|
class="mb-9"
|
||||||
|
label="Server name"
|
||||||
|
name="server name"
|
||||||
|
value={existingSettings.value['server name']}
|
||||||
|
invalid={!!saveAction.value?.fieldErrors?.['server name']}
|
||||||
|
description="How people may refer to your server besides its domain name."
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextArea
|
||||||
|
label="Server description"
|
||||||
|
name="server description"
|
||||||
|
value={existingSettings.value['server description']}
|
||||||
|
invalid={!!saveAction.value?.fieldErrors?.['server description']}
|
||||||
|
description="A short description to help uniquely identify your server. Who is running it, who is it for?"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
class="w-full my-10 bg-wildebeest-vibrant-600 hover:bg-wildebeest-vibrant-500 p-2 text-white text-uppercase border-wildebeest-vibrant-600 text-lg text-semi outline-none border rounded hover:border-wildebeest-vibrant-500 focus:border-wildebeest-vibrant-500"
|
||||||
|
>
|
||||||
|
Save Changes
|
||||||
|
</button>
|
||||||
|
</Form>
|
||||||
|
)
|
||||||
|
})
|
|
@ -0,0 +1,8 @@
|
||||||
|
import { component$ } from '@builder.io/qwik'
|
||||||
|
import { loader$ } from '@builder.io/qwik-city'
|
||||||
|
|
||||||
|
export const loader = loader$(({ redirect }) => {
|
||||||
|
redirect(303, 'server-settings/branding')
|
||||||
|
})
|
||||||
|
|
||||||
|
export default component$(() => <></>)
|
|
@ -0,0 +1,74 @@
|
||||||
|
import { component$, Slot } from '@builder.io/qwik'
|
||||||
|
import { Link, loader$, useLocation } from '@builder.io/qwik-city'
|
||||||
|
import { getDatabase } from 'wildebeest/backend/src/database'
|
||||||
|
import { handleRequestGet } from 'wildebeest/functions/api/wb/settings/server/server'
|
||||||
|
import { ServerAboutData } from './about'
|
||||||
|
import { ServerBrandingData } from './branding'
|
||||||
|
|
||||||
|
export type ServerSettingsData = ServerBrandingData & ServerAboutData
|
||||||
|
|
||||||
|
export const serverSettingsLoader = loader$<Promise<Partial<ServerSettingsData>>>(async ({ platform }) => {
|
||||||
|
const database = await getDatabase(platform)
|
||||||
|
|
||||||
|
const settingsResp = await handleRequestGet(database)
|
||||||
|
let settingsData: Partial<ServerSettingsData> = {}
|
||||||
|
try {
|
||||||
|
settingsData = await settingsResp.json()
|
||||||
|
} catch {
|
||||||
|
settingsData = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
return JSON.parse(JSON.stringify(settingsData))
|
||||||
|
})
|
||||||
|
|
||||||
|
export default component$(() => {
|
||||||
|
const sectionLinks = [
|
||||||
|
{
|
||||||
|
text: 'Branding',
|
||||||
|
faIcon: 'fa-pen',
|
||||||
|
path: 'branding',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'About',
|
||||||
|
faIcon: 'fa-file-lines',
|
||||||
|
path: 'about',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Rules',
|
||||||
|
faIcon: 'fa-pen-ruler',
|
||||||
|
path: 'rules',
|
||||||
|
},
|
||||||
|
] as const
|
||||||
|
|
||||||
|
const currentPath = useLocation().url.pathname.replace(/\/$/, '')
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div class="max-w-4xl py-14 px-8">
|
||||||
|
<h2 class="text-2xl font-bold mb-6">Server Settings</h2>
|
||||||
|
|
||||||
|
<ul class="flex gap-4 mb-6">
|
||||||
|
{sectionLinks.map(({ text, faIcon, path }) => {
|
||||||
|
const isActive = currentPath.endsWith(path)
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
key={text}
|
||||||
|
class={`
|
||||||
|
py-2 px-3 rounded text-sm no-underline flex gap-2
|
||||||
|
${
|
||||||
|
isActive
|
||||||
|
? 'bg-wildebeest-vibrant-500 hover:bg-wildebeest-vibrant-400 focus-visible:bg-wildebeest-vibrant-400'
|
||||||
|
: 'hover:bg-wildebeest-700 focus-visible:bg-wildebeest-700'
|
||||||
|
}`}
|
||||||
|
href={`/settings/server-settings/${path}`}
|
||||||
|
>
|
||||||
|
<i class={`fa-solid ${faIcon} leading-normal w-3 h-3`}></i>
|
||||||
|
{text}
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<Slot />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})
|
|
@ -0,0 +1,96 @@
|
||||||
|
import { component$ } from '@builder.io/qwik'
|
||||||
|
import { action$, Form, loader$, useNavigate, z, zod$ } from '@builder.io/qwik-city'
|
||||||
|
import { getDatabase } from 'wildebeest/backend/src/database'
|
||||||
|
import { handleRequestGet } from 'wildebeest/functions/api/v1/instance/rules'
|
||||||
|
import { upsertRule } from 'wildebeest/functions/api/wb/settings/server/rules'
|
||||||
|
import { TextArea } from '~/components/Settings/TextArea'
|
||||||
|
import { getErrorHtml } from '~/utils/getErrorHtml/getErrorHtml'
|
||||||
|
|
||||||
|
export type ServerSettingsData = { rules: string[] }
|
||||||
|
|
||||||
|
export const editAction = action$(
|
||||||
|
async (data, { platform }) => {
|
||||||
|
let success = false
|
||||||
|
try {
|
||||||
|
const result = await upsertRule(await getDatabase(platform), {
|
||||||
|
id: +data.id,
|
||||||
|
text: data.text,
|
||||||
|
})
|
||||||
|
success = result.success
|
||||||
|
} catch (e: unknown) {
|
||||||
|
success = false
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
success,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
zod$({
|
||||||
|
id: z.string().min(1),
|
||||||
|
text: z.string().min(1),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
export const ruleLoader = loader$<Promise<{ id: number; text: string }>>(async ({ params, platform, html }) => {
|
||||||
|
const database = await getDatabase(platform)
|
||||||
|
|
||||||
|
const settingsResp = await handleRequestGet(database)
|
||||||
|
let rules: { id: number; text: string }[] = []
|
||||||
|
try {
|
||||||
|
rules = await settingsResp.json()
|
||||||
|
} catch {
|
||||||
|
rules = []
|
||||||
|
}
|
||||||
|
|
||||||
|
const rule: { id: number; text: string } | undefined = rules.find((r) => r.id === +params['id'])
|
||||||
|
|
||||||
|
if (!rule) {
|
||||||
|
throw html(404, getErrorHtml('The selected rule could not be found'))
|
||||||
|
}
|
||||||
|
|
||||||
|
return JSON.parse(JSON.stringify(rule))
|
||||||
|
})
|
||||||
|
|
||||||
|
export default component$(() => {
|
||||||
|
const rule = ruleLoader()
|
||||||
|
const editActionObj = editAction()
|
||||||
|
|
||||||
|
const nav = useNavigate()
|
||||||
|
|
||||||
|
if (editActionObj.value?.success) {
|
||||||
|
nav('/settings/server-settings/rules')
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Form action={editActionObj} spaReset>
|
||||||
|
<p class="mt-12 mb-9">
|
||||||
|
While most claim to have read and agree to the terms of service, usually people do not read through until
|
||||||
|
after a problem arises. Make it easier to see your server's rules at a glance by providing them in a flat
|
||||||
|
bullet point list. Try to keep individual rules short and simple, but try not to split them up into many
|
||||||
|
separate items either.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<input hidden name="id" value={rule.value.id} />
|
||||||
|
|
||||||
|
<div class="mb-12">
|
||||||
|
<TextArea
|
||||||
|
class="mb-1"
|
||||||
|
label="Rule"
|
||||||
|
required
|
||||||
|
name="text"
|
||||||
|
value={rule.value.text}
|
||||||
|
description="Describe a rule or requirement for users on this server. Try to keep it short and simple."
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
class="w-full my-5 bg-wildebeest-vibrant-600 hover:bg-wildebeest-vibrant-500 p-2 text-white text-uppercase border-wildebeest-vibrant-600 text-lg text-semi outline-none border rounded hover:border-wildebeest-vibrant-500 focus:border-wildebeest-vibrant-500"
|
||||||
|
>
|
||||||
|
Save Changes
|
||||||
|
</button>
|
||||||
|
</Form>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
})
|
|
@ -0,0 +1,122 @@
|
||||||
|
import { component$ } from '@builder.io/qwik'
|
||||||
|
import { action$, Form, Link, loader$, z, zod$ } from '@builder.io/qwik-city'
|
||||||
|
import { getDatabase } from 'wildebeest/backend/src/database'
|
||||||
|
import { handleRequestGet } from 'wildebeest/functions/api/v1/instance/rules'
|
||||||
|
import { deleteRule, upsertRule } from 'wildebeest/functions/api/wb/settings/server/rules'
|
||||||
|
import { TextArea } from '~/components/Settings/TextArea'
|
||||||
|
|
||||||
|
export type ServerSettingsData = { rules: string[] }
|
||||||
|
|
||||||
|
export const addAction = action$(
|
||||||
|
async (data, { platform }) => {
|
||||||
|
let success = false
|
||||||
|
try {
|
||||||
|
const result = await upsertRule(await getDatabase(platform), data.text)
|
||||||
|
success = result.success
|
||||||
|
} catch (e: unknown) {
|
||||||
|
success = false
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
success,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
zod$({
|
||||||
|
text: z.string().min(1),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
export const deleteAction = action$(
|
||||||
|
async (data, { platform }) => {
|
||||||
|
let success = false
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await deleteRule(await getDatabase(platform), data.id)
|
||||||
|
success = result.success
|
||||||
|
} catch (e: unknown) {
|
||||||
|
success = false
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
success,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
zod$({
|
||||||
|
id: z.number(),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
export const rulesLoader = loader$<Promise<{ id: number; text: string }[]>>(async ({ platform }) => {
|
||||||
|
const database = await getDatabase(platform)
|
||||||
|
|
||||||
|
const settingsResp = await handleRequestGet(database)
|
||||||
|
let rules: { id: number; text: string }[] = []
|
||||||
|
try {
|
||||||
|
rules = await settingsResp.json()
|
||||||
|
} catch {
|
||||||
|
rules = []
|
||||||
|
}
|
||||||
|
|
||||||
|
return JSON.parse(JSON.stringify(rules))
|
||||||
|
})
|
||||||
|
|
||||||
|
export default component$(() => {
|
||||||
|
const rules = rulesLoader()
|
||||||
|
const addActionObj = addAction()
|
||||||
|
const deleteActionObj = deleteAction()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Form action={addActionObj} spaReset>
|
||||||
|
<p class="mt-12 mb-9">
|
||||||
|
While most claim to have read and agree to the terms of service, usually people do not read through until
|
||||||
|
after a problem arises. Make it easier to see your server's rules at a glance by providing them in a flat
|
||||||
|
bullet point list. Try to keep individual rules short and simple, but try not to split them up into many
|
||||||
|
separate items either.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="mb-12">
|
||||||
|
<TextArea
|
||||||
|
class="mb-1"
|
||||||
|
label="Rule"
|
||||||
|
name="text"
|
||||||
|
required
|
||||||
|
description="Describe a rule or requirement for users on this server. Try to keep it short and simple."
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
class="w-full my-5 bg-wildebeest-vibrant-600 hover:bg-wildebeest-vibrant-500 p-2 text-white text-uppercase border-wildebeest-vibrant-600 text-lg text-semi outline-none border rounded hover:border-wildebeest-vibrant-500 focus:border-wildebeest-vibrant-500"
|
||||||
|
>
|
||||||
|
Add Rule
|
||||||
|
</button>
|
||||||
|
</Form>
|
||||||
|
<div>
|
||||||
|
{rules.value.map(({ id, text }, idx) => {
|
||||||
|
const ruleId = idx + 1
|
||||||
|
const ruleBtnText = `${ruleId}. ${text.slice(0, 27)}${text.length > 27 ? '...' : ''}`
|
||||||
|
return (
|
||||||
|
<div key={id} class="p-4 my-4 bg-wildebeest-600 rounded">
|
||||||
|
<Link href={`./edit/${ruleId}`} class="max-w-max inline-block mb-4 no-underline text-lg font-semibold">
|
||||||
|
{ruleBtnText}
|
||||||
|
</Link>
|
||||||
|
<div class="flex justify-between text-wildebeest-400">
|
||||||
|
<span>{text}</span>
|
||||||
|
<button
|
||||||
|
onClick$={() => {
|
||||||
|
if (confirm('Are you sure?')) {
|
||||||
|
deleteActionObj.run({ id })
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<i class="fa-solid fa-trash"></i> Delete
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
})
|
|
@ -15,8 +15,7 @@ import { getDatabase } from 'wildebeest/backend/src/database'
|
||||||
import { Person } from 'wildebeest/backend/src/activitypub/actors'
|
import { Person } from 'wildebeest/backend/src/activitypub/actors'
|
||||||
|
|
||||||
export const accountPageLoader = loader$<
|
export const accountPageLoader = loader$<
|
||||||
Promise<{ account: MastodonAccount; accountHandle: string; isValidStatus: boolean }>,
|
Promise<{ account: MastodonAccount; accountHandle: string; isValidStatus: boolean }>
|
||||||
{ DATABASE: D1Database }
|
|
||||||
>(async ({ platform, params, request, html }) => {
|
>(async ({ platform, params, request, html }) => {
|
||||||
let isValidStatus = false
|
let isValidStatus = false
|
||||||
let account: MastodonAccount | null = null
|
let account: MastodonAccount | null = null
|
||||||
|
|
|
@ -1,66 +1,76 @@
|
||||||
import { component$ } from '@builder.io/qwik'
|
import { component$ } from '@builder.io/qwik'
|
||||||
import { DocumentHead, loader$ } from '@builder.io/qwik-city'
|
import { DocumentHead, loader$ } from '@builder.io/qwik-city'
|
||||||
|
import { getDatabase } from 'wildebeest/backend/src/database'
|
||||||
import { getDomain } from 'wildebeest/backend/src/utils/getDomain'
|
import { getDomain } from 'wildebeest/backend/src/utils/getDomain'
|
||||||
|
import { handleRequestGet as settingsHandleRequestGet } from 'wildebeest/functions/api/wb/settings/server/server'
|
||||||
|
import { handleRequestGet as rulesHandleRequestGet } from 'wildebeest/functions/api/v1/instance/rules'
|
||||||
import { Accordion } from '~/components/Accordion/Accordion'
|
import { Accordion } from '~/components/Accordion/Accordion'
|
||||||
import { AccountCard } from '~/components/AccountCard/AccountCard'
|
// import { AccountCard } from '~/components/AccountCard/AccountCard'
|
||||||
import { HtmlContent } from '~/components/HtmlContent/HtmlContent'
|
import { HtmlContent } from '~/components/HtmlContent/HtmlContent'
|
||||||
import { george } from '~/dummyData/accounts'
|
import { ServerSettingsData } from '~/routes/(admin)/settings/server-settings/layout'
|
||||||
import { Account } from '~/types'
|
import { Account } from '~/types'
|
||||||
import { getDocumentHead } from '~/utils/getDocumentHead'
|
import { getDocumentHead } from '~/utils/getDocumentHead'
|
||||||
import { instanceLoader } from '../layout'
|
import { instanceLoader } from '../layout'
|
||||||
|
import { getAdmins } from 'wildebeest/functions/api/wb/settings/server/admins'
|
||||||
|
import { emailSymbol } from 'wildebeest/backend/src/activitypub/actors'
|
||||||
|
import { loadLocalMastodonAccount } from 'wildebeest/backend/src/mastodon/account'
|
||||||
|
import { AccountCard } from '~/components/AccountCard/AccountCard'
|
||||||
|
import { getNotFoundHtml } from '~/utils/getNotFoundHtml/getNotFoundHtml'
|
||||||
|
|
||||||
type AboutInfo = {
|
type AboutInfo = {
|
||||||
image: string
|
image: string
|
||||||
domain: string
|
domain: string
|
||||||
contact: {
|
admin: { account: Account | null; email: string }
|
||||||
account: Account
|
rules: { id: number; text: string }[]
|
||||||
email: string
|
|
||||||
}
|
|
||||||
rules: { id: string; text: string }[]
|
|
||||||
extended_description: {
|
extended_description: {
|
||||||
updated_at: string
|
|
||||||
content: string
|
content: string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const aboutInfoLoader = loader$<Promise<AboutInfo>>(async ({ resolveValue, request, redirect }) => {
|
export const aboutInfoLoader = loader$<Promise<AboutInfo>>(async ({ resolveValue, request, platform, html }) => {
|
||||||
// TODO: properly implement loader and remove redirect
|
throw html(404, getNotFoundHtml())
|
||||||
throw redirect(302, '/')
|
|
||||||
|
|
||||||
|
// TODO: fetching the instance for the thumbnail, but that should be part of the settings
|
||||||
const instance = await resolveValue(instanceLoader)
|
const instance = await resolveValue(instanceLoader)
|
||||||
|
|
||||||
|
const database = await getDatabase(platform)
|
||||||
|
|
||||||
|
const brandingDataResp = await settingsHandleRequestGet(database)
|
||||||
|
let brandingData: ServerSettingsData | null
|
||||||
|
try {
|
||||||
|
brandingData = await brandingDataResp.json()
|
||||||
|
} catch {
|
||||||
|
brandingData = null
|
||||||
|
}
|
||||||
|
|
||||||
|
const rulesResp = await rulesHandleRequestGet(database)
|
||||||
|
let rules: { id: number; text: string }[] = []
|
||||||
|
try {
|
||||||
|
rules = await rulesResp.json()
|
||||||
|
} catch {
|
||||||
|
rules = []
|
||||||
|
}
|
||||||
|
|
||||||
|
const admins = await getAdmins(database)
|
||||||
|
let adminAccount: Account | null = null
|
||||||
|
|
||||||
|
const adminPerson = admins.find((admin) => admin[emailSymbol] === platform.ADMIN_EMAIL)
|
||||||
|
|
||||||
|
if (adminPerson) {
|
||||||
|
try {
|
||||||
|
adminAccount = (await loadLocalMastodonAccount(database, adminPerson)) as Account
|
||||||
|
} catch {
|
||||||
|
/* empty */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
image: instance.thumbnail,
|
image: instance.thumbnail,
|
||||||
domain: getDomain(request.url),
|
domain: getDomain(request.url),
|
||||||
contact: {
|
admin: { account: JSON.parse(JSON.stringify(adminAccount)), email: platform.ADMIN_EMAIL },
|
||||||
account: george,
|
rules: JSON.parse(JSON.stringify(rules.sort(({ id: idA }, { id: idB }) => idA - idB))),
|
||||||
email: 'test@test.com',
|
|
||||||
},
|
|
||||||
rules: [
|
|
||||||
{
|
|
||||||
id: '1',
|
|
||||||
text: 'Sexually explicit or violent media must be marked as sensitive when posting',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '2',
|
|
||||||
text: 'No racism, sexism, homophobia, transphobia, xenophobia, or casteism',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '3',
|
|
||||||
text: 'No incitement of violence or promotion of violent ideologies',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '4',
|
|
||||||
text: 'No harassment, dogpiling or doxxing of other users',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '7',
|
|
||||||
text: 'Do not share intentionally false or misleading information',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
extended_description: {
|
extended_description: {
|
||||||
updated_at: '2023-01-19T14:55:44Z',
|
content: brandingData?.['extended description'] ?? '',
|
||||||
content:
|
|
||||||
'<p>Please mind that the <a href="mailto:staff@mastodon.social">staff@mastodon.social</a> e-mail is for inquiries related to the operation of the mastodon.social server specifically. If your account is on another server, <strong>we will not be able to assist you</strong>. For inquiries not related specifically to the operation of this server, such as press inquiries about Mastodon gGmbH, please contact <a href="mailto:press@joinmastodon.org">press@joinmastodon.org</a>. Additional addresses:</p>\n\n<ul>\n<li>Legal, GDPR, DMCA: <a href="mailto:legal@mastodon.social">legal@mastodon.social</a></li>\n<li>Appeals: <a href="mailto:moderation@mastodon.social">moderation@mastodon.social</a></li>\n</ul>\n\n<h2>Funding</h2>\n\n<p>This server is crowdfunded by <a href="https://patreon.com/mastodon">Patreon donations</a>. For a list of sponsors, see <a href="https://joinmastodon.org/sponsors">joinmastodon.org</a>.</p>\n\n<h2>Reporting and moderation</h2>\n\n<p>When reporting accounts, please make sure to include at least a few posts that show rule-breaking behaviour, when applicable. If there is any additional context that might help make a decision, please also include it in the comment. This is especially important when the content is in a language nobody on the moderation team speaks.</p>\n\n<p>We usually handle reports within 24 hours. Please mind that you are not notified when a report you have made has led to a punitive action, and that not all punitive actions are externally visible. For first time offenses, we may opt to delete offending content, escalating to harsher measures on repeat offenses.</p>\n\n<p>We have a team of paid moderators. If you would like to become a moderator, get in touch with us through the e-mail address above.</p>\n\n<h2>Impressum</h2>\n\n<p>Mastodon gGmbH<br>\nMühlenstraße 8a<br>\n14167 Berlin<br>\nGermany</p>\n\n<p>E-Mail-Adresse: hello@joinmastodon.org</p>\n\n<p>Vertretungsberechtigt: Eugen Rochko (Geschäftsführer)</p>\n\n<p>Umsatzsteuer Identifikationsnummer (USt-ID): DE344258260</p>\n\n<p>Handelsregister<br>\nGeführt bei: Amtsgericht Charlottenburg<br>\nNummer: HRB 230086 B</p>\n',
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -85,14 +95,19 @@ export default component$(() => {
|
||||||
</span>
|
</span>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div class="rounded bg-wildebeest-700 flex flex-col md:flex-row p-2 w-full my-5" data-testid="contact">
|
<div
|
||||||
<div class="flex-1 p-4">
|
class="rounded bg-wildebeest-700 flex flex-col md:flex-row p-2 w-full my-5 overflow-auto"
|
||||||
<span class="block uppercase text-wildebeest-500 font-semibold mb-5">Administered by:</span>
|
data-testid="contact"
|
||||||
<AccountCard account={aboutInfo.contact.account} subText="username" />
|
>
|
||||||
</div>
|
{!!aboutInfo.admin.account && (
|
||||||
<div class="flex-1 p-4 pt-6 md:pt-4 md:pl-6 border-wildebeest-500 border-solid border-t md:border-t-0 md:border-l">
|
<div class="flex-1 p-4 border-wildebeest-500 border-solid border-b md:border-b-0 md:border-r">
|
||||||
|
<span class="block uppercase text-wildebeest-500 font-semibold mb-5">Administered by:</span>
|
||||||
|
<AccountCard account={aboutInfo.admin.account} subText="username" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div class="flex-1 p-4 pt-6 md:pt-4 md:pl-6 min-w-max">
|
||||||
<span class="block uppercase text-wildebeest-500 font-semibold mb-5">Contact:</span>
|
<span class="block uppercase text-wildebeest-500 font-semibold mb-5">Contact:</span>
|
||||||
<span>{aboutInfo.contact.email}</span>
|
<span>{aboutInfo.admin.email}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -11,10 +11,7 @@ import { InstanceConfigContext } from '~/utils/instanceConfig'
|
||||||
import { getDocumentHead } from '~/utils/getDocumentHead'
|
import { getDocumentHead } from '~/utils/getDocumentHead'
|
||||||
import { getErrorHtml } from '~/utils/getErrorHtml/getErrorHtml'
|
import { getErrorHtml } from '~/utils/getErrorHtml/getErrorHtml'
|
||||||
|
|
||||||
export const instanceLoader = loader$<
|
export const instanceLoader = loader$<Promise<InstanceConfig>>(async ({ platform, html }) => {
|
||||||
Promise<InstanceConfig>,
|
|
||||||
{ DATABASE: D1Database; INSTANCE_TITLE: string; INSTANCE_DESCR: string; ADMIN_EMAIL: string }
|
|
||||||
>(async ({ platform, html }) => {
|
|
||||||
const env = {
|
const env = {
|
||||||
INSTANCE_DESCR: platform.INSTANCE_DESCR,
|
INSTANCE_DESCR: platform.INSTANCE_DESCR,
|
||||||
INSTANCE_TITLE: platform.INSTANCE_TITLE,
|
INSTANCE_TITLE: platform.INSTANCE_TITLE,
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
import * as access from 'wildebeest/backend/src/access'
|
||||||
|
|
||||||
|
export function getJwtEmail(jwtCookie: string) {
|
||||||
|
let payload: access.JWTPayload
|
||||||
|
if (!jwtCookie) {
|
||||||
|
throw new Error('Missing Authorization')
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
// TODO: eventually, verify the JWT with Access, however this
|
||||||
|
// is not critical.
|
||||||
|
payload = access.getPayload(jwtCookie)
|
||||||
|
} catch (e: unknown) {
|
||||||
|
const error = e as { stack: string; cause: string }
|
||||||
|
console.warn(error.stack, error.cause)
|
||||||
|
throw new Error('Failed to validate Access JWT')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!payload.email) {
|
||||||
|
throw new Error("The Access JWT doesn't contain an email")
|
||||||
|
}
|
||||||
|
|
||||||
|
return payload.email
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { emailSymbol } from 'wildebeest/backend/src/activitypub/actors'
|
||||||
|
import { Database } from 'wildebeest/backend/src/database'
|
||||||
|
import { getAdmins } from 'wildebeest/functions/api/wb/settings/server/admins'
|
||||||
|
import { getJwtEmail } from './getJwtEmail'
|
||||||
|
|
||||||
|
export async function isUserAdmin(jwtCookie: string, database: Database): Promise<boolean> {
|
||||||
|
let email: string
|
||||||
|
try {
|
||||||
|
email = getJwtEmail(jwtCookie)
|
||||||
|
} catch {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const admins = await getAdmins(database)
|
||||||
|
|
||||||
|
return admins.some((admin) => admin[emailSymbol] === email)
|
||||||
|
}
|
|
@ -297,10 +297,10 @@
|
||||||
resolved "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz"
|
resolved "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz"
|
||||||
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
|
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
|
||||||
|
|
||||||
"@builder.io/qwik-city@0.2.1":
|
"@builder.io/qwik-city@0.4.0":
|
||||||
version "0.2.1"
|
version "0.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/@builder.io/qwik-city/-/qwik-city-0.2.1.tgz#c57f481a75534ff54ddb0f38403acc66b5d02f41"
|
resolved "https://registry.yarnpkg.com/@builder.io/qwik-city/-/qwik-city-0.4.0.tgz#9afb97ba0e11119e44a2527f545e0d84a8fe9759"
|
||||||
integrity sha512-g+ZC4Neo1XYQ/8uquUp6GKwr0eagpuCyQ3LkAtFhaIARaO67+cZfR6EFLJzf9wz5AVSt8/0QSD7wJEpni1i4IA==
|
integrity sha512-XNpmHzSHam7ZYrd12kJdwFerMEck0iOk3Wgb9IlVIuaN/nLuN033qrNWLVq+ZzlhplUea9DGc4job8qMix7WWA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@mdx-js/mdx" "2.3.0"
|
"@mdx-js/mdx" "2.3.0"
|
||||||
"@types/mdx" "2.0.3"
|
"@types/mdx" "2.0.3"
|
||||||
|
@ -308,10 +308,10 @@
|
||||||
vfile "5.3.7"
|
vfile "5.3.7"
|
||||||
zod "^3.20.6"
|
zod "^3.20.6"
|
||||||
|
|
||||||
"@builder.io/qwik@0.18.1":
|
"@builder.io/qwik@0.19.2":
|
||||||
version "0.18.1"
|
version "0.19.2"
|
||||||
resolved "https://registry.yarnpkg.com/@builder.io/qwik/-/qwik-0.18.1.tgz#341d01c5749a07230c700a5e4df859b857654cd0"
|
resolved "https://registry.yarnpkg.com/@builder.io/qwik/-/qwik-0.19.2.tgz#d2f7d39bf6adb6de8497690cb3c41ad62a5fc1c6"
|
||||||
integrity sha512-11qx5Wh6WRxgvHDJDppJORhykzkACUYuu9qRKEGdS3vTkBQ2Rr8NFDjYon2x6+8Wu9WukHk84ANywWnS91gS/w==
|
integrity sha512-Rxdyx96ucx0+nABLsg1sH7SgrlMdHgpxbR+NR3c53Ux4Jj+Xf+QHNQSIKF9zTV8KClrzmDlqILgmkDU4uMTVcg==
|
||||||
|
|
||||||
"@cush/relative@^1.0.0":
|
"@cush/relative@^1.0.0":
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
|
@ -1031,6 +1031,11 @@
|
||||||
"@typescript-eslint/types" "5.46.1"
|
"@typescript-eslint/types" "5.46.1"
|
||||||
eslint-visitor-keys "^3.3.0"
|
eslint-visitor-keys "^3.3.0"
|
||||||
|
|
||||||
|
abbrev@1:
|
||||||
|
version "1.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
|
||||||
|
integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==
|
||||||
|
|
||||||
acorn-jsx@^5.0.0, acorn-jsx@^5.3.2:
|
acorn-jsx@^5.0.0, acorn-jsx@^5.3.2:
|
||||||
version "5.3.2"
|
version "5.3.2"
|
||||||
resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz"
|
resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz"
|
||||||
|
@ -1323,7 +1328,7 @@ chalk@^2.0.0:
|
||||||
escape-string-regexp "^1.0.5"
|
escape-string-regexp "^1.0.5"
|
||||||
supports-color "^5.3.0"
|
supports-color "^5.3.0"
|
||||||
|
|
||||||
chalk@^4.0.0, chalk@^4.1.0:
|
chalk@^4.0.0:
|
||||||
version "4.1.2"
|
version "4.1.2"
|
||||||
resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz"
|
resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz"
|
||||||
integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
|
integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
|
||||||
|
@ -1356,7 +1361,7 @@ character-reference-invalid@^2.0.0:
|
||||||
resolved "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz"
|
resolved "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz"
|
||||||
integrity sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==
|
integrity sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==
|
||||||
|
|
||||||
"chokidar@>=3.0.0 <4.0.0", chokidar@^3.5.3:
|
"chokidar@>=3.0.0 <4.0.0", chokidar@^3.5.2, chokidar@^3.5.3:
|
||||||
version "3.5.3"
|
version "3.5.3"
|
||||||
resolved "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz"
|
resolved "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz"
|
||||||
integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==
|
integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==
|
||||||
|
@ -1451,21 +1456,6 @@ concat-map@0.0.1:
|
||||||
resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz"
|
resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz"
|
||||||
integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==
|
integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==
|
||||||
|
|
||||||
concurrently@^7.6.0:
|
|
||||||
version "7.6.0"
|
|
||||||
resolved "https://registry.npmjs.org/concurrently/-/concurrently-7.6.0.tgz"
|
|
||||||
integrity sha512-BKtRgvcJGeZ4XttiDiNcFiRlxoAeZOseqUvyYRUp/Vtd+9p1ULmeoSqGsDA+2ivdeDFpqrJvGvmI+StKfKl5hw==
|
|
||||||
dependencies:
|
|
||||||
chalk "^4.1.0"
|
|
||||||
date-fns "^2.29.1"
|
|
||||||
lodash "^4.17.21"
|
|
||||||
rxjs "^7.0.0"
|
|
||||||
shell-quote "^1.7.3"
|
|
||||||
spawn-command "^0.0.2-1"
|
|
||||||
supports-color "^8.1.0"
|
|
||||||
tree-kill "^1.2.2"
|
|
||||||
yargs "^17.3.1"
|
|
||||||
|
|
||||||
convert-source-map@^1.6.0, convert-source-map@^1.7.0:
|
convert-source-map@^1.6.0, convert-source-map@^1.7.0:
|
||||||
version "1.9.0"
|
version "1.9.0"
|
||||||
resolved "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz"
|
resolved "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz"
|
||||||
|
@ -1495,10 +1485,12 @@ data-uri-to-buffer@^4.0.0:
|
||||||
resolved "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.0.tgz"
|
resolved "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.0.tgz"
|
||||||
integrity sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA==
|
integrity sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA==
|
||||||
|
|
||||||
date-fns@^2.29.1:
|
debug@^3.2.7:
|
||||||
version "2.29.3"
|
version "3.2.7"
|
||||||
resolved "https://registry.npmjs.org/date-fns/-/date-fns-2.29.3.tgz"
|
resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a"
|
||||||
integrity sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA==
|
integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==
|
||||||
|
dependencies:
|
||||||
|
ms "^2.1.1"
|
||||||
|
|
||||||
debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.4:
|
debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.4:
|
||||||
version "4.3.4"
|
version "4.3.4"
|
||||||
|
@ -2149,6 +2141,11 @@ human-signals@^2.1.0:
|
||||||
resolved "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz"
|
resolved "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz"
|
||||||
integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==
|
integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==
|
||||||
|
|
||||||
|
ignore-by-default@^1.0.1:
|
||||||
|
version "1.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09"
|
||||||
|
integrity sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==
|
||||||
|
|
||||||
ignore@^5.2.0:
|
ignore@^5.2.0:
|
||||||
version "5.2.4"
|
version "5.2.4"
|
||||||
resolved "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz"
|
resolved "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz"
|
||||||
|
@ -2809,11 +2806,6 @@ lodash.merge@^4.6.2:
|
||||||
resolved "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz"
|
resolved "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz"
|
||||||
integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
|
integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
|
||||||
|
|
||||||
lodash@^4.17.21:
|
|
||||||
version "4.17.21"
|
|
||||||
resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz"
|
|
||||||
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
|
|
||||||
|
|
||||||
longest-streak@^3.0.0:
|
longest-streak@^3.0.0:
|
||||||
version "3.1.0"
|
version "3.1.0"
|
||||||
resolved "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz"
|
resolved "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz"
|
||||||
|
@ -3311,6 +3303,11 @@ ms@2.1.2:
|
||||||
resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz"
|
resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz"
|
||||||
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
|
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
|
||||||
|
|
||||||
|
ms@^2.1.1:
|
||||||
|
version "2.1.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
|
||||||
|
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
|
||||||
|
|
||||||
mz@^2.7.0:
|
mz@^2.7.0:
|
||||||
version "2.7.0"
|
version "2.7.0"
|
||||||
resolved "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz"
|
resolved "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz"
|
||||||
|
@ -3359,6 +3356,29 @@ node-releases@^2.0.6:
|
||||||
resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.8.tgz"
|
resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.8.tgz"
|
||||||
integrity sha512-dFSmB8fFHEH/s81Xi+Y/15DQY6VHW81nXRj86EMSL3lmuTmK1e+aT4wrFCkTbm+gSwkw4KpX+rT/pMM2c1mF+A==
|
integrity sha512-dFSmB8fFHEH/s81Xi+Y/15DQY6VHW81nXRj86EMSL3lmuTmK1e+aT4wrFCkTbm+gSwkw4KpX+rT/pMM2c1mF+A==
|
||||||
|
|
||||||
|
nodemon@^2.0.20:
|
||||||
|
version "2.0.20"
|
||||||
|
resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-2.0.20.tgz#e3537de768a492e8d74da5c5813cb0c7486fc701"
|
||||||
|
integrity sha512-Km2mWHKKY5GzRg6i1j5OxOHQtuvVsgskLfigG25yTtbyfRGn/GNvIbRyOf1PSCKJ2aT/58TiuUsuOU5UToVViw==
|
||||||
|
dependencies:
|
||||||
|
chokidar "^3.5.2"
|
||||||
|
debug "^3.2.7"
|
||||||
|
ignore-by-default "^1.0.1"
|
||||||
|
minimatch "^3.1.2"
|
||||||
|
pstree.remy "^1.1.8"
|
||||||
|
semver "^5.7.1"
|
||||||
|
simple-update-notifier "^1.0.7"
|
||||||
|
supports-color "^5.5.0"
|
||||||
|
touch "^3.1.0"
|
||||||
|
undefsafe "^2.0.5"
|
||||||
|
|
||||||
|
nopt@~1.0.10:
|
||||||
|
version "1.0.10"
|
||||||
|
resolved "https://registry.yarnpkg.com/nopt/-/nopt-1.0.10.tgz#6ddd21bd2a31417b92727dd585f8a6f37608ebee"
|
||||||
|
integrity sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==
|
||||||
|
dependencies:
|
||||||
|
abbrev "1"
|
||||||
|
|
||||||
normalize-path@^3.0.0, normalize-path@~3.0.0:
|
normalize-path@^3.0.0, normalize-path@~3.0.0:
|
||||||
version "3.0.0"
|
version "3.0.0"
|
||||||
resolved "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz"
|
resolved "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz"
|
||||||
|
@ -3621,6 +3641,11 @@ property-information@^6.0.0:
|
||||||
resolved "https://registry.npmjs.org/property-information/-/property-information-6.2.0.tgz"
|
resolved "https://registry.npmjs.org/property-information/-/property-information-6.2.0.tgz"
|
||||||
integrity sha512-kma4U7AFCTwpqq5twzC1YVIDXSqg6qQK6JN0smOw8fgRy1OkMi0CYSzFmsy6dnqSenamAtj0CyXMUJ1Mf6oROg==
|
integrity sha512-kma4U7AFCTwpqq5twzC1YVIDXSqg6qQK6JN0smOw8fgRy1OkMi0CYSzFmsy6dnqSenamAtj0CyXMUJ1Mf6oROg==
|
||||||
|
|
||||||
|
pstree.remy@^1.1.8:
|
||||||
|
version "1.1.8"
|
||||||
|
resolved "https://registry.yarnpkg.com/pstree.remy/-/pstree.remy-1.1.8.tgz#c242224f4a67c21f686839bbdb4ac282b8373d3a"
|
||||||
|
integrity sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==
|
||||||
|
|
||||||
punycode@^2.1.0:
|
punycode@^2.1.0:
|
||||||
version "2.1.1"
|
version "2.1.1"
|
||||||
resolved "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz"
|
resolved "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz"
|
||||||
|
@ -3760,13 +3785,6 @@ run-parallel@^1.1.9:
|
||||||
dependencies:
|
dependencies:
|
||||||
queue-microtask "^1.2.2"
|
queue-microtask "^1.2.2"
|
||||||
|
|
||||||
rxjs@^7.0.0:
|
|
||||||
version "7.8.0"
|
|
||||||
resolved "https://registry.npmjs.org/rxjs/-/rxjs-7.8.0.tgz"
|
|
||||||
integrity sha512-F2+gxDshqmIub1KdvZkaEfGDwLNpPvk9Fs6LD/MyQxNgMds/WH9OdDDXOmxUZpME+iSK3rQCctkL0DYyytUqMg==
|
|
||||||
dependencies:
|
|
||||||
tslib "^2.1.0"
|
|
||||||
|
|
||||||
sade@^1.7.3:
|
sade@^1.7.3:
|
||||||
version "1.8.1"
|
version "1.8.1"
|
||||||
resolved "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz"
|
resolved "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz"
|
||||||
|
@ -3790,11 +3808,21 @@ semver@7.x, semver@^7.3.5, semver@^7.3.7:
|
||||||
dependencies:
|
dependencies:
|
||||||
lru-cache "^6.0.0"
|
lru-cache "^6.0.0"
|
||||||
|
|
||||||
|
semver@^5.7.1:
|
||||||
|
version "5.7.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
|
||||||
|
integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
|
||||||
|
|
||||||
semver@^6.0.0, semver@^6.3.0:
|
semver@^6.0.0, semver@^6.3.0:
|
||||||
version "6.3.0"
|
version "6.3.0"
|
||||||
resolved "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz"
|
resolved "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz"
|
||||||
integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
|
integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
|
||||||
|
|
||||||
|
semver@~7.0.0:
|
||||||
|
version "7.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e"
|
||||||
|
integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==
|
||||||
|
|
||||||
shebang-command@^2.0.0:
|
shebang-command@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz"
|
resolved "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz"
|
||||||
|
@ -3807,16 +3835,18 @@ shebang-regex@^3.0.0:
|
||||||
resolved "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz"
|
resolved "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz"
|
||||||
integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
|
integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
|
||||||
|
|
||||||
shell-quote@^1.7.3:
|
|
||||||
version "1.7.4"
|
|
||||||
resolved "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.4.tgz"
|
|
||||||
integrity sha512-8o/QEhSSRb1a5i7TFR0iM4G16Z0vYB2OQVs4G3aAFXjn3T6yEx8AZxy1PgDF7I00LZHYA3WxaSYIf5e5sAX8Rw==
|
|
||||||
|
|
||||||
signal-exit@^3.0.3, signal-exit@^3.0.7:
|
signal-exit@^3.0.3, signal-exit@^3.0.7:
|
||||||
version "3.0.7"
|
version "3.0.7"
|
||||||
resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz"
|
resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz"
|
||||||
integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==
|
integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==
|
||||||
|
|
||||||
|
simple-update-notifier@^1.0.7:
|
||||||
|
version "1.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz#67694c121de354af592b347cdba798463ed49c82"
|
||||||
|
integrity sha512-VpsrsJSUcJEseSbMHkrsrAVSdvVS5I96Qo1QAQ4FxQ9wXFcB+pjj7FB7/us9+GcgfW4ziHtYMc1J0PLczb55mg==
|
||||||
|
dependencies:
|
||||||
|
semver "~7.0.0"
|
||||||
|
|
||||||
sisteransi@^1.0.5:
|
sisteransi@^1.0.5:
|
||||||
version "1.0.5"
|
version "1.0.5"
|
||||||
resolved "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz"
|
resolved "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz"
|
||||||
|
@ -3855,11 +3885,6 @@ space-separated-tokens@^2.0.0:
|
||||||
resolved "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz"
|
resolved "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz"
|
||||||
integrity sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==
|
integrity sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==
|
||||||
|
|
||||||
spawn-command@^0.0.2-1:
|
|
||||||
version "0.0.2-1"
|
|
||||||
resolved "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2-1.tgz"
|
|
||||||
integrity sha512-n98l9E2RMSJ9ON1AKisHzz7V42VDiBQGY6PB1BwRglz99wpVsSuGzQ+jOi6lFXBGVTCrRpltvjm+/XA+tpeJrg==
|
|
||||||
|
|
||||||
sprintf-js@~1.0.2:
|
sprintf-js@~1.0.2:
|
||||||
version "1.0.3"
|
version "1.0.3"
|
||||||
resolved "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz"
|
resolved "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz"
|
||||||
|
@ -3948,7 +3973,7 @@ sucrase@^3.20.3:
|
||||||
pirates "^4.0.1"
|
pirates "^4.0.1"
|
||||||
ts-interface-checker "^0.1.9"
|
ts-interface-checker "^0.1.9"
|
||||||
|
|
||||||
supports-color@^5.3.0:
|
supports-color@^5.3.0, supports-color@^5.5.0:
|
||||||
version "5.5.0"
|
version "5.5.0"
|
||||||
resolved "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz"
|
resolved "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz"
|
||||||
integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==
|
integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==
|
||||||
|
@ -3962,7 +3987,7 @@ supports-color@^7.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
has-flag "^4.0.0"
|
has-flag "^4.0.0"
|
||||||
|
|
||||||
supports-color@^8.0.0, supports-color@^8.1.0:
|
supports-color@^8.0.0:
|
||||||
version "8.1.1"
|
version "8.1.1"
|
||||||
resolved "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz"
|
resolved "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz"
|
||||||
integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==
|
integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==
|
||||||
|
@ -4048,10 +4073,12 @@ to-regex-range@^5.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
is-number "^7.0.0"
|
is-number "^7.0.0"
|
||||||
|
|
||||||
tree-kill@^1.2.2:
|
touch@^3.1.0:
|
||||||
version "1.2.2"
|
version "3.1.0"
|
||||||
resolved "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz"
|
resolved "https://registry.yarnpkg.com/touch/-/touch-3.1.0.tgz#fe365f5f75ec9ed4e56825e0bb76d24ab74af83b"
|
||||||
integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==
|
integrity sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==
|
||||||
|
dependencies:
|
||||||
|
nopt "~1.0.10"
|
||||||
|
|
||||||
trim-lines@^3.0.0:
|
trim-lines@^3.0.0:
|
||||||
version "3.0.1"
|
version "3.0.1"
|
||||||
|
@ -4096,11 +4123,6 @@ tslib@^1.8.1, tslib@^1.9.3:
|
||||||
resolved "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz"
|
resolved "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz"
|
||||||
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
|
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
|
||||||
|
|
||||||
tslib@^2.1.0:
|
|
||||||
version "2.4.1"
|
|
||||||
resolved "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz"
|
|
||||||
integrity sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==
|
|
||||||
|
|
||||||
tsutils@^3.21.0:
|
tsutils@^3.21.0:
|
||||||
version "3.21.0"
|
version "3.21.0"
|
||||||
resolved "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz"
|
resolved "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz"
|
||||||
|
@ -4135,6 +4157,11 @@ typescript@4.9.4:
|
||||||
resolved "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz"
|
resolved "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz"
|
||||||
integrity sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==
|
integrity sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==
|
||||||
|
|
||||||
|
undefsafe@^2.0.5:
|
||||||
|
version "2.0.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.5.tgz#38733b9327bdcd226db889fb723a6efd162e6e2c"
|
||||||
|
integrity sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==
|
||||||
|
|
||||||
undici@5.19.1:
|
undici@5.19.1:
|
||||||
version "5.19.1"
|
version "5.19.1"
|
||||||
resolved "https://registry.yarnpkg.com/undici/-/undici-5.19.1.tgz#92b1fd3ab2c089b5a6bd3e579dcda8f1934ebf6d"
|
resolved "https://registry.yarnpkg.com/undici/-/undici-5.19.1.tgz#92b1fd3ab2c089b5a6bd3e579dcda8f1934ebf6d"
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
import type { Env } from 'wildebeest/backend/src/types/env'
|
||||||
|
import type { ContextData } from 'wildebeest/backend/src/types/context'
|
||||||
|
import { type Database, getDatabase } from 'wildebeest/backend/src/database'
|
||||||
|
|
||||||
|
export const onRequestGet: PagesFunction<Env, any, ContextData> = async ({ env }) => {
|
||||||
|
return handleRequestGet(await getDatabase(env))
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function handleRequestGet(db: Database) {
|
||||||
|
const query = `SELECT * from server_rules;`
|
||||||
|
const result = await db.prepare(query).all<{ id: string; text: string }>()
|
||||||
|
|
||||||
|
if (!result.success) {
|
||||||
|
return new Response('SQL error: ' + result.error, { status: 500 })
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Response(JSON.stringify(result.results ?? []), { status: 200 })
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
import type { Env } from 'wildebeest/backend/src/types/env'
|
||||||
|
import type { ContextData } from 'wildebeest/backend/src/types/context'
|
||||||
|
import { type Database, getDatabase } from 'wildebeest/backend/src/database'
|
||||||
|
import { Person, personFromRow } from 'wildebeest/backend/src/activitypub/actors'
|
||||||
|
|
||||||
|
export const onRequestGet: PagesFunction<Env, any, ContextData> = async ({ env }) => {
|
||||||
|
return handleRequestGet(await getDatabase(env))
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function handleRequestGet(db: Database) {
|
||||||
|
const admins = await getAdmins(db)
|
||||||
|
return new Response(JSON.stringify(admins), { status: 200 })
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getAdmins(db: Database): Promise<Person[]> {
|
||||||
|
let rows: unknown[] = []
|
||||||
|
try {
|
||||||
|
const stmt = db.prepare('SELECT * FROM actors WHERE is_admin=TRUE')
|
||||||
|
const result = await stmt.all<unknown>()
|
||||||
|
rows = result.success ? (result.results as unknown[]) : []
|
||||||
|
} catch {
|
||||||
|
/* empty */
|
||||||
|
}
|
||||||
|
|
||||||
|
return rows.map(personFromRow)
|
||||||
|
}
|
|
@ -0,0 +1,80 @@
|
||||||
|
import type { Env } from 'wildebeest/backend/src/types/env'
|
||||||
|
import type { ContextData } from 'wildebeest/backend/src/types/context'
|
||||||
|
import * as errors from 'wildebeest/backend/src/errors'
|
||||||
|
import { type Database, getDatabase } from 'wildebeest/backend/src/database'
|
||||||
|
import { parse } from 'cookie'
|
||||||
|
import { isUserAdmin } from 'wildebeest/frontend/src/utils/isUserAdmin'
|
||||||
|
|
||||||
|
export const onRequestGet: PagesFunction<Env, any, ContextData> = async ({ env, request }) => {
|
||||||
|
return handleRequestPost(await getDatabase(env), request)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function handleRequestGet(db: Database) {
|
||||||
|
const query = `SELECT * from server_rules;`
|
||||||
|
const result = await db.prepare(query).all<{ id: string; text: string }>()
|
||||||
|
|
||||||
|
if (!result.success) {
|
||||||
|
return new Response('SQL error: ' + result.error, { status: 500 })
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Response(JSON.stringify(result.results ?? []), { status: 200 })
|
||||||
|
}
|
||||||
|
|
||||||
|
export const onRequestPost: PagesFunction<Env, any, ContextData> = async ({ env, request }) => {
|
||||||
|
return handleRequestPost(await getDatabase(env), request)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function handleRequestPost(db: Database, request: Request) {
|
||||||
|
const cookie = parse(request.headers.get('Cookie') || '')
|
||||||
|
const jwt = cookie['CF_Authorization']
|
||||||
|
const isAdmin = await isUserAdmin(jwt, db)
|
||||||
|
|
||||||
|
if (!isAdmin) {
|
||||||
|
return errors.notAuthorized('Lacking authorization rights to edit server rules')
|
||||||
|
}
|
||||||
|
|
||||||
|
const rule = await request.json<{ id?: number; text: string }>()
|
||||||
|
const result = await upsertRule(db, rule)
|
||||||
|
|
||||||
|
if (!result.success) {
|
||||||
|
return new Response('SQL error: ' + result.error, { status: 500 })
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Response('', { status: 200 })
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function upsertRule(db: Database, rule: { id?: number; text: string } | string) {
|
||||||
|
const id = typeof rule === 'string' ? null : rule.id ?? null
|
||||||
|
const text = typeof rule === 'string' ? rule : rule.text
|
||||||
|
return await db
|
||||||
|
.prepare(
|
||||||
|
`INSERT INTO server_rules (id, text)
|
||||||
|
VALUES (?, ?)
|
||||||
|
ON CONFLICT(id) DO UPDATE SET text=excluded.text;`
|
||||||
|
)
|
||||||
|
.bind(id, text)
|
||||||
|
.run()
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function handleRequestDelete(db: Database, request: Request) {
|
||||||
|
const cookie = parse(request.headers.get('Cookie') || '')
|
||||||
|
const jwt = cookie['CF_Authorization']
|
||||||
|
const isAdmin = await isUserAdmin(jwt, db)
|
||||||
|
|
||||||
|
if (!isAdmin) {
|
||||||
|
return errors.notAuthorized('Lacking authorization rights to edit server rules')
|
||||||
|
}
|
||||||
|
|
||||||
|
const rule = await request.json<{ id: number }>()
|
||||||
|
const result = await deleteRule(db, rule.id)
|
||||||
|
|
||||||
|
if (!result.success) {
|
||||||
|
return new Response('SQL error: ' + result.error, { status: 500 })
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Response('', { status: 200 })
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteRule(db: Database, ruleId: number) {
|
||||||
|
return await db.prepare('DELETE FROM server_rules WHERE id=?').bind(ruleId).run()
|
||||||
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
import type { Env } from 'wildebeest/backend/src/types/env'
|
||||||
|
import type { ContextData } from 'wildebeest/backend/src/types/context'
|
||||||
|
import * as errors from 'wildebeest/backend/src/errors'
|
||||||
|
import { type Database, getDatabase } from 'wildebeest/backend/src/database'
|
||||||
|
import { parse } from 'cookie'
|
||||||
|
import { isUserAdmin } from 'wildebeest/frontend/src/utils/isUserAdmin'
|
||||||
|
import { ServerSettingsData } from 'wildebeest/frontend/src/routes/(admin)/settings/server-settings/layout'
|
||||||
|
|
||||||
|
export const onRequestGet: PagesFunction<Env, any, ContextData> = async ({ env, request }) => {
|
||||||
|
return handleRequestPost(await getDatabase(env), request)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function handleRequestGet(db: Database) {
|
||||||
|
const query = `SELECT * from server_settings`
|
||||||
|
const result = await db.prepare(query).all<{ setting_name: string; setting_value: string }>()
|
||||||
|
|
||||||
|
const data = (result.results ?? []).reduce(
|
||||||
|
(settings, { setting_name, setting_value }) => ({
|
||||||
|
...settings,
|
||||||
|
[setting_name]: setting_value,
|
||||||
|
}),
|
||||||
|
{} as Object
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!result.success) {
|
||||||
|
return new Response('SQL error: ' + result.error, { status: 500 })
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Response(JSON.stringify(data), { status: 200 })
|
||||||
|
}
|
||||||
|
|
||||||
|
export const onRequestPost: PagesFunction<Env, any, ContextData> = async ({ env, request }) => {
|
||||||
|
return handleRequestPost(await getDatabase(env), request)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function handleRequestPost(db: Database, request: Request) {
|
||||||
|
const cookie = parse(request.headers.get('Cookie') || '')
|
||||||
|
const jwt = cookie['CF_Authorization']
|
||||||
|
const isAdmin = await isUserAdmin(jwt, db)
|
||||||
|
|
||||||
|
if (!isAdmin) {
|
||||||
|
return errors.notAuthorized('Lacking authorization rights to edit server settings')
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await request.json<ServerSettingsData>()
|
||||||
|
|
||||||
|
const result = await upsertServerSettings(db, data)
|
||||||
|
|
||||||
|
if (result && !result.success) {
|
||||||
|
return new Response('SQL error: ' + result.error, { status: 500 })
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Response('', { status: 200 })
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function upsertServerSettings(db: Database, settings: Partial<ServerSettingsData>) {
|
||||||
|
const settingsEntries = Object.entries(settings)
|
||||||
|
|
||||||
|
if (!settingsEntries.length) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const query = `
|
||||||
|
INSERT INTO server_settings (setting_name, setting_value)
|
||||||
|
VALUES ${settingsEntries.map(() => `(?, ?)`).join(', ')}
|
||||||
|
ON CONFLICT(setting_name) DO UPDATE SET setting_value=excluded.setting_value
|
||||||
|
`
|
||||||
|
|
||||||
|
return await db
|
||||||
|
.prepare(query)
|
||||||
|
.bind(...settingsEntries.flat())
|
||||||
|
.run()
|
||||||
|
}
|
|
@ -7,6 +7,7 @@ import { parse } from 'cookie'
|
||||||
import * as errors from 'wildebeest/backend/src/errors'
|
import * as errors from 'wildebeest/backend/src/errors'
|
||||||
import * as access from 'wildebeest/backend/src/access'
|
import * as access from 'wildebeest/backend/src/access'
|
||||||
import { type Database, getDatabase } from 'wildebeest/backend/src/database'
|
import { type Database, getDatabase } from 'wildebeest/backend/src/database'
|
||||||
|
import { getJwtEmail } from 'wildebeest/frontend/src/utils/getJwtEmail'
|
||||||
|
|
||||||
export const onRequestPost: PagesFunction<Env, any, ContextData> = async ({ request, env }) => {
|
export const onRequestPost: PagesFunction<Env, any, ContextData> = async ({ request, env }) => {
|
||||||
return handlePostRequest(request, await getDatabase(env), env.userKEK, env.ACCESS_AUTH_DOMAIN, env.ACCESS_AUD)
|
return handlePostRequest(request, await getDatabase(env), env.userKEK, env.ACCESS_AUTH_DOMAIN, env.ACCESS_AUD)
|
||||||
|
@ -21,23 +22,19 @@ export async function handlePostRequest(
|
||||||
): Promise<Response> {
|
): Promise<Response> {
|
||||||
const url = new URL(request.url)
|
const url = new URL(request.url)
|
||||||
const cookie = parse(request.headers.get('Cookie') || '')
|
const cookie = parse(request.headers.get('Cookie') || '')
|
||||||
|
let email = ''
|
||||||
const jwt = cookie['CF_Authorization']
|
const jwt = cookie['CF_Authorization']
|
||||||
if (!jwt) {
|
try {
|
||||||
return errors.notAuthorized('missing CF_Authorization')
|
email = getJwtEmail(jwt ?? '')
|
||||||
|
} catch (e) {
|
||||||
|
return errors.notAuthorized((e as Error)?.message)
|
||||||
}
|
}
|
||||||
|
|
||||||
const payload = access.getPayload(jwt)
|
await access.generateValidator({
|
||||||
if (!payload.email) {
|
|
||||||
return errors.notAuthorized('missing email')
|
|
||||||
}
|
|
||||||
|
|
||||||
const validatate = access.generateValidator({
|
|
||||||
jwt,
|
jwt,
|
||||||
domain: accessDomain,
|
domain: accessDomain,
|
||||||
aud: accessAud,
|
aud: accessAud,
|
||||||
})
|
})(request)
|
||||||
await validatate(request)
|
|
||||||
|
|
||||||
const domain = url.hostname
|
const domain = url.hostname
|
||||||
|
|
||||||
|
@ -52,7 +49,7 @@ export async function handlePostRequest(
|
||||||
properties.name = formData.get('name') || ''
|
properties.name = formData.get('name') || ''
|
||||||
}
|
}
|
||||||
|
|
||||||
await createPerson(domain, db, userKEK, payload.email, properties)
|
await createPerson(domain, db, userKEK, email, properties)
|
||||||
|
|
||||||
if (!url.searchParams.has('redirect_uri')) {
|
if (!url.searchParams.has('redirect_uri')) {
|
||||||
return new Response('', { status: 400 })
|
return new Response('', { status: 400 })
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
-- Migration number: 0003 2023-02-24T15:03:27.478Z
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS server_settings (
|
||||||
|
setting_name TEXT UNIQUE NOT NULL,
|
||||||
|
setting_value TEXT NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS server_rules (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
text TEXT NOT NULL
|
||||||
|
);
|
|
@ -14,7 +14,6 @@
|
||||||
"@typescript-eslint/eslint-plugin": "^5.46.1",
|
"@typescript-eslint/eslint-plugin": "^5.46.1",
|
||||||
"@typescript-eslint/parser": "^5.46.1",
|
"@typescript-eslint/parser": "^5.46.1",
|
||||||
"better-sqlite3": "8",
|
"better-sqlite3": "8",
|
||||||
"concurrently": "^7.6.0",
|
|
||||||
"eslint": "^8.29.0",
|
"eslint": "^8.29.0",
|
||||||
"jest": "^29.3.1",
|
"jest": "^29.3.1",
|
||||||
"jest-environment-miniflare": "^2.11.0",
|
"jest-environment-miniflare": "^2.11.0",
|
||||||
|
@ -38,8 +37,8 @@
|
||||||
"pages": "NO_D1_WARNING=true wrangler pages",
|
"pages": "NO_D1_WARNING=true wrangler pages",
|
||||||
"database:migrate": "yarn d1 migrations apply DATABASE",
|
"database:migrate": "yarn d1 migrations apply DATABASE",
|
||||||
"database:create-mock": "rm -f .wrangler/state/d1/DATABASE.sqlite3 && CI=true yarn database:migrate --local && node ./frontend/mock-db/run.mjs",
|
"database:create-mock": "rm -f .wrangler/state/d1/DATABASE.sqlite3 && CI=true yarn database:migrate --local && node ./frontend/mock-db/run.mjs",
|
||||||
"dev": "export COMMIT_HASH=$(git rev-parse HEAD) && yarn build && yarn database:migrate --local && yarn pages dev frontend/dist --d1 DATABASE --persist --compatibility-date=2022-12-20 --binding 'INSTANCE_TITLE=Test Wildebeest' 'INSTANCE_DESCR=My Wildebeest Instance' 'ACCESS_AUTH_DOMAIN=0.0.0.0.cloudflareaccess.com' 'ACCESS_AUD=DEV_AUD' --live-reload",
|
"dev": "export COMMIT_HASH=$(git rev-parse HEAD) && yarn build && yarn database:migrate --local && yarn pages dev frontend/dist --d1 DATABASE --persist --compatibility-date=2022-12-20 --binding 'INSTANCE_TITLE=Test Wildebeest' 'INSTANCE_DESCR=My Wildebeest Instance' 'ACCESS_AUTH_DOMAIN=0.0.0.0.cloudflareaccess.com' 'ACCESS_AUD=DEV_AUD' 'ADMIN_EMAIL=george@test.email' --live-reload",
|
||||||
"ci-dev-test-ui": "yarn build && yarn database:create-mock && yarn pages dev frontend/dist --d1 DATABASE --persist --port 8788 --binding 'INSTANCE_TITLE=Test Wildebeest' 'INSTANCE_DESCR=My Wildebeest Instance' 'ACCESS_AUTH_DOMAIN=0.0.0.0.cloudflareaccess.com' 'ACCESS_AUD=DEV_AUD' --compatibility-date=2022-12-20",
|
"ci-dev-test-ui": "yarn build && yarn database:create-mock && yarn pages dev frontend/dist --d1 DATABASE --persist --port 8788 --binding 'INSTANCE_TITLE=Test Wildebeest' 'INSTANCE_DESCR=My Wildebeest Instance' 'ACCESS_AUTH_DOMAIN=0.0.0.0.cloudflareaccess.com' 'ACCESS_AUD=DEV_AUD' 'ADMIN_EMAIL=george@test.email' --compatibility-date=2022-12-20",
|
||||||
"deploy:init": "yarn pages project create wildebeest && yarn d1 create wildebeest",
|
"deploy:init": "yarn pages project create wildebeest && yarn d1 create wildebeest",
|
||||||
"deploy": "yarn build && yarn database:migrate && yarn pages publish frontend/dist --project-name=wildebeest"
|
"deploy": "yarn build && yarn database:migrate && yarn pages publish frontend/dist --project-name=wildebeest"
|
||||||
},
|
},
|
||||||
|
|
|
@ -11,28 +11,28 @@ test.skip('View of the about page', async ({ page }) => {
|
||||||
await expect(page.getByTestId('domain-text')).toHaveText('0.0.0.0')
|
await expect(page.getByTestId('domain-text')).toHaveText('0.0.0.0')
|
||||||
await expect(page.getByTestId('social-text')).toHaveText('Decentralised social media powered by Mastodon')
|
await expect(page.getByTestId('social-text')).toHaveText('Decentralised social media powered by Mastodon')
|
||||||
|
|
||||||
await expect(page.getByTestId('contact').getByText('Administered by:George 👍@george')).toBeVisible()
|
// await expect(page.getByTestId('contact').getByText('Administered by: ...')).toBeVisible()
|
||||||
await expect(page.getByTestId('contact').getByText('contact:test@test.com')).toBeVisible()
|
// await expect(page.getByTestId('contact').getByText('contact: ...')).toBeVisible()
|
||||||
|
|
||||||
await expect(page.getByRole('region').filter({ hasText: 'Please mind that the staff' })).not.toBeVisible()
|
await expect(page.getByRole('region').filter({ hasText: 'this is a test wildebeest instance!' })).not.toBeVisible()
|
||||||
await page.getByRole('button', { name: 'About' }).click()
|
await page.getByRole('button', { name: 'About' }).click()
|
||||||
await expect(page.getByRole('region').filter({ hasText: 'Please mind that the staff' })).toBeVisible()
|
await expect(page.getByRole('region').filter({ hasText: 'this is a test wildebeest instance!' })).toBeVisible()
|
||||||
await page.getByRole('button', { name: 'About' }).click()
|
await page.getByRole('button', { name: 'About' }).click()
|
||||||
await expect(page.getByRole('region').filter({ hasText: 'Please mind that the staff' })).not.toBeVisible()
|
await expect(page.getByRole('region').filter({ hasText: 'this is a test wildebeest instance!' })).not.toBeVisible()
|
||||||
|
|
||||||
const getRuleLocator = (ruleId: string) =>
|
const getRuleLocator = (ruleId: string) =>
|
||||||
page.getByRole('listitem').filter({ has: page.getByText(ruleId, { exact: true }) })
|
page.getByRole('listitem').filter({ has: page.getByText(ruleId, { exact: true }) })
|
||||||
|
|
||||||
|
await expect(page.getByRole('listitem')).toHaveCount(0)
|
||||||
await expect(getRuleLocator('1')).not.toBeVisible()
|
await expect(getRuleLocator('1')).not.toBeVisible()
|
||||||
await expect(getRuleLocator('2')).not.toBeVisible()
|
await expect(getRuleLocator('2')).not.toBeVisible()
|
||||||
await expect(getRuleLocator('3')).not.toBeVisible()
|
await expect(getRuleLocator('3')).not.toBeVisible()
|
||||||
await page.getByRole('button', { name: 'Server rules' }).click()
|
await page.getByRole('button', { name: 'Server rules' }).click()
|
||||||
|
await expect(page.getByRole('listitem')).toHaveCount(3)
|
||||||
await expect(getRuleLocator('1')).toBeVisible()
|
await expect(getRuleLocator('1')).toBeVisible()
|
||||||
await expect(getRuleLocator('1')).toContainText(
|
await expect(getRuleLocator('1')).toContainText("don't be mean")
|
||||||
'Sexually explicit or violent media must be marked as sensitive when posting'
|
|
||||||
)
|
|
||||||
await expect(getRuleLocator('2')).toBeVisible()
|
await expect(getRuleLocator('2')).toBeVisible()
|
||||||
await expect(getRuleLocator('2')).toContainText('No racism, sexism, homophobia, transphobia, xenophobia, or casteism')
|
await expect(getRuleLocator('2')).toContainText("don't insult people")
|
||||||
await expect(getRuleLocator('3')).toBeVisible()
|
await expect(getRuleLocator('3')).toBeVisible()
|
||||||
await expect(getRuleLocator('3')).toContainText('No incitement of violence or promotion of violent ideologies')
|
await expect(getRuleLocator('3')).toContainText('respect the rules')
|
||||||
})
|
})
|
||||||
|
|
53
yarn.lock
53
yarn.lock
|
@ -1374,7 +1374,7 @@ chalk@^2.0.0, chalk@^2.4.1:
|
||||||
escape-string-regexp "^1.0.5"
|
escape-string-regexp "^1.0.5"
|
||||||
supports-color "^5.3.0"
|
supports-color "^5.3.0"
|
||||||
|
|
||||||
chalk@^4.0.0, chalk@^4.1.0:
|
chalk@^4.0.0:
|
||||||
version "4.1.2"
|
version "4.1.2"
|
||||||
resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz"
|
resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz"
|
||||||
integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
|
integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
|
||||||
|
@ -1465,21 +1465,6 @@ concat-map@0.0.1:
|
||||||
resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz"
|
resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz"
|
||||||
integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==
|
integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==
|
||||||
|
|
||||||
concurrently@^7.6.0:
|
|
||||||
version "7.6.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/concurrently/-/concurrently-7.6.0.tgz#531a6f5f30cf616f355a4afb8f8fcb2bba65a49a"
|
|
||||||
integrity sha512-BKtRgvcJGeZ4XttiDiNcFiRlxoAeZOseqUvyYRUp/Vtd+9p1ULmeoSqGsDA+2ivdeDFpqrJvGvmI+StKfKl5hw==
|
|
||||||
dependencies:
|
|
||||||
chalk "^4.1.0"
|
|
||||||
date-fns "^2.29.1"
|
|
||||||
lodash "^4.17.21"
|
|
||||||
rxjs "^7.0.0"
|
|
||||||
shell-quote "^1.7.3"
|
|
||||||
spawn-command "^0.0.2-1"
|
|
||||||
supports-color "^8.1.0"
|
|
||||||
tree-kill "^1.2.2"
|
|
||||||
yargs "^17.3.1"
|
|
||||||
|
|
||||||
convert-source-map@^1.6.0, convert-source-map@^1.7.0:
|
convert-source-map@^1.6.0, convert-source-map@^1.7.0:
|
||||||
version "1.9.0"
|
version "1.9.0"
|
||||||
resolved "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz"
|
resolved "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz"
|
||||||
|
@ -1525,11 +1510,6 @@ cross-spawn@^7.0.2, cross-spawn@^7.0.3:
|
||||||
shebang-command "^2.0.0"
|
shebang-command "^2.0.0"
|
||||||
which "^2.0.1"
|
which "^2.0.1"
|
||||||
|
|
||||||
date-fns@^2.29.1:
|
|
||||||
version "2.29.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.29.3.tgz#27402d2fc67eb442b511b70bbdf98e6411cd68a8"
|
|
||||||
integrity sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA==
|
|
||||||
|
|
||||||
debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.4:
|
debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.4:
|
||||||
version "4.3.4"
|
version "4.3.4"
|
||||||
resolved "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz"
|
resolved "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz"
|
||||||
|
@ -3095,11 +3075,6 @@ lodash.merge@^4.6.2:
|
||||||
resolved "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz"
|
resolved "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz"
|
||||||
integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
|
integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
|
||||||
|
|
||||||
lodash@^4.17.21:
|
|
||||||
version "4.17.21"
|
|
||||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
|
|
||||||
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
|
|
||||||
|
|
||||||
lru-cache@^6.0.0:
|
lru-cache@^6.0.0:
|
||||||
version "6.0.0"
|
version "6.0.0"
|
||||||
resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz"
|
resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz"
|
||||||
|
@ -3726,13 +3701,6 @@ run-parallel@^1.1.9:
|
||||||
dependencies:
|
dependencies:
|
||||||
queue-microtask "^1.2.2"
|
queue-microtask "^1.2.2"
|
||||||
|
|
||||||
rxjs@^7.0.0:
|
|
||||||
version "7.8.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.0.tgz#90a938862a82888ff4c7359811a595e14e1e09a4"
|
|
||||||
integrity sha512-F2+gxDshqmIub1KdvZkaEfGDwLNpPvk9Fs6LD/MyQxNgMds/WH9OdDDXOmxUZpME+iSK3rQCctkL0DYyytUqMg==
|
|
||||||
dependencies:
|
|
||||||
tslib "^2.1.0"
|
|
||||||
|
|
||||||
safe-buffer@^5.0.1, safe-buffer@~5.2.0:
|
safe-buffer@^5.0.1, safe-buffer@~5.2.0:
|
||||||
version "5.2.1"
|
version "5.2.1"
|
||||||
resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz"
|
resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz"
|
||||||
|
@ -3805,7 +3773,7 @@ shebang-regex@^3.0.0:
|
||||||
resolved "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz"
|
resolved "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz"
|
||||||
integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
|
integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
|
||||||
|
|
||||||
shell-quote@^1.6.1, shell-quote@^1.7.3:
|
shell-quote@^1.6.1:
|
||||||
version "1.7.4"
|
version "1.7.4"
|
||||||
resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.4.tgz#33fe15dee71ab2a81fcbd3a52106c5cfb9fb75d8"
|
resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.4.tgz#33fe15dee71ab2a81fcbd3a52106c5cfb9fb75d8"
|
||||||
integrity sha512-8o/QEhSSRb1a5i7TFR0iM4G16Z0vYB2OQVs4G3aAFXjn3T6yEx8AZxy1PgDF7I00LZHYA3WxaSYIf5e5sAX8Rw==
|
integrity sha512-8o/QEhSSRb1a5i7TFR0iM4G16Z0vYB2OQVs4G3aAFXjn3T6yEx8AZxy1PgDF7I00LZHYA3WxaSYIf5e5sAX8Rw==
|
||||||
|
@ -3879,11 +3847,6 @@ sourcemap-codec@^1.4.8:
|
||||||
resolved "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz"
|
resolved "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz"
|
||||||
integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==
|
integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==
|
||||||
|
|
||||||
spawn-command@^0.0.2-1:
|
|
||||||
version "0.0.2-1"
|
|
||||||
resolved "https://registry.yarnpkg.com/spawn-command/-/spawn-command-0.0.2-1.tgz#62f5e9466981c1b796dc5929937e11c9c6921bd0"
|
|
||||||
integrity sha512-n98l9E2RMSJ9ON1AKisHzz7V42VDiBQGY6PB1BwRglz99wpVsSuGzQ+jOi6lFXBGVTCrRpltvjm+/XA+tpeJrg==
|
|
||||||
|
|
||||||
spdx-correct@^3.0.0:
|
spdx-correct@^3.0.0:
|
||||||
version "3.1.1"
|
version "3.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9"
|
resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9"
|
||||||
|
@ -4034,7 +3997,7 @@ supports-color@^7.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
has-flag "^4.0.0"
|
has-flag "^4.0.0"
|
||||||
|
|
||||||
supports-color@^8.0.0, supports-color@^8.1.0:
|
supports-color@^8.0.0:
|
||||||
version "8.1.1"
|
version "8.1.1"
|
||||||
resolved "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz"
|
resolved "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz"
|
||||||
integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==
|
integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==
|
||||||
|
@ -4107,11 +4070,6 @@ toucan-js@^3.1.0:
|
||||||
"@sentry/types" "7.28.1"
|
"@sentry/types" "7.28.1"
|
||||||
"@sentry/utils" "7.28.1"
|
"@sentry/utils" "7.28.1"
|
||||||
|
|
||||||
tree-kill@^1.2.2:
|
|
||||||
version "1.2.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc"
|
|
||||||
integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==
|
|
||||||
|
|
||||||
ts-jest@^29.0.3:
|
ts-jest@^29.0.3:
|
||||||
version "29.0.3"
|
version "29.0.3"
|
||||||
resolved "https://registry.npmjs.org/ts-jest/-/ts-jest-29.0.3.tgz"
|
resolved "https://registry.npmjs.org/ts-jest/-/ts-jest-29.0.3.tgz"
|
||||||
|
@ -4131,11 +4089,6 @@ tslib@^1.8.1, tslib@^1.9.3:
|
||||||
resolved "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz"
|
resolved "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz"
|
||||||
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
|
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
|
||||||
|
|
||||||
tslib@^2.1.0:
|
|
||||||
version "2.4.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.1.tgz#0d0bfbaac2880b91e22df0768e55be9753a5b17e"
|
|
||||||
integrity sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==
|
|
||||||
|
|
||||||
tsutils@^3.21.0:
|
tsutils@^3.21.0:
|
||||||
version "3.21.0"
|
version "3.21.0"
|
||||||
resolved "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz"
|
resolved "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz"
|
||||||
|
|
Ładowanie…
Reference in New Issue