mirror of
https://gitlab.com/soapbox-pub/ditto.git
synced 2025-12-06 11:29:46 +00:00
feat: create updateInstanceController
for now only update: title, description, screenshots (user must provide the image URL) and thumbnail (user must provide the image URL) screenshots array is stored in the content of the kind 0 of the
This commit is contained in:
parent
c6ff4a9d25
commit
339b13c084
6 changed files with 96 additions and 4 deletions
|
|
@ -49,6 +49,7 @@ import {
|
||||||
nameRequestController,
|
nameRequestController,
|
||||||
nameRequestsController,
|
nameRequestsController,
|
||||||
statusZapSplitsController,
|
statusZapSplitsController,
|
||||||
|
updateInstanceController,
|
||||||
updateZapSplitsController,
|
updateZapSplitsController,
|
||||||
} from '@/controllers/api/ditto.ts';
|
} from '@/controllers/api/ditto.ts';
|
||||||
import { emptyArrayController, notImplementedController } from '@/controllers/api/fallback.ts';
|
import { emptyArrayController, notImplementedController } from '@/controllers/api/fallback.ts';
|
||||||
|
|
@ -303,6 +304,8 @@ app.delete('/api/v1/pleroma/admin/statuses/:id', requireRole('admin'), pleromaAd
|
||||||
app.get('/api/v1/admin/ditto/relays', requireRole('admin'), adminRelaysController);
|
app.get('/api/v1/admin/ditto/relays', requireRole('admin'), adminRelaysController);
|
||||||
app.put('/api/v1/admin/ditto/relays', requireRole('admin'), adminSetRelaysController);
|
app.put('/api/v1/admin/ditto/relays', requireRole('admin'), adminSetRelaysController);
|
||||||
|
|
||||||
|
app.put('/api/v1/admin/ditto/instance', requireRole('admin'), updateInstanceController);
|
||||||
|
|
||||||
app.post('/api/v1/ditto/names', requireSigner, nameRequestController);
|
app.post('/api/v1/ditto/names', requireSigner, nameRequestController);
|
||||||
app.get('/api/v1/ditto/names', requireSigner, nameRequestsController);
|
app.get('/api/v1/ditto/names', requireSigner, nameRequestsController);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,11 +7,13 @@ import { addTag } from '@/utils/tags.ts';
|
||||||
import { AdminSigner } from '@/signers/AdminSigner.ts';
|
import { AdminSigner } from '@/signers/AdminSigner.ts';
|
||||||
import { booleanParamSchema, percentageSchema } from '@/schema.ts';
|
import { booleanParamSchema, percentageSchema } from '@/schema.ts';
|
||||||
import { Conf } from '@/config.ts';
|
import { Conf } from '@/config.ts';
|
||||||
import { createEvent, paginated, parseBody } from '@/utils/api.ts';
|
import { createEvent, paginated, parseBody, updateEvent } from '@/utils/api.ts';
|
||||||
|
import { getInstanceMetadata } from '@/utils/instance.ts';
|
||||||
import { deleteTag } from '@/utils/tags.ts';
|
import { deleteTag } from '@/utils/tags.ts';
|
||||||
import { DittoEvent } from '@/interfaces/DittoEvent.ts';
|
import { DittoEvent } from '@/interfaces/DittoEvent.ts';
|
||||||
import { DittoZapSplits, getZapSplits } from '@/utils/zap-split.ts';
|
import { DittoZapSplits, getZapSplits } from '@/utils/zap-split.ts';
|
||||||
import { getAuthor } from '@/queries.ts';
|
import { getAuthor } from '@/queries.ts';
|
||||||
|
import { screenshotsSchema } from '@/schemas/nostr.ts';
|
||||||
import { hydrateEvents } from '@/storages/hydrate.ts';
|
import { hydrateEvents } from '@/storages/hydrate.ts';
|
||||||
import { renderNameRequest } from '@/views/ditto.ts';
|
import { renderNameRequest } from '@/views/ditto.ts';
|
||||||
import { renderAccount } from '@/views/mastodon/accounts.ts';
|
import { renderAccount } from '@/views/mastodon/accounts.ts';
|
||||||
|
|
@ -287,3 +289,54 @@ export const statusZapSplitsController: AppController = async (c) => {
|
||||||
|
|
||||||
return c.json(zapSplits, 200);
|
return c.json(zapSplits, 200);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const updateInstanceSchema = z.object({
|
||||||
|
title: z.string().optional(),
|
||||||
|
description: z.string().optional(),
|
||||||
|
screenshots: screenshotsSchema.optional(),
|
||||||
|
thumbnail: z.object({
|
||||||
|
url: z.string().url(),
|
||||||
|
blurhash: z.string().optional(),
|
||||||
|
versions: z.object({
|
||||||
|
'@1x': z.string().url().optional(),
|
||||||
|
'@2x': z.string().url().optional(),
|
||||||
|
}).optional(),
|
||||||
|
}).optional(),
|
||||||
|
}).strict();
|
||||||
|
|
||||||
|
export const updateInstanceController: AppController = async (c) => {
|
||||||
|
const body = await parseBody(c.req.raw);
|
||||||
|
const result = updateInstanceSchema.safeParse(body);
|
||||||
|
const pubkey = Conf.pubkey;
|
||||||
|
|
||||||
|
if (!result.success) {
|
||||||
|
return c.json(result.error, 422);
|
||||||
|
}
|
||||||
|
|
||||||
|
await updateEvent(
|
||||||
|
{ kinds: [0], authors: [pubkey], limit: 1 },
|
||||||
|
async (_) => {
|
||||||
|
const meta = await getInstanceMetadata(await Storages.db(), c.req.raw.signal);
|
||||||
|
const {
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
screenshots,
|
||||||
|
thumbnail,
|
||||||
|
} = result.data;
|
||||||
|
|
||||||
|
meta.name = title ?? meta.name;
|
||||||
|
meta.about = description ?? meta.about;
|
||||||
|
meta.screenshots = screenshots ?? meta.screenshots;
|
||||||
|
meta.thumbnail = thumbnail ?? meta.thumbnail;
|
||||||
|
|
||||||
|
return {
|
||||||
|
kind: 0,
|
||||||
|
content: JSON.stringify(meta),
|
||||||
|
tags: [],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
c,
|
||||||
|
);
|
||||||
|
|
||||||
|
return c.json(204);
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -96,6 +96,7 @@ const instanceV2Controller: AppController = async (c) => {
|
||||||
'@2x': meta.picture,
|
'@2x': meta.picture,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
screenshots: meta.screenshots,
|
||||||
languages: [
|
languages: [
|
||||||
'en',
|
'en',
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ export const manifestController: AppController = async (c) => {
|
||||||
scope: '/',
|
scope: '/',
|
||||||
short_name: meta.name,
|
short_name: meta.name,
|
||||||
start_url: '/',
|
start_url: '/',
|
||||||
|
screenshots: meta.screenshots,
|
||||||
};
|
};
|
||||||
|
|
||||||
return c.json(manifest, {
|
return c.json(manifest, {
|
||||||
|
|
|
||||||
|
|
@ -2,17 +2,48 @@ import { NSchema as n } from '@nostrify/nostrify';
|
||||||
import { getEventHash, verifyEvent } from 'nostr-tools';
|
import { getEventHash, verifyEvent } from 'nostr-tools';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { safeUrlSchema } from '@/schema.ts';
|
import { safeUrlSchema, sizesSchema } from '@/schema.ts';
|
||||||
|
|
||||||
/** Nostr event schema that also verifies the event's signature. */
|
/** Nostr event schema that also verifies the event's signature. */
|
||||||
const signedEventSchema = n.event()
|
const signedEventSchema = n.event()
|
||||||
.refine((event) => event.id === getEventHash(event), 'Event ID does not match hash')
|
.refine((event) => event.id === getEventHash(event), 'Event ID does not match hash')
|
||||||
.refine(verifyEvent, 'Event signature is invalid');
|
.refine(verifyEvent, 'Event signature is invalid');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stored in the kind 0 content.
|
||||||
|
* https://developer.mozilla.org/en-US/docs/Web/Manifest/screenshots
|
||||||
|
*/
|
||||||
|
const screenshotsSchema = z.array(z.object({
|
||||||
|
form_factor: z.enum(['narrow', 'wide']).optional(),
|
||||||
|
label: z.string().optional(),
|
||||||
|
platform: z.enum([
|
||||||
|
'android',
|
||||||
|
'chromeos',
|
||||||
|
'ipados',
|
||||||
|
'ios',
|
||||||
|
'kaios',
|
||||||
|
'macos',
|
||||||
|
'windows',
|
||||||
|
'xbox',
|
||||||
|
'chrome_web_store',
|
||||||
|
'itunes',
|
||||||
|
'microsoft-inbox',
|
||||||
|
'microsoft-store',
|
||||||
|
'play',
|
||||||
|
]).optional(),
|
||||||
|
/** https://developer.mozilla.org/en-US/docs/Web/Manifest/screenshots#sizes */
|
||||||
|
sizes: sizesSchema,
|
||||||
|
/** Absolute URL. */
|
||||||
|
src: z.string().url(),
|
||||||
|
/** MIME type of the image. */
|
||||||
|
type: z.string().optional(),
|
||||||
|
}));
|
||||||
|
|
||||||
/** Kind 0 content schema for the Ditto server admin user. */
|
/** Kind 0 content schema for the Ditto server admin user. */
|
||||||
const serverMetaSchema = n.metadata().and(z.object({
|
const serverMetaSchema = n.metadata().and(z.object({
|
||||||
tagline: z.string().optional().catch(undefined),
|
tagline: z.string().optional().catch(undefined),
|
||||||
email: z.string().optional().catch(undefined),
|
email: z.string().optional().catch(undefined),
|
||||||
|
screenshots: screenshotsSchema.optional(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
/** NIP-11 Relay Information Document. */
|
/** NIP-11 Relay Information Document. */
|
||||||
|
|
@ -32,4 +63,4 @@ const emojiTagSchema = z.tuple([z.literal('emoji'), z.string(), z.string().url()
|
||||||
/** NIP-30 custom emoji tag. */
|
/** NIP-30 custom emoji tag. */
|
||||||
type EmojiTag = z.infer<typeof emojiTagSchema>;
|
type EmojiTag = z.infer<typeof emojiTagSchema>;
|
||||||
|
|
||||||
export { type EmojiTag, emojiTagSchema, relayInfoDocSchema, serverMetaSchema, signedEventSchema };
|
export { type EmojiTag, emojiTagSchema, relayInfoDocSchema, screenshotsSchema, serverMetaSchema, signedEventSchema };
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
import { NostrEvent, NostrMetadata, NSchema as n, NStore } from '@nostrify/nostrify';
|
import { NostrEvent, NostrMetadata, NSchema as n, NStore } from '@nostrify/nostrify';
|
||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { Conf } from '@/config.ts';
|
import { Conf } from '@/config.ts';
|
||||||
import { serverMetaSchema } from '@/schemas/nostr.ts';
|
import { screenshotsSchema, serverMetaSchema } from '@/schemas/nostr.ts';
|
||||||
|
|
||||||
/** Like NostrMetadata, but some fields are required and also contains some extra fields. */
|
/** Like NostrMetadata, but some fields are required and also contains some extra fields. */
|
||||||
export interface InstanceMetadata extends NostrMetadata {
|
export interface InstanceMetadata extends NostrMetadata {
|
||||||
|
|
@ -11,6 +12,7 @@ export interface InstanceMetadata extends NostrMetadata {
|
||||||
picture: string;
|
picture: string;
|
||||||
tagline: string;
|
tagline: string;
|
||||||
event?: NostrEvent;
|
event?: NostrEvent;
|
||||||
|
screenshots: z.infer<typeof screenshotsSchema>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get and parse instance metadata from the kind 0 of the admin user. */
|
/** Get and parse instance metadata from the kind 0 of the admin user. */
|
||||||
|
|
@ -34,5 +36,6 @@ export async function getInstanceMetadata(store: NStore, signal?: AbortSignal):
|
||||||
email: meta.email ?? `postmaster@${Conf.url.host}`,
|
email: meta.email ?? `postmaster@${Conf.url.host}`,
|
||||||
picture: meta.picture ?? Conf.local('/images/thumbnail.png'),
|
picture: meta.picture ?? Conf.local('/images/thumbnail.png'),
|
||||||
event,
|
event,
|
||||||
|
screenshots: meta.screenshots ?? [],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue