kopia lustrzana https://github.com/cloudflare/wildebeest
Revert "Merge remote-tracking branch 'upstream/main' into fix-spread-and-rest-based-binding"
This reverts commitpull/366/head85b0ac44a7
, reversing changes made to05c993151b
.
rodzic
1d571d686e
commit
b7b8651384
|
@ -53,9 +53,6 @@ jobs:
|
||||||
- name: Check frontend linting
|
- name: Check frontend linting
|
||||||
run: yarn lint:frontend
|
run: yarn lint:frontend
|
||||||
|
|
||||||
- name: Check frontend types
|
|
||||||
run: yarn --cwd types-check
|
|
||||||
|
|
||||||
test-ui:
|
test-ui:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
|
|
@ -325,8 +325,7 @@ function getContentRewriter() {
|
||||||
contentRewriter.on('*', {
|
contentRewriter.on('*', {
|
||||||
element(el) {
|
element(el) {
|
||||||
if (!['p', 'span', 'br', 'a'].includes(el.tagName)) {
|
if (!['p', 'span', 'br', 'a'].includes(el.tagName)) {
|
||||||
const element = el as { tagName: string }
|
el.tagName = 'p'
|
||||||
element.tagName = 'p'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (el.hasAttribute('class')) {
|
if (el.hasAttribute('class')) {
|
||||||
|
|
|
@ -1,29 +0,0 @@
|
||||||
import { type Database } from 'wildebeest/backend/src/database'
|
|
||||||
|
|
||||||
export async function getRules(db: Database): Promise<Array<{ id: string; text: string }>> {
|
|
||||||
const query = `SELECT * from server_rules;`
|
|
||||||
const result = await db.prepare(query).all<{ id: string; text: string }>()
|
|
||||||
|
|
||||||
if (!result.success) {
|
|
||||||
throw new Error('SQL error: ' + result.error)
|
|
||||||
}
|
|
||||||
|
|
||||||
return result.results ?? []
|
|
||||||
}
|
|
||||||
|
|
||||||
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 deleteRule(db: Database, ruleId: number) {
|
|
||||||
return await db.prepare('DELETE FROM server_rules WHERE id=?').bind(ruleId).run()
|
|
||||||
}
|
|
|
@ -1,49 +0,0 @@
|
||||||
import { type Database } from 'wildebeest/backend/src/database'
|
|
||||||
import { type ServerSettingsData } from 'wildebeest/frontend/src/routes/(admin)/settings/(admin)/server-settings/layout'
|
|
||||||
|
|
||||||
export async function getSettings(db: Database): Promise<ServerSettingsData> {
|
|
||||||
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) {
|
|
||||||
throw new Error('SQL Error: ' + result.error)
|
|
||||||
}
|
|
||||||
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function updateSettings(db: Database, data: ServerSettingsData) {
|
|
||||||
const result = await upsertServerSettings(db, data)
|
|
||||||
if (result && !result.success) {
|
|
||||||
throw new Error('SQL Error: ' + result.error)
|
|
||||||
}
|
|
||||||
|
|
||||||
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()
|
|
||||||
}
|
|
|
@ -4,7 +4,7 @@ import type { Env } from 'wildebeest/backend/src/types/env'
|
||||||
|
|
||||||
function sqliteToPsql(query: string): string {
|
function sqliteToPsql(query: string): string {
|
||||||
let c = 0
|
let c = 0
|
||||||
return query.replace(/\?([0-9])?/g, (match: string, p1: string) => {
|
return query.replaceAll(/\?([0-9])?/g, (match: string, p1: string) => {
|
||||||
c += 1
|
c += 1
|
||||||
return `$${p1 || c}`
|
return `$${p1 || c}`
|
||||||
})
|
})
|
||||||
|
|
|
@ -281,12 +281,11 @@ export function parseRequest(request: Request, options?: Options): ParsedSignatu
|
||||||
|
|
||||||
if (h === 'request-line') {
|
if (h === 'request-line') {
|
||||||
if (!options.strict) {
|
if (!options.strict) {
|
||||||
const cf = (request as { cf?: IncomingRequestCfProperties }).cf
|
|
||||||
/*
|
/*
|
||||||
* We allow headers from the older spec drafts if strict parsing isn't
|
* We allow headers from the older spec drafts if strict parsing isn't
|
||||||
* specified in options.
|
* specified in options.
|
||||||
*/
|
*/
|
||||||
parsed.signingString += request.method + ' ' + request.url + ' ' + cf?.httpProtocol
|
parsed.signingString += request.method + ' ' + request.url + ' ' + request.cf?.httpProtocol
|
||||||
} else {
|
} else {
|
||||||
/* Strict parsing doesn't allow older draft headers. */
|
/* Strict parsing doesn't allow older draft headers. */
|
||||||
throw new StrictParsingError('request-line is not a valid header ' + 'with strict parsing enabled.')
|
throw new StrictParsingError('request-line is not a valid header ' + 'with strict parsing enabled.')
|
||||||
|
|
|
@ -19,8 +19,7 @@ export function initSentry(request: Request, env: Env, context: any) {
|
||||||
request,
|
request,
|
||||||
transportOptions: { headers },
|
transportOptions: { headers },
|
||||||
})
|
})
|
||||||
const cf = (request as { cf?: IncomingRequestCfProperties }).cf
|
const colo = request.cf && request.cf.colo ? request.cf.colo : 'UNKNOWN'
|
||||||
const colo = cf?.colo ? cf.colo : 'UNKNOWN'
|
|
||||||
sentry.setTag('colo', colo)
|
sentry.setTag('colo', colo)
|
||||||
|
|
||||||
// cf-connecting-ip should always be present, but if not we can fallback to XFF.
|
// cf-connecting-ip should always be present, but if not we can fallback to XFF.
|
||||||
|
|
|
@ -26,12 +26,12 @@ export function arrayBufferToBase64(buffer: ArrayBuffer): string {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function b64ToUrlEncoded(str: string): string {
|
export function b64ToUrlEncoded(str: string): string {
|
||||||
return str.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+/g, '')
|
return str.replaceAll(/\+/g, '-').replaceAll(/\//g, '_').replace(/=+/g, '')
|
||||||
}
|
}
|
||||||
|
|
||||||
export function urlEncodedToB64(str: string): string {
|
export function urlEncodedToB64(str: string): string {
|
||||||
const padding = '='.repeat((4 - (str.length % 4)) % 4)
|
const padding = '='.repeat((4 - (str.length % 4)) % 4)
|
||||||
return str.replace(/-/g, '+').replace(/_/g, '/') + padding
|
return str.replaceAll(/-/g, '+').replaceAll(/_/g, '/') + padding
|
||||||
}
|
}
|
||||||
|
|
||||||
export function stringToU8Array(str: string): Uint8Array {
|
export function stringToU8Array(str: string): Uint8Array {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { cloudflarePagesAdapter } from '@builder.io/qwik-city/adapters/cloudflare-pages/vite'
|
import { cloudflarePagesAdaptor } from '@builder.io/qwik-city/adaptors/cloudflare-pages/vite'
|
||||||
import { extendConfig } from '@builder.io/qwik-city/vite'
|
import { extendConfig } from '@builder.io/qwik-city/vite'
|
||||||
import baseConfig from '../../vite.config'
|
import baseConfig from '../../vite.config'
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ export default extendConfig(baseConfig, () => {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
cloudflarePagesAdapter({
|
cloudflarePagesAdaptor({
|
||||||
// Do not SSG as the D1 database is not available at build time, I think.
|
// Do not SSG as the D1 database is not available at build time, I think.
|
||||||
// staticGenerate: true,
|
// staticGenerate: true,
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -7,8 +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/backend/src/config/rules'
|
import { upsertRule } from 'wildebeest/functions/api/wb/settings/server/rules'
|
||||||
import { upsertServerSettings } from 'wildebeest/backend/src/config/server'
|
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.
|
||||||
|
|
|
@ -7,8 +7,6 @@
|
||||||
},
|
},
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"pretypes-check": "yarn build",
|
|
||||||
"types-check": "tsc",
|
|
||||||
"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",
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import { component$, useContext } from '@builder.io/qwik'
|
import { component$, useContext } from '@builder.io/qwik'
|
||||||
import { Link } from '@builder.io/qwik-city'
|
|
||||||
import { InstanceConfigContext } from '~/utils/instanceConfig'
|
import { InstanceConfigContext } from '~/utils/instanceConfig'
|
||||||
import { useDomain } from '~/utils/useDomain'
|
import { useDomain } from '~/utils/useDomain'
|
||||||
|
|
||||||
|
@ -17,12 +16,6 @@ export default component$(() => {
|
||||||
<img class="w-full" src={config.thumbnail} alt="Wildebeest instance thumbnail" />
|
<img class="w-full" src={config.thumbnail} alt="Wildebeest instance thumbnail" />
|
||||||
<p>{config.description}</p>
|
<p>{config.description}</p>
|
||||||
</div>
|
</div>
|
||||||
<Link
|
|
||||||
class="block text-wildebeest-500 border border-current my-4 p-2 text-center rounded-md no-underline"
|
|
||||||
href="/about"
|
|
||||||
>
|
|
||||||
Learn More
|
|
||||||
</Link>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
|
@ -37,7 +37,7 @@ export default component$(() => {
|
||||||
{ iconName: 'fa-globe', linkText: 'Federated', linkTarget: '/public', linkActiveRegex: /^\/public\/?$/ },
|
{ iconName: 'fa-globe', linkText: 'Federated', linkTarget: '/public', linkActiveRegex: /^\/public\/?$/ },
|
||||||
]
|
]
|
||||||
|
|
||||||
const aboutLink = { iconName: 'fa-ellipsis', linkText: 'About', linkTarget: '/about', linkActiveRegex: /^\/about/ }
|
// const aboutLink = { iconName: 'fa-ellipsis', linkText: 'About', linkTarget: '/about', linkActiveRegex: /^\/about/ }
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="bg-wildebeest-600 xl:bg-transparent flex flex-col justify-between right-column-wrapper text-wildebeest-200 flex-1 z-10">
|
<div class="bg-wildebeest-600 xl:bg-transparent flex flex-col justify-between right-column-wrapper text-wildebeest-200 flex-1 z-10">
|
||||||
|
@ -49,15 +49,16 @@ export default component$(() => {
|
||||||
</div>
|
</div>
|
||||||
<hr class="hidden xl:block border-t border-wildebeest-700 my-3" />
|
<hr class="hidden xl:block border-t border-wildebeest-700 my-3" />
|
||||||
{links.map((link) => renderNavLink(link))}
|
{links.map((link) => renderNavLink(link))}
|
||||||
<div class="xl:hidden">
|
{/* *********** Hiding the about link until the backend support is available ***************** */}
|
||||||
|
{/* <div class="xl:hidden">
|
||||||
<hr class="border-t border-wildebeest-700 my-3" />
|
<hr class="border-t border-wildebeest-700 my-3" />
|
||||||
{renderNavLink(aboutLink)}
|
{renderNavLink(aboutLink)}
|
||||||
</div>
|
</div> */}
|
||||||
|
|
||||||
{!isAuthorized && (
|
{!isAuthorized && (
|
||||||
<a
|
<a
|
||||||
class="w-full block mb-4 no-underline text-center 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"
|
class="w-full block mb-4 no-underline text-center 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"
|
||||||
href={`${loginUrl}`}
|
href={loginUrl}
|
||||||
>
|
>
|
||||||
Sign in
|
Sign in
|
||||||
</a>
|
</a>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { component$ } from '@builder.io/qwik'
|
import { component$ } from '@builder.io/qwik'
|
||||||
import { action$, Form, Link, z, zod$ } from '@builder.io/qwik-city'
|
import { action$, Form, Link, z, zod$ } from '@builder.io/qwik-city'
|
||||||
import { getDatabase } from 'wildebeest/backend/src/database'
|
import { getDatabase } from 'wildebeest/backend/src/database'
|
||||||
import { updateSettings } from 'wildebeest/backend/src/config/server'
|
import { handleRequestPost } from 'wildebeest/functions/api/wb/settings/server/server'
|
||||||
import { TextArea } from '~/components/Settings/TextArea'
|
import { TextArea } from '~/components/Settings/TextArea'
|
||||||
import { serverSettingsLoader } from '../layout'
|
import { serverSettingsLoader } from '../layout'
|
||||||
|
|
||||||
|
@ -12,12 +12,16 @@ const zodSchema = zod$({
|
||||||
|
|
||||||
export type ServerAboutData = Awaited<typeof zodSchema>['_type']
|
export type ServerAboutData = Awaited<typeof zodSchema>['_type']
|
||||||
|
|
||||||
export const action = action$(async (data, { platform }) => {
|
export const action = action$(async (data, { request, platform }) => {
|
||||||
const db = await getDatabase(platform)
|
|
||||||
let success = false
|
let success = false
|
||||||
try {
|
try {
|
||||||
await updateSettings(db, data)
|
const response = await handleRequestPost(
|
||||||
success = true
|
await getDatabase(platform),
|
||||||
|
new Request(request, { body: JSON.stringify(data) }),
|
||||||
|
platform.ACCESS_AUTH_DOMAIN,
|
||||||
|
platform.ACCESS_AUD
|
||||||
|
)
|
||||||
|
success = response.ok
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
success = false
|
success = false
|
||||||
}
|
}
|
||||||
|
@ -32,7 +36,7 @@ export default component$(() => {
|
||||||
const saveAction = action()
|
const saveAction = action()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form action={saveAction}>
|
<Form action={saveAction} spaReset>
|
||||||
<p class="mt-12 mb-9">Provide in-depth information about how the server is operated, moderated, funded.</p>
|
<p class="mt-12 mb-9">Provide in-depth information about how the server is operated, moderated, funded.</p>
|
||||||
|
|
||||||
<div class="mb-12">
|
<div class="mb-12">
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { component$ } from '@builder.io/qwik'
|
import { component$ } from '@builder.io/qwik'
|
||||||
import { action$, Form, zod$, z } from '@builder.io/qwik-city'
|
import { action$, Form, zod$, z } from '@builder.io/qwik-city'
|
||||||
import { getDatabase } from 'wildebeest/backend/src/database'
|
import { getDatabase } from 'wildebeest/backend/src/database'
|
||||||
import { updateSettings } from 'wildebeest/backend/src/config/server'
|
import { handleRequestPost } from 'wildebeest/functions/api/wb/settings/server/server'
|
||||||
import { TextArea } from '~/components/Settings/TextArea'
|
import { TextArea } from '~/components/Settings/TextArea'
|
||||||
import { TextInput } from '~/components/Settings/TextInput'
|
import { TextInput } from '~/components/Settings/TextInput'
|
||||||
import { serverSettingsLoader } from '../layout'
|
import { serverSettingsLoader } from '../layout'
|
||||||
|
@ -13,12 +13,16 @@ const zodSchema = zod$({
|
||||||
|
|
||||||
export type ServerBrandingData = Awaited<typeof zodSchema>['_type']
|
export type ServerBrandingData = Awaited<typeof zodSchema>['_type']
|
||||||
|
|
||||||
export const action = action$(async (data, { platform }) => {
|
export const action = action$(async (data, { request, platform }) => {
|
||||||
const db = await getDatabase(platform)
|
|
||||||
let success = false
|
let success = false
|
||||||
try {
|
try {
|
||||||
await updateSettings(db, data)
|
const response = await handleRequestPost(
|
||||||
success = true
|
await getDatabase(platform),
|
||||||
|
new Request(request, { body: JSON.stringify(data) }),
|
||||||
|
platform.ACCESS_AUTH_DOMAIN,
|
||||||
|
platform.ACCESS_AUD
|
||||||
|
)
|
||||||
|
success = response.ok
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
success = false
|
success = false
|
||||||
}
|
}
|
||||||
|
@ -33,7 +37,7 @@ export default component$(() => {
|
||||||
const saveAction = action()
|
const saveAction = action()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form action={saveAction}>
|
<Form action={saveAction} spaReset>
|
||||||
<p class="mt-12 mb-9">
|
<p class="mt-12 mb-9">
|
||||||
Your server's branding differentiates it from other servers in the network. This information may be displayed
|
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
|
across a variety of environments, such as Mastodon's web interface, native applications, in link previews on
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { component$, Slot } from '@builder.io/qwik'
|
import { component$, Slot } from '@builder.io/qwik'
|
||||||
import { Link, loader$, useLocation } from '@builder.io/qwik-city'
|
import { Link, loader$, useLocation } from '@builder.io/qwik-city'
|
||||||
import { getDatabase } from 'wildebeest/backend/src/database'
|
import { getDatabase } from 'wildebeest/backend/src/database'
|
||||||
import { getSettings } from 'wildebeest/backend/src/config/server'
|
import { handleRequestGet } from 'wildebeest/functions/api/wb/settings/server/server'
|
||||||
import { ServerAboutData } from './about'
|
import { ServerAboutData } from './about'
|
||||||
import { ServerBrandingData } from './branding'
|
import { ServerBrandingData } from './branding'
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ export type ServerSettingsData = ServerBrandingData & ServerAboutData
|
||||||
export const serverSettingsLoader = loader$<Promise<Partial<ServerSettingsData>>>(async ({ platform }) => {
|
export const serverSettingsLoader = loader$<Promise<Partial<ServerSettingsData>>>(async ({ platform }) => {
|
||||||
const database = await getDatabase(platform)
|
const database = await getDatabase(platform)
|
||||||
|
|
||||||
const settingsResp = await getSettings(database)
|
const settingsResp = await handleRequestGet(database)
|
||||||
let settingsData: Partial<ServerSettingsData> = {}
|
let settingsData: Partial<ServerSettingsData> = {}
|
||||||
try {
|
try {
|
||||||
settingsData = await settingsResp.json()
|
settingsData = await settingsResp.json()
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import { component$ } from '@builder.io/qwik'
|
import { component$ } from '@builder.io/qwik'
|
||||||
import { action$, Form, loader$, useNavigate, z, zod$ } from '@builder.io/qwik-city'
|
import { action$, Form, loader$, useNavigate, z, zod$ } from '@builder.io/qwik-city'
|
||||||
import { getDatabase } from 'wildebeest/backend/src/database'
|
import { getDatabase } from 'wildebeest/backend/src/database'
|
||||||
import { getRules, upsertRule } from 'wildebeest/backend/src/config/rules'
|
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 { TextArea } from '~/components/Settings/TextArea'
|
||||||
import { getErrorHtml } from '~/utils/getErrorHtml/getErrorHtml'
|
import { getErrorHtml } from '~/utils/getErrorHtml/getErrorHtml'
|
||||||
|
|
||||||
|
@ -32,7 +33,14 @@ export const editAction = action$(
|
||||||
|
|
||||||
export const ruleLoader = loader$<Promise<{ id: number; text: string }>>(async ({ params, platform, html }) => {
|
export const ruleLoader = loader$<Promise<{ id: number; text: string }>>(async ({ params, platform, html }) => {
|
||||||
const database = await getDatabase(platform)
|
const database = await getDatabase(platform)
|
||||||
const rules = await getRules(database)
|
|
||||||
|
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'])
|
const rule: { id: number; text: string } | undefined = rules.find((r) => r.id === +params['id'])
|
||||||
|
|
||||||
|
@ -55,7 +63,7 @@ export default component$(() => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Form action={editActionObj}>
|
<Form action={editActionObj} spaReset>
|
||||||
<p class="mt-12 mb-9">
|
<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
|
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
|
after a problem arises. Make it easier to see your server's rules at a glance by providing them in a flat
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import { component$ } from '@builder.io/qwik'
|
import { component$ } from '@builder.io/qwik'
|
||||||
import { action$, Form, Link, loader$, z, zod$ } from '@builder.io/qwik-city'
|
import { action$, Form, Link, loader$, z, zod$ } from '@builder.io/qwik-city'
|
||||||
import { getDatabase } from 'wildebeest/backend/src/database'
|
import { getDatabase } from 'wildebeest/backend/src/database'
|
||||||
import { getRules, deleteRule, upsertRule } from 'wildebeest/backend/src/config/rules'
|
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'
|
import { TextArea } from '~/components/Settings/TextArea'
|
||||||
|
|
||||||
export type ServerSettingsData = { rules: string[] }
|
export type ServerSettingsData = { rules: string[] }
|
||||||
|
@ -47,7 +48,15 @@ export const deleteAction = action$(
|
||||||
|
|
||||||
export const rulesLoader = loader$<Promise<{ id: number; text: string }[]>>(async ({ platform }) => {
|
export const rulesLoader = loader$<Promise<{ id: number; text: string }[]>>(async ({ platform }) => {
|
||||||
const database = await getDatabase(platform)
|
const database = await getDatabase(platform)
|
||||||
const rules = await getRules(database)
|
|
||||||
|
const settingsResp = await handleRequestGet(database)
|
||||||
|
let rules: { id: number; text: string }[] = []
|
||||||
|
try {
|
||||||
|
rules = await settingsResp.json()
|
||||||
|
} catch {
|
||||||
|
rules = []
|
||||||
|
}
|
||||||
|
|
||||||
return JSON.parse(JSON.stringify(rules))
|
return JSON.parse(JSON.stringify(rules))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -85,11 +94,11 @@ export default component$(() => {
|
||||||
</Form>
|
</Form>
|
||||||
<div>
|
<div>
|
||||||
{rules.value.map(({ id, text }, idx) => {
|
{rules.value.map(({ id, text }, idx) => {
|
||||||
const ruleNumber = idx + 1
|
const ruleId = idx + 1
|
||||||
const ruleBtnText = `${ruleNumber}. ${text.slice(0, 27)}${text.length > 27 ? '...' : ''}`
|
const ruleBtnText = `${ruleId}. ${text.slice(0, 27)}${text.length > 27 ? '...' : ''}`
|
||||||
return (
|
return (
|
||||||
<div key={id} class="p-4 my-4 bg-wildebeest-600 rounded">
|
<div key={id} class="p-4 my-4 bg-wildebeest-600 rounded">
|
||||||
<Link href={`./edit/${id}`} class="max-w-max inline-block mb-4 no-underline text-lg font-semibold">
|
<Link href={`./edit/${ruleId}`} class="max-w-max inline-block mb-4 no-underline text-lg font-semibold">
|
||||||
{ruleBtnText}
|
{ruleBtnText}
|
||||||
</Link>
|
</Link>
|
||||||
<div class="flex justify-between text-wildebeest-400">
|
<div class="flex justify-between text-wildebeest-400">
|
||||||
|
|
|
@ -2,10 +2,11 @@ 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 { getDatabase } from 'wildebeest/backend/src/database'
|
||||||
import { getDomain } from 'wildebeest/backend/src/utils/getDomain'
|
import { getDomain } from 'wildebeest/backend/src/utils/getDomain'
|
||||||
import { getSettings } from 'wildebeest/backend/src/config/server'
|
import { handleRequestGet as settingsHandleRequestGet } from 'wildebeest/functions/api/wb/settings/server/server'
|
||||||
import { getRules } from 'wildebeest/backend/src/config/rules'
|
import { handleRequestGet as rulesHandleRequestGet } from 'wildebeest/functions/api/v1/instance/rules'
|
||||||
import { Accordion } from '~/components/Accordion/Accordion'
|
import { Accordion } from '~/components/Accordion/Accordion'
|
||||||
import { HtmlContent } from '~/components/HtmlContent/HtmlContent'
|
import { HtmlContent } from '~/components/HtmlContent/HtmlContent'
|
||||||
|
import { ServerSettingsData } from '~/routes/(admin)/settings/(admin)/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'
|
||||||
|
@ -27,9 +28,25 @@ type AboutInfo = {
|
||||||
export const aboutInfoLoader = loader$<Promise<AboutInfo>>(async ({ resolveValue, request, platform }) => {
|
export const aboutInfoLoader = loader$<Promise<AboutInfo>>(async ({ resolveValue, request, platform }) => {
|
||||||
// TODO: fetching the instance for the thumbnail, but that should be part of the settings
|
// 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 database = await getDatabase(platform)
|
||||||
const brandingData = await getSettings(database)
|
|
||||||
const rules = await getRules(database)
|
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)
|
const admins = await getAdmins(database)
|
||||||
let adminAccount: Account | null = null
|
let adminAccount: Account | null = null
|
||||||
|
|
||||||
|
@ -105,10 +122,10 @@ export default component$(() => {
|
||||||
<div class="my-1">
|
<div class="my-1">
|
||||||
<Accordion title="Server rules">
|
<Accordion title="Server rules">
|
||||||
<ol class="list-none flex flex-col gap-1 my-5 px-6">
|
<ol class="list-none flex flex-col gap-1 my-5 px-6">
|
||||||
{aboutInfo.rules.map(({ id, text }, idx) => (
|
{aboutInfo.rules.map(({ id, text }) => (
|
||||||
<li key={id} class="flex items-center border-wildebeest-700 border-b last-of-type:border-b-0 py-2">
|
<li key={id} class="flex items-center border-wildebeest-700 border-b last-of-type:border-b-0 py-2">
|
||||||
<span class="bg-wildebeest-vibrant-400 text-wildebeest-900 mr-4 my-1 p-4 rounded-full w-5 h-5 grid place-content-center">
|
<span class="bg-wildebeest-vibrant-400 text-wildebeest-900 mr-4 my-1 p-4 rounded-full w-5 h-5 grid place-content-center">
|
||||||
{idx + 1}
|
{id}
|
||||||
</span>
|
</span>
|
||||||
<span>{text}</span>
|
<span>{text}</span>
|
||||||
</li>
|
</li>
|
||||||
|
|
|
@ -60,12 +60,12 @@ export async function handleRequest(
|
||||||
|
|
||||||
if (formData.has('display_name')) {
|
if (formData.has('display_name')) {
|
||||||
const value = formData.get('display_name')!
|
const value = formData.get('display_name')!
|
||||||
await updateActorProperty(db, connectedActor.id, 'name', value as string)
|
await updateActorProperty(db, connectedActor.id, 'name', value)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (formData.has('note')) {
|
if (formData.has('note')) {
|
||||||
const value = formData.get('note')!
|
const value = formData.get('note')!
|
||||||
await updateActorProperty(db, connectedActor.id, 'summary', value as string)
|
await updateActorProperty(db, connectedActor.id, 'summary', value)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (formData.has('avatar')) {
|
if (formData.has('avatar')) {
|
||||||
|
|
|
@ -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/backend/src/utils/auth/isUserAdmin'
|
||||||
|
|
||||||
|
export const onRequestGet: PagesFunction<Env, any, ContextData> = async ({ env, request }) => {
|
||||||
|
return handleRequestPost(await getDatabase(env), request, env.ACCESS_AUTH_DOMAIN, env.ACCESS_AUD)
|
||||||
|
}
|
||||||
|
|
||||||
|
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, env.ACCESS_AUTH_DOMAIN, env.ACCESS_AUD)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function handleRequestPost(db: Database, request: Request, accessAuthDomain: string, accessAud: string) {
|
||||||
|
const cookie = parse(request.headers.get('Cookie') || '')
|
||||||
|
const jwt = cookie['CF_Authorization']
|
||||||
|
const isAdmin = await isUserAdmin(request, jwt, accessAuthDomain, accessAud, 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, accessAuthDomain: string, accessAud: string) {
|
||||||
|
const cookie = parse(request.headers.get('Cookie') || '')
|
||||||
|
const jwt = cookie['CF_Authorization']
|
||||||
|
const isAdmin = await isUserAdmin(request, jwt, accessAuthDomain, accessAud, 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()
|
||||||
|
}
|
|
@ -42,11 +42,11 @@ export async function handlePostRequest(
|
||||||
const properties: Record<string, string> = {}
|
const properties: Record<string, string> = {}
|
||||||
|
|
||||||
if (formData.has('username')) {
|
if (formData.has('username')) {
|
||||||
properties.preferredUsername = (formData.get('username') as string) || ''
|
properties.preferredUsername = formData.get('username') || ''
|
||||||
}
|
}
|
||||||
|
|
||||||
if (formData.has('name')) {
|
if (formData.has('name')) {
|
||||||
properties.name = (formData.get('name') as string) || ''
|
properties.name = formData.get('name') || ''
|
||||||
}
|
}
|
||||||
|
|
||||||
await createPerson(domain, db, userKEK, email, properties)
|
await createPerson(domain, db, userKEK, email, properties)
|
||||||
|
|
|
@ -50,7 +50,7 @@
|
||||||
"http-message-signatures": "^0.1.2",
|
"http-message-signatures": "^0.1.2",
|
||||||
"toucan-js": "^3.1.0"
|
"toucan-js": "^3.1.0"
|
||||||
},
|
},
|
||||||
"simple-git-hooks": {
|
"simple-git-hooks": {
|
||||||
"pre-commit": "yarn lint"
|
"pre-commit": "yarn lint"
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -19,14 +19,14 @@ const config: PlaywrightTestConfig = {
|
||||||
* Maximum time expect() should wait for the condition to be met.
|
* Maximum time expect() should wait for the condition to be met.
|
||||||
* For example in `await expect(locator).toHaveText();`
|
* For example in `await expect(locator).toHaveText();`
|
||||||
*/
|
*/
|
||||||
timeout: (process.env.CI ? 30 : 5) * 1000,
|
timeout: process.env.CI ? 5000 : 500,
|
||||||
},
|
},
|
||||||
/* Run tests in files in parallel */
|
/* Run tests in files in parallel */
|
||||||
fullyParallel: true,
|
fullyParallel: true,
|
||||||
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
||||||
forbidOnly: !!process.env.CI,
|
forbidOnly: !!process.env.CI,
|
||||||
/* Retry on CI only */
|
/* Retry on CI only */
|
||||||
retries: process.env.CI ? 1 : 0,
|
retries: process.env.CI ? 3 : 0,
|
||||||
/* Opt out of parallel tests on CI. */
|
/* Opt out of parallel tests on CI. */
|
||||||
workers: process.env.CI ? 1 : undefined,
|
workers: process.env.CI ? 1 : undefined,
|
||||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||||
|
@ -34,7 +34,7 @@ const config: PlaywrightTestConfig = {
|
||||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||||
use: {
|
use: {
|
||||||
/* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */
|
/* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */
|
||||||
actionTimeout: (process.env.CI ? 30 : 10) * 1000,
|
actionTimeout: 0,
|
||||||
/* Base URL to use in actions like `await page.goto('/')`. */
|
/* Base URL to use in actions like `await page.goto('/')`. */
|
||||||
// baseURL: 'http://localhost:3000',
|
// baseURL: 'http://localhost:3000',
|
||||||
|
|
||||||
|
|
Ładowanie…
Reference in New Issue