diff --git a/packages/ditto/app.ts b/packages/ditto/app.ts index bd86ac51..eb77053d 100644 --- a/packages/ditto/app.ts +++ b/packages/ditto/app.ts @@ -151,6 +151,9 @@ import dittoNamesRoute from '@/routes/dittoNamesRoute.ts'; import pleromaAdminPermissionGroupsRoute from '@/routes/pleromaAdminPermissionGroupsRoute.ts'; import pleromaStatusesRoute from '@/routes/pleromaStatusesRoute.ts'; import { DittoRelayStore } from '@/storages/DittoRelayStore.ts'; +import { PolicyRegistry } from '@nostrify/policies'; +import { adminListPoliciesController, adminCurrentPolicyController } from '@/controllers/api/policies.ts'; +import { createPolicyEvent, DEFAULT_POLICIES } from '@/utils/policies.ts'; export interface AppEnv extends DittoEnv { Variables: DittoEnv['Variables'] & { @@ -197,6 +200,12 @@ const pgstore = new DittoPgStore({ const pool = new DittoPool({ conf, relay: pgstore }); const relay = new DittoRelayStore({ db, conf, pool, relay: pgstore }); +const policyRegistry = new PolicyRegistry({ antiDuplicationPolicyStore: await Deno.openKv(), store: relay }); +const havePolicy = await relay.count([{ kinds: [11984], authors: [await conf.signer.getPublicKey()] }]); + +if (!havePolicy) { + relay.event(await createPolicyEvent(conf, DEFAULT_POLICIES)); +} await seedZapSplits({ conf, relay }); @@ -496,6 +505,9 @@ app.delete('/api/v1/pleroma/admin/users/tag', userMiddleware({ role: 'admin' }), app.patch('/api/v1/pleroma/admin/users/suggest', userMiddleware({ role: 'admin' }), pleromaAdminSuggestController); app.patch('/api/v1/pleroma/admin/users/unsuggest', userMiddleware({ role: 'admin' }), pleromaAdminUnsuggestController); +app.get('/api/v1/admin/policies', userMiddleware({ role: 'admin' }), adminListPoliciesController); +app.get('/api/v1/admin/policies/current', userMiddleware({ role: 'admin' }), adminCurrentPolicyController); + app.route('/api/v1/custom_emojis', customEmojisRoute); // Not (yet) implemented. @@ -558,5 +570,5 @@ app.get('*', publicFiles, staticFiles, ratelimit, frontendController); app.onError(errorHandler); export default app; - +export { policyRegistry }; export type { AppContext, AppController, AppMiddleware }; diff --git a/packages/ditto/controllers/api/policies.ts b/packages/ditto/controllers/api/policies.ts new file mode 100644 index 00000000..6c527b91 --- /dev/null +++ b/packages/ditto/controllers/api/policies.ts @@ -0,0 +1,26 @@ +import { policyRegistry, type AppController } from '@/app.ts'; +import { DEFAULT_POLICIES } from "@/utils/policies.ts"; + +export const adminListPoliciesController: AppController = (c) => { + return c.json(Object.entries(policyRegistry.policies) + .map(([internalName, item]) => { + return { + internalName, + ...item, + instantiate: undefined, + } + })) +}; + +export const adminCurrentPolicyController: AppController = async (c) => { + const { relay, conf } = c.var; + const pubkey = await conf.signer.getPublicKey(); + + const current = await relay.query([{ + authors: [pubkey], + kinds: [11984] + }]).then(events => events[0]); + + if (current) return c.json({ mode: conf.policyMode, policies: current }); + return c.json({ mode: conf.policyMode, policies: DEFAULT_POLICIES }); +} diff --git a/packages/ditto/utils/policies.ts b/packages/ditto/utils/policies.ts new file mode 100644 index 00000000..cc7c3270 --- /dev/null +++ b/packages/ditto/utils/policies.ts @@ -0,0 +1,29 @@ +import { policyRegistry } from '@/app.ts'; +import { nostrNow } from '@/utils.ts'; +import type { DittoConf } from "@ditto/conf"; + +type ParamValue = string | number | boolean; + +interface PolicySpec { + name: keyof typeof policyRegistry.policies; + params?: Record; +} + +export const DEFAULT_POLICIES: PolicySpec[] = [ + { "name": "AntiDuplicationPolicy" }, + { "name": "AuthorPolicy" }, + { "name": "DomainPolicy" }, + { "name": "HellthreadPolicy" }, + { "name": "ReplyBotPolicy" }, + { "name": "SizePolicy" }, + { "name": "HashtagPolicy", "params": { "hashtags": ["NSFW", "explicit", "violence", "cp", "porn"] } }, +]; + +export const createPolicyEvent = async (conf: DittoConf, policies: PolicySpec[]) => { + return await conf.signer.signEvent({ + kind: 11984, + content: JSON.stringify({ policies }), + created_at: nostrNow(), + tags: [] + }) +} \ No newline at end of file