mirror of
https://gitlab.com/soapbox-pub/ditto.git
synced 2025-12-06 11:29:46 +00:00
Merge branch 'rm-conf' into 'main'
Replace `import Conf` calls with Hono context in some places See merge request soapbox-pub/ditto!665
This commit is contained in:
commit
d8960c79dd
29 changed files with 246 additions and 189 deletions
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { confMw } from '@ditto/api/middleware';
|
||||||
|
import { type DittoConf } from '@ditto/conf';
|
||||||
import { type Context, Env as HonoEnv, Handler, Hono, Input as HonoInput, MiddlewareHandler } from '@hono/hono';
|
import { type Context, Env as HonoEnv, Handler, Hono, Input as HonoInput, MiddlewareHandler } from '@hono/hono';
|
||||||
import { every } from '@hono/hono/combine';
|
import { every } from '@hono/hono/combine';
|
||||||
import { cors } from '@hono/hono/cors';
|
import { cors } from '@hono/hono/cors';
|
||||||
|
|
@ -149,6 +151,7 @@ import { logiMiddleware } from '@/middleware/logiMiddleware.ts';
|
||||||
|
|
||||||
export interface AppEnv extends HonoEnv {
|
export interface AppEnv extends HonoEnv {
|
||||||
Variables: {
|
Variables: {
|
||||||
|
conf: DittoConf;
|
||||||
/** Signer to get the logged-in user's pubkey, relays, and to sign events, or `undefined` if the user isn't logged in. */
|
/** Signer to get the logged-in user's pubkey, relays, and to sign events, or `undefined` if the user isn't logged in. */
|
||||||
signer?: NostrSigner;
|
signer?: NostrSigner;
|
||||||
/** Uploader for the user to upload files. */
|
/** Uploader for the user to upload files. */
|
||||||
|
|
@ -180,7 +183,7 @@ const publicFiles = serveStatic({ root: './public/' });
|
||||||
/** Static files provided by the Ditto repo, checked into git. */
|
/** Static files provided by the Ditto repo, checked into git. */
|
||||||
const staticFiles = serveStatic({ root: new URL('./static/', import.meta.url).pathname });
|
const staticFiles = serveStatic({ root: new URL('./static/', import.meta.url).pathname });
|
||||||
|
|
||||||
app.use('*', cacheControlMiddleware({ noStore: true }));
|
app.use(confMw(Deno.env), cacheControlMiddleware({ noStore: true }));
|
||||||
|
|
||||||
const ratelimit = every(
|
const ratelimit = every(
|
||||||
rateLimitMiddleware(30, Time.seconds(5), false),
|
rateLimitMiddleware(30, Time.seconds(5), false),
|
||||||
|
|
@ -196,7 +199,6 @@ app.get('/api/v1/streaming', metricsMiddleware, ratelimit, streamingController);
|
||||||
app.get('/relay', metricsMiddleware, ratelimit, relayController);
|
app.get('/relay', metricsMiddleware, ratelimit, relayController);
|
||||||
|
|
||||||
app.use(
|
app.use(
|
||||||
'*',
|
|
||||||
cspMiddleware(),
|
cspMiddleware(),
|
||||||
cors({ origin: '*', exposeHeaders: ['link'] }),
|
cors({ origin: '*', exposeHeaders: ['link'] }),
|
||||||
signerMiddleware,
|
signerMiddleware,
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ import { nip19 } from 'nostr-tools';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { type AppController } from '@/app.ts';
|
import { type AppController } from '@/app.ts';
|
||||||
import { Conf } from '@/config.ts';
|
|
||||||
import { getAuthor, getFollowedPubkeys } from '@/queries.ts';
|
import { getAuthor, getFollowedPubkeys } from '@/queries.ts';
|
||||||
import { booleanParamSchema, fileSchema } from '@/schema.ts';
|
import { booleanParamSchema, fileSchema } from '@/schema.ts';
|
||||||
import { Storages } from '@/storages.ts';
|
import { Storages } from '@/storages.ts';
|
||||||
|
|
@ -22,13 +21,8 @@ import { addTag, deleteTag, findReplyTag, getTagSet } from '@/utils/tags.ts';
|
||||||
import { getPubkeysBySearch } from '@/utils/search.ts';
|
import { getPubkeysBySearch } from '@/utils/search.ts';
|
||||||
import { MastodonAccount } from '@/entities/MastodonAccount.ts';
|
import { MastodonAccount } from '@/entities/MastodonAccount.ts';
|
||||||
|
|
||||||
const usernameSchema = z
|
|
||||||
.string().min(1).max(30)
|
|
||||||
.regex(/^[a-z0-9_]+$/i)
|
|
||||||
.refine((username) => !Conf.forbiddenUsernames.includes(username), 'Username is reserved.');
|
|
||||||
|
|
||||||
const createAccountSchema = z.object({
|
const createAccountSchema = z.object({
|
||||||
username: usernameSchema,
|
username: z.string().min(1).max(30).regex(/^[a-z0-9_]+$/i),
|
||||||
});
|
});
|
||||||
|
|
||||||
const createAccountController: AppController = async (c) => {
|
const createAccountController: AppController = async (c) => {
|
||||||
|
|
@ -39,6 +33,10 @@ const createAccountController: AppController = async (c) => {
|
||||||
return c.json({ error: 'Bad request', schema: result.error }, 400);
|
return c.json({ error: 'Bad request', schema: result.error }, 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (c.var.conf.forbiddenUsernames.includes(result.data.username)) {
|
||||||
|
return c.json({ error: 'Username is reserved.' }, 422);
|
||||||
|
}
|
||||||
|
|
||||||
return c.json({
|
return c.json({
|
||||||
access_token: nip19.npubEncode(pubkey),
|
access_token: nip19.npubEncode(pubkey),
|
||||||
token_type: 'Bearer',
|
token_type: 'Bearer',
|
||||||
|
|
@ -204,7 +202,8 @@ const accountStatusesQuerySchema = z.object({
|
||||||
|
|
||||||
const accountStatusesController: AppController = async (c) => {
|
const accountStatusesController: AppController = async (c) => {
|
||||||
const pubkey = c.req.param('pubkey');
|
const pubkey = c.req.param('pubkey');
|
||||||
const { since, until } = c.get('pagination');
|
const { conf } = c.var;
|
||||||
|
const { since, until } = c.var.pagination;
|
||||||
const { pinned, limit, exclude_replies, tagged, only_media } = accountStatusesQuerySchema.parse(c.req.query());
|
const { pinned, limit, exclude_replies, tagged, only_media } = accountStatusesQuerySchema.parse(c.req.query());
|
||||||
const { signal } = c.req.raw;
|
const { signal } = c.req.raw;
|
||||||
|
|
||||||
|
|
@ -212,7 +211,7 @@ const accountStatusesController: AppController = async (c) => {
|
||||||
|
|
||||||
const [[author], [user]] = await Promise.all([
|
const [[author], [user]] = await Promise.all([
|
||||||
store.query([{ kinds: [0], authors: [pubkey], limit: 1 }], { signal }),
|
store.query([{ kinds: [0], authors: [pubkey], limit: 1 }], { signal }),
|
||||||
store.query([{ kinds: [30382], authors: [Conf.pubkey], '#d': [pubkey], limit: 1 }], { signal }),
|
store.query([{ kinds: [30382], authors: [conf.pubkey], '#d': [pubkey], limit: 1 }], { signal }),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (author) {
|
if (author) {
|
||||||
|
|
@ -261,7 +260,7 @@ const accountStatusesController: AppController = async (c) => {
|
||||||
filter.search = search.join(' ');
|
filter.search = search.join(' ');
|
||||||
}
|
}
|
||||||
|
|
||||||
const opts = { signal, limit, timeout: Conf.db.timeouts.timelines };
|
const opts = { signal, limit, timeout: conf.db.timeouts.timelines };
|
||||||
|
|
||||||
const events = await store.query([filter], opts)
|
const events = await store.query([filter], opts)
|
||||||
.then((events) => hydrateEvents({ events, store, signal }))
|
.then((events) => hydrateEvents({ events, store, signal }))
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ import { logi } from '@soapbox/logi';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { type AppController } from '@/app.ts';
|
import { type AppController } from '@/app.ts';
|
||||||
import { Conf } from '@/config.ts';
|
|
||||||
import { booleanParamSchema } from '@/schema.ts';
|
import { booleanParamSchema } from '@/schema.ts';
|
||||||
import { Storages } from '@/storages.ts';
|
import { Storages } from '@/storages.ts';
|
||||||
import { hydrateEvents } from '@/storages/hydrate.ts';
|
import { hydrateEvents } from '@/storages/hydrate.ts';
|
||||||
|
|
@ -30,6 +29,7 @@ const adminAccountQuerySchema = z.object({
|
||||||
});
|
});
|
||||||
|
|
||||||
const adminAccountsController: AppController = async (c) => {
|
const adminAccountsController: AppController = async (c) => {
|
||||||
|
const { conf } = c.var;
|
||||||
const store = await Storages.db();
|
const store = await Storages.db();
|
||||||
const params = c.get('pagination');
|
const params = c.get('pagination');
|
||||||
const { signal } = c.req.raw;
|
const { signal } = c.req.raw;
|
||||||
|
|
@ -49,7 +49,7 @@ const adminAccountsController: AppController = async (c) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const orig = await store.query(
|
const orig = await store.query(
|
||||||
[{ kinds: [30383], authors: [Conf.pubkey], '#k': ['3036'], '#n': ['pending'], ...params }],
|
[{ kinds: [30383], authors: [conf.pubkey], '#k': ['3036'], '#n': ['pending'], ...params }],
|
||||||
{ signal },
|
{ signal },
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -86,7 +86,7 @@ const adminAccountsController: AppController = async (c) => {
|
||||||
n.push('moderator');
|
n.push('moderator');
|
||||||
}
|
}
|
||||||
|
|
||||||
const events = await store.query([{ kinds: [30382], authors: [Conf.pubkey], '#n': n, ...params }], { signal });
|
const events = await store.query([{ kinds: [30382], authors: [conf.pubkey], '#n': n, ...params }], { signal });
|
||||||
|
|
||||||
const pubkeys = new Set<string>(
|
const pubkeys = new Set<string>(
|
||||||
events
|
events
|
||||||
|
|
@ -110,7 +110,7 @@ const adminAccountsController: AppController = async (c) => {
|
||||||
const filter: NostrFilter = { kinds: [0], ...params };
|
const filter: NostrFilter = { kinds: [0], ...params };
|
||||||
|
|
||||||
if (local) {
|
if (local) {
|
||||||
filter.search = `domain:${Conf.url.host}`;
|
filter.search = `domain:${conf.url.host}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const events = await store.query([filter], { signal })
|
const events = await store.query([filter], { signal })
|
||||||
|
|
@ -125,6 +125,7 @@ const adminAccountActionSchema = z.object({
|
||||||
});
|
});
|
||||||
|
|
||||||
const adminActionController: AppController = async (c) => {
|
const adminActionController: AppController = async (c) => {
|
||||||
|
const { conf } = c.var;
|
||||||
const body = await parseBody(c.req.raw);
|
const body = await parseBody(c.req.raw);
|
||||||
const store = await Storages.db();
|
const store = await Storages.db();
|
||||||
const result = adminAccountActionSchema.safeParse(body);
|
const result = adminAccountActionSchema.safeParse(body);
|
||||||
|
|
@ -156,7 +157,7 @@ const adminActionController: AppController = async (c) => {
|
||||||
}
|
}
|
||||||
if (data.type === 'revoke_name') {
|
if (data.type === 'revoke_name') {
|
||||||
n.revoke_name = true;
|
n.revoke_name = true;
|
||||||
store.remove([{ kinds: [30360], authors: [Conf.pubkey], '#p': [authorId] }]).catch((e: unknown) => {
|
store.remove([{ kinds: [30360], authors: [conf.pubkey], '#p': [authorId] }]).catch((e: unknown) => {
|
||||||
logi({ level: 'error', ns: 'ditto.api.admin.account.action', type: data.type, error: errorJson(e) });
|
logi({ level: 'error', ns: 'ditto.api.admin.account.action', type: data.type, error: errorJson(e) });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -167,6 +168,7 @@ const adminActionController: AppController = async (c) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const adminApproveController: AppController = async (c) => {
|
const adminApproveController: AppController = async (c) => {
|
||||||
|
const { conf } = c.var;
|
||||||
const eventId = c.req.param('id');
|
const eventId = c.req.param('id');
|
||||||
const store = await Storages.db();
|
const store = await Storages.db();
|
||||||
|
|
||||||
|
|
@ -183,7 +185,7 @@ const adminApproveController: AppController = async (c) => {
|
||||||
return c.json({ error: 'Invalid NIP-05' }, 400);
|
return c.json({ error: 'Invalid NIP-05' }, 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
const [existing] = await store.query([{ kinds: [30360], authors: [Conf.pubkey], '#d': [r], limit: 1 }]);
|
const [existing] = await store.query([{ kinds: [30360], authors: [conf.pubkey], '#d': [r], limit: 1 }]);
|
||||||
if (existing) {
|
if (existing) {
|
||||||
return c.json({ error: 'NIP-05 already granted to another user' }, 400);
|
return c.json({ error: 'NIP-05 already granted to another user' }, 400);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ import TTLCache from '@isaacs/ttlcache';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { AppController } from '@/app.ts';
|
import { AppController } from '@/app.ts';
|
||||||
import { Conf } from '@/config.ts';
|
|
||||||
import { updateUser } from '@/utils/api.ts';
|
import { updateUser } from '@/utils/api.ts';
|
||||||
|
|
||||||
interface Point {
|
interface Point {
|
||||||
|
|
@ -24,6 +23,8 @@ const PUZZLE_SIZE = { w: 65, h: 65 };
|
||||||
|
|
||||||
/** Puzzle captcha controller. */
|
/** Puzzle captcha controller. */
|
||||||
export const captchaController: AppController = async (c) => {
|
export const captchaController: AppController = async (c) => {
|
||||||
|
const { conf } = c.var;
|
||||||
|
|
||||||
const { bg, puzzle, solution } = generateCaptcha(
|
const { bg, puzzle, solution } = generateCaptcha(
|
||||||
await imagesAsync,
|
await imagesAsync,
|
||||||
BG_SIZE,
|
BG_SIZE,
|
||||||
|
|
@ -32,7 +33,7 @@ export const captchaController: AppController = async (c) => {
|
||||||
|
|
||||||
const id = crypto.randomUUID();
|
const id = crypto.randomUUID();
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const ttl = Conf.captchaTTL;
|
const ttl = conf.captchaTTL;
|
||||||
|
|
||||||
captchas.set(id, solution, { ttl });
|
captchas.set(id, solution, { ttl });
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { confMw } from '@ditto/api/middleware';
|
||||||
import { Env as HonoEnv, Hono } from '@hono/hono';
|
import { Env as HonoEnv, Hono } from '@hono/hono';
|
||||||
import { NostrSigner, NSecSigner, NStore } from '@nostrify/nostrify';
|
import { NostrSigner, NSecSigner, NStore } from '@nostrify/nostrify';
|
||||||
import { generateSecretKey, getPublicKey } from 'nostr-tools';
|
import { generateSecretKey, getPublicKey } from 'nostr-tools';
|
||||||
|
|
@ -40,7 +41,10 @@ Deno.test('PUT /wallet must be successful', {
|
||||||
c.set('store', store);
|
c.set('store', store);
|
||||||
await next();
|
await next();
|
||||||
},
|
},
|
||||||
).route('/', cashuApp);
|
);
|
||||||
|
|
||||||
|
app.use(confMw(new Map()));
|
||||||
|
app.route('/', cashuApp);
|
||||||
|
|
||||||
const response = await app.request('/wallet', {
|
const response = await app.request('/wallet', {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
|
|
@ -116,7 +120,10 @@ Deno.test('PUT /wallet must NOT be successful: wrong request body/schema', async
|
||||||
c.set('store', store);
|
c.set('store', store);
|
||||||
await next();
|
await next();
|
||||||
},
|
},
|
||||||
).route('/', cashuApp);
|
);
|
||||||
|
|
||||||
|
app.use(confMw(new Map()));
|
||||||
|
app.route('/', cashuApp);
|
||||||
|
|
||||||
const response = await app.request('/wallet', {
|
const response = await app.request('/wallet', {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
|
|
@ -149,7 +156,10 @@ Deno.test('PUT /wallet must NOT be successful: wallet already exists', async ()
|
||||||
c.set('store', store);
|
c.set('store', store);
|
||||||
await next();
|
await next();
|
||||||
},
|
},
|
||||||
).route('/', cashuApp);
|
);
|
||||||
|
|
||||||
|
app.use(confMw(new Map()));
|
||||||
|
app.route('/', cashuApp);
|
||||||
|
|
||||||
await db.store.event(genEvent({ kind: 17375 }, sk));
|
await db.store.event(genEvent({ kind: 17375 }, sk));
|
||||||
|
|
||||||
|
|
@ -187,7 +197,10 @@ Deno.test('GET /wallet must be successful', async () => {
|
||||||
c.set('store', store);
|
c.set('store', store);
|
||||||
await next();
|
await next();
|
||||||
},
|
},
|
||||||
).route('/', cashuApp);
|
);
|
||||||
|
|
||||||
|
app.use(confMw(new Map()));
|
||||||
|
app.route('/', cashuApp);
|
||||||
|
|
||||||
// Wallet
|
// Wallet
|
||||||
await db.store.event(genEvent({
|
await db.store.event(genEvent({
|
||||||
|
|
@ -290,7 +303,10 @@ Deno.test('GET /mints must be successful', async () => {
|
||||||
c.set('store', store);
|
c.set('store', store);
|
||||||
await next();
|
await next();
|
||||||
},
|
},
|
||||||
).route('/', cashuApp);
|
);
|
||||||
|
|
||||||
|
app.use(confMw(new Map()));
|
||||||
|
app.route('/', cashuApp);
|
||||||
|
|
||||||
const response = await app.request('/mints', {
|
const response = await app.request('/mints', {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
import { Proof } from '@cashu/cashu-ts';
|
import { Proof } from '@cashu/cashu-ts';
|
||||||
|
import { confRequiredMw } from '@ditto/api/middleware';
|
||||||
import { Hono } from '@hono/hono';
|
import { Hono } from '@hono/hono';
|
||||||
import { generateSecretKey, getPublicKey } from 'nostr-tools';
|
import { generateSecretKey, getPublicKey } from 'nostr-tools';
|
||||||
import { bytesToString, stringToBytes } from '@scure/base';
|
import { bytesToString, stringToBytes } from '@scure/base';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { Conf } from '@/config.ts';
|
|
||||||
import { createEvent, parseBody } from '@/utils/api.ts';
|
import { createEvent, parseBody } from '@/utils/api.ts';
|
||||||
import { requireNip44Signer } from '@/middleware/requireSigner.ts';
|
import { requireNip44Signer } from '@/middleware/requireSigner.ts';
|
||||||
import { requireStore } from '@/middleware/storeMiddleware.ts';
|
import { requireStore } from '@/middleware/storeMiddleware.ts';
|
||||||
|
|
@ -16,7 +16,7 @@ import { errorJson } from '@/utils/log.ts';
|
||||||
|
|
||||||
type Wallet = z.infer<typeof walletSchema>;
|
type Wallet = z.infer<typeof walletSchema>;
|
||||||
|
|
||||||
const app = new Hono().use('*', requireStore);
|
const app = new Hono().use('*', confRequiredMw, requireStore);
|
||||||
|
|
||||||
// app.delete('/wallet') -> 204
|
// app.delete('/wallet') -> 204
|
||||||
|
|
||||||
|
|
@ -45,7 +45,7 @@ const createCashuWalletAndNutzapInfoSchema = z.object({
|
||||||
* https://github.com/nostr-protocol/nips/blob/master/61.md#nutzap-informational-event
|
* https://github.com/nostr-protocol/nips/blob/master/61.md#nutzap-informational-event
|
||||||
*/
|
*/
|
||||||
app.put('/wallet', requireNip44Signer, async (c) => {
|
app.put('/wallet', requireNip44Signer, async (c) => {
|
||||||
const signer = c.var.signer;
|
const { conf, signer } = c.var;
|
||||||
const store = c.get('store');
|
const store = c.get('store');
|
||||||
const pubkey = await signer.getPublicKey();
|
const pubkey = await signer.getPublicKey();
|
||||||
const body = await parseBody(c.req.raw);
|
const body = await parseBody(c.req.raw);
|
||||||
|
|
@ -88,7 +88,7 @@ app.put('/wallet', requireNip44Signer, async (c) => {
|
||||||
kind: 10019,
|
kind: 10019,
|
||||||
tags: [
|
tags: [
|
||||||
...mints.map((mint) => ['mint', mint, 'sat']),
|
...mints.map((mint) => ['mint', mint, 'sat']),
|
||||||
['relay', Conf.relay], // TODO: add more relays once things get more stable
|
['relay', conf.relay], // TODO: add more relays once things get more stable
|
||||||
['pubkey', p2pk],
|
['pubkey', p2pk],
|
||||||
],
|
],
|
||||||
}, c);
|
}, c);
|
||||||
|
|
@ -97,7 +97,7 @@ app.put('/wallet', requireNip44Signer, async (c) => {
|
||||||
const walletEntity: Wallet = {
|
const walletEntity: Wallet = {
|
||||||
pubkey_p2pk: p2pk,
|
pubkey_p2pk: p2pk,
|
||||||
mints,
|
mints,
|
||||||
relays: [Conf.relay],
|
relays: [conf.relay],
|
||||||
balance: 0, // Newly created wallet, balance is zero.
|
balance: 0, // Newly created wallet, balance is zero.
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -106,7 +106,7 @@ app.put('/wallet', requireNip44Signer, async (c) => {
|
||||||
|
|
||||||
/** Gets a wallet, if it exists. */
|
/** Gets a wallet, if it exists. */
|
||||||
app.get('/wallet', requireNip44Signer, swapNutzapsMiddleware, async (c) => {
|
app.get('/wallet', requireNip44Signer, swapNutzapsMiddleware, async (c) => {
|
||||||
const signer = c.get('signer');
|
const { conf, signer } = c.var;
|
||||||
const store = c.get('store');
|
const store = c.get('store');
|
||||||
const pubkey = await signer.getPublicKey();
|
const pubkey = await signer.getPublicKey();
|
||||||
const { signal } = c.req.raw;
|
const { signal } = c.req.raw;
|
||||||
|
|
@ -151,7 +151,7 @@ app.get('/wallet', requireNip44Signer, swapNutzapsMiddleware, async (c) => {
|
||||||
const walletEntity: Wallet = {
|
const walletEntity: Wallet = {
|
||||||
pubkey_p2pk: p2pk,
|
pubkey_p2pk: p2pk,
|
||||||
mints,
|
mints,
|
||||||
relays: [Conf.relay],
|
relays: [conf.relay],
|
||||||
balance,
|
balance,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -160,8 +160,10 @@ app.get('/wallet', requireNip44Signer, swapNutzapsMiddleware, async (c) => {
|
||||||
|
|
||||||
/** Get mints set by the CASHU_MINTS environment variable. */
|
/** Get mints set by the CASHU_MINTS environment variable. */
|
||||||
app.get('/mints', (c) => {
|
app.get('/mints', (c) => {
|
||||||
|
const { conf } = c.var;
|
||||||
|
|
||||||
// TODO: Return full Mint information: https://github.com/cashubtc/nuts/blob/main/06.md
|
// TODO: Return full Mint information: https://github.com/cashubtc/nuts/blob/main/06.md
|
||||||
const mints = Conf.cashuMints;
|
const mints = conf.cashuMints;
|
||||||
|
|
||||||
return c.json({ mints }, 200);
|
return c.json({ mints }, 200);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ import { NostrEvent, NostrFilter, NSchema as n } from '@nostrify/nostrify';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { AppController } from '@/app.ts';
|
import { AppController } from '@/app.ts';
|
||||||
import { Conf } from '@/config.ts';
|
|
||||||
import { DittoEvent } from '@/interfaces/DittoEvent.ts';
|
import { DittoEvent } from '@/interfaces/DittoEvent.ts';
|
||||||
import { getAuthor } from '@/queries.ts';
|
import { getAuthor } from '@/queries.ts';
|
||||||
import { addTag } from '@/utils/tags.ts';
|
import { addTag } from '@/utils/tags.ts';
|
||||||
|
|
@ -30,10 +29,11 @@ const relaySchema = z.object({
|
||||||
type RelayEntity = z.infer<typeof relaySchema>;
|
type RelayEntity = z.infer<typeof relaySchema>;
|
||||||
|
|
||||||
export const adminRelaysController: AppController = async (c) => {
|
export const adminRelaysController: AppController = async (c) => {
|
||||||
|
const { conf } = c.var;
|
||||||
const store = await Storages.db();
|
const store = await Storages.db();
|
||||||
|
|
||||||
const [event] = await store.query([
|
const [event] = await store.query([
|
||||||
{ kinds: [10002], authors: [Conf.pubkey], limit: 1 },
|
{ kinds: [10002], authors: [conf.pubkey], limit: 1 },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (!event) {
|
if (!event) {
|
||||||
|
|
@ -82,6 +82,7 @@ export const nameRequestController: AppController = async (c) => {
|
||||||
const store = await Storages.db();
|
const store = await Storages.db();
|
||||||
const signer = c.get('signer')!;
|
const signer = c.get('signer')!;
|
||||||
const pubkey = await signer.getPublicKey();
|
const pubkey = await signer.getPublicKey();
|
||||||
|
const { conf } = c.var;
|
||||||
|
|
||||||
const { name, reason } = nameRequestSchema.parse(await c.req.json());
|
const { name, reason } = nameRequestSchema.parse(await c.req.json());
|
||||||
|
|
||||||
|
|
@ -97,7 +98,7 @@ export const nameRequestController: AppController = async (c) => {
|
||||||
['r', name],
|
['r', name],
|
||||||
['L', 'nip05.domain'],
|
['L', 'nip05.domain'],
|
||||||
['l', name.split('@')[1], 'nip05.domain'],
|
['l', name.split('@')[1], 'nip05.domain'],
|
||||||
['p', Conf.pubkey],
|
['p', conf.pubkey],
|
||||||
],
|
],
|
||||||
}, c);
|
}, c);
|
||||||
|
|
||||||
|
|
@ -113,6 +114,7 @@ const nameRequestsSchema = z.object({
|
||||||
});
|
});
|
||||||
|
|
||||||
export const nameRequestsController: AppController = async (c) => {
|
export const nameRequestsController: AppController = async (c) => {
|
||||||
|
const { conf } = c.var;
|
||||||
const store = await Storages.db();
|
const store = await Storages.db();
|
||||||
const signer = c.get('signer')!;
|
const signer = c.get('signer')!;
|
||||||
const pubkey = await signer.getPublicKey();
|
const pubkey = await signer.getPublicKey();
|
||||||
|
|
@ -122,7 +124,7 @@ export const nameRequestsController: AppController = async (c) => {
|
||||||
|
|
||||||
const filter: NostrFilter = {
|
const filter: NostrFilter = {
|
||||||
kinds: [30383],
|
kinds: [30383],
|
||||||
authors: [Conf.pubkey],
|
authors: [conf.pubkey],
|
||||||
'#k': ['3036'],
|
'#k': ['3036'],
|
||||||
'#p': [pubkey],
|
'#p': [pubkey],
|
||||||
...params,
|
...params,
|
||||||
|
|
@ -168,6 +170,7 @@ const zapSplitSchema = z.record(
|
||||||
);
|
);
|
||||||
|
|
||||||
export const updateZapSplitsController: AppController = async (c) => {
|
export const updateZapSplitsController: AppController = async (c) => {
|
||||||
|
const { conf } = c.var;
|
||||||
const body = await parseBody(c.req.raw);
|
const body = await parseBody(c.req.raw);
|
||||||
const result = zapSplitSchema.safeParse(body);
|
const result = zapSplitSchema.safeParse(body);
|
||||||
const store = c.get('store');
|
const store = c.get('store');
|
||||||
|
|
@ -176,7 +179,7 @@ export const updateZapSplitsController: AppController = async (c) => {
|
||||||
return c.json({ error: result.error }, 400);
|
return c.json({ error: result.error }, 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
const dittoZapSplit = await getZapSplits(store, Conf.pubkey);
|
const dittoZapSplit = await getZapSplits(store, conf.pubkey);
|
||||||
if (!dittoZapSplit) {
|
if (!dittoZapSplit) {
|
||||||
return c.json({ error: 'Zap split not activated, restart the server.' }, 404);
|
return c.json({ error: 'Zap split not activated, restart the server.' }, 404);
|
||||||
}
|
}
|
||||||
|
|
@ -189,7 +192,7 @@ export const updateZapSplitsController: AppController = async (c) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
await updateListAdminEvent(
|
await updateListAdminEvent(
|
||||||
{ kinds: [30078], authors: [Conf.pubkey], '#d': ['pub.ditto.zapSplits'], limit: 1 },
|
{ kinds: [30078], authors: [conf.pubkey], '#d': ['pub.ditto.zapSplits'], limit: 1 },
|
||||||
(tags) =>
|
(tags) =>
|
||||||
pubkeys.reduce((accumulator, pubkey) => {
|
pubkeys.reduce((accumulator, pubkey) => {
|
||||||
return addTag(accumulator, ['p', pubkey, data[pubkey].weight.toString(), data[pubkey].message]);
|
return addTag(accumulator, ['p', pubkey, data[pubkey].weight.toString(), data[pubkey].message]);
|
||||||
|
|
@ -203,6 +206,7 @@ export const updateZapSplitsController: AppController = async (c) => {
|
||||||
const deleteZapSplitSchema = z.array(n.id()).min(1);
|
const deleteZapSplitSchema = z.array(n.id()).min(1);
|
||||||
|
|
||||||
export const deleteZapSplitsController: AppController = async (c) => {
|
export const deleteZapSplitsController: AppController = async (c) => {
|
||||||
|
const { conf } = c.var;
|
||||||
const body = await parseBody(c.req.raw);
|
const body = await parseBody(c.req.raw);
|
||||||
const result = deleteZapSplitSchema.safeParse(body);
|
const result = deleteZapSplitSchema.safeParse(body);
|
||||||
const store = c.get('store');
|
const store = c.get('store');
|
||||||
|
|
@ -211,7 +215,7 @@ export const deleteZapSplitsController: AppController = async (c) => {
|
||||||
return c.json({ error: result.error }, 400);
|
return c.json({ error: result.error }, 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
const dittoZapSplit = await getZapSplits(store, Conf.pubkey);
|
const dittoZapSplit = await getZapSplits(store, conf.pubkey);
|
||||||
if (!dittoZapSplit) {
|
if (!dittoZapSplit) {
|
||||||
return c.json({ error: 'Zap split not activated, restart the server.' }, 404);
|
return c.json({ error: 'Zap split not activated, restart the server.' }, 404);
|
||||||
}
|
}
|
||||||
|
|
@ -219,7 +223,7 @@ export const deleteZapSplitsController: AppController = async (c) => {
|
||||||
const { data } = result;
|
const { data } = result;
|
||||||
|
|
||||||
await updateListAdminEvent(
|
await updateListAdminEvent(
|
||||||
{ kinds: [30078], authors: [Conf.pubkey], '#d': ['pub.ditto.zapSplits'], limit: 1 },
|
{ kinds: [30078], authors: [conf.pubkey], '#d': ['pub.ditto.zapSplits'], limit: 1 },
|
||||||
(tags) =>
|
(tags) =>
|
||||||
data.reduce((accumulator, currentValue) => {
|
data.reduce((accumulator, currentValue) => {
|
||||||
return deleteTag(accumulator, ['p', currentValue]);
|
return deleteTag(accumulator, ['p', currentValue]);
|
||||||
|
|
@ -231,9 +235,10 @@ export const deleteZapSplitsController: AppController = async (c) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getZapSplitsController: AppController = async (c) => {
|
export const getZapSplitsController: AppController = async (c) => {
|
||||||
|
const { conf } = c.var;
|
||||||
const store = c.get('store');
|
const store = c.get('store');
|
||||||
|
|
||||||
const dittoZapSplit: DittoZapSplits | undefined = await getZapSplits(store, Conf.pubkey) ?? {};
|
const dittoZapSplit: DittoZapSplits | undefined = await getZapSplits(store, conf.pubkey) ?? {};
|
||||||
if (!dittoZapSplit) {
|
if (!dittoZapSplit) {
|
||||||
return c.json({ error: 'Zap split not activated, restart the server.' }, 404);
|
return c.json({ error: 'Zap split not activated, restart the server.' }, 404);
|
||||||
}
|
}
|
||||||
|
|
@ -303,9 +308,10 @@ const updateInstanceSchema = z.object({
|
||||||
});
|
});
|
||||||
|
|
||||||
export const updateInstanceController: AppController = async (c) => {
|
export const updateInstanceController: AppController = async (c) => {
|
||||||
|
const { conf } = c.var;
|
||||||
const body = await parseBody(c.req.raw);
|
const body = await parseBody(c.req.raw);
|
||||||
const result = updateInstanceSchema.safeParse(body);
|
const result = updateInstanceSchema.safeParse(body);
|
||||||
const pubkey = Conf.pubkey;
|
const pubkey = conf.pubkey;
|
||||||
|
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
return c.json(result.error, 422);
|
return c.json(result.error, 422);
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import denoJson from 'deno.json' with { type: 'json' };
|
import denoJson from 'deno.json' with { type: 'json' };
|
||||||
|
|
||||||
import { AppController } from '@/app.ts';
|
import { AppController } from '@/app.ts';
|
||||||
import { Conf } from '@/config.ts';
|
|
||||||
import { Storages } from '@/storages.ts';
|
import { Storages } from '@/storages.ts';
|
||||||
import { getInstanceMetadata } from '@/utils/instance.ts';
|
import { getInstanceMetadata } from '@/utils/instance.ts';
|
||||||
|
|
||||||
|
|
@ -17,7 +16,8 @@ const features = [
|
||||||
];
|
];
|
||||||
|
|
||||||
const instanceV1Controller: AppController = async (c) => {
|
const instanceV1Controller: AppController = async (c) => {
|
||||||
const { host, protocol } = Conf.url;
|
const { conf } = c.var;
|
||||||
|
const { host, protocol } = conf.url;
|
||||||
const meta = await getInstanceMetadata(await Storages.db(), c.req.raw.signal);
|
const meta = await getInstanceMetadata(await Storages.db(), c.req.raw.signal);
|
||||||
|
|
||||||
/** Protocol to use for WebSocket URLs, depending on the protocol of the `LOCAL_DOMAIN`. */
|
/** Protocol to use for WebSocket URLs, depending on the protocol of the `LOCAL_DOMAIN`. */
|
||||||
|
|
@ -29,7 +29,7 @@ const instanceV1Controller: AppController = async (c) => {
|
||||||
description: meta.about,
|
description: meta.about,
|
||||||
short_description: meta.tagline,
|
short_description: meta.tagline,
|
||||||
registrations: true,
|
registrations: true,
|
||||||
max_toot_chars: Conf.postCharLimit,
|
max_toot_chars: conf.postCharLimit,
|
||||||
configuration: {
|
configuration: {
|
||||||
media_attachments: {
|
media_attachments: {
|
||||||
image_size_limit: 100000000,
|
image_size_limit: 100000000,
|
||||||
|
|
@ -42,7 +42,7 @@ const instanceV1Controller: AppController = async (c) => {
|
||||||
min_expiration: 0,
|
min_expiration: 0,
|
||||||
},
|
},
|
||||||
statuses: {
|
statuses: {
|
||||||
max_characters: Conf.postCharLimit,
|
max_characters: conf.postCharLimit,
|
||||||
max_media_attachments: 20,
|
max_media_attachments: 20,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -50,9 +50,9 @@ const instanceV1Controller: AppController = async (c) => {
|
||||||
metadata: {
|
metadata: {
|
||||||
features,
|
features,
|
||||||
fields_limits: {
|
fields_limits: {
|
||||||
max_fields: Conf.profileFields.maxFields,
|
max_fields: conf.profileFields.maxFields,
|
||||||
name_length: Conf.profileFields.nameLength,
|
name_length: conf.profileFields.nameLength,
|
||||||
value_length: Conf.profileFields.valueLength,
|
value_length: conf.profileFields.valueLength,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -68,7 +68,7 @@ const instanceV1Controller: AppController = async (c) => {
|
||||||
version,
|
version,
|
||||||
email: meta.email,
|
email: meta.email,
|
||||||
nostr: {
|
nostr: {
|
||||||
pubkey: Conf.pubkey,
|
pubkey: conf.pubkey,
|
||||||
relay: `${wsProtocol}//${host}/relay`,
|
relay: `${wsProtocol}//${host}/relay`,
|
||||||
},
|
},
|
||||||
rules: [],
|
rules: [],
|
||||||
|
|
@ -76,7 +76,8 @@ const instanceV1Controller: AppController = async (c) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const instanceV2Controller: AppController = async (c) => {
|
const instanceV2Controller: AppController = async (c) => {
|
||||||
const { host, protocol } = Conf.url;
|
const { conf } = c.var;
|
||||||
|
const { host, protocol } = conf.url;
|
||||||
const meta = await getInstanceMetadata(await Storages.db(), c.req.raw.signal);
|
const meta = await getInstanceMetadata(await Storages.db(), c.req.raw.signal);
|
||||||
|
|
||||||
/** Protocol to use for WebSocket URLs, depending on the protocol of the `LOCAL_DOMAIN`. */
|
/** Protocol to use for WebSocket URLs, depending on the protocol of the `LOCAL_DOMAIN`. */
|
||||||
|
|
@ -111,14 +112,14 @@ const instanceV2Controller: AppController = async (c) => {
|
||||||
streaming: `${wsProtocol}//${host}`,
|
streaming: `${wsProtocol}//${host}`,
|
||||||
},
|
},
|
||||||
vapid: {
|
vapid: {
|
||||||
public_key: await Conf.vapidPublicKey,
|
public_key: await conf.vapidPublicKey,
|
||||||
},
|
},
|
||||||
accounts: {
|
accounts: {
|
||||||
max_featured_tags: 10,
|
max_featured_tags: 10,
|
||||||
max_pinned_statuses: 5,
|
max_pinned_statuses: 5,
|
||||||
},
|
},
|
||||||
statuses: {
|
statuses: {
|
||||||
max_characters: Conf.postCharLimit,
|
max_characters: conf.postCharLimit,
|
||||||
max_media_attachments: 20,
|
max_media_attachments: 20,
|
||||||
characters_reserved_per_url: 23,
|
characters_reserved_per_url: 23,
|
||||||
},
|
},
|
||||||
|
|
@ -136,20 +137,20 @@ const instanceV2Controller: AppController = async (c) => {
|
||||||
max_expiration: 2629746,
|
max_expiration: 2629746,
|
||||||
},
|
},
|
||||||
translation: {
|
translation: {
|
||||||
enabled: Boolean(Conf.translationProvider),
|
enabled: Boolean(conf.translationProvider),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
nostr: {
|
nostr: {
|
||||||
pubkey: Conf.pubkey,
|
pubkey: conf.pubkey,
|
||||||
relay: `${wsProtocol}//${host}/relay`,
|
relay: `${wsProtocol}//${host}/relay`,
|
||||||
},
|
},
|
||||||
pleroma: {
|
pleroma: {
|
||||||
metadata: {
|
metadata: {
|
||||||
features,
|
features,
|
||||||
fields_limits: {
|
fields_limits: {
|
||||||
max_fields: Conf.profileFields.maxFields,
|
max_fields: conf.profileFields.maxFields,
|
||||||
name_length: Conf.profileFields.nameLength,
|
name_length: conf.profileFields.nameLength,
|
||||||
value_length: Conf.profileFields.valueLength,
|
value_length: conf.profileFields.valueLength,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ import { NostrFilter, NSchema as n } from '@nostrify/nostrify';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { AppContext, AppController } from '@/app.ts';
|
import { AppContext, AppController } from '@/app.ts';
|
||||||
import { Conf } from '@/config.ts';
|
|
||||||
import { DittoPagination } from '@/interfaces/DittoPagination.ts';
|
import { DittoPagination } from '@/interfaces/DittoPagination.ts';
|
||||||
import { hydrateEvents } from '@/storages/hydrate.ts';
|
import { hydrateEvents } from '@/storages/hydrate.ts';
|
||||||
import { paginated } from '@/utils/api.ts';
|
import { paginated } from '@/utils/api.ts';
|
||||||
|
|
@ -31,6 +30,7 @@ const notificationsSchema = z.object({
|
||||||
});
|
});
|
||||||
|
|
||||||
const notificationsController: AppController = async (c) => {
|
const notificationsController: AppController = async (c) => {
|
||||||
|
const { conf } = c.var;
|
||||||
const pubkey = await c.get('signer')?.getPublicKey()!;
|
const pubkey = await c.get('signer')?.getPublicKey()!;
|
||||||
const params = c.get('pagination');
|
const params = c.get('pagination');
|
||||||
|
|
||||||
|
|
@ -68,7 +68,7 @@ const notificationsController: AppController = async (c) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (types.has('ditto:name_grant') && !account_id) {
|
if (types.has('ditto:name_grant') && !account_id) {
|
||||||
filters.push({ kinds: [30360], authors: [Conf.pubkey], '#p': [pubkey], ...params });
|
filters.push({ kinds: [30360], authors: [conf.pubkey], '#p': [pubkey], ...params });
|
||||||
}
|
}
|
||||||
|
|
||||||
return renderNotifications(filters, types, params, c);
|
return renderNotifications(filters, types, params, c);
|
||||||
|
|
@ -105,10 +105,11 @@ async function renderNotifications(
|
||||||
params: DittoPagination,
|
params: DittoPagination,
|
||||||
c: AppContext,
|
c: AppContext,
|
||||||
) {
|
) {
|
||||||
|
const { conf } = c.var;
|
||||||
const store = c.get('store');
|
const store = c.get('store');
|
||||||
const pubkey = await c.get('signer')?.getPublicKey()!;
|
const pubkey = await c.get('signer')?.getPublicKey()!;
|
||||||
const { signal } = c.req.raw;
|
const { signal } = c.req.raw;
|
||||||
const opts = { signal, limit: params.limit, timeout: Conf.db.timeouts.timelines };
|
const opts = { signal, limit: params.limit, timeout: conf.db.timeouts.timelines };
|
||||||
|
|
||||||
const events = await store
|
const events = await store
|
||||||
.query(filters, opts)
|
.query(filters, opts)
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ import { generateSecretKey } from 'nostr-tools';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { AppController } from '@/app.ts';
|
import { AppController } from '@/app.ts';
|
||||||
import { Conf } from '@/config.ts';
|
|
||||||
import { Storages } from '@/storages.ts';
|
import { Storages } from '@/storages.ts';
|
||||||
import { nostrNow } from '@/utils.ts';
|
import { nostrNow } from '@/utils.ts';
|
||||||
import { parseBody } from '@/utils/api.ts';
|
import { parseBody } from '@/utils/api.ts';
|
||||||
|
|
@ -40,6 +39,7 @@ const createTokenSchema = z.discriminatedUnion('grant_type', [
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const createTokenController: AppController = async (c) => {
|
const createTokenController: AppController = async (c) => {
|
||||||
|
const { conf } = c.var;
|
||||||
const body = await parseBody(c.req.raw);
|
const body = await parseBody(c.req.raw);
|
||||||
const result = createTokenSchema.safeParse(body);
|
const result = createTokenSchema.safeParse(body);
|
||||||
|
|
||||||
|
|
@ -50,7 +50,7 @@ const createTokenController: AppController = async (c) => {
|
||||||
switch (result.data.grant_type) {
|
switch (result.data.grant_type) {
|
||||||
case 'nostr_bunker':
|
case 'nostr_bunker':
|
||||||
return c.json({
|
return c.json({
|
||||||
access_token: await getToken(result.data),
|
access_token: await getToken(result.data, conf.seckey),
|
||||||
token_type: 'Bearer',
|
token_type: 'Bearer',
|
||||||
scope: 'read write follow push',
|
scope: 'read write follow push',
|
||||||
created_at: nostrNow(),
|
created_at: nostrNow(),
|
||||||
|
|
@ -112,6 +112,7 @@ const revokeTokenController: AppController = async (c) => {
|
||||||
|
|
||||||
async function getToken(
|
async function getToken(
|
||||||
{ pubkey: bunkerPubkey, secret, relays = [] }: { pubkey: string; secret?: string; relays?: string[] },
|
{ pubkey: bunkerPubkey, secret, relays = [] }: { pubkey: string; secret?: string; relays?: string[] },
|
||||||
|
dittoSeckey: Uint8Array,
|
||||||
): Promise<`token1${string}`> {
|
): Promise<`token1${string}`> {
|
||||||
const kysely = await Storages.kysely();
|
const kysely = await Storages.kysely();
|
||||||
const { token, hash } = await generateToken();
|
const { token, hash } = await generateToken();
|
||||||
|
|
@ -133,7 +134,7 @@ async function getToken(
|
||||||
token_hash: hash,
|
token_hash: hash,
|
||||||
pubkey: userPubkey,
|
pubkey: userPubkey,
|
||||||
bunker_pubkey: bunkerPubkey,
|
bunker_pubkey: bunkerPubkey,
|
||||||
nip46_sk_enc: await aesEncrypt(Conf.seckey, nip46Seckey),
|
nip46_sk_enc: await aesEncrypt(dittoSeckey, nip46Seckey),
|
||||||
nip46_relays: relays,
|
nip46_relays: relays,
|
||||||
created_at: new Date(),
|
created_at: new Date(),
|
||||||
}).execute();
|
}).execute();
|
||||||
|
|
@ -143,6 +144,7 @@ async function getToken(
|
||||||
|
|
||||||
/** Display the OAuth form. */
|
/** Display the OAuth form. */
|
||||||
const oauthController: AppController = (c) => {
|
const oauthController: AppController = (c) => {
|
||||||
|
const { conf } = c.var;
|
||||||
const encodedUri = c.req.query('redirect_uri');
|
const encodedUri = c.req.query('redirect_uri');
|
||||||
if (!encodedUri) {
|
if (!encodedUri) {
|
||||||
return c.text('Missing `redirect_uri` query param.', 422);
|
return c.text('Missing `redirect_uri` query param.', 422);
|
||||||
|
|
@ -192,7 +194,7 @@ const oauthController: AppController = (c) => {
|
||||||
<input type="hidden" name="state" value="${escape(state ?? '')}">
|
<input type="hidden" name="state" value="${escape(state ?? '')}">
|
||||||
<button type="submit">Authorize</button>
|
<button type="submit">Authorize</button>
|
||||||
</form>
|
</form>
|
||||||
<p>Sign in with a Nostr bunker app. Please configure the app to use this relay: ${Conf.relay}</p>
|
<p>Sign in with a Nostr bunker app. Please configure the app to use this relay: ${conf.relay}</p>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
`);
|
`);
|
||||||
|
|
@ -220,6 +222,8 @@ const oauthAuthorizeSchema = z.object({
|
||||||
|
|
||||||
/** Controller the OAuth form is POSTed to. */
|
/** Controller the OAuth form is POSTed to. */
|
||||||
const oauthAuthorizeController: AppController = async (c) => {
|
const oauthAuthorizeController: AppController = async (c) => {
|
||||||
|
const { conf } = c.var;
|
||||||
|
|
||||||
/** FormData results in JSON. */
|
/** FormData results in JSON. */
|
||||||
const result = oauthAuthorizeSchema.safeParse(await parseBody(c.req.raw));
|
const result = oauthAuthorizeSchema.safeParse(await parseBody(c.req.raw));
|
||||||
|
|
||||||
|
|
@ -236,7 +240,7 @@ const oauthAuthorizeController: AppController = async (c) => {
|
||||||
pubkey: bunker.hostname,
|
pubkey: bunker.hostname,
|
||||||
secret: bunker.searchParams.get('secret') || undefined,
|
secret: bunker.searchParams.get('secret') || undefined,
|
||||||
relays: bunker.searchParams.getAll('relay'),
|
relays: bunker.searchParams.getAll('relay'),
|
||||||
});
|
}, conf.seckey);
|
||||||
|
|
||||||
if (redirectUri === 'urn:ietf:wg:oauth:2.0:oob') {
|
if (redirectUri === 'urn:ietf:wg:oauth:2.0:oob') {
|
||||||
return c.text(token);
|
return c.text(token);
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { type AppController } from '@/app.ts';
|
import { type AppController } from '@/app.ts';
|
||||||
import { Conf } from '@/config.ts';
|
|
||||||
import { configSchema, elixirTupleSchema } from '@/schemas/pleroma-api.ts';
|
import { configSchema, elixirTupleSchema } from '@/schemas/pleroma-api.ts';
|
||||||
import { AdminSigner } from '@/signers/AdminSigner.ts';
|
import { AdminSigner } from '@/signers/AdminSigner.ts';
|
||||||
import { Storages } from '@/storages.ts';
|
import { Storages } from '@/storages.ts';
|
||||||
|
|
@ -34,7 +33,8 @@ const configController: AppController = async (c) => {
|
||||||
|
|
||||||
/** Pleroma admin config controller. */
|
/** Pleroma admin config controller. */
|
||||||
const updateConfigController: AppController = async (c) => {
|
const updateConfigController: AppController = async (c) => {
|
||||||
const { pubkey } = Conf;
|
const { conf } = c.var;
|
||||||
|
const { pubkey } = conf;
|
||||||
|
|
||||||
const store = await Storages.db();
|
const store = await Storages.db();
|
||||||
const configs = await getPleromaConfigs(store, c.req.raw.signal);
|
const configs = await getPleromaConfigs(store, c.req.raw.signal);
|
||||||
|
|
@ -69,6 +69,7 @@ const pleromaAdminTagSchema = z.object({
|
||||||
});
|
});
|
||||||
|
|
||||||
const pleromaAdminTagController: AppController = async (c) => {
|
const pleromaAdminTagController: AppController = async (c) => {
|
||||||
|
const { conf } = c.var;
|
||||||
const params = pleromaAdminTagSchema.parse(await c.req.json());
|
const params = pleromaAdminTagSchema.parse(await c.req.json());
|
||||||
|
|
||||||
for (const nickname of params.nicknames) {
|
for (const nickname of params.nicknames) {
|
||||||
|
|
@ -76,7 +77,7 @@ const pleromaAdminTagController: AppController = async (c) => {
|
||||||
if (!pubkey) continue;
|
if (!pubkey) continue;
|
||||||
|
|
||||||
await updateAdminEvent(
|
await updateAdminEvent(
|
||||||
{ kinds: [30382], authors: [Conf.pubkey], '#d': [pubkey], limit: 1 },
|
{ kinds: [30382], authors: [conf.pubkey], '#d': [pubkey], limit: 1 },
|
||||||
(prev) => {
|
(prev) => {
|
||||||
const tags = prev?.tags ?? [['d', pubkey]];
|
const tags = prev?.tags ?? [['d', pubkey]];
|
||||||
|
|
||||||
|
|
@ -101,6 +102,7 @@ const pleromaAdminTagController: AppController = async (c) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const pleromaAdminUntagController: AppController = async (c) => {
|
const pleromaAdminUntagController: AppController = async (c) => {
|
||||||
|
const { conf } = c.var;
|
||||||
const params = pleromaAdminTagSchema.parse(await c.req.json());
|
const params = pleromaAdminTagSchema.parse(await c.req.json());
|
||||||
|
|
||||||
for (const nickname of params.nicknames) {
|
for (const nickname of params.nicknames) {
|
||||||
|
|
@ -108,7 +110,7 @@ const pleromaAdminUntagController: AppController = async (c) => {
|
||||||
if (!pubkey) continue;
|
if (!pubkey) continue;
|
||||||
|
|
||||||
await updateAdminEvent(
|
await updateAdminEvent(
|
||||||
{ kinds: [30382], authors: [Conf.pubkey], '#d': [pubkey], limit: 1 },
|
{ kinds: [30382], authors: [conf.pubkey], '#d': [pubkey], limit: 1 },
|
||||||
(prev) => ({
|
(prev) => ({
|
||||||
kind: 30382,
|
kind: 30382,
|
||||||
content: prev?.content ?? '',
|
content: prev?.content ?? '',
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ import { nip19 } from 'nostr-tools';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { AppController } from '@/app.ts';
|
import { AppController } from '@/app.ts';
|
||||||
import { Conf } from '@/config.ts';
|
|
||||||
import { Storages } from '@/storages.ts';
|
import { Storages } from '@/storages.ts';
|
||||||
import { parseBody } from '@/utils/api.ts';
|
import { parseBody } from '@/utils/api.ts';
|
||||||
import { getTokenHash } from '@/utils/auth.ts';
|
import { getTokenHash } from '@/utils/auth.ts';
|
||||||
|
|
@ -43,7 +42,8 @@ const pushSubscribeSchema = z.object({
|
||||||
});
|
});
|
||||||
|
|
||||||
export const pushSubscribeController: AppController = async (c) => {
|
export const pushSubscribeController: AppController = async (c) => {
|
||||||
const vapidPublicKey = await Conf.vapidPublicKey;
|
const { conf } = c.var;
|
||||||
|
const vapidPublicKey = await conf.vapidPublicKey;
|
||||||
|
|
||||||
if (!vapidPublicKey) {
|
if (!vapidPublicKey) {
|
||||||
return c.json({ error: 'The administrator of this server has not enabled Web Push notifications.' }, 404);
|
return c.json({ error: 'The administrator of this server has not enabled Web Push notifications.' }, 404);
|
||||||
|
|
@ -97,7 +97,8 @@ export const pushSubscribeController: AppController = async (c) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getSubscriptionController: AppController = async (c) => {
|
export const getSubscriptionController: AppController = async (c) => {
|
||||||
const vapidPublicKey = await Conf.vapidPublicKey;
|
const { conf } = c.var;
|
||||||
|
const vapidPublicKey = await conf.vapidPublicKey;
|
||||||
|
|
||||||
if (!vapidPublicKey) {
|
if (!vapidPublicKey) {
|
||||||
return c.json({ error: 'The administrator of this server has not enabled Web Push notifications.' }, 404);
|
return c.json({ error: 'The administrator of this server has not enabled Web Push notifications.' }, 404);
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ import { NostrFilter, NSchema as n } from '@nostrify/nostrify';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { type AppController } from '@/app.ts';
|
import { type AppController } from '@/app.ts';
|
||||||
import { Conf } from '@/config.ts';
|
|
||||||
import { createEvent, paginated, parseBody, updateEventInfo } from '@/utils/api.ts';
|
import { createEvent, paginated, parseBody, updateEventInfo } from '@/utils/api.ts';
|
||||||
import { hydrateEvents } from '@/storages/hydrate.ts';
|
import { hydrateEvents } from '@/storages/hydrate.ts';
|
||||||
import { renderAdminReport } from '@/views/mastodon/reports.ts';
|
import { renderAdminReport } from '@/views/mastodon/reports.ts';
|
||||||
|
|
@ -19,6 +18,7 @@ const reportSchema = z.object({
|
||||||
|
|
||||||
/** https://docs.joinmastodon.org/methods/reports/#post */
|
/** https://docs.joinmastodon.org/methods/reports/#post */
|
||||||
const reportController: AppController = async (c) => {
|
const reportController: AppController = async (c) => {
|
||||||
|
const { conf } = c.var;
|
||||||
const store = c.get('store');
|
const store = c.get('store');
|
||||||
const body = await parseBody(c.req.raw);
|
const body = await parseBody(c.req.raw);
|
||||||
const result = reportSchema.safeParse(body);
|
const result = reportSchema.safeParse(body);
|
||||||
|
|
@ -36,7 +36,7 @@ const reportController: AppController = async (c) => {
|
||||||
|
|
||||||
const tags = [
|
const tags = [
|
||||||
['p', account_id, category],
|
['p', account_id, category],
|
||||||
['P', Conf.pubkey],
|
['P', conf.pubkey],
|
||||||
];
|
];
|
||||||
|
|
||||||
for (const status of status_ids) {
|
for (const status of status_ids) {
|
||||||
|
|
@ -61,6 +61,7 @@ const adminReportsSchema = z.object({
|
||||||
|
|
||||||
/** https://docs.joinmastodon.org/methods/admin/reports/#get */
|
/** https://docs.joinmastodon.org/methods/admin/reports/#get */
|
||||||
const adminReportsController: AppController = async (c) => {
|
const adminReportsController: AppController = async (c) => {
|
||||||
|
const { conf } = c.var;
|
||||||
const store = c.get('store');
|
const store = c.get('store');
|
||||||
const viewerPubkey = await c.get('signer')?.getPublicKey();
|
const viewerPubkey = await c.get('signer')?.getPublicKey();
|
||||||
|
|
||||||
|
|
@ -69,7 +70,7 @@ const adminReportsController: AppController = async (c) => {
|
||||||
|
|
||||||
const filter: NostrFilter = {
|
const filter: NostrFilter = {
|
||||||
kinds: [30383],
|
kinds: [30383],
|
||||||
authors: [Conf.pubkey],
|
authors: [conf.pubkey],
|
||||||
'#k': ['1984'],
|
'#k': ['1984'],
|
||||||
...params,
|
...params,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ import { nip19 } from 'nostr-tools';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { type AppController } from '@/app.ts';
|
import { type AppController } from '@/app.ts';
|
||||||
import { Conf } from '@/config.ts';
|
|
||||||
import { DittoUpload, dittoUploads } from '@/DittoUploads.ts';
|
import { DittoUpload, dittoUploads } from '@/DittoUploads.ts';
|
||||||
import { DittoEvent } from '@/interfaces/DittoEvent.ts';
|
import { DittoEvent } from '@/interfaces/DittoEvent.ts';
|
||||||
import { getAncestors, getAuthor, getDescendants, getEvent } from '@/queries.ts';
|
import { getAncestors, getAuthor, getDescendants, getEvent } from '@/queries.ts';
|
||||||
|
|
@ -66,6 +65,7 @@ const statusController: AppController = async (c) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const createStatusController: AppController = async (c) => {
|
const createStatusController: AppController = async (c) => {
|
||||||
|
const { conf } = c.var;
|
||||||
const body = await parseBody(c.req.raw);
|
const body = await parseBody(c.req.raw);
|
||||||
const result = createStatusSchema.safeParse(body);
|
const result = createStatusSchema.safeParse(body);
|
||||||
const store = c.get('store');
|
const store = c.get('store');
|
||||||
|
|
@ -97,12 +97,12 @@ const createStatusController: AppController = async (c) => {
|
||||||
const root = rootId === ancestor.id ? ancestor : await store.query([{ ids: [rootId] }]).then(([event]) => event);
|
const root = rootId === ancestor.id ? ancestor : await store.query([{ ids: [rootId] }]).then(([event]) => event);
|
||||||
|
|
||||||
if (root) {
|
if (root) {
|
||||||
tags.push(['e', root.id, Conf.relay, 'root', root.pubkey]);
|
tags.push(['e', root.id, conf.relay, 'root', root.pubkey]);
|
||||||
} else {
|
} else {
|
||||||
tags.push(['e', rootId, Conf.relay, 'root']);
|
tags.push(['e', rootId, conf.relay, 'root']);
|
||||||
}
|
}
|
||||||
|
|
||||||
tags.push(['e', ancestor.id, Conf.relay, 'reply', ancestor.pubkey]);
|
tags.push(['e', ancestor.id, conf.relay, 'reply', ancestor.pubkey]);
|
||||||
}
|
}
|
||||||
|
|
||||||
let quoted: DittoEvent | undefined;
|
let quoted: DittoEvent | undefined;
|
||||||
|
|
@ -114,7 +114,7 @@ const createStatusController: AppController = async (c) => {
|
||||||
return c.json({ error: 'Quoted post not found.' }, 404);
|
return c.json({ error: 'Quoted post not found.' }, 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
tags.push(['q', quoted.id, Conf.relay, quoted.pubkey]);
|
tags.push(['q', quoted.id, conf.relay, quoted.pubkey]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.sensitive && data.spoiler_text) {
|
if (data.sensitive && data.spoiler_text) {
|
||||||
|
|
@ -162,7 +162,7 @@ const createStatusController: AppController = async (c) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return `nostr:${nip19.nprofileEncode({ pubkey, relays: [Conf.relay] })}`;
|
return `nostr:${nip19.nprofileEncode({ pubkey, relays: [conf.relay] })}`;
|
||||||
} catch {
|
} catch {
|
||||||
return match;
|
return match;
|
||||||
}
|
}
|
||||||
|
|
@ -178,7 +178,7 @@ const createStatusController: AppController = async (c) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const pubkey of pubkeys) {
|
for (const pubkey of pubkeys) {
|
||||||
tags.push(['p', pubkey, Conf.relay]);
|
tags.push(['p', pubkey, conf.relay]);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const link of linkify.find(data.status ?? '')) {
|
for (const link of linkify.find(data.status ?? '')) {
|
||||||
|
|
@ -193,10 +193,10 @@ const createStatusController: AppController = async (c) => {
|
||||||
const pubkey = await c.get('signer')?.getPublicKey()!;
|
const pubkey = await c.get('signer')?.getPublicKey()!;
|
||||||
const author = pubkey ? await getAuthor(pubkey) : undefined;
|
const author = pubkey ? await getAuthor(pubkey) : undefined;
|
||||||
|
|
||||||
if (Conf.zapSplitsEnabled) {
|
if (conf.zapSplitsEnabled) {
|
||||||
const meta = n.json().pipe(n.metadata()).catch({}).parse(author?.content);
|
const meta = n.json().pipe(n.metadata()).catch({}).parse(author?.content);
|
||||||
const lnurl = getLnurl(meta);
|
const lnurl = getLnurl(meta);
|
||||||
const dittoZapSplit = await getZapSplits(store, Conf.pubkey);
|
const dittoZapSplit = await getZapSplits(store, conf.pubkey);
|
||||||
if (lnurl && dittoZapSplit) {
|
if (lnurl && dittoZapSplit) {
|
||||||
const totalSplit = Object.values(dittoZapSplit).reduce((total, { weight }) => total + weight, 0);
|
const totalSplit = Object.values(dittoZapSplit).reduce((total, { weight }) => total + weight, 0);
|
||||||
for (const zapPubkey in dittoZapSplit) {
|
for (const zapPubkey in dittoZapSplit) {
|
||||||
|
|
@ -204,7 +204,7 @@ const createStatusController: AppController = async (c) => {
|
||||||
tags.push([
|
tags.push([
|
||||||
'zap',
|
'zap',
|
||||||
zapPubkey,
|
zapPubkey,
|
||||||
Conf.relay,
|
conf.relay,
|
||||||
(Math.max(0, 100 - totalSplit) + dittoZapSplit[zapPubkey].weight).toString(),
|
(Math.max(0, 100 - totalSplit) + dittoZapSplit[zapPubkey].weight).toString(),
|
||||||
]);
|
]);
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -212,13 +212,13 @@ const createStatusController: AppController = async (c) => {
|
||||||
tags.push([
|
tags.push([
|
||||||
'zap',
|
'zap',
|
||||||
zapPubkey,
|
zapPubkey,
|
||||||
Conf.relay,
|
conf.relay,
|
||||||
dittoZapSplit[zapPubkey].weight.toString(),
|
dittoZapSplit[zapPubkey].weight.toString(),
|
||||||
dittoZapSplit[zapPubkey].message,
|
dittoZapSplit[zapPubkey].message,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
if (totalSplit && !dittoZapSplit[pubkey]) {
|
if (totalSplit && !dittoZapSplit[pubkey]) {
|
||||||
tags.push(['zap', pubkey, Conf.relay, Math.max(0, 100 - totalSplit).toString()]);
|
tags.push(['zap', pubkey, conf.relay, Math.max(0, 100 - totalSplit).toString()]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -235,7 +235,7 @@ const createStatusController: AppController = async (c) => {
|
||||||
id: quoted.id,
|
id: quoted.id,
|
||||||
kind: quoted.kind,
|
kind: quoted.kind,
|
||||||
author: quoted.pubkey,
|
author: quoted.pubkey,
|
||||||
relays: [Conf.relay],
|
relays: [conf.relay],
|
||||||
});
|
});
|
||||||
content += `nostr:${nevent}`;
|
content += `nostr:${nevent}`;
|
||||||
}
|
}
|
||||||
|
|
@ -265,6 +265,7 @@ const createStatusController: AppController = async (c) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const deleteStatusController: AppController = async (c) => {
|
const deleteStatusController: AppController = async (c) => {
|
||||||
|
const { conf } = c.var;
|
||||||
const id = c.req.param('id');
|
const id = c.req.param('id');
|
||||||
const pubkey = await c.get('signer')?.getPublicKey();
|
const pubkey = await c.get('signer')?.getPublicKey();
|
||||||
|
|
||||||
|
|
@ -274,7 +275,7 @@ const deleteStatusController: AppController = async (c) => {
|
||||||
if (event.pubkey === pubkey) {
|
if (event.pubkey === pubkey) {
|
||||||
await createEvent({
|
await createEvent({
|
||||||
kind: 5,
|
kind: 5,
|
||||||
tags: [['e', id, Conf.relay, '', pubkey]],
|
tags: [['e', id, conf.relay, '', pubkey]],
|
||||||
}, c);
|
}, c);
|
||||||
|
|
||||||
const author = await getAuthor(event.pubkey);
|
const author = await getAuthor(event.pubkey);
|
||||||
|
|
@ -324,6 +325,7 @@ const contextController: AppController = async (c) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const favouriteController: AppController = async (c) => {
|
const favouriteController: AppController = async (c) => {
|
||||||
|
const { conf } = c.var;
|
||||||
const id = c.req.param('id');
|
const id = c.req.param('id');
|
||||||
const store = await Storages.db();
|
const store = await Storages.db();
|
||||||
const [target] = await store.query([{ ids: [id], kinds: [1, 20] }]);
|
const [target] = await store.query([{ ids: [id], kinds: [1, 20] }]);
|
||||||
|
|
@ -333,8 +335,8 @@ const favouriteController: AppController = async (c) => {
|
||||||
kind: 7,
|
kind: 7,
|
||||||
content: '+',
|
content: '+',
|
||||||
tags: [
|
tags: [
|
||||||
['e', target.id, Conf.relay, '', target.pubkey],
|
['e', target.id, conf.relay, '', target.pubkey],
|
||||||
['p', target.pubkey, Conf.relay],
|
['p', target.pubkey, conf.relay],
|
||||||
],
|
],
|
||||||
}, c);
|
}, c);
|
||||||
|
|
||||||
|
|
@ -364,6 +366,7 @@ const favouritedByController: AppController = (c) => {
|
||||||
|
|
||||||
/** https://docs.joinmastodon.org/methods/statuses/#boost */
|
/** https://docs.joinmastodon.org/methods/statuses/#boost */
|
||||||
const reblogStatusController: AppController = async (c) => {
|
const reblogStatusController: AppController = async (c) => {
|
||||||
|
const { conf } = c.var;
|
||||||
const eventId = c.req.param('id');
|
const eventId = c.req.param('id');
|
||||||
const { signal } = c.req.raw;
|
const { signal } = c.req.raw;
|
||||||
|
|
||||||
|
|
@ -378,8 +381,8 @@ const reblogStatusController: AppController = async (c) => {
|
||||||
const reblogEvent = await createEvent({
|
const reblogEvent = await createEvent({
|
||||||
kind: 6,
|
kind: 6,
|
||||||
tags: [
|
tags: [
|
||||||
['e', event.id, Conf.relay, '', event.pubkey],
|
['e', event.id, conf.relay, '', event.pubkey],
|
||||||
['p', event.pubkey, Conf.relay],
|
['p', event.pubkey, conf.relay],
|
||||||
],
|
],
|
||||||
}, c);
|
}, c);
|
||||||
|
|
||||||
|
|
@ -396,6 +399,7 @@ const reblogStatusController: AppController = async (c) => {
|
||||||
|
|
||||||
/** https://docs.joinmastodon.org/methods/statuses/#unreblog */
|
/** https://docs.joinmastodon.org/methods/statuses/#unreblog */
|
||||||
const unreblogStatusController: AppController = async (c) => {
|
const unreblogStatusController: AppController = async (c) => {
|
||||||
|
const { conf } = c.var;
|
||||||
const eventId = c.req.param('id');
|
const eventId = c.req.param('id');
|
||||||
const pubkey = await c.get('signer')?.getPublicKey()!;
|
const pubkey = await c.get('signer')?.getPublicKey()!;
|
||||||
const store = await Storages.db();
|
const store = await Storages.db();
|
||||||
|
|
@ -415,7 +419,7 @@ const unreblogStatusController: AppController = async (c) => {
|
||||||
|
|
||||||
await createEvent({
|
await createEvent({
|
||||||
kind: 5,
|
kind: 5,
|
||||||
tags: [['e', repostEvent.id, Conf.relay, '', repostEvent.pubkey]],
|
tags: [['e', repostEvent.id, conf.relay, '', repostEvent.pubkey]],
|
||||||
}, c);
|
}, c);
|
||||||
|
|
||||||
return c.json(await renderStatus(event, { viewerPubkey: pubkey }));
|
return c.json(await renderStatus(event, { viewerPubkey: pubkey }));
|
||||||
|
|
@ -456,6 +460,7 @@ const quotesController: AppController = async (c) => {
|
||||||
|
|
||||||
/** https://docs.joinmastodon.org/methods/statuses/#bookmark */
|
/** https://docs.joinmastodon.org/methods/statuses/#bookmark */
|
||||||
const bookmarkController: AppController = async (c) => {
|
const bookmarkController: AppController = async (c) => {
|
||||||
|
const { conf } = c.var;
|
||||||
const pubkey = await c.get('signer')?.getPublicKey()!;
|
const pubkey = await c.get('signer')?.getPublicKey()!;
|
||||||
const eventId = c.req.param('id');
|
const eventId = c.req.param('id');
|
||||||
|
|
||||||
|
|
@ -467,7 +472,7 @@ const bookmarkController: AppController = async (c) => {
|
||||||
if (event) {
|
if (event) {
|
||||||
await updateListEvent(
|
await updateListEvent(
|
||||||
{ kinds: [10003], authors: [pubkey], limit: 1 },
|
{ kinds: [10003], authors: [pubkey], limit: 1 },
|
||||||
(tags) => addTag(tags, ['e', event.id, Conf.relay, '', event.pubkey]),
|
(tags) => addTag(tags, ['e', event.id, conf.relay, '', event.pubkey]),
|
||||||
c,
|
c,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -483,6 +488,7 @@ const bookmarkController: AppController = async (c) => {
|
||||||
|
|
||||||
/** https://docs.joinmastodon.org/methods/statuses/#unbookmark */
|
/** https://docs.joinmastodon.org/methods/statuses/#unbookmark */
|
||||||
const unbookmarkController: AppController = async (c) => {
|
const unbookmarkController: AppController = async (c) => {
|
||||||
|
const { conf } = c.var;
|
||||||
const pubkey = await c.get('signer')?.getPublicKey()!;
|
const pubkey = await c.get('signer')?.getPublicKey()!;
|
||||||
const eventId = c.req.param('id');
|
const eventId = c.req.param('id');
|
||||||
|
|
||||||
|
|
@ -494,7 +500,7 @@ const unbookmarkController: AppController = async (c) => {
|
||||||
if (event) {
|
if (event) {
|
||||||
await updateListEvent(
|
await updateListEvent(
|
||||||
{ kinds: [10003], authors: [pubkey], limit: 1 },
|
{ kinds: [10003], authors: [pubkey], limit: 1 },
|
||||||
(tags) => deleteTag(tags, ['e', event.id, Conf.relay, '', event.pubkey]),
|
(tags) => deleteTag(tags, ['e', event.id, conf.relay, '', event.pubkey]),
|
||||||
c,
|
c,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -510,6 +516,7 @@ const unbookmarkController: AppController = async (c) => {
|
||||||
|
|
||||||
/** https://docs.joinmastodon.org/methods/statuses/#pin */
|
/** https://docs.joinmastodon.org/methods/statuses/#pin */
|
||||||
const pinController: AppController = async (c) => {
|
const pinController: AppController = async (c) => {
|
||||||
|
const { conf } = c.var;
|
||||||
const pubkey = await c.get('signer')?.getPublicKey()!;
|
const pubkey = await c.get('signer')?.getPublicKey()!;
|
||||||
const eventId = c.req.param('id');
|
const eventId = c.req.param('id');
|
||||||
|
|
||||||
|
|
@ -521,7 +528,7 @@ const pinController: AppController = async (c) => {
|
||||||
if (event) {
|
if (event) {
|
||||||
await updateListEvent(
|
await updateListEvent(
|
||||||
{ kinds: [10001], authors: [pubkey], limit: 1 },
|
{ kinds: [10001], authors: [pubkey], limit: 1 },
|
||||||
(tags) => addTag(tags, ['e', event.id, Conf.relay, '', event.pubkey]),
|
(tags) => addTag(tags, ['e', event.id, conf.relay, '', event.pubkey]),
|
||||||
c,
|
c,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -537,6 +544,7 @@ const pinController: AppController = async (c) => {
|
||||||
|
|
||||||
/** https://docs.joinmastodon.org/methods/statuses/#unpin */
|
/** https://docs.joinmastodon.org/methods/statuses/#unpin */
|
||||||
const unpinController: AppController = async (c) => {
|
const unpinController: AppController = async (c) => {
|
||||||
|
const { conf } = c.var;
|
||||||
const pubkey = await c.get('signer')?.getPublicKey()!;
|
const pubkey = await c.get('signer')?.getPublicKey()!;
|
||||||
const eventId = c.req.param('id');
|
const eventId = c.req.param('id');
|
||||||
const { signal } = c.req.raw;
|
const { signal } = c.req.raw;
|
||||||
|
|
@ -550,7 +558,7 @@ const unpinController: AppController = async (c) => {
|
||||||
if (event) {
|
if (event) {
|
||||||
await updateListEvent(
|
await updateListEvent(
|
||||||
{ kinds: [10001], authors: [pubkey], limit: 1 },
|
{ kinds: [10001], authors: [pubkey], limit: 1 },
|
||||||
(tags) => deleteTag(tags, ['e', event.id, Conf.relay, '', event.pubkey]),
|
(tags) => deleteTag(tags, ['e', event.id, conf.relay, '', event.pubkey]),
|
||||||
c,
|
c,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -572,6 +580,7 @@ const zapSchema = z.object({
|
||||||
});
|
});
|
||||||
|
|
||||||
const zapController: AppController = async (c) => {
|
const zapController: AppController = async (c) => {
|
||||||
|
const { conf } = c.var;
|
||||||
const body = await parseBody(c.req.raw);
|
const body = await parseBody(c.req.raw);
|
||||||
const result = zapSchema.safeParse(body);
|
const result = zapSchema.safeParse(body);
|
||||||
const { signal } = c.req.raw;
|
const { signal } = c.req.raw;
|
||||||
|
|
@ -594,10 +603,10 @@ const zapController: AppController = async (c) => {
|
||||||
lnurl = getLnurl(meta);
|
lnurl = getLnurl(meta);
|
||||||
if (target && lnurl) {
|
if (target && lnurl) {
|
||||||
tags.push(
|
tags.push(
|
||||||
['e', target.id, Conf.relay],
|
['e', target.id, conf.relay],
|
||||||
['p', target.pubkey, Conf.relay],
|
['p', target.pubkey, conf.relay],
|
||||||
['amount', amount.toString()],
|
['amount', amount.toString()],
|
||||||
['relays', Conf.relay],
|
['relays', conf.relay],
|
||||||
['lnurl', lnurl],
|
['lnurl', lnurl],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -607,9 +616,9 @@ const zapController: AppController = async (c) => {
|
||||||
lnurl = getLnurl(meta);
|
lnurl = getLnurl(meta);
|
||||||
if (target && lnurl) {
|
if (target && lnurl) {
|
||||||
tags.push(
|
tags.push(
|
||||||
['p', target.pubkey, Conf.relay],
|
['p', target.pubkey, conf.relay],
|
||||||
['amount', amount.toString()],
|
['amount', amount.toString()],
|
||||||
['relays', Conf.relay],
|
['relays', conf.relay],
|
||||||
['lnurl', lnurl],
|
['lnurl', lnurl],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ import { logi } from '@soapbox/logi';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { type AppController } from '@/app.ts';
|
import { type AppController } from '@/app.ts';
|
||||||
import { Conf } from '@/config.ts';
|
|
||||||
import {
|
import {
|
||||||
streamingClientMessagesCounter,
|
streamingClientMessagesCounter,
|
||||||
streamingConnectionsGauge,
|
streamingConnectionsGauge,
|
||||||
|
|
@ -69,6 +68,7 @@ const limiter = new TTLCache<string, number>();
|
||||||
const connections = new Set<WebSocket>();
|
const connections = new Set<WebSocket>();
|
||||||
|
|
||||||
const streamingController: AppController = async (c) => {
|
const streamingController: AppController = async (c) => {
|
||||||
|
const { conf } = c.var;
|
||||||
const upgrade = c.req.header('upgrade');
|
const upgrade = c.req.header('upgrade');
|
||||||
const token = c.req.header('sec-websocket-protocol');
|
const token = c.req.header('sec-websocket-protocol');
|
||||||
const stream = streamSchema.optional().catch(undefined).parse(c.req.query('stream'));
|
const stream = streamSchema.optional().catch(undefined).parse(c.req.query('stream'));
|
||||||
|
|
@ -137,7 +137,7 @@ const streamingController: AppController = async (c) => {
|
||||||
streamingConnectionsGauge.set(connections.size);
|
streamingConnectionsGauge.set(connections.size);
|
||||||
|
|
||||||
if (!stream) return;
|
if (!stream) return;
|
||||||
const topicFilter = await topicToFilter(stream, c.req.query(), pubkey);
|
const topicFilter = await topicToFilter(stream, c.req.query(), pubkey, conf.url.host);
|
||||||
|
|
||||||
if (topicFilter) {
|
if (topicFilter) {
|
||||||
sub([topicFilter], async (event) => {
|
sub([topicFilter], async (event) => {
|
||||||
|
|
@ -208,9 +208,8 @@ async function topicToFilter(
|
||||||
topic: Stream,
|
topic: Stream,
|
||||||
query: Record<string, string>,
|
query: Record<string, string>,
|
||||||
pubkey: string | undefined,
|
pubkey: string | undefined,
|
||||||
|
host: string,
|
||||||
): Promise<NostrFilter | undefined> {
|
): Promise<NostrFilter | undefined> {
|
||||||
const { host } = Conf.url;
|
|
||||||
|
|
||||||
switch (topic) {
|
switch (topic) {
|
||||||
case 'public':
|
case 'public':
|
||||||
return { kinds: [1, 6, 20] };
|
return { kinds: [1, 6, 20] };
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ import { NostrFilter } from '@nostrify/nostrify';
|
||||||
import { matchFilter } from 'nostr-tools';
|
import { matchFilter } from 'nostr-tools';
|
||||||
|
|
||||||
import { AppContext, AppController } from '@/app.ts';
|
import { AppContext, AppController } from '@/app.ts';
|
||||||
import { Conf } from '@/config.ts';
|
|
||||||
import { hydrateEvents } from '@/storages/hydrate.ts';
|
import { hydrateEvents } from '@/storages/hydrate.ts';
|
||||||
import { paginated, paginatedList } from '@/utils/api.ts';
|
import { paginated, paginatedList } from '@/utils/api.ts';
|
||||||
import { getTagSet } from '@/utils/tags.ts';
|
import { getTagSet } from '@/utils/tags.ts';
|
||||||
|
|
@ -24,6 +23,7 @@ export const suggestionsV2Controller: AppController = async (c) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
async function renderV2Suggestions(c: AppContext, params: { offset: number; limit: number }, signal?: AbortSignal) {
|
async function renderV2Suggestions(c: AppContext, params: { offset: number; limit: number }, signal?: AbortSignal) {
|
||||||
|
const { conf } = c.var;
|
||||||
const { offset, limit } = params;
|
const { offset, limit } = params;
|
||||||
|
|
||||||
const store = c.get('store');
|
const store = c.get('store');
|
||||||
|
|
@ -31,8 +31,8 @@ async function renderV2Suggestions(c: AppContext, params: { offset: number; limi
|
||||||
const pubkey = await signer?.getPublicKey();
|
const pubkey = await signer?.getPublicKey();
|
||||||
|
|
||||||
const filters: NostrFilter[] = [
|
const filters: NostrFilter[] = [
|
||||||
{ kinds: [30382], authors: [Conf.pubkey], '#n': ['suggested'], limit },
|
{ kinds: [30382], authors: [conf.pubkey], '#n': ['suggested'], limit },
|
||||||
{ kinds: [1985], '#L': ['pub.ditto.trends'], '#l': [`#p`], authors: [Conf.pubkey], limit: 1 },
|
{ kinds: [1985], '#L': ['pub.ditto.trends'], '#l': [`#p`], authors: [conf.pubkey], limit: 1 },
|
||||||
];
|
];
|
||||||
|
|
||||||
if (pubkey) {
|
if (pubkey) {
|
||||||
|
|
@ -43,11 +43,11 @@ async function renderV2Suggestions(c: AppContext, params: { offset: number; limi
|
||||||
const events = await store.query(filters, { signal });
|
const events = await store.query(filters, { signal });
|
||||||
|
|
||||||
const [userEvents, followsEvent, mutesEvent, trendingEvent] = [
|
const [userEvents, followsEvent, mutesEvent, trendingEvent] = [
|
||||||
events.filter((event) => matchFilter({ kinds: [30382], authors: [Conf.pubkey], '#n': ['suggested'] }, event)),
|
events.filter((event) => matchFilter({ kinds: [30382], authors: [conf.pubkey], '#n': ['suggested'] }, event)),
|
||||||
pubkey ? events.find((event) => matchFilter({ kinds: [3], authors: [pubkey] }, event)) : undefined,
|
pubkey ? events.find((event) => matchFilter({ kinds: [3], authors: [pubkey] }, event)) : undefined,
|
||||||
pubkey ? events.find((event) => matchFilter({ kinds: [10000], authors: [pubkey] }, event)) : undefined,
|
pubkey ? events.find((event) => matchFilter({ kinds: [10000], authors: [pubkey] }, event)) : undefined,
|
||||||
events.find((event) =>
|
events.find((event) =>
|
||||||
matchFilter({ kinds: [1985], '#L': ['pub.ditto.trends'], '#l': [`#p`], authors: [Conf.pubkey], limit: 1 }, event)
|
matchFilter({ kinds: [1985], '#L': ['pub.ditto.trends'], '#l': [`#p`], authors: [conf.pubkey], limit: 1 }, event)
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
@ -89,12 +89,13 @@ async function renderV2Suggestions(c: AppContext, params: { offset: number; limi
|
||||||
}
|
}
|
||||||
|
|
||||||
export const localSuggestionsController: AppController = async (c) => {
|
export const localSuggestionsController: AppController = async (c) => {
|
||||||
|
const { conf } = c.var;
|
||||||
const signal = c.req.raw.signal;
|
const signal = c.req.raw.signal;
|
||||||
const params = c.get('pagination');
|
const params = c.get('pagination');
|
||||||
const store = c.get('store');
|
const store = c.get('store');
|
||||||
|
|
||||||
const grants = await store.query(
|
const grants = await store.query(
|
||||||
[{ kinds: [30360], authors: [Conf.pubkey], ...params }],
|
[{ kinds: [30360], authors: [conf.pubkey], ...params }],
|
||||||
{ signal },
|
{ signal },
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -108,20 +109,20 @@ export const localSuggestionsController: AppController = async (c) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const profiles = await store.query(
|
const profiles = await store.query(
|
||||||
[{ kinds: [0], authors: [...pubkeys], search: `domain:${Conf.url.host}`, ...params }],
|
[{ kinds: [0], authors: [...pubkeys], search: `domain:${conf.url.host}`, ...params }],
|
||||||
{ signal },
|
{ signal },
|
||||||
)
|
)
|
||||||
.then((events) => hydrateEvents({ store, events, signal }));
|
.then((events) => hydrateEvents({ store, events, signal }));
|
||||||
|
|
||||||
const suggestions = (await Promise.all([...pubkeys].map(async (pubkey) => {
|
const suggestions = [...pubkeys].map((pubkey) => {
|
||||||
const profile = profiles.find((event) => event.pubkey === pubkey);
|
const profile = profiles.find((event) => event.pubkey === pubkey);
|
||||||
if (!profile) return;
|
if (!profile) return;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
source: 'global',
|
source: 'global',
|
||||||
account: await renderAccount(profile),
|
account: renderAccount(profile),
|
||||||
};
|
};
|
||||||
}))).filter(Boolean);
|
}).filter(Boolean);
|
||||||
|
|
||||||
return paginated(c, grants, suggestions);
|
return paginated(c, grants, suggestions);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ import { NostrFilter } from '@nostrify/nostrify';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { type AppContext, type AppController } from '@/app.ts';
|
import { type AppContext, type AppController } from '@/app.ts';
|
||||||
import { Conf } from '@/config.ts';
|
|
||||||
import { getFeedPubkeys } from '@/queries.ts';
|
import { getFeedPubkeys } from '@/queries.ts';
|
||||||
import { booleanParamSchema, languageSchema } from '@/schema.ts';
|
import { booleanParamSchema, languageSchema } from '@/schema.ts';
|
||||||
import { hydrateEvents } from '@/storages/hydrate.ts';
|
import { hydrateEvents } from '@/storages/hydrate.ts';
|
||||||
|
|
@ -53,6 +52,7 @@ const publicQuerySchema = z.object({
|
||||||
});
|
});
|
||||||
|
|
||||||
const publicTimelineController: AppController = (c) => {
|
const publicTimelineController: AppController = (c) => {
|
||||||
|
const { conf } = c.var;
|
||||||
const params = c.get('pagination');
|
const params = c.get('pagination');
|
||||||
const result = publicQuerySchema.safeParse(c.req.query());
|
const result = publicQuerySchema.safeParse(c.req.query());
|
||||||
|
|
||||||
|
|
@ -67,7 +67,7 @@ const publicTimelineController: AppController = (c) => {
|
||||||
const search: `${string}:${string}`[] = [];
|
const search: `${string}:${string}`[] = [];
|
||||||
|
|
||||||
if (local) {
|
if (local) {
|
||||||
search.push(`domain:${Conf.url.host}`);
|
search.push(`domain:${conf.url.host}`);
|
||||||
} else if (instance) {
|
} else if (instance) {
|
||||||
search.push(`domain:${instance}`);
|
search.push(`domain:${instance}`);
|
||||||
}
|
}
|
||||||
|
|
@ -90,11 +90,12 @@ const hashtagTimelineController: AppController = (c) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const suggestedTimelineController: AppController = async (c) => {
|
const suggestedTimelineController: AppController = async (c) => {
|
||||||
|
const { conf } = c.var;
|
||||||
const store = c.get('store');
|
const store = c.get('store');
|
||||||
const params = c.get('pagination');
|
const params = c.get('pagination');
|
||||||
|
|
||||||
const [follows] = await store.query(
|
const [follows] = await store.query(
|
||||||
[{ kinds: [3], authors: [Conf.pubkey], limit: 1 }],
|
[{ kinds: [3], authors: [conf.pubkey], limit: 1 }],
|
||||||
);
|
);
|
||||||
|
|
||||||
const authors = [...getTagSet(follows?.tags ?? [], 'p')];
|
const authors = [...getTagSet(follows?.tags ?? [], 'p')];
|
||||||
|
|
@ -104,9 +105,10 @@ const suggestedTimelineController: AppController = async (c) => {
|
||||||
|
|
||||||
/** Render statuses for timelines. */
|
/** Render statuses for timelines. */
|
||||||
async function renderStatuses(c: AppContext, filters: NostrFilter[]) {
|
async function renderStatuses(c: AppContext, filters: NostrFilter[]) {
|
||||||
|
const { conf } = c.var;
|
||||||
const { signal } = c.req.raw;
|
const { signal } = c.req.raw;
|
||||||
const store = c.get('store');
|
const store = c.get('store');
|
||||||
const opts = { signal, timeout: Conf.db.timeouts.timelines };
|
const opts = { signal, timeout: conf.db.timeouts.timelines };
|
||||||
|
|
||||||
const events = await store
|
const events = await store
|
||||||
.query(filters, opts)
|
.query(filters, opts)
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { type DittoConf } from '@ditto/conf';
|
||||||
import { NostrEvent, NostrFilter, NStore } from '@nostrify/nostrify';
|
import { NostrEvent, NostrFilter, NStore } from '@nostrify/nostrify';
|
||||||
import { logi } from '@soapbox/logi';
|
import { logi } from '@soapbox/logi';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
@ -13,7 +14,7 @@ import { paginated } from '@/utils/api.ts';
|
||||||
import { errorJson } from '@/utils/log.ts';
|
import { errorJson } from '@/utils/log.ts';
|
||||||
import { renderStatus } from '@/views/mastodon/statuses.ts';
|
import { renderStatus } from '@/views/mastodon/statuses.ts';
|
||||||
|
|
||||||
let trendingHashtagsCache = getTrendingHashtags().catch((e: unknown) => {
|
let trendingHashtagsCache = getTrendingHashtags(Conf).catch((e: unknown) => {
|
||||||
logi({
|
logi({
|
||||||
level: 'error',
|
level: 'error',
|
||||||
ns: 'ditto.trends.api',
|
ns: 'ditto.trends.api',
|
||||||
|
|
@ -26,7 +27,7 @@ let trendingHashtagsCache = getTrendingHashtags().catch((e: unknown) => {
|
||||||
|
|
||||||
Deno.cron('update trending hashtags cache', '35 * * * *', async () => {
|
Deno.cron('update trending hashtags cache', '35 * * * *', async () => {
|
||||||
try {
|
try {
|
||||||
const trends = await getTrendingHashtags();
|
const trends = await getTrendingHashtags(Conf);
|
||||||
trendingHashtagsCache = Promise.resolve(trends);
|
trendingHashtagsCache = Promise.resolve(trends);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logi({
|
logi({
|
||||||
|
|
@ -50,9 +51,9 @@ const trendingTagsController: AppController = async (c) => {
|
||||||
return c.json(trends.slice(offset, offset + limit));
|
return c.json(trends.slice(offset, offset + limit));
|
||||||
};
|
};
|
||||||
|
|
||||||
async function getTrendingHashtags() {
|
async function getTrendingHashtags(conf: DittoConf) {
|
||||||
const store = await Storages.db();
|
const store = await Storages.db();
|
||||||
const trends = await getTrendingTags(store, 't');
|
const trends = await getTrendingTags(store, 't', conf.pubkey);
|
||||||
|
|
||||||
return trends.map((trend) => {
|
return trends.map((trend) => {
|
||||||
const hashtag = trend.value;
|
const hashtag = trend.value;
|
||||||
|
|
@ -65,13 +66,13 @@ async function getTrendingHashtags() {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: hashtag,
|
name: hashtag,
|
||||||
url: Conf.local(`/tags/${hashtag}`),
|
url: conf.local(`/tags/${hashtag}`),
|
||||||
history,
|
history,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let trendingLinksCache = getTrendingLinks().catch((e: unknown) => {
|
let trendingLinksCache = getTrendingLinks(Conf).catch((e: unknown) => {
|
||||||
logi({
|
logi({
|
||||||
level: 'error',
|
level: 'error',
|
||||||
ns: 'ditto.trends.api',
|
ns: 'ditto.trends.api',
|
||||||
|
|
@ -84,7 +85,7 @@ let trendingLinksCache = getTrendingLinks().catch((e: unknown) => {
|
||||||
|
|
||||||
Deno.cron('update trending links cache', '50 * * * *', async () => {
|
Deno.cron('update trending links cache', '50 * * * *', async () => {
|
||||||
try {
|
try {
|
||||||
const trends = await getTrendingLinks();
|
const trends = await getTrendingLinks(Conf);
|
||||||
trendingLinksCache = Promise.resolve(trends);
|
trendingLinksCache = Promise.resolve(trends);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logi({
|
logi({
|
||||||
|
|
@ -103,9 +104,9 @@ const trendingLinksController: AppController = async (c) => {
|
||||||
return c.json(trends.slice(offset, offset + limit));
|
return c.json(trends.slice(offset, offset + limit));
|
||||||
};
|
};
|
||||||
|
|
||||||
async function getTrendingLinks() {
|
async function getTrendingLinks(conf: DittoConf) {
|
||||||
const store = await Storages.db();
|
const store = await Storages.db();
|
||||||
const trends = await getTrendingTags(store, 'r');
|
const trends = await getTrendingTags(store, 'r', conf.pubkey);
|
||||||
|
|
||||||
return Promise.all(trends.map(async (trend) => {
|
return Promise.all(trends.map(async (trend) => {
|
||||||
const link = trend.value;
|
const link = trend.value;
|
||||||
|
|
@ -139,6 +140,7 @@ async function getTrendingLinks() {
|
||||||
}
|
}
|
||||||
|
|
||||||
const trendingStatusesController: AppController = async (c) => {
|
const trendingStatusesController: AppController = async (c) => {
|
||||||
|
const { conf } = c.var;
|
||||||
const store = await Storages.db();
|
const store = await Storages.db();
|
||||||
const { limit, offset, until } = paginationSchema.parse(c.req.query());
|
const { limit, offset, until } = paginationSchema.parse(c.req.query());
|
||||||
|
|
||||||
|
|
@ -146,7 +148,7 @@ const trendingStatusesController: AppController = async (c) => {
|
||||||
kinds: [1985],
|
kinds: [1985],
|
||||||
'#L': ['pub.ditto.trends'],
|
'#L': ['pub.ditto.trends'],
|
||||||
'#l': ['#e'],
|
'#l': ['#e'],
|
||||||
authors: [Conf.pubkey],
|
authors: [conf.pubkey],
|
||||||
until,
|
until,
|
||||||
limit: 1,
|
limit: 1,
|
||||||
}]);
|
}]);
|
||||||
|
|
@ -185,12 +187,12 @@ interface TrendingTag {
|
||||||
}[];
|
}[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getTrendingTags(store: NStore, tagName: string): Promise<TrendingTag[]> {
|
export async function getTrendingTags(store: NStore, tagName: string, pubkey: string): Promise<TrendingTag[]> {
|
||||||
const [label] = await store.query([{
|
const [label] = await store.query([{
|
||||||
kinds: [1985],
|
kinds: [1985],
|
||||||
'#L': ['pub.ditto.trends'],
|
'#L': ['pub.ditto.trends'],
|
||||||
'#l': [`#${tagName}`],
|
'#l': [`#${tagName}`],
|
||||||
authors: [Conf.pubkey],
|
authors: [pubkey],
|
||||||
limit: 1,
|
limit: 1,
|
||||||
}]);
|
}]);
|
||||||
|
|
||||||
|
|
@ -213,7 +215,7 @@ export async function getTrendingTags(store: NStore, tagName: string): Promise<T
|
||||||
'#L': ['pub.ditto.trends'],
|
'#L': ['pub.ditto.trends'],
|
||||||
'#l': [`#${tagName}`],
|
'#l': [`#${tagName}`],
|
||||||
[`#${tagName}`]: [value],
|
[`#${tagName}`]: [value],
|
||||||
authors: [Conf.pubkey],
|
authors: [pubkey],
|
||||||
since: Math.floor(date.getTime() / 1000),
|
since: Math.floor(date.getTime() / 1000),
|
||||||
until: Math.floor((date.getTime() + Time.days(1)) / 1000),
|
until: Math.floor((date.getTime() + Time.days(1)) / 1000),
|
||||||
limit: 1,
|
limit: 1,
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
import denoJson from 'deno.json' with { type: 'json' };
|
import denoJson from 'deno.json' with { type: 'json' };
|
||||||
|
|
||||||
import { AppController } from '@/app.ts';
|
import { AppController } from '@/app.ts';
|
||||||
import { Conf } from '@/config.ts';
|
|
||||||
import { Storages } from '@/storages.ts';
|
import { Storages } from '@/storages.ts';
|
||||||
import { getInstanceMetadata } from '@/utils/instance.ts';
|
import { getInstanceMetadata } from '@/utils/instance.ts';
|
||||||
|
|
||||||
const relayInfoController: AppController = async (c) => {
|
const relayInfoController: AppController = async (c) => {
|
||||||
|
const { conf } = c.var;
|
||||||
const store = await Storages.db();
|
const store = await Storages.db();
|
||||||
const meta = await getInstanceMetadata(store, c.req.raw.signal);
|
const meta = await getInstanceMetadata(store, c.req.raw.signal);
|
||||||
|
|
||||||
|
|
@ -14,7 +14,7 @@ const relayInfoController: AppController = async (c) => {
|
||||||
return c.json({
|
return c.json({
|
||||||
name: meta.name,
|
name: meta.name,
|
||||||
description: meta.about,
|
description: meta.about,
|
||||||
pubkey: Conf.pubkey,
|
pubkey: conf.pubkey,
|
||||||
contact: meta.email,
|
contact: meta.email,
|
||||||
supported_nips: [1, 5, 9, 11, 16, 45, 50, 46, 98],
|
supported_nips: [1, 5, 9, 11, 16, 45, 50, 46, 98],
|
||||||
software: 'Ditto',
|
software: 'Ditto',
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { type DittoConf } from '@ditto/conf';
|
||||||
import { logi } from '@soapbox/logi';
|
import { logi } from '@soapbox/logi';
|
||||||
import { JsonValue } from '@std/json';
|
import { JsonValue } from '@std/json';
|
||||||
import {
|
import {
|
||||||
|
|
@ -12,7 +13,6 @@ import {
|
||||||
} from '@nostrify/nostrify';
|
} from '@nostrify/nostrify';
|
||||||
|
|
||||||
import { AppController } from '@/app.ts';
|
import { AppController } from '@/app.ts';
|
||||||
import { Conf } from '@/config.ts';
|
|
||||||
import { relayInfoController } from '@/controllers/nostr/relay-info.ts';
|
import { relayInfoController } from '@/controllers/nostr/relay-info.ts';
|
||||||
import { relayConnectionsGauge, relayEventsCounter, relayMessagesCounter } from '@/metrics.ts';
|
import { relayConnectionsGauge, relayEventsCounter, relayMessagesCounter } from '@/metrics.ts';
|
||||||
import * as pipeline from '@/pipeline.ts';
|
import * as pipeline from '@/pipeline.ts';
|
||||||
|
|
@ -47,7 +47,7 @@ const limiters = {
|
||||||
const connections = new Set<WebSocket>();
|
const connections = new Set<WebSocket>();
|
||||||
|
|
||||||
/** Set up the Websocket connection. */
|
/** Set up the Websocket connection. */
|
||||||
function connectStream(socket: WebSocket, ip: string | undefined) {
|
function connectStream(socket: WebSocket, ip: string | undefined, conf: DittoConf) {
|
||||||
const controllers = new Map<string, AbortController>();
|
const controllers = new Map<string, AbortController>();
|
||||||
|
|
||||||
socket.onopen = () => {
|
socket.onopen = () => {
|
||||||
|
|
@ -126,7 +126,7 @@ function connectStream(socket: WebSocket, ip: string | undefined) {
|
||||||
const pubsub = await Storages.pubsub();
|
const pubsub = await Storages.pubsub();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
for (const event of await store.query(filters, { limit: FILTER_LIMIT, timeout: Conf.db.timeouts.relay })) {
|
for (const event of await store.query(filters, { limit: FILTER_LIMIT, timeout: conf.db.timeouts.relay })) {
|
||||||
send(['EVENT', subId, purifyEvent(event)]);
|
send(['EVENT', subId, purifyEvent(event)]);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
@ -188,7 +188,7 @@ function connectStream(socket: WebSocket, ip: string | undefined) {
|
||||||
async function handleCount([_, subId, ...filters]: NostrClientCOUNT): Promise<void> {
|
async function handleCount([_, subId, ...filters]: NostrClientCOUNT): Promise<void> {
|
||||||
if (rateLimited(limiters.req)) return;
|
if (rateLimited(limiters.req)) return;
|
||||||
const store = await Storages.db();
|
const store = await Storages.db();
|
||||||
const { count } = await store.count(filters, { timeout: Conf.db.timeouts.relay });
|
const { count } = await store.count(filters, { timeout: conf.db.timeouts.relay });
|
||||||
send(['COUNT', subId, { count, approximate: false }]);
|
send(['COUNT', subId, { count, approximate: false }]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -201,6 +201,7 @@ function connectStream(socket: WebSocket, ip: string | undefined) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const relayController: AppController = (c, next) => {
|
const relayController: AppController = (c, next) => {
|
||||||
|
const { conf } = c.var;
|
||||||
const upgrade = c.req.header('upgrade');
|
const upgrade = c.req.header('upgrade');
|
||||||
|
|
||||||
// NIP-11: https://github.com/nostr-protocol/nips/blob/master/11.md
|
// NIP-11: https://github.com/nostr-protocol/nips/blob/master/11.md
|
||||||
|
|
@ -214,7 +215,7 @@ const relayController: AppController = (c, next) => {
|
||||||
|
|
||||||
let ip = c.req.header('x-real-ip');
|
let ip = c.req.header('x-real-ip');
|
||||||
|
|
||||||
if (ip && Conf.ipWhitelist.includes(ip)) {
|
if (ip && conf.ipWhitelist.includes(ip)) {
|
||||||
ip = undefined;
|
ip = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -229,7 +230,7 @@ const relayController: AppController = (c, next) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const { socket, response } = Deno.upgradeWebSocket(c.req.raw, { idleTimeout: 30 });
|
const { socket, response } = Deno.upgradeWebSocket(c.req.raw, { idleTimeout: 30 });
|
||||||
connectStream(socket, ip);
|
connectStream(socket, ip, conf);
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,17 @@
|
||||||
import { Conf } from '@/config.ts';
|
|
||||||
|
|
||||||
import type { AppController } from '@/app.ts';
|
import type { AppController } from '@/app.ts';
|
||||||
|
|
||||||
const nodeInfoController: AppController = (c) => {
|
const nodeInfoController: AppController = (c) => {
|
||||||
|
const { conf } = c.var;
|
||||||
|
|
||||||
return c.json({
|
return c.json({
|
||||||
links: [
|
links: [
|
||||||
{
|
{
|
||||||
rel: 'http://nodeinfo.diaspora.software/ns/schema/2.0',
|
rel: 'http://nodeinfo.diaspora.software/ns/schema/2.0',
|
||||||
href: Conf.local('/nodeinfo/2.0'),
|
href: conf.local('/nodeinfo/2.0'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
rel: 'http://nodeinfo.diaspora.software/ns/schema/2.1',
|
rel: 'http://nodeinfo.diaspora.software/ns/schema/2.1',
|
||||||
href: Conf.local('/nodeinfo/2.1'),
|
href: conf.local('/nodeinfo/2.1'),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@ import {
|
||||||
type ParseAuthRequestOpts,
|
type ParseAuthRequestOpts,
|
||||||
validateAuthEvent,
|
validateAuthEvent,
|
||||||
} from '@/utils/nip98.ts';
|
} from '@/utils/nip98.ts';
|
||||||
import { Conf } from '@/config.ts';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* NIP-98 auth.
|
* NIP-98 auth.
|
||||||
|
|
@ -35,12 +34,13 @@ type UserRole = 'user' | 'admin';
|
||||||
|
|
||||||
/** Require the user to prove their role before invoking the controller. */
|
/** Require the user to prove their role before invoking the controller. */
|
||||||
function requireRole(role: UserRole, opts?: ParseAuthRequestOpts): AppMiddleware {
|
function requireRole(role: UserRole, opts?: ParseAuthRequestOpts): AppMiddleware {
|
||||||
return withProof(async (_c, proof, next) => {
|
return withProof(async (c, proof, next) => {
|
||||||
|
const { conf } = c.var;
|
||||||
const store = await Storages.db();
|
const store = await Storages.db();
|
||||||
|
|
||||||
const [user] = await store.query([{
|
const [user] = await store.query([{
|
||||||
kinds: [30382],
|
kinds: [30382],
|
||||||
authors: [Conf.pubkey],
|
authors: [conf.pubkey],
|
||||||
'#d': [proof.pubkey],
|
'#d': [proof.pubkey],
|
||||||
limit: 1,
|
limit: 1,
|
||||||
}]);
|
}]);
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import { AppMiddleware } from '@/app.ts';
|
import { AppMiddleware } from '@/app.ts';
|
||||||
import { Conf } from '@/config.ts';
|
|
||||||
import { PleromaConfigDB } from '@/utils/PleromaConfigDB.ts';
|
import { PleromaConfigDB } from '@/utils/PleromaConfigDB.ts';
|
||||||
import { Storages } from '@/storages.ts';
|
import { Storages } from '@/storages.ts';
|
||||||
import { getPleromaConfigs } from '@/utils/pleroma.ts';
|
import { getPleromaConfigs } from '@/utils/pleroma.ts';
|
||||||
|
|
@ -8,13 +7,14 @@ let configDBCache: Promise<PleromaConfigDB> | undefined;
|
||||||
|
|
||||||
export const cspMiddleware = (): AppMiddleware => {
|
export const cspMiddleware = (): AppMiddleware => {
|
||||||
return async (c, next) => {
|
return async (c, next) => {
|
||||||
|
const { conf } = c.var;
|
||||||
const store = await Storages.db();
|
const store = await Storages.db();
|
||||||
|
|
||||||
if (!configDBCache) {
|
if (!configDBCache) {
|
||||||
configDBCache = getPleromaConfigs(store);
|
configDBCache = getPleromaConfigs(store);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { host, protocol, origin } = Conf.url;
|
const { host, protocol, origin } = conf.url;
|
||||||
const wsProtocol = protocol === 'http:' ? 'ws:' : 'wss:';
|
const wsProtocol = protocol === 'http:' ? 'ws:' : 'wss:';
|
||||||
const configDB = await configDBCache;
|
const configDB = await configDBCache;
|
||||||
const sentryDsn = configDB.getIn(':pleroma', ':frontend_configurations', ':soapbox_fe', 'sentryDsn');
|
const sentryDsn = configDB.getIn(':pleroma', ':frontend_configurations', ':soapbox_fe', 'sentryDsn');
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,13 @@
|
||||||
|
import { type DittoConf } from '@ditto/conf';
|
||||||
import { MiddlewareHandler } from '@hono/hono';
|
import { MiddlewareHandler } from '@hono/hono';
|
||||||
import { rateLimiter } from 'hono-rate-limiter';
|
import { rateLimiter } from 'hono-rate-limiter';
|
||||||
|
|
||||||
import { Conf } from '@/config.ts';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rate limit middleware for Hono, based on [`hono-rate-limiter`](https://github.com/rhinobase/hono-rate-limiter).
|
* Rate limit middleware for Hono, based on [`hono-rate-limiter`](https://github.com/rhinobase/hono-rate-limiter).
|
||||||
*/
|
*/
|
||||||
export function rateLimitMiddleware(limit: number, windowMs: number, includeHeaders?: boolean): MiddlewareHandler {
|
export function rateLimitMiddleware(limit: number, windowMs: number, includeHeaders?: boolean): MiddlewareHandler {
|
||||||
// @ts-ignore Mismatched hono versions.
|
// @ts-ignore Mismatched hono versions.
|
||||||
return rateLimiter({
|
return rateLimiter<{ Variables: { conf: DittoConf } }>({
|
||||||
limit,
|
limit,
|
||||||
windowMs,
|
windowMs,
|
||||||
standardHeaders: includeHeaders,
|
standardHeaders: includeHeaders,
|
||||||
|
|
@ -17,8 +16,9 @@ export function rateLimitMiddleware(limit: number, windowMs: number, includeHead
|
||||||
return c.text('Too many requests, please try again later.', 429);
|
return c.text('Too many requests, please try again later.', 429);
|
||||||
},
|
},
|
||||||
skip: (c) => {
|
skip: (c) => {
|
||||||
|
const { conf } = c.var;
|
||||||
const ip = c.req.header('x-real-ip');
|
const ip = c.req.header('x-real-ip');
|
||||||
return !ip || Conf.ipWhitelist.includes(ip);
|
return !ip || conf.ipWhitelist.includes(ip);
|
||||||
},
|
},
|
||||||
keyGenerator: (c) => c.req.header('x-real-ip')!,
|
keyGenerator: (c) => c.req.header('x-real-ip')!,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
|
import { type DittoConf } from '@ditto/conf';
|
||||||
import { MiddlewareHandler } from '@hono/hono';
|
import { MiddlewareHandler } from '@hono/hono';
|
||||||
import { HTTPException } from '@hono/hono/http-exception';
|
import { HTTPException } from '@hono/hono/http-exception';
|
||||||
import { NostrSigner, NSecSigner } from '@nostrify/nostrify';
|
import { NostrSigner, NSecSigner } from '@nostrify/nostrify';
|
||||||
import { nip19 } from 'nostr-tools';
|
import { nip19 } from 'nostr-tools';
|
||||||
|
|
||||||
import { Conf } from '@/config.ts';
|
|
||||||
import { ConnectSigner } from '@/signers/ConnectSigner.ts';
|
import { ConnectSigner } from '@/signers/ConnectSigner.ts';
|
||||||
import { ReadOnlySigner } from '@/signers/ReadOnlySigner.ts';
|
import { ReadOnlySigner } from '@/signers/ReadOnlySigner.ts';
|
||||||
import { Storages } from '@/storages.ts';
|
import { Storages } from '@/storages.ts';
|
||||||
|
|
@ -14,7 +14,11 @@ import { getTokenHash } from '@/utils/auth.ts';
|
||||||
const BEARER_REGEX = new RegExp(`^Bearer (${nip19.BECH32_REGEX.source})$`);
|
const BEARER_REGEX = new RegExp(`^Bearer (${nip19.BECH32_REGEX.source})$`);
|
||||||
|
|
||||||
/** Make a `signer` object available to all controllers, or unset if the user isn't logged in. */
|
/** Make a `signer` object available to all controllers, or unset if the user isn't logged in. */
|
||||||
export const signerMiddleware: MiddlewareHandler<{ Variables: { signer: NostrSigner } }> = async (c, next) => {
|
export const signerMiddleware: MiddlewareHandler<{ Variables: { signer: NostrSigner; conf: DittoConf } }> = async (
|
||||||
|
c,
|
||||||
|
next,
|
||||||
|
) => {
|
||||||
|
const { conf } = c.var;
|
||||||
const header = c.req.header('authorization');
|
const header = c.req.header('authorization');
|
||||||
const match = header?.match(BEARER_REGEX);
|
const match = header?.match(BEARER_REGEX);
|
||||||
|
|
||||||
|
|
@ -32,7 +36,7 @@ export const signerMiddleware: MiddlewareHandler<{ Variables: { signer: NostrSig
|
||||||
.where('token_hash', '=', tokenHash)
|
.where('token_hash', '=', tokenHash)
|
||||||
.executeTakeFirstOrThrow();
|
.executeTakeFirstOrThrow();
|
||||||
|
|
||||||
const nep46Seckey = await aesDecrypt(Conf.seckey, nip46_sk_enc);
|
const nep46Seckey = await aesDecrypt(conf.seckey, nip46_sk_enc);
|
||||||
|
|
||||||
c.set(
|
c.set(
|
||||||
'signer',
|
'signer',
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { CashuMint, CashuWallet, getEncodedToken, type Proof } from '@cashu/cashu-ts';
|
import { CashuMint, CashuWallet, getEncodedToken, type Proof } from '@cashu/cashu-ts';
|
||||||
|
import { type DittoConf } from '@ditto/conf';
|
||||||
import { MiddlewareHandler } from '@hono/hono';
|
import { MiddlewareHandler } from '@hono/hono';
|
||||||
import { HTTPException } from '@hono/hono/http-exception';
|
import { HTTPException } from '@hono/hono/http-exception';
|
||||||
import { getPublicKey } from 'nostr-tools';
|
import { getPublicKey } from 'nostr-tools';
|
||||||
|
|
@ -9,7 +10,6 @@ import { logi } from '@soapbox/logi';
|
||||||
|
|
||||||
import { isNostrId } from '@/utils.ts';
|
import { isNostrId } from '@/utils.ts';
|
||||||
import { errorJson } from '@/utils/log.ts';
|
import { errorJson } from '@/utils/log.ts';
|
||||||
import { Conf } from '@/config.ts';
|
|
||||||
import { createEvent } from '@/utils/api.ts';
|
import { createEvent } from '@/utils/api.ts';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
|
@ -18,8 +18,9 @@ import { z } from 'zod';
|
||||||
* Errors are only thrown if 'signer' and 'store' middlewares are not set.
|
* Errors are only thrown if 'signer' and 'store' middlewares are not set.
|
||||||
*/
|
*/
|
||||||
export const swapNutzapsMiddleware: MiddlewareHandler<
|
export const swapNutzapsMiddleware: MiddlewareHandler<
|
||||||
{ Variables: { signer: SetRequired<NostrSigner, 'nip44'>; store: NStore } }
|
{ Variables: { signer: SetRequired<NostrSigner, 'nip44'>; store: NStore; conf: DittoConf } }
|
||||||
> = async (c, next) => {
|
> = async (c, next) => {
|
||||||
|
const { conf } = c.var;
|
||||||
const signer = c.get('signer');
|
const signer = c.get('signer');
|
||||||
const store = c.get('store');
|
const store = c.get('store');
|
||||||
|
|
||||||
|
|
@ -133,7 +134,7 @@ export const swapNutzapsMiddleware: MiddlewareHandler<
|
||||||
[
|
[
|
||||||
'e', // nutzap event that has been redeemed
|
'e', // nutzap event that has been redeemed
|
||||||
event.id,
|
event.id,
|
||||||
Conf.relay,
|
conf.relay,
|
||||||
'redeemed',
|
'redeemed',
|
||||||
],
|
],
|
||||||
['p', event.pubkey], // pubkey of the author of the 9321 event (nutzap sender)
|
['p', event.pubkey], // pubkey of the author of the 9321 event (nutzap sender)
|
||||||
|
|
@ -173,7 +174,7 @@ export const swapNutzapsMiddleware: MiddlewareHandler<
|
||||||
JSON.stringify([
|
JSON.stringify([
|
||||||
['direction', 'in'],
|
['direction', 'in'],
|
||||||
['amount', amount],
|
['amount', amount],
|
||||||
['e', unspentProofs.id, Conf.relay, 'created'],
|
['e', unspentProofs.id, conf.relay, 'created'],
|
||||||
]),
|
]),
|
||||||
),
|
),
|
||||||
tags: mintsToProofs[mint].redeemed,
|
tags: mintsToProofs[mint].redeemed,
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,16 @@
|
||||||
import { safeFetch } from '@soapbox/safe-fetch';
|
import { safeFetch } from '@soapbox/safe-fetch';
|
||||||
|
|
||||||
import { AppMiddleware } from '@/app.ts';
|
import { AppMiddleware } from '@/app.ts';
|
||||||
import { Conf } from '@/config.ts';
|
|
||||||
import { DeepLTranslator } from '@/translators/DeepLTranslator.ts';
|
import { DeepLTranslator } from '@/translators/DeepLTranslator.ts';
|
||||||
import { LibreTranslateTranslator } from '@/translators/LibreTranslateTranslator.ts';
|
import { LibreTranslateTranslator } from '@/translators/LibreTranslateTranslator.ts';
|
||||||
|
|
||||||
/** Set the translator used for translating posts. */
|
/** Set the translator used for translating posts. */
|
||||||
export const translatorMiddleware: AppMiddleware = async (c, next) => {
|
export const translatorMiddleware: AppMiddleware = async (c, next) => {
|
||||||
switch (Conf.translationProvider) {
|
const { conf } = c.var;
|
||||||
|
|
||||||
|
switch (conf.translationProvider) {
|
||||||
case 'deepl': {
|
case 'deepl': {
|
||||||
const { deeplApiKey: apiKey, deeplBaseUrl: baseUrl } = Conf;
|
const { deeplApiKey: apiKey, deeplBaseUrl: baseUrl } = conf;
|
||||||
if (apiKey) {
|
if (apiKey) {
|
||||||
c.set('translator', new DeepLTranslator({ baseUrl, apiKey, fetch: safeFetch }));
|
c.set('translator', new DeepLTranslator({ baseUrl, apiKey, fetch: safeFetch }));
|
||||||
}
|
}
|
||||||
|
|
@ -17,7 +18,7 @@ export const translatorMiddleware: AppMiddleware = async (c, next) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'libretranslate': {
|
case 'libretranslate': {
|
||||||
const { libretranslateApiKey: apiKey, libretranslateBaseUrl: baseUrl } = Conf;
|
const { libretranslateApiKey: apiKey, libretranslateBaseUrl: baseUrl } = conf;
|
||||||
if (apiKey) {
|
if (apiKey) {
|
||||||
c.set('translator', new LibreTranslateTranslator({ baseUrl, apiKey, fetch: safeFetch }));
|
c.set('translator', new LibreTranslateTranslator({ baseUrl, apiKey, fetch: safeFetch }));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,44 +2,44 @@ import { BlossomUploader, NostrBuildUploader } from '@nostrify/nostrify/uploader
|
||||||
import { safeFetch } from '@soapbox/safe-fetch';
|
import { safeFetch } from '@soapbox/safe-fetch';
|
||||||
|
|
||||||
import { AppMiddleware } from '@/app.ts';
|
import { AppMiddleware } from '@/app.ts';
|
||||||
import { Conf } from '@/config.ts';
|
|
||||||
import { DenoUploader } from '@/uploaders/DenoUploader.ts';
|
import { DenoUploader } from '@/uploaders/DenoUploader.ts';
|
||||||
import { IPFSUploader } from '@/uploaders/IPFSUploader.ts';
|
import { IPFSUploader } from '@/uploaders/IPFSUploader.ts';
|
||||||
import { S3Uploader } from '@/uploaders/S3Uploader.ts';
|
import { S3Uploader } from '@/uploaders/S3Uploader.ts';
|
||||||
|
|
||||||
/** Set an uploader for the user. */
|
/** Set an uploader for the user. */
|
||||||
export const uploaderMiddleware: AppMiddleware = async (c, next) => {
|
export const uploaderMiddleware: AppMiddleware = async (c, next) => {
|
||||||
const signer = c.get('signer');
|
const { signer, conf } = c.var;
|
||||||
|
|
||||||
switch (Conf.uploader) {
|
switch (conf.uploader) {
|
||||||
case 's3':
|
case 's3':
|
||||||
c.set(
|
c.set(
|
||||||
'uploader',
|
'uploader',
|
||||||
new S3Uploader({
|
new S3Uploader({
|
||||||
accessKey: Conf.s3.accessKey,
|
accessKey: conf.s3.accessKey,
|
||||||
bucket: Conf.s3.bucket,
|
bucket: conf.s3.bucket,
|
||||||
endPoint: Conf.s3.endPoint!,
|
endPoint: conf.s3.endPoint!,
|
||||||
pathStyle: Conf.s3.pathStyle,
|
pathStyle: conf.s3.pathStyle,
|
||||||
port: Conf.s3.port,
|
port: conf.s3.port,
|
||||||
region: Conf.s3.region!,
|
region: conf.s3.region!,
|
||||||
secretKey: Conf.s3.secretKey,
|
secretKey: conf.s3.secretKey,
|
||||||
sessionToken: Conf.s3.sessionToken,
|
sessionToken: conf.s3.sessionToken,
|
||||||
useSSL: Conf.s3.useSSL,
|
useSSL: conf.s3.useSSL,
|
||||||
|
baseUrl: conf.mediaDomain,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case 'ipfs':
|
case 'ipfs':
|
||||||
c.set('uploader', new IPFSUploader({ baseUrl: Conf.mediaDomain, apiUrl: Conf.ipfs.apiUrl, fetch: safeFetch }));
|
c.set('uploader', new IPFSUploader({ baseUrl: conf.mediaDomain, apiUrl: conf.ipfs.apiUrl, fetch: safeFetch }));
|
||||||
break;
|
break;
|
||||||
case 'local':
|
case 'local':
|
||||||
c.set('uploader', new DenoUploader({ baseUrl: Conf.mediaDomain, dir: Conf.uploadsDir }));
|
c.set('uploader', new DenoUploader({ baseUrl: conf.mediaDomain, dir: conf.uploadsDir }));
|
||||||
break;
|
break;
|
||||||
case 'nostrbuild':
|
case 'nostrbuild':
|
||||||
c.set('uploader', new NostrBuildUploader({ endpoint: Conf.nostrbuildEndpoint, signer, fetch: safeFetch }));
|
c.set('uploader', new NostrBuildUploader({ endpoint: conf.nostrbuildEndpoint, signer, fetch: safeFetch }));
|
||||||
break;
|
break;
|
||||||
case 'blossom':
|
case 'blossom':
|
||||||
if (signer) {
|
if (signer) {
|
||||||
c.set('uploader', new BlossomUploader({ servers: Conf.blossomServers, signer, fetch: safeFetch }));
|
c.set('uploader', new BlossomUploader({ servers: conf.blossomServers, signer, fetch: safeFetch }));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,6 @@ import { crypto } from '@std/crypto';
|
||||||
import { encodeHex } from '@std/encoding/hex';
|
import { encodeHex } from '@std/encoding/hex';
|
||||||
import { extensionsByType } from '@std/media-types';
|
import { extensionsByType } from '@std/media-types';
|
||||||
|
|
||||||
import { Conf } from '@/config.ts';
|
|
||||||
|
|
||||||
export interface S3UploaderOpts {
|
export interface S3UploaderOpts {
|
||||||
endPoint: string;
|
endPoint: string;
|
||||||
region: string;
|
region: string;
|
||||||
|
|
@ -18,13 +16,14 @@ export interface S3UploaderOpts {
|
||||||
port?: number;
|
port?: number;
|
||||||
sessionToken?: string;
|
sessionToken?: string;
|
||||||
useSSL?: boolean;
|
useSSL?: boolean;
|
||||||
|
baseUrl?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** S3-compatible uploader for AWS, Wasabi, DigitalOcean Spaces, and more. */
|
/** S3-compatible uploader for AWS, Wasabi, DigitalOcean Spaces, and more. */
|
||||||
export class S3Uploader implements NUploader {
|
export class S3Uploader implements NUploader {
|
||||||
private client: S3Client;
|
private client: S3Client;
|
||||||
|
|
||||||
constructor(opts: S3UploaderOpts) {
|
constructor(private opts: S3UploaderOpts) {
|
||||||
this.client = new S3Client(opts);
|
this.client = new S3Client(opts);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -40,10 +39,10 @@ export class S3Uploader implements NUploader {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const { pathStyle, bucket } = Conf.s3;
|
const { pathStyle, bucket, baseUrl } = this.opts;
|
||||||
|
|
||||||
const path = (pathStyle && bucket) ? join(bucket, filename) : filename;
|
const path = (pathStyle && bucket) ? join(bucket, filename) : filename;
|
||||||
const url = new URL(path, Conf.mediaDomain).toString();
|
const url = new URL(path, baseUrl).toString();
|
||||||
|
|
||||||
return [
|
return [
|
||||||
['url', url],
|
['url', url],
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue