From 899e7672dc9ca4ddeb8549d28df57ca128181988 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 10 Jun 2024 20:04:39 -0500 Subject: [PATCH 01/11] Disable Hono strict mode --- src/app.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/app.ts b/src/app.ts index 074359d..15d4fc5 100644 --- a/src/app.ts +++ b/src/app.ts @@ -129,7 +129,7 @@ type AppContext = Context; type AppMiddleware = MiddlewareHandler; type AppController = Handler>; -const app = new Hono(); +const app = new Hono({ strict: false }); const debug = Debug('ditto:http'); @@ -141,14 +141,12 @@ if (Conf.cronEnabled) { } app.use('/api/*', logger(debug)); -app.use('/relay/*', logger(debug)); app.use('/.well-known/*', logger(debug)); app.use('/users/*', logger(debug)); app.use('/nodeinfo/*', logger(debug)); app.use('/oauth/*', logger(debug)); app.get('/api/v1/streaming', streamingController); -app.get('/api/v1/streaming/', streamingController); app.get('/relay', relayController); app.use( @@ -297,6 +295,9 @@ app.get('/api/v1/conversations', emptyArrayController); app.get('/api/v1/lists', emptyArrayController); app.use('/api/*', notImplementedController); +app.use('/.well-known/*', notImplementedController); +app.use('/nodeinfo/*', notImplementedController); +app.use('/oauth/*', notImplementedController); const publicFiles = serveStatic({ root: './public/' }); const staticFiles = serveStatic({ root: './static/' }); From ddae4f408a9f4bf36bd1db94348162ce1106ebcf Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Tue, 11 Jun 2024 13:40:09 -0500 Subject: [PATCH 02/11] Support OAuth OOB --- src/controllers/api/oauth.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/controllers/api/oauth.ts b/src/controllers/api/oauth.ts index 5837a5e..18e107b 100644 --- a/src/controllers/api/oauth.ts +++ b/src/controllers/api/oauth.ts @@ -209,6 +209,10 @@ const oauthAuthorizeController: AppController = async (c) => { relays: bunker.searchParams.getAll('relay'), }); + if (redirectUri === 'urn:ietf:wg:oauth:2.0:oob') { + return c.text(token); + } + const url = addCodeToRedirectUri(redirectUri, token); return c.redirect(url); From b8546ae447e91f04f413dd1521abc0c942d90c99 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Tue, 11 Jun 2024 18:15:40 -0500 Subject: [PATCH 03/11] Include kind 3 in trending pubkeys again --- src/cron.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cron.ts b/src/cron.ts index aa0a8d8..166e42e 100644 --- a/src/cron.ts +++ b/src/cron.ts @@ -59,7 +59,7 @@ export function cron() { Deno.cron( 'update trending pubkeys', '0 * * * *', - () => updateTrendingTags('#p', 'p', [1, 6, 7, 9735], 40, Conf.relay), + () => updateTrendingTags('#p', 'p', [1, 3, 6, 7, 9735], 40, Conf.relay), ); Deno.cron( 'update trending zapped events', From 1151f0c28b1aa477381bd1b33ed0928cbe4fc3e1 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Tue, 11 Jun 2024 18:56:11 -0500 Subject: [PATCH 04/11] EventsDB: strip nip27 mentions from search index --- src/storages/EventsDB.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/storages/EventsDB.ts b/src/storages/EventsDB.ts index c26ebf1..98c32d5 100644 --- a/src/storages/EventsDB.ts +++ b/src/storages/EventsDB.ts @@ -3,6 +3,7 @@ import { NDatabase, NIP50, NKinds, NostrEvent, NostrFilter, NSchema as n, NStore } from '@nostrify/nostrify'; import { Stickynotes } from '@soapbox/stickynotes'; import { Kysely } from 'kysely'; +import { nip27 } from 'nostr-tools'; import { Conf } from '@/config.ts'; import { DittoTables } from '@/db/DittoTables.ts'; @@ -220,7 +221,7 @@ class EventsDB implements NStore { case 0: return EventsDB.buildUserSearchContent(event); case 1: - return event.content; + return nip27.replaceAll(event.content, () => ''); case 30009: return EventsDB.buildTagsSearchContent(event.tags.filter(([t]) => t !== 'alt')); case 30360: From c6da216b4e789fe94e2fb502882341a7e1f2bad1 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Tue, 11 Jun 2024 19:08:24 -0500 Subject: [PATCH 05/11] trends: calculate since the last label date instead of current date --- src/controllers/api/trends.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/controllers/api/trends.ts b/src/controllers/api/trends.ts index 349d86d..b98fc1b 100644 --- a/src/controllers/api/trends.ts +++ b/src/controllers/api/trends.ts @@ -164,9 +164,9 @@ export async function getTrendingTags(store: NStore, tagName: string): Promise name === tagName); - const now = new Date(); - const lastWeek = new Date(now.getTime() - Time.days(7)); - const dates = generateDateRange(lastWeek, now).reverse(); + const labelDate = new Date(label.created_at * 1000); + const lastWeek = new Date(labelDate.getTime() - Time.days(7)); + const dates = generateDateRange(lastWeek, labelDate).reverse(); return Promise.all(tags.map(async ([_, value]) => { const filters = dates.map((date) => ({ From 7dbd40a88b16ff80343eadd201657bdfc4271209 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Wed, 12 Jun 2024 12:50:22 -0500 Subject: [PATCH 06/11] Fix issues with adminAccountsController --- src/controllers/api/admin.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/controllers/api/admin.ts b/src/controllers/api/admin.ts index bcc40ce..003d0cf 100644 --- a/src/controllers/api/admin.ts +++ b/src/controllers/api/admin.ts @@ -85,7 +85,13 @@ const adminAccountsController: AppController = async (c) => { } const events = await store.query([{ kinds: [30382], authors: [Conf.pubkey], '#n': n, ...params }], { signal }); - const pubkeys = new Set(events.map(({ pubkey }) => pubkey)); + + const pubkeys = new Set( + events + .map(({ tags }) => tags.find(([name]) => name === 'd')?.[1]) + .filter((pubkey): pubkey is string => !!pubkey), + ); + const authors = await store.query([{ kinds: [0], authors: [...pubkeys] }]) .then((events) => hydrateEvents({ store, events, signal })); @@ -100,10 +106,14 @@ const adminAccountsController: AppController = async (c) => { } const filter: NostrFilter = { kinds: [0], ...params }; + if (local) { filter.search = `domain:${Conf.url.host}`; } - const events = await store.query([filter], { signal }); + + const events = await store.query([filter], { signal }) + .then((events) => hydrateEvents({ store, events, signal })); + const accounts = await Promise.all(events.map(renderAdminAccount)); return paginated(c, events, accounts); }; From edddc5384c26dd379ca7d4303ea48a59a998049d Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Wed, 12 Jun 2024 18:04:04 -0500 Subject: [PATCH 07/11] Support OAuth "state" param --- src/controllers/api/oauth.ts | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/controllers/api/oauth.ts b/src/controllers/api/oauth.ts index 18e107b..01f80bf 100644 --- a/src/controllers/api/oauth.ts +++ b/src/controllers/api/oauth.ts @@ -122,6 +122,7 @@ const oauthController: AppController = (c) => { return c.text('Missing `redirect_uri` query param.', 422); } + const state = c.req.query('state'); const redirectUri = maybeDecodeUri(encodedUri); return c.html(` @@ -162,6 +163,7 @@ const oauthController: AppController = (c) => {
+

Sign in with a Nostr bunker app. Please configure the app to use this relay: ${Conf.relay}

@@ -187,6 +189,7 @@ function maybeDecodeUri(uri: string): string { const oauthAuthorizeSchema = z.object({ bunker_uri: z.string().url().refine((v) => v.startsWith('bunker://')), redirect_uri: z.string().url(), + state: z.string().optional(), }); /** Controller the OAuth form is POSTed to. */ @@ -199,7 +202,7 @@ const oauthAuthorizeController: AppController = async (c) => { } // Parsed FormData values. - const { bunker_uri, redirect_uri: redirectUri } = result.data; + const { bunker_uri, redirect_uri: redirectUri, state } = result.data; const bunker = new URL(bunker_uri); @@ -213,17 +216,22 @@ const oauthAuthorizeController: AppController = async (c) => { return c.text(token); } - const url = addCodeToRedirectUri(redirectUri, token); + const url = addCodeToRedirectUri(redirectUri, token, state); return c.redirect(url); }; /** Append the given `code` as a query param to the `redirect_uri`. */ -function addCodeToRedirectUri(redirectUri: string, code: string): string { +function addCodeToRedirectUri(redirectUri: string, code: string, state?: string): string { const url = new URL(redirectUri); const q = new URLSearchParams(); q.set('code', code); + + if (state) { + q.set('state', state); + } + url.search = q.toString(); return url.toString(); From 4e0a2100411b74a41cd6280e0ce5edb36e5f2902 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Wed, 12 Jun 2024 19:19:48 -0500 Subject: [PATCH 08/11] Add a setup script to generate the .env file --- deno.json | 3 ++ deno.lock | 18 +++++++++++ scripts/setup.ts | 82 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 103 insertions(+) create mode 100644 scripts/setup.ts diff --git a/deno.json b/deno.json index ecf32a3..a2b8733 100644 --- a/deno.json +++ b/deno.json @@ -11,6 +11,7 @@ "nsec": "deno run scripts/nsec.ts", "admin:event": "deno run -A scripts/admin-event.ts", "admin:role": "deno run -A scripts/admin-role.ts", + "setup": "deno run -A scripts/setup.ts", "stats:recompute": "deno run -A scripts/stats-recompute.ts", "soapbox": "curl -O https://dl.soapbox.pub/main/soapbox.zip && mkdir -p public && mv soapbox.zip public/ && cd public/ && unzip soapbox.zip && rm soapbox.zip" }, @@ -32,6 +33,7 @@ "@std/crypto": "jsr:@std/crypto@^0.224.0", "@std/dotenv": "jsr:@std/dotenv@^0.224.0", "@std/encoding": "jsr:@std/encoding@^0.224.0", + "@std/fs": "jsr:@std/fs@^0.229.3", "@std/json": "jsr:@std/json@^0.223.0", "@std/media-types": "jsr:@std/media-types@^0.224.1", "@std/streams": "jsr:@std/streams@^0.223.0", @@ -53,6 +55,7 @@ "nostr-relaypool": "npm:nostr-relaypool2@0.6.34", "nostr-tools": "npm:nostr-tools@2.5.1", "nostr-wasm": "npm:nostr-wasm@^0.1.0", + "question-deno": "https://raw.githubusercontent.com/ocpu/question-deno/10022b8e52555335aa510adb08b0a300df3cf904/mod.ts", "tldts": "npm:tldts@^6.0.14", "tseep": "npm:tseep@^1.2.1", "type-fest": "npm:type-fest@^4.3.0", diff --git a/deno.lock b/deno.lock index 7d767ab..928bf2c 100644 --- a/deno.lock +++ b/deno.lock @@ -26,6 +26,7 @@ "jsr:@std/encoding@^0.224.1": "jsr:@std/encoding@0.224.3", "jsr:@std/fmt@^0.221.0": "jsr:@std/fmt@0.221.0", "jsr:@std/fs@^0.221.0": "jsr:@std/fs@0.221.0", + "jsr:@std/fs@^0.229.3": "jsr:@std/fs@0.229.3", "jsr:@std/internal@^1.0.0": "jsr:@std/internal@1.0.0", "jsr:@std/io@^0.224": "jsr:@std/io@0.224.1", "jsr:@std/media-types@^0.224.1": "jsr:@std/media-types@0.224.1", @@ -191,6 +192,9 @@ "jsr:@std/path@^0.221.0" ] }, + "@std/fs@0.229.3": { + "integrity": "783bca21f24da92e04c3893c9e79653227ab016c48e96b3078377ebd5222e6eb" + }, "@std/internal@1.0.0": { "integrity": "ac6a6dfebf838582c4b4f61a6907374e27e05bedb6ce276e0f1608fe84e7cd9a" }, @@ -1215,6 +1219,7 @@ "https://deno.land/std@0.214.0/path/windows/separator.ts": "e51c5522140eff4f8402617c5c68a201fdfa3a1a8b28dc23587cff931b665e43", "https://deno.land/std@0.214.0/path/windows/to_file_url.ts": "1cd63fd35ec8d1370feaa4752eccc4cc05ea5362a878be8dc7db733650995484", "https://deno.land/std@0.214.0/path/windows/to_namespaced_path.ts": "4ffa4fb6fae321448d5fe810b3ca741d84df4d7897e61ee29be961a6aac89a4c", + "https://deno.land/std@0.80.0/encoding/utf8.ts": "1b7e77db9a12363c67872f8a208886ca1329f160c1ca9133b13d2ed399688b99", "https://deno.land/x/hono@v3.10.1/adapter/deno/serve-static.ts": "ba10cf6aaf39da942b0d49c3b9877ddba69d41d414c6551d890beb1085f58eea", "https://deno.land/x/hono@v3.10.1/client/client.ts": "ff340f58041203879972dd368b011ed130c66914f789826610869a90603406bf", "https://deno.land/x/hono@v3.10.1/client/index.ts": "3ff4cf246f3543f827a85a2c84d66a025ac350ee927613629bda47e854bfb7ba", @@ -1279,6 +1284,8 @@ "https://deno.land/x/hono@v3.10.1/utils/url.ts": "5fc3307ef3cb2e6f34ec2a03e3d7f2126c6a9f5f0eab677222df3f0e40bd7567", "https://deno.land/x/hono@v3.10.1/validator/index.ts": "6c986e8b91dcf857ecc8164a506ae8eea8665792a4ff7215471df669c632ae7c", "https://deno.land/x/hono@v3.10.1/validator/validator.ts": "afa5e52495e0996fbba61996736fab5c486590d72d376f809e9f9ff4e0c463e9", + "https://deno.land/x/keypress@0.0.7/dep.ts": "feeb0056d332c126343249b79fe86cb0bf3abd03ea4c270cd39575c38d37a911", + "https://deno.land/x/keypress@0.0.7/mod.ts": "1130570c2397118a3a301b1137400a8e55486716cc3557b3bd5e9947b6b9c035", "https://deno.land/x/kysely_deno_postgres@v0.4.0/deps.ts": "7970f66a52a9fa0cef607cb7ef0171212af2ccb83e73ecfa7629aabc28a38793", "https://deno.land/x/kysely_deno_postgres@v0.4.0/mod.ts": "662438fd3909984bb8cbaf3fd44d2121e949d11301baf21d6c3f057ccf9887de", "https://deno.land/x/kysely_deno_postgres@v0.4.0/src/PostgreSQLDriver.ts": "590c2fa248cff38e6e0f623050983039b5fde61e9c7131593d2922fb1f0eb921", @@ -1337,6 +1344,16 @@ "https://gitlab.com/soapbox-pub/kysely-deno-postgres/-/raw/main/mod.ts": "662438fd3909984bb8cbaf3fd44d2121e949d11301baf21d6c3f057ccf9887de", "https://gitlab.com/soapbox-pub/kysely-deno-postgres/-/raw/main/src/PostgreSQLDriver.ts": "590c2fa248cff38e6e0f623050983039b5fde61e9c7131593d2922fb1f0eb921", "https://gitlab.com/soapbox-pub/kysely-deno-postgres/-/raw/main/src/PostgreSQLDriverDatabaseConnection.ts": "2158de426860bfd4f8e73afff0289bd40a11e273c8d883d4fd6474db01a9c2a7", + "https://raw.githubusercontent.com/ocpu/question-deno/10022b8e52555335aa510adb08b0a300df3cf904/KeyCombo.ts": "a370b2dca76faa416d00e45479c8ce344971b5b86b44b4d0b213245c4bd2f8a3", + "https://raw.githubusercontent.com/ocpu/question-deno/10022b8e52555335aa510adb08b0a300df3cf904/checkbox.ts": "e337ee7396aaefe6cc8c6349a445542fe7f0760311773369c9012b3fa278d21e", + "https://raw.githubusercontent.com/ocpu/question-deno/10022b8e52555335aa510adb08b0a300df3cf904/config.ts": "a94a022c757f63ee7c410e29b97d3bfab1811889fb4483f56395cf376a911d1b", + "https://raw.githubusercontent.com/ocpu/question-deno/10022b8e52555335aa510adb08b0a300df3cf904/confirm.ts": "dde395f351e14ebff9dd882c49c1376f0c648a5978d17dfad997f9958df01d1c", + "https://raw.githubusercontent.com/ocpu/question-deno/10022b8e52555335aa510adb08b0a300df3cf904/input.ts": "5b7a3e194e6a5b74bb26d5ef1363d78619769772ad01244fd6f95b9c66cc6f0d", + "https://raw.githubusercontent.com/ocpu/question-deno/10022b8e52555335aa510adb08b0a300df3cf904/list.ts": "3b96403b043b5b5bc417d8d07b34828961c1f9d2bdbc9f24e6ef40fb9c95438e", + "https://raw.githubusercontent.com/ocpu/question-deno/10022b8e52555335aa510adb08b0a300df3cf904/mod.ts": "cb10c598652cf7edf600af17f73bcadcdedf6900d9f5b5647e89ba2ea378b7d5", + "https://raw.githubusercontent.com/ocpu/question-deno/10022b8e52555335aa510adb08b0a300df3cf904/password.ts": "3c578bd21e4fd283431aa940357f40fed2e26d3de12ad129a696d7fe38ae744d", + "https://raw.githubusercontent.com/ocpu/question-deno/10022b8e52555335aa510adb08b0a300df3cf904/text-util.ts": "37c0437d2030c0b6255f10afec7ccfcb6b195e9a0a011bb7956595142c3d7383", + "https://raw.githubusercontent.com/ocpu/question-deno/10022b8e52555335aa510adb08b0a300df3cf904/util.ts": "a8285450db7b56a3e507f478aaad68927ecb1ee545449cb869ccc4aace13fada", "https://unpkg.com/nostr-relaypool2@0.6.34/lib/nostr-relaypool.worker.js": "a336e5c58b1e6946ae8943eb4fef21b810dc2a5a233438cff92b883673e29c96" }, "workspace": { @@ -1351,6 +1368,7 @@ "jsr:@std/crypto@^0.224.0", "jsr:@std/dotenv@^0.224.0", "jsr:@std/encoding@^0.224.0", + "jsr:@std/fs@^0.229.3", "jsr:@std/json@^0.223.0", "jsr:@std/media-types@^0.224.1", "jsr:@std/streams@^0.223.0", diff --git a/scripts/setup.ts b/scripts/setup.ts new file mode 100644 index 0000000..255d8aa --- /dev/null +++ b/scripts/setup.ts @@ -0,0 +1,82 @@ +import { exists } from '@std/fs/exists'; +import { generateSecretKey, nip19 } from 'nostr-tools'; +import question from 'question-deno'; + +const vars: Record = {}; + +if (await exists('./.env')) { + const overwrite = await question('confirm', 'Overwrite existing .env file? (this is a destructive action)', false); + if (!overwrite) { + console.log('Aborted'); + Deno.exit(0); + } +} + +console.log('Generating secret key...'); +const sk = generateSecretKey(); +vars.DITTO_NSEC = nip19.nsecEncode(sk); + +const domain = await question('input', 'What is the domain of your instance? (eg ditto.pub)'); +vars.LOCAL_DOMAIN = `https://${domain}`; + +const database = await question('list', 'Which database do you want to use?', ['postgres', 'sqlite']); +if (database === 'sqlite') { + const path = await question('input', 'Path to SQLite database', 'data/db.sqlite3'); + vars.DATABASE_URL = `sqlite://${path}`; +} +if (database === 'postgres') { + const host = await question('input', 'Postgres host', 'localhost'); + const port = await question('input', 'Postgres port', '5432'); + const user = await question('input', 'Postgres user', 'ditto'); + const password = await question('input', 'Postgres password', 'ditto'); + const database = await question('input', 'Postgres database', 'ditto'); + vars.DATABASE_URL = `postgres://${user}:${password}@${host}:${port}/${database}`; +} + +vars.DITTO_UPLOADER = await question('list', 'How do you want to upload files?', [ + 'nostrbuild', + 'blossom', + 's3', + 'ipfs', + 'local', +]); + +if (vars.DITTO_UPLOADER === 'nostrbuild') { + vars.NOSTRBUILD_ENDPOINT = await question('input', 'nostr.build endpoint', 'https://nostr.build/api/v2/upload/files'); +} +if (vars.DITTO_UPLOADER === 'blossom') { + vars.BLOSSOM_SERVERS = await question('input', 'Blossom servers (comma separated)', 'https://blossom.primal.net/'); +} +if (vars.DITTO_UPLOADER === 's3') { + vars.S3_ACCESS_KEY = await question('input', 'S3 access key'); + vars.S3_SECRET_KEY = await question('input', 'S3 secret key'); + vars.S3_ENDPOINT = await question('input', 'S3 endpoint'); + vars.S3_BUCKET = await question('input', 'S3 bucket'); + vars.S3_REGION = await question('input', 'S3 region'); + vars.S3_PATH_STYLE = String(await question('confirm', 'Use path style?', false)); + const mediaDomain = await question('input', 'Media domain', `media.${domain}`); + vars.MEDIA_DOMAIN = `https://${mediaDomain}`; +} +if (vars.DITTO_UPLOADER === 'ipfs') { + vars.IPFS_API_URL = await question('input', 'IPFS API URL', 'http://localhost:5001'); + const mediaDomain = await question('input', 'Media domain', `media.${domain}`); + vars.MEDIA_DOMAIN = `https://${mediaDomain}`; +} +if (vars.DITTO_UPLOADER === 'local') { + vars.UPLOADS_DIR = await question('input', 'Local uploads directory', 'data/uploads'); + const mediaDomain = await question('input', 'Media domain', `media.${domain}`); + vars.MEDIA_DOMAIN = `https://${mediaDomain}`; +} + +console.log('Writing to .env file...'); + +const result = Object.entries(vars).reduce((acc, [key, value]) => { + if (value) { + return `${acc}${key}="${value}"\n`; + } + return acc; +}, ''); + +await Deno.writeTextFile('./.env', result); + +console.log('Done'); From 4285763c9dac0c35197091b070f0403938c009ad Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Wed, 12 Jun 2024 19:47:26 -0500 Subject: [PATCH 09/11] Add instance v2 controller --- src/app.ts | 12 +++-- src/controllers/api/instance.ts | 90 +++++++++++++++++++++++++++++++-- 2 files changed, 95 insertions(+), 7 deletions(-) diff --git a/src/app.ts b/src/app.ts index 15d4fc5..5ab31d8 100644 --- a/src/app.ts +++ b/src/app.ts @@ -6,7 +6,6 @@ import { cors, logger, serveStatic } from 'hono/middleware'; import { Conf } from '@/config.ts'; import { cron } from '@/cron.ts'; import { startFirehose } from '@/firehose.ts'; -import { Time } from '@/utils.ts'; import { accountController, @@ -42,7 +41,11 @@ import { nameRequestsController, } from '@/controllers/api/ditto.ts'; import { emptyArrayController, emptyObjectController, notImplementedController } from '@/controllers/api/fallback.ts'; -import { instanceController } from '@/controllers/api/instance.ts'; +import { + instanceDescriptionController, + instanceV1Controller, + instanceV2Controller, +} from '@/controllers/api/instance.ts'; import { markersController, updateMarkersController } from '@/controllers/api/markers.ts'; import { mediaController } from '@/controllers/api/media.ts'; import { mutesController } from '@/controllers/api/mutes.ts'; @@ -103,7 +106,6 @@ import { indexController } from '@/controllers/site.ts'; import { nodeInfoController, nodeInfoSchemaController } from '@/controllers/well-known/nodeinfo.ts'; import { nostrController } from '@/controllers/well-known/nostr.ts'; import { auth98Middleware, requireProof, requireRole } from '@/middleware/auth98Middleware.ts'; -import { cacheMiddleware } from '@/middleware/cacheMiddleware.ts'; import { cspMiddleware } from '@/middleware/cspMiddleware.ts'; import { requireSigner } from '@/middleware/requireSigner.ts'; import { signerMiddleware } from '@/middleware/signerMiddleware.ts'; @@ -164,7 +166,9 @@ app.get('/.well-known/nostr.json', nostrController); app.get('/nodeinfo/:version', nodeInfoSchemaController); -app.get('/api/v1/instance', cacheMiddleware({ cacheName: 'web', expires: Time.minutes(5) }), instanceController); +app.get('/api/v1/instance', instanceV1Controller); +app.get('/api/v2/instance', instanceV2Controller); +app.get('/api/v1/instance/extended_description', instanceDescriptionController); app.get('/api/v1/apps/verify_credentials', appCredentialsController); app.post('/api/v1/apps', createAppController); diff --git a/src/controllers/api/instance.ts b/src/controllers/api/instance.ts index 6287325..4c3e981 100644 --- a/src/controllers/api/instance.ts +++ b/src/controllers/api/instance.ts @@ -3,7 +3,9 @@ import { Conf } from '@/config.ts'; import { Storages } from '@/storages.ts'; import { getInstanceMetadata } from '@/utils/instance.ts'; -const instanceController: AppController = async (c) => { +const version = '0.0.0 (compatible; Ditto 0.0.1)'; + +const instanceV1Controller: AppController = async (c) => { const { host, protocol } = Conf.url; const meta = await getInstanceMetadata(await Storages.db(), c.req.raw.signal); @@ -54,7 +56,7 @@ const instanceController: AppController = async (c) => { urls: { streaming_api: `${wsProtocol}//${host}`, }, - version: '0.0.0 (compatible; Ditto 0.0.1)', + version, email: meta.email, nostr: { pubkey: Conf.pubkey, @@ -67,4 +69,86 @@ const instanceController: AppController = async (c) => { }); }; -export { instanceController }; +const instanceV2Controller: AppController = async (c) => { + const { host, protocol } = Conf.url; + 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`. */ + const wsProtocol = protocol === 'http:' ? 'ws:' : 'wss:'; + + return c.json({ + domain: host, + title: meta.name, + version, + source_url: 'https://gitlab.com/soapbox-pub/ditto', + description: meta.about, + usage: { + users: { + active_month: 0, + }, + }, + thumbnail: { + url: meta.picture, + blurhash: '', + versions: { + '@1x': meta.picture, + '@2x': meta.picture, + }, + }, + languages: [ + 'en', + ], + configuration: { + urls: { + streaming: `${wsProtocol}//${host}`, + }, + vapid: { + public_key: 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=', + }, + accounts: { + max_featured_tags: 10, + max_pinned_statuses: 5, + }, + statuses: { + max_characters: Conf.postCharLimit, + max_media_attachments: 4, + characters_reserved_per_url: 23, + }, + media_attachments: { + supported_mime_types: [], + image_size_limit: 16777216, + image_matrix_limit: 33177600, + video_size_limit: 103809024, + video_frame_rate_limit: 120, + video_matrix_limit: 8294400, + }, + polls: { + max_options: 4, + max_characters_per_option: 50, + min_expiration: 300, + max_expiration: 2629746, + }, + translation: { + enabled: false, + }, + }, + registrations: { + enabled: false, + approval_required: false, + message: null, + url: null, + }, + rules: [], + }); +}; + +const instanceDescriptionController: AppController = async (c) => { + const meta = await getInstanceMetadata(await Storages.db(), c.req.raw.signal); + + return c.json({ + content: meta.about, + updated_at: new Date((meta.event?.created_at ?? 0) * 1000).toISOString(), + }); +}; + +export { instanceDescriptionController, instanceV1Controller, instanceV2Controller }; From e96cb3ed40138982f0470f78b953da8f727a4bfb Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Wed, 12 Jun 2024 21:10:56 -0500 Subject: [PATCH 10/11] Update ditto.conf, remove ipfs.service --- installation/ditto.conf | 72 ++++++++++++--------------------------- installation/ipfs.service | 13 ------- 2 files changed, 22 insertions(+), 63 deletions(-) delete mode 100644 installation/ipfs.service diff --git a/installation/ditto.conf b/installation/ditto.conf index afdf65c..773573d 100644 --- a/installation/ditto.conf +++ b/installation/ditto.conf @@ -1,4 +1,4 @@ -# Nginx configuration for Ditto with IPFS. +# Nginx configuration for Ditto. # # Edit this file to change occurences of "example.com" to your own domain. @@ -6,10 +6,6 @@ upstream ditto { server 127.0.0.1:4036; } -upstream ipfs_gateway { - server 127.0.0.1:8080; -} - server { listen 80; listen [::]:80; @@ -18,21 +14,8 @@ server { } server { - # Uncomment these lines once you acquire a certificate: - # listen 443 ssl http2; - # listen [::]:443 ssl http2; server_name example.com; - ssl_protocols TLSv1.2 TLSv1.3; - ssl_ciphers HIGH:!MEDIUM:!LOW:!aNULL:!NULL:!SHA; - ssl_prefer_server_ciphers on; - ssl_session_cache shared:SSL:10m; - ssl_session_tickets off; - - # Uncomment these lines once you acquire a certificate: - # ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; - # ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; - keepalive_timeout 70; sendfile on; client_max_body_size 100m; @@ -44,53 +27,42 @@ server { proxy_set_header Host $http_host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + root /opt/ditto/public; + + location @spa { + try_files /index.html /dev/null; + } + + location @frontend { + try_files $uri @ditto-static; + } + + location @ditto-static { + root /opt/ditto/static; + try_files $uri @spa; + } + location /packs { add_header Cache-Control "public, max-age=31536000, immutable"; add_header Strict-Transport-Security "max-age=31536000" always; root /opt/ditto/public; } - location ~ ^/(instance|sw.js$|sw.js.map$) { + location ~ ^/(instance|sw\.js$|sw\.js\.map$) { root /opt/ditto/public; + try_files $uri =404; } - location /images { + location = /favicon.ico { root /opt/ditto/static; + try_files $uri =404; } - location / { + location ~ ^/(api|relay|oauth|manifest.json|nodeinfo|.well-known/(nodeinfo|nostr.json)) { proxy_pass http://ditto; } -} - -server { - # Uncomment these lines once you acquire a certificate: - # listen 443 ssl http2; - # listen [::]:443 ssl http2; - server_name media.example.com; - - ssl_protocols TLSv1.2 TLSv1.3; - ssl_ciphers HIGH:!MEDIUM:!LOW:!aNULL:!NULL:!SHA; - ssl_prefer_server_ciphers on; - ssl_session_cache shared:SSL:10m; - ssl_session_tickets off; - - # Uncomment these lines once you acquire a certificate: - # ssl_certificate /etc/letsencrypt/live/media.example.com/fullchain.pem; - # ssl_certificate_key /etc/letsencrypt/live/media.example.com/privkey.pem; - - keepalive_timeout 70; - sendfile on; - client_max_body_size 1m; - ignore_invalid_headers off; - - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; - proxy_set_header Host $http_host; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; location / { - proxy_pass http://ipfs_gateway; + try_files /dev/null @frontend; } } diff --git a/installation/ipfs.service b/installation/ipfs.service deleted file mode 100644 index e345097..0000000 --- a/installation/ipfs.service +++ /dev/null @@ -1,13 +0,0 @@ -[Unit] -Description=IPFS Daemon -Wants=network-online.target -After=network-online.target - -[Service] -Type=simple -User=ditto -ExecStart=/usr/local/bin/ipfs daemon -Restart=on-failure - -[Install] -WantedBy=multi-user.target \ No newline at end of file From f1bce3d701c5c60c090fc4ea24a0e64b87334595 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Wed, 12 Jun 2024 21:12:58 -0500 Subject: [PATCH 11/11] Add a CHANGELOG --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..04774f2 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,12 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +- Initial release + +[unreleased]: https://gitlab.com/soapbox-pub/ditto/-/compare/main...HEAD