Merge branch 'main' into feat-zap-counter

This commit is contained in:
P. Reis 2024-06-12 23:59:50 -03:00
commit 87967e4137
13 changed files with 269 additions and 83 deletions

12
CHANGELOG.md Normal file
View File

@ -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

View File

@ -11,6 +11,7 @@
"nsec": "deno run scripts/nsec.ts", "nsec": "deno run scripts/nsec.ts",
"admin:event": "deno run -A scripts/admin-event.ts", "admin:event": "deno run -A scripts/admin-event.ts",
"admin:role": "deno run -A scripts/admin-role.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", "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" "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/crypto": "jsr:@std/crypto@^0.224.0",
"@std/dotenv": "jsr:@std/dotenv@^0.224.0", "@std/dotenv": "jsr:@std/dotenv@^0.224.0",
"@std/encoding": "jsr:@std/encoding@^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/json": "jsr:@std/json@^0.223.0",
"@std/media-types": "jsr:@std/media-types@^0.224.1", "@std/media-types": "jsr:@std/media-types@^0.224.1",
"@std/streams": "jsr:@std/streams@^0.223.0", "@std/streams": "jsr:@std/streams@^0.223.0",
@ -53,6 +55,7 @@
"nostr-relaypool": "npm:nostr-relaypool2@0.6.34", "nostr-relaypool": "npm:nostr-relaypool2@0.6.34",
"nostr-tools": "npm:nostr-tools@2.5.1", "nostr-tools": "npm:nostr-tools@2.5.1",
"nostr-wasm": "npm:nostr-wasm@^0.1.0", "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", "tldts": "npm:tldts@^6.0.14",
"tseep": "npm:tseep@^1.2.1", "tseep": "npm:tseep@^1.2.1",
"type-fest": "npm:type-fest@^4.3.0", "type-fest": "npm:type-fest@^4.3.0",

View File

@ -26,6 +26,7 @@
"jsr:@std/encoding@^0.224.1": "jsr:@std/encoding@0.224.3", "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/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.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/internal@^1.0.0": "jsr:@std/internal@1.0.0",
"jsr:@std/io@^0.224": "jsr:@std/io@0.224.1", "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", "jsr:@std/media-types@^0.224.1": "jsr:@std/media-types@0.224.1",
@ -191,6 +192,9 @@
"jsr:@std/path@^0.221.0" "jsr:@std/path@^0.221.0"
] ]
}, },
"@std/fs@0.229.3": {
"integrity": "783bca21f24da92e04c3893c9e79653227ab016c48e96b3078377ebd5222e6eb"
},
"@std/internal@1.0.0": { "@std/internal@1.0.0": {
"integrity": "ac6a6dfebf838582c4b4f61a6907374e27e05bedb6ce276e0f1608fe84e7cd9a" "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/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_file_url.ts": "1cd63fd35ec8d1370feaa4752eccc4cc05ea5362a878be8dc7db733650995484",
"https://deno.land/std@0.214.0/path/windows/to_namespaced_path.ts": "4ffa4fb6fae321448d5fe810b3ca741d84df4d7897e61ee29be961a6aac89a4c", "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/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/client.ts": "ff340f58041203879972dd368b011ed130c66914f789826610869a90603406bf",
"https://deno.land/x/hono@v3.10.1/client/index.ts": "3ff4cf246f3543f827a85a2c84d66a025ac350ee927613629bda47e854bfb7ba", "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/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/index.ts": "6c986e8b91dcf857ecc8164a506ae8eea8665792a4ff7215471df669c632ae7c",
"https://deno.land/x/hono@v3.10.1/validator/validator.ts": "afa5e52495e0996fbba61996736fab5c486590d72d376f809e9f9ff4e0c463e9", "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/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/mod.ts": "662438fd3909984bb8cbaf3fd44d2121e949d11301baf21d6c3f057ccf9887de",
"https://deno.land/x/kysely_deno_postgres@v0.4.0/src/PostgreSQLDriver.ts": "590c2fa248cff38e6e0f623050983039b5fde61e9c7131593d2922fb1f0eb921", "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/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/PostgreSQLDriver.ts": "590c2fa248cff38e6e0f623050983039b5fde61e9c7131593d2922fb1f0eb921",
"https://gitlab.com/soapbox-pub/kysely-deno-postgres/-/raw/main/src/PostgreSQLDriverDatabaseConnection.ts": "2158de426860bfd4f8e73afff0289bd40a11e273c8d883d4fd6474db01a9c2a7", "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" "https://unpkg.com/nostr-relaypool2@0.6.34/lib/nostr-relaypool.worker.js": "a336e5c58b1e6946ae8943eb4fef21b810dc2a5a233438cff92b883673e29c96"
}, },
"workspace": { "workspace": {
@ -1351,6 +1368,7 @@
"jsr:@std/crypto@^0.224.0", "jsr:@std/crypto@^0.224.0",
"jsr:@std/dotenv@^0.224.0", "jsr:@std/dotenv@^0.224.0",
"jsr:@std/encoding@^0.224.0", "jsr:@std/encoding@^0.224.0",
"jsr:@std/fs@^0.229.3",
"jsr:@std/json@^0.223.0", "jsr:@std/json@^0.223.0",
"jsr:@std/media-types@^0.224.1", "jsr:@std/media-types@^0.224.1",
"jsr:@std/streams@^0.223.0", "jsr:@std/streams@^0.223.0",

View File

@ -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. # 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; server 127.0.0.1:4036;
} }
upstream ipfs_gateway {
server 127.0.0.1:8080;
}
server { server {
listen 80; listen 80;
listen [::]:80; listen [::]:80;
@ -18,21 +14,8 @@ server {
} }
server { server {
# Uncomment these lines once you acquire a certificate:
# listen 443 ssl http2;
# listen [::]:443 ssl http2;
server_name example.com; 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; keepalive_timeout 70;
sendfile on; sendfile on;
client_max_body_size 100m; client_max_body_size 100m;
@ -44,53 +27,42 @@ server {
proxy_set_header Host $http_host; proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 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 { location /packs {
add_header Cache-Control "public, max-age=31536000, immutable"; add_header Cache-Control "public, max-age=31536000, immutable";
add_header Strict-Transport-Security "max-age=31536000" always; add_header Strict-Transport-Security "max-age=31536000" always;
root /opt/ditto/public; root /opt/ditto/public;
} }
location ~ ^/(instance|sw.js$|sw.js.map$) { location ~ ^/(instance|sw\.js$|sw\.js\.map$) {
root /opt/ditto/public; root /opt/ditto/public;
try_files $uri =404;
} }
location /images { location = /favicon.ico {
root /opt/ditto/static; 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; 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 / { location / {
proxy_pass http://ipfs_gateway; try_files /dev/null @frontend;
} }
} }

View File

@ -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

82
scripts/setup.ts Normal file
View File

@ -0,0 +1,82 @@
import { exists } from '@std/fs/exists';
import { generateSecretKey, nip19 } from 'nostr-tools';
import question from 'question-deno';
const vars: Record<string, string | undefined> = {};
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');

View File

@ -6,7 +6,6 @@ import { cors, logger, serveStatic } from 'hono/middleware';
import { Conf } from '@/config.ts'; import { Conf } from '@/config.ts';
import { cron } from '@/cron.ts'; import { cron } from '@/cron.ts';
import { startFirehose } from '@/firehose.ts'; import { startFirehose } from '@/firehose.ts';
import { Time } from '@/utils.ts';
import { import {
accountController, accountController,
@ -42,7 +41,11 @@ import {
nameRequestsController, nameRequestsController,
} from '@/controllers/api/ditto.ts'; } from '@/controllers/api/ditto.ts';
import { emptyArrayController, emptyObjectController, notImplementedController } from '@/controllers/api/fallback.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 { markersController, updateMarkersController } from '@/controllers/api/markers.ts';
import { mediaController } from '@/controllers/api/media.ts'; import { mediaController } from '@/controllers/api/media.ts';
import { mutesController } from '@/controllers/api/mutes.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 { nodeInfoController, nodeInfoSchemaController } from '@/controllers/well-known/nodeinfo.ts';
import { nostrController } from '@/controllers/well-known/nostr.ts'; import { nostrController } from '@/controllers/well-known/nostr.ts';
import { auth98Middleware, requireProof, requireRole } from '@/middleware/auth98Middleware.ts'; import { auth98Middleware, requireProof, requireRole } from '@/middleware/auth98Middleware.ts';
import { cacheMiddleware } from '@/middleware/cacheMiddleware.ts';
import { cspMiddleware } from '@/middleware/cspMiddleware.ts'; import { cspMiddleware } from '@/middleware/cspMiddleware.ts';
import { requireSigner } from '@/middleware/requireSigner.ts'; import { requireSigner } from '@/middleware/requireSigner.ts';
import { signerMiddleware } from '@/middleware/signerMiddleware.ts'; import { signerMiddleware } from '@/middleware/signerMiddleware.ts';
@ -129,7 +131,7 @@ type AppContext = Context<AppEnv>;
type AppMiddleware = MiddlewareHandler<AppEnv>; type AppMiddleware = MiddlewareHandler<AppEnv>;
type AppController = Handler<AppEnv, any, HonoInput, Response | Promise<Response>>; type AppController = Handler<AppEnv, any, HonoInput, Response | Promise<Response>>;
const app = new Hono<AppEnv>(); const app = new Hono<AppEnv>({ strict: false });
const debug = Debug('ditto:http'); const debug = Debug('ditto:http');
@ -141,14 +143,12 @@ if (Conf.cronEnabled) {
} }
app.use('/api/*', logger(debug)); app.use('/api/*', logger(debug));
app.use('/relay/*', logger(debug));
app.use('/.well-known/*', logger(debug)); app.use('/.well-known/*', logger(debug));
app.use('/users/*', logger(debug)); app.use('/users/*', logger(debug));
app.use('/nodeinfo/*', logger(debug)); app.use('/nodeinfo/*', logger(debug));
app.use('/oauth/*', logger(debug)); app.use('/oauth/*', logger(debug));
app.get('/api/v1/streaming', streamingController); app.get('/api/v1/streaming', streamingController);
app.get('/api/v1/streaming/', streamingController);
app.get('/relay', relayController); app.get('/relay', relayController);
app.use( app.use(
@ -166,7 +166,9 @@ app.get('/.well-known/nostr.json', nostrController);
app.get('/nodeinfo/:version', nodeInfoSchemaController); 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.get('/api/v1/apps/verify_credentials', appCredentialsController);
app.post('/api/v1/apps', createAppController); app.post('/api/v1/apps', createAppController);
@ -297,6 +299,9 @@ app.get('/api/v1/conversations', emptyArrayController);
app.get('/api/v1/lists', emptyArrayController); app.get('/api/v1/lists', emptyArrayController);
app.use('/api/*', notImplementedController); app.use('/api/*', notImplementedController);
app.use('/.well-known/*', notImplementedController);
app.use('/nodeinfo/*', notImplementedController);
app.use('/oauth/*', notImplementedController);
const publicFiles = serveStatic({ root: './public/' }); const publicFiles = serveStatic({ root: './public/' });
const staticFiles = serveStatic({ root: './static/' }); const staticFiles = serveStatic({ root: './static/' });

View File

@ -85,7 +85,13 @@ const adminAccountsController: AppController = async (c) => {
} }
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>(events.map(({ pubkey }) => pubkey));
const pubkeys = new Set<string>(
events
.map(({ tags }) => tags.find(([name]) => name === 'd')?.[1])
.filter((pubkey): pubkey is string => !!pubkey),
);
const authors = await store.query([{ kinds: [0], authors: [...pubkeys] }]) const authors = await store.query([{ kinds: [0], authors: [...pubkeys] }])
.then((events) => hydrateEvents({ store, events, signal })); .then((events) => hydrateEvents({ store, events, signal }));
@ -100,10 +106,14 @@ 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 })
.then((events) => hydrateEvents({ store, events, signal }));
const accounts = await Promise.all(events.map(renderAdminAccount)); const accounts = await Promise.all(events.map(renderAdminAccount));
return paginated(c, events, accounts); return paginated(c, events, accounts);
}; };

View File

@ -3,7 +3,9 @@ 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 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 { 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);
@ -54,7 +56,7 @@ const instanceController: AppController = async (c) => {
urls: { urls: {
streaming_api: `${wsProtocol}//${host}`, streaming_api: `${wsProtocol}//${host}`,
}, },
version: '0.0.0 (compatible; Ditto 0.0.1)', version,
email: meta.email, email: meta.email,
nostr: { nostr: {
pubkey: Conf.pubkey, 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 };

View File

@ -122,6 +122,7 @@ const oauthController: AppController = (c) => {
return c.text('Missing `redirect_uri` query param.', 422); return c.text('Missing `redirect_uri` query param.', 422);
} }
const state = c.req.query('state');
const redirectUri = maybeDecodeUri(encodedUri); const redirectUri = maybeDecodeUri(encodedUri);
return c.html(`<!DOCTYPE html> return c.html(`<!DOCTYPE html>
@ -162,6 +163,7 @@ const oauthController: AppController = (c) => {
<form id="oauth_form" action="/oauth/authorize" method="post"> <form id="oauth_form" action="/oauth/authorize" method="post">
<input type="text" placeholder="bunker://..." name="bunker_uri" autocomplete="off" required> <input type="text" placeholder="bunker://..." name="bunker_uri" autocomplete="off" required>
<input type="hidden" name="redirect_uri" id="redirect_uri" value="${escape(redirectUri)}"> <input type="hidden" name="redirect_uri" id="redirect_uri" value="${escape(redirectUri)}">
<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>
@ -187,6 +189,7 @@ function maybeDecodeUri(uri: string): string {
const oauthAuthorizeSchema = z.object({ const oauthAuthorizeSchema = z.object({
bunker_uri: z.string().url().refine((v) => v.startsWith('bunker://')), bunker_uri: z.string().url().refine((v) => v.startsWith('bunker://')),
redirect_uri: z.string().url(), redirect_uri: z.string().url(),
state: z.string().optional(),
}); });
/** Controller the OAuth form is POSTed to. */ /** Controller the OAuth form is POSTed to. */
@ -199,7 +202,7 @@ const oauthAuthorizeController: AppController = async (c) => {
} }
// Parsed FormData values. // 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); const bunker = new URL(bunker_uri);
@ -209,17 +212,26 @@ const oauthAuthorizeController: AppController = async (c) => {
relays: bunker.searchParams.getAll('relay'), relays: bunker.searchParams.getAll('relay'),
}); });
const url = addCodeToRedirectUri(redirectUri, token); if (redirectUri === 'urn:ietf:wg:oauth:2.0:oob') {
return c.text(token);
}
const url = addCodeToRedirectUri(redirectUri, token, state);
return c.redirect(url); return c.redirect(url);
}; };
/** Append the given `code` as a query param to the `redirect_uri`. */ /** 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 url = new URL(redirectUri);
const q = new URLSearchParams(); const q = new URLSearchParams();
q.set('code', code); q.set('code', code);
if (state) {
q.set('state', state);
}
url.search = q.toString(); url.search = q.toString();
return url.toString(); return url.toString();

View File

@ -164,9 +164,9 @@ export async function getTrendingTags(store: NStore, tagName: string): Promise<T
const tags = label.tags.filter(([name]) => name === tagName); const tags = label.tags.filter(([name]) => name === tagName);
const now = new Date(); const labelDate = new Date(label.created_at * 1000);
const lastWeek = new Date(now.getTime() - Time.days(7)); const lastWeek = new Date(labelDate.getTime() - Time.days(7));
const dates = generateDateRange(lastWeek, now).reverse(); const dates = generateDateRange(lastWeek, labelDate).reverse();
return Promise.all(tags.map(async ([_, value]) => { return Promise.all(tags.map(async ([_, value]) => {
const filters = dates.map((date) => ({ const filters = dates.map((date) => ({

View File

@ -59,7 +59,7 @@ export function cron() {
Deno.cron( Deno.cron(
'update trending pubkeys', 'update trending pubkeys',
'0 * * * *', '0 * * * *',
() => updateTrendingTags('#p', 'p', [1, 6, 7, 9735], 40, Conf.relay), () => updateTrendingTags('#p', 'p', [1, 3, 6, 7, 9735], 40, Conf.relay),
); );
Deno.cron( Deno.cron(
'update trending zapped events', 'update trending zapped events',

View File

@ -3,6 +3,7 @@
import { NDatabase, NIP50, NKinds, NostrEvent, NostrFilter, NSchema as n, NStore } from '@nostrify/nostrify'; import { NDatabase, NIP50, NKinds, NostrEvent, NostrFilter, NSchema as n, NStore } from '@nostrify/nostrify';
import { Stickynotes } from '@soapbox/stickynotes'; import { Stickynotes } from '@soapbox/stickynotes';
import { Kysely } from 'kysely'; import { Kysely } from 'kysely';
import { nip27 } from 'nostr-tools';
import { Conf } from '@/config.ts'; import { Conf } from '@/config.ts';
import { DittoTables } from '@/db/DittoTables.ts'; import { DittoTables } from '@/db/DittoTables.ts';
@ -220,7 +221,7 @@ class EventsDB implements NStore {
case 0: case 0:
return EventsDB.buildUserSearchContent(event); return EventsDB.buildUserSearchContent(event);
case 1: case 1:
return event.content; return nip27.replaceAll(event.content, () => '');
case 30009: case 30009:
return EventsDB.buildTagsSearchContent(event.tags.filter(([t]) => t !== 'alt')); return EventsDB.buildTagsSearchContent(event.tags.filter(([t]) => t !== 'alt'));
case 30360: case 30360: