Merge branch 'main' into feat-zap-counter
This commit is contained in:
commit
87967e4137
|
@ -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
|
|
@ -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",
|
||||
|
|
18
deno.lock
18
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",
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
|
@ -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');
|
19
src/app.ts
19
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';
|
||||
|
@ -129,7 +131,7 @@ type AppContext = Context<AppEnv>;
|
|||
type AppMiddleware = MiddlewareHandler<AppEnv>;
|
||||
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');
|
||||
|
||||
|
@ -141,14 +143,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(
|
||||
|
@ -166,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);
|
||||
|
@ -297,6 +299,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/' });
|
||||
|
|
|
@ -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<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] }])
|
||||
.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);
|
||||
};
|
||||
|
|
|
@ -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 };
|
||||
|
|
|
@ -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(`<!DOCTYPE html>
|
||||
|
@ -162,6 +163,7 @@ const oauthController: AppController = (c) => {
|
|||
<form id="oauth_form" action="/oauth/authorize" method="post">
|
||||
<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="state" value="${escape(state ?? '')}">
|
||||
<button type="submit">Authorize</button>
|
||||
</form>
|
||||
<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({
|
||||
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);
|
||||
|
||||
|
@ -209,17 +212,26 @@ const oauthAuthorizeController: AppController = async (c) => {
|
|||
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);
|
||||
};
|
||||
|
||||
/** 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();
|
||||
|
|
|
@ -164,9 +164,9 @@ export async function getTrendingTags(store: NStore, tagName: string): Promise<T
|
|||
|
||||
const tags = label.tags.filter(([name]) => 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) => ({
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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:
|
||||
|
|
Loading…
Reference in New Issue