Convert users to Events
This commit is contained in:
parent
716a7019c2
commit
13bf936088
|
@ -1,13 +1,25 @@
|
|||
# Ditto NIP-78 events
|
||||
# Ditto custom events
|
||||
|
||||
## Users
|
||||
|
||||
Ditto user events describe a pubkey's relationship with the Ditto server. They are parameterized replaceable events of kind `30361` where the `d` tag is a pubkey. These events are published by Ditto's internal admin keypair.
|
||||
|
||||
User events have the following tags:
|
||||
|
||||
- `d` - pubkey of the user.
|
||||
- `name` - NIP-05 username granted to the user, without the domain.
|
||||
- `role` - one of `admin` or `user`.
|
||||
|
||||
## NIP-78
|
||||
|
||||
[NIP-78](https://github.com/nostr-protocol/nips/blob/master/78.md) defines events of kind `30078` with a globally unique `d` tag. These events are queried by the `d` tag, which allows Ditto to store custom data on relays. Ditto uses reverse DNS names like `pub.ditto.<thing>` for `d` tags.
|
||||
|
||||
The sections below describe the `content` field. Some are encrypted and some are not, depending on whether the data should be public. Also, some events are user events, and some are admin events.
|
||||
|
||||
## `pub.ditto.blocks`
|
||||
### `pub.ditto.blocks`
|
||||
|
||||
An encrypted array of blocked pubkeys, JSON stringified and encrypted with `nip07.encrypt`.
|
||||
An encrypted array of blocked pubkeys, JSON stringified in `content` and encrypted with `nip04.encrypt`.
|
||||
|
||||
## `pub.ditto.frontendConfig`
|
||||
### `pub.ditto.frontendConfig`
|
||||
|
||||
JSON data for Pleroma frontends served on `/api/pleroma/frontend_configurations`. Each key contains arbitrary data used by a different frontend.
|
|
@ -0,0 +1,34 @@
|
|||
import { Conf } from '@/config.ts';
|
||||
import { db } from '@/db.ts';
|
||||
import { eventsDB } from '@/db/events.ts';
|
||||
import { signAdminEvent } from '@/sign.ts';
|
||||
|
||||
switch (Deno.args[0]) {
|
||||
case 'users-to-events':
|
||||
await usersToEvents();
|
||||
break;
|
||||
default:
|
||||
console.log('Usage: deno run -A scripts/db.ts <command>');
|
||||
}
|
||||
|
||||
async function usersToEvents() {
|
||||
const { origin, host } = Conf.url;
|
||||
|
||||
for (const row of await db.selectFrom('users').selectAll().execute()) {
|
||||
const event = await signAdminEvent({
|
||||
kind: 30361,
|
||||
tags: [
|
||||
['d', row.pubkey],
|
||||
['name', row.username],
|
||||
['role', row.admin ? 'admin' : 'user'],
|
||||
['origin', origin],
|
||||
// NIP-31: https://github.com/nostr-protocol/nips/blob/master/31.md
|
||||
['alt', `@${row.username}@${host}'s account was updated by the admins of ${host}`],
|
||||
],
|
||||
content: '',
|
||||
created_at: Math.floor(new Date(row.inserted_at).getTime() / 1000),
|
||||
});
|
||||
|
||||
await eventsDB.storeEvent(event);
|
||||
}
|
||||
}
|
|
@ -1,7 +1,8 @@
|
|||
import { Conf } from '@/config.ts';
|
||||
import { db, type DittoDB } from '@/db.ts';
|
||||
import { Debug, type Event, type SelectQueryBuilder } from '@/deps.ts';
|
||||
import { type DittoFilter } from '@/filter.ts';
|
||||
import { isParameterizedReplaceableKind } from '@/kinds.ts';
|
||||
import { isDittoInternalKind, isParameterizedReplaceableKind } from '@/kinds.ts';
|
||||
import { jsonMetaContentSchema } from '@/schemas/nostr.ts';
|
||||
import { type DittoEvent, EventStore, type GetEventsOpts, type StoreEventOpts } from '@/store.ts';
|
||||
import { isNostrId, isURL } from '@/utils.ts';
|
||||
|
@ -25,12 +26,18 @@ const tagConditions: Record<string, TagCondition> = {
|
|||
'proxy': ({ count, value }) => count === 0 && isURL(value),
|
||||
'q': ({ event, count, value }) => count === 0 && event.kind === 1 && isNostrId(value),
|
||||
't': ({ count, value }) => count < 5 && value.length < 50,
|
||||
'name': ({ event, count }) => event.kind === 30361 && count === 0,
|
||||
'role': ({ event, count }) => event.kind === 30361 && count === 0,
|
||||
};
|
||||
|
||||
/** Insert an event (and its tags) into the database. */
|
||||
function storeEvent(event: Event, opts: StoreEventOpts = {}): Promise<void> {
|
||||
debug('EVENT', JSON.stringify(event));
|
||||
|
||||
if (isDittoInternalKind(event.kind) && event.pubkey !== Conf.pubkey) {
|
||||
throw new Error('Internal events can only be stored by the server keypair');
|
||||
}
|
||||
|
||||
return db.transaction().execute(async (trx) => {
|
||||
/** Insert the event into the database. */
|
||||
async function addEvent() {
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
import { Debug, type Insertable } from '@/deps.ts';
|
||||
|
||||
import { db, type UserRow } from '../db.ts';
|
||||
import { Conf } from '@/config.ts';
|
||||
import { Debug, type Filter, type Insertable } from '@/deps.ts';
|
||||
import { type UserRow } from '@/db.ts';
|
||||
import { eventsDB } from '@/db/events.ts';
|
||||
import * as pipeline from '@/pipeline.ts';
|
||||
import { signAdminEvent } from '@/sign.ts';
|
||||
import { nostrNow } from '@/utils.ts';
|
||||
|
||||
const debug = Debug('ditto:users');
|
||||
|
||||
|
@ -12,9 +16,25 @@ interface User {
|
|||
}
|
||||
|
||||
/** Adds a user to the database. */
|
||||
function insertUser(user: Insertable<UserRow>) {
|
||||
async function insertUser(user: Insertable<UserRow>) {
|
||||
debug('insertUser', JSON.stringify(user));
|
||||
return db.insertInto('users').values(user).execute();
|
||||
const { origin, host } = Conf.url;
|
||||
|
||||
const event = await signAdminEvent({
|
||||
kind: 30361,
|
||||
tags: [
|
||||
['d', user.pubkey],
|
||||
['name', user.username],
|
||||
['role', user.admin ? 'admin' : 'user'],
|
||||
['origin', origin],
|
||||
// NIP-31: https://github.com/nostr-protocol/nips/blob/master/31.md
|
||||
['alt', `@${user.username}@${host}'s account was updated by the admins of ${host}`],
|
||||
],
|
||||
content: '',
|
||||
created_at: nostrNow(),
|
||||
});
|
||||
|
||||
return pipeline.handleEvent(event);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -25,18 +45,30 @@ function insertUser(user: Insertable<UserRow>) {
|
|||
* ```
|
||||
*/
|
||||
async function findUser(user: Partial<Insertable<UserRow>>): Promise<User | undefined> {
|
||||
let query = db.selectFrom('users').selectAll();
|
||||
const filter: Filter = { kinds: [30361], authors: [Conf.pubkey], limit: 1 };
|
||||
|
||||
for (const [key, value] of Object.entries(user)) {
|
||||
query = query.where(key as keyof UserRow, '=', value);
|
||||
switch (key) {
|
||||
case 'pubkey':
|
||||
filter['#d'] = [String(value)];
|
||||
break;
|
||||
case 'username':
|
||||
filter['#name'] = [String(value)];
|
||||
break;
|
||||
case 'admin':
|
||||
filter['#role'] = [value ? 'admin' : 'user'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const row = await query.executeTakeFirst();
|
||||
const [event] = await eventsDB.getEvents([filter]);
|
||||
|
||||
if (row) {
|
||||
if (event) {
|
||||
return {
|
||||
...row,
|
||||
admin: row.admin === 1,
|
||||
pubkey: event.tags.find(([name]) => name === 'd')?.[1]!,
|
||||
username: event.tags.find(([name]) => name === 'name')?.[1]!,
|
||||
inserted_at: new Date(event.created_at * 1000),
|
||||
admin: event.tags.find(([name]) => name === 'role')?.[1] === 'admin',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,11 @@ function isParameterizedReplaceableKind(kind: number) {
|
|||
return 30000 <= kind && kind < 40000;
|
||||
}
|
||||
|
||||
/** These events are only valid if published by the server keypair. */
|
||||
function isDittoInternalKind(kind: number) {
|
||||
return kind === 30361;
|
||||
}
|
||||
|
||||
/** Classification of the event kind. */
|
||||
type KindClassification = 'regular' | 'replaceable' | 'ephemeral' | 'parameterized' | 'unknown';
|
||||
|
||||
|
@ -32,6 +37,7 @@ function classifyKind(kind: number): KindClassification {
|
|||
|
||||
export {
|
||||
classifyKind,
|
||||
isDittoInternalKind,
|
||||
isEphemeralKind,
|
||||
isParameterizedReplaceableKind,
|
||||
isRegularKind,
|
||||
|
|
Loading…
Reference in New Issue