feature: events polling
This commit is contained in:
parent
61a51017a3
commit
b700251278
19 changed files with 353 additions and 77 deletions
21
.zed/settings.json
Normal file
21
.zed/settings.json
Normal file
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"lsp": {
|
||||
"deno": {
|
||||
"settings": {
|
||||
"deno": {
|
||||
"enable": true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"languages": {
|
||||
"TypeScript": {
|
||||
"language_servers": ["deno", "!typescript-language-server", "!vtsls", "!eslint"],
|
||||
"formatter": "language_server"
|
||||
},
|
||||
"TSX": {
|
||||
"language_servers": ["deno", "!typescript-language-server", "!vtsls", "!eslint"],
|
||||
"formatter": "language_server"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -32,13 +32,13 @@
|
|||
}
|
||||
},
|
||||
"imports": {
|
||||
"@andyburke/fsdb": "jsr:@andyburke/fsdb@^0.6.1",
|
||||
"@andyburke/fsdb": "jsr:@andyburke/fsdb@^0.9.0",
|
||||
"@andyburke/lurid": "jsr:@andyburke/lurid@^0.2.0",
|
||||
"@andyburke/serverus": "jsr:@andyburke/serverus@^0.7.1",
|
||||
"@std/assert": "jsr:@std/assert@^1.0.13",
|
||||
"@std/encoding": "jsr:@std/encoding@^1.0.10",
|
||||
"@std/http": "jsr:@std/http@^1.0.18",
|
||||
"@std/path": "jsr:@std/path@^1.1.0",
|
||||
"@std/http": "jsr:@std/http@^1.0.19",
|
||||
"@std/path": "jsr:@std/path@^1.1.1",
|
||||
"@stdext/crypto": "jsr:@stdext/crypto@^0.1.0"
|
||||
}
|
||||
}
|
||||
|
|
63
deno.lock
generated
63
deno.lock
generated
|
@ -1,9 +1,11 @@
|
|||
{
|
||||
"version": "5",
|
||||
"specifiers": {
|
||||
"jsr:@andyburke/fsdb@~0.6.1": "0.6.1",
|
||||
"jsr:@andyburke/fsdb@*": "0.9.0",
|
||||
"jsr:@andyburke/fsdb@0.9": "0.9.0",
|
||||
"jsr:@andyburke/lurid@*": "0.2.0",
|
||||
"jsr:@andyburke/lurid@0.2": "0.2.0",
|
||||
"jsr:@andyburke/serverus@*": "0.7.1",
|
||||
"jsr:@andyburke/serverus@~0.7.1": "0.7.1",
|
||||
"jsr:@std/assert@*": "1.0.13",
|
||||
"jsr:@std/assert@^1.0.13": "1.0.13",
|
||||
|
@ -15,24 +17,29 @@
|
|||
"jsr:@std/encoding@^1.0.10": "1.0.10",
|
||||
"jsr:@std/fmt@^1.0.6": "1.0.8",
|
||||
"jsr:@std/fmt@^1.0.8": "1.0.8",
|
||||
"jsr:@std/fs@^1.0.14": "1.0.18",
|
||||
"jsr:@std/fs@^1.0.18": "1.0.18",
|
||||
"jsr:@std/fs@^1.0.14": "1.0.19",
|
||||
"jsr:@std/fs@^1.0.18": "1.0.19",
|
||||
"jsr:@std/fs@^1.0.19": "1.0.19",
|
||||
"jsr:@std/html@^1.0.4": "1.0.4",
|
||||
"jsr:@std/http@*": "1.0.18",
|
||||
"jsr:@std/http@^1.0.13": "1.0.18",
|
||||
"jsr:@std/http@^1.0.13": "1.0.19",
|
||||
"jsr:@std/http@^1.0.18": "1.0.18",
|
||||
"jsr:@std/internal@^1.0.6": "1.0.8",
|
||||
"jsr:@std/http@^1.0.19": "1.0.19",
|
||||
"jsr:@std/internal@^1.0.6": "1.0.9",
|
||||
"jsr:@std/internal@^1.0.9": "1.0.9",
|
||||
"jsr:@std/media-types@^1.1.0": "1.1.0",
|
||||
"jsr:@std/net@^1.0.4": "1.0.4",
|
||||
"jsr:@std/path@^1.0.8": "1.1.0",
|
||||
"jsr:@std/path@^1.1.0": "1.1.0",
|
||||
"jsr:@std/path@*": "1.1.1",
|
||||
"jsr:@std/path@^1.0.8": "1.1.1",
|
||||
"jsr:@std/path@^1.1.0": "1.1.1",
|
||||
"jsr:@std/path@^1.1.1": "1.1.1",
|
||||
"jsr:@std/streams@^1.0.10": "1.0.10",
|
||||
"jsr:@stdext/crypto@*": "0.1.0",
|
||||
"jsr:@stdext/crypto@0.1": "0.1.0"
|
||||
},
|
||||
"jsr": {
|
||||
"@andyburke/fsdb@0.6.1": {
|
||||
"integrity": "059ad6702e40a39a188e648a8ebf2547087782becae040af916aa843830328ea",
|
||||
"@andyburke/fsdb@0.9.0": {
|
||||
"integrity": "726c138ac8b751c969cb045d9d1fc3541a054130aa741d256864b376afdb8f89",
|
||||
"dependencies": [
|
||||
"jsr:@std/cli@^1.0.20",
|
||||
"jsr:@std/fs@^1.0.18",
|
||||
|
@ -60,7 +67,7 @@
|
|||
"@std/assert@1.0.13": {
|
||||
"integrity": "ae0d31e41919b12c656c742b22522c32fb26ed0cba32975cb0de2a273cb68b29",
|
||||
"dependencies": [
|
||||
"jsr:@std/internal"
|
||||
"jsr:@std/internal@^1.0.6"
|
||||
]
|
||||
},
|
||||
"@std/async@1.0.13": {
|
||||
|
@ -81,6 +88,13 @@
|
|||
"jsr:@std/path@^1.1.0"
|
||||
]
|
||||
},
|
||||
"@std/fs@1.0.19": {
|
||||
"integrity": "051968c2b1eae4d2ea9f79a08a3845740ef6af10356aff43d3e2ef11ed09fb06",
|
||||
"dependencies": [
|
||||
"jsr:@std/internal@^1.0.9",
|
||||
"jsr:@std/path@^1.1.1"
|
||||
]
|
||||
},
|
||||
"@std/html@1.0.4": {
|
||||
"integrity": "eff3497c08164e6ada49b7f81a28b5108087033823153d065e3f89467dd3d50e"
|
||||
},
|
||||
|
@ -101,9 +115,26 @@
|
|||
"jsr:@std/streams"
|
||||
]
|
||||
},
|
||||
"@std/http@1.0.19": {
|
||||
"integrity": "52128c8d00a1f0b20019f8b72376e7ef5f3133375b6f805b5bc89b9de2ad4686",
|
||||
"dependencies": [
|
||||
"jsr:@std/cli@^1.0.20",
|
||||
"jsr:@std/encoding@^1.0.10",
|
||||
"jsr:@std/fmt@^1.0.8",
|
||||
"jsr:@std/fs@^1.0.19",
|
||||
"jsr:@std/html",
|
||||
"jsr:@std/media-types",
|
||||
"jsr:@std/net",
|
||||
"jsr:@std/path@^1.1.1",
|
||||
"jsr:@std/streams"
|
||||
]
|
||||
},
|
||||
"@std/internal@1.0.8": {
|
||||
"integrity": "fc66e846d8d38a47cffd274d80d2ca3f0de71040f855783724bb6b87f60891f5"
|
||||
},
|
||||
"@std/internal@1.0.9": {
|
||||
"integrity": "bdfb97f83e4db7a13e8faab26fb1958d1b80cc64366501af78a0aee151696eb8"
|
||||
},
|
||||
"@std/media-types@1.1.0": {
|
||||
"integrity": "c9d093f0c05c3512932b330e3cc1fe1d627b301db33a4c2c2185c02471d6eaa4"
|
||||
},
|
||||
|
@ -113,6 +144,12 @@
|
|||
"@std/path@1.1.0": {
|
||||
"integrity": "ddc94f8e3c275627281cbc23341df6b8bcc874d70374f75fec2533521e3d6886"
|
||||
},
|
||||
"@std/path@1.1.1": {
|
||||
"integrity": "fe00026bd3a7e6a27f73709b83c607798be40e20c81dde655ce34052fd82ec76",
|
||||
"dependencies": [
|
||||
"jsr:@std/internal@^1.0.9"
|
||||
]
|
||||
},
|
||||
"@std/streams@1.0.10": {
|
||||
"integrity": "75c0b1431873cd0d8b3d679015220204d36d3c7420d93b60acfc379eb0dc30af"
|
||||
},
|
||||
|
@ -165,13 +202,13 @@
|
|||
},
|
||||
"workspace": {
|
||||
"dependencies": [
|
||||
"jsr:@andyburke/fsdb@~0.6.1",
|
||||
"jsr:@andyburke/fsdb@0.9",
|
||||
"jsr:@andyburke/lurid@0.2",
|
||||
"jsr:@andyburke/serverus@~0.7.1",
|
||||
"jsr:@std/assert@^1.0.13",
|
||||
"jsr:@std/encoding@^1.0.10",
|
||||
"jsr:@std/http@^1.0.18",
|
||||
"jsr:@std/path@^1.1.0",
|
||||
"jsr:@std/http@^1.0.19",
|
||||
"jsr:@std/path@^1.1.1",
|
||||
"jsr:@stdext/crypto@0.1"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ import { FSDB_INDEXER_SYMLINKS } from 'jsr:@andyburke/fsdb/indexers';
|
|||
/**
|
||||
* Event
|
||||
*
|
||||
* @property {string} id - room_id(lurid):event_id(lurid)
|
||||
* @property {string} id - lurid
|
||||
* @property {string} creator_id - id of the source user
|
||||
* @property {string} room_id - id of the target room
|
||||
* @property {string} type - event type
|
||||
|
@ -31,13 +31,18 @@ export type EVENT = {
|
|||
};
|
||||
};
|
||||
|
||||
export const EVENTS = new FSDB_COLLECTION<EVENT>({
|
||||
name: 'events',
|
||||
type ROOM_EVENT_CACHE_ENTRY = {
|
||||
collection: FSDB_COLLECTION<EVENT>;
|
||||
eviction_timeout: number;
|
||||
};
|
||||
|
||||
const ROOM_EVENTS: Record<string, ROOM_EVENT_CACHE_ENTRY> = {};
|
||||
export function get_events_collection_for_room(room_id: string): FSDB_COLLECTION<EVENT> {
|
||||
ROOM_EVENTS[room_id] = ROOM_EVENTS[room_id] ?? {
|
||||
collection: new FSDB_COLLECTION<EVENT>({
|
||||
name: `rooms/${room_id.substring(0, 14)}/${room_id.substring(0, 36)}/${room_id}/events`,
|
||||
id_field: 'id',
|
||||
organize: (combined_id: string) => {
|
||||
const [room_id, event_id] = combined_id.split(':', 2);
|
||||
return ['rooms', room_id, event_id.substring(0, 14), `${event_id}.json`];
|
||||
},
|
||||
organize: by_lurid,
|
||||
indexers: {
|
||||
creator_id: new FSDB_INDEXER_SYMLINKS<EVENT>({
|
||||
name: 'creator_id',
|
||||
|
@ -55,4 +60,26 @@ export const EVENTS = new FSDB_COLLECTION<EVENT>({
|
|||
organize: by_character
|
||||
})
|
||||
}
|
||||
});
|
||||
}),
|
||||
eviction_timeout: 0
|
||||
};
|
||||
|
||||
if (ROOM_EVENTS[room_id].eviction_timeout) {
|
||||
clearTimeout(ROOM_EVENTS[room_id].eviction_timeout);
|
||||
}
|
||||
|
||||
ROOM_EVENTS[room_id].eviction_timeout = setTimeout(() => {
|
||||
delete ROOM_EVENTS[room_id];
|
||||
}, 60_000 * 5);
|
||||
|
||||
return ROOM_EVENTS[room_id].collection;
|
||||
}
|
||||
|
||||
export function clear_room_events_cache() {
|
||||
for (const [room_id, cached] of Object.entries(ROOM_EVENTS)) {
|
||||
if (cached.eviction_timeout) {
|
||||
clearTimeout(cached.eviction_timeout);
|
||||
}
|
||||
delete ROOM_EVENTS[room_id];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -53,7 +53,7 @@ export async function POST(req: Request, meta: Record<string, any>): Promise<Res
|
|||
let user: USER | undefined = undefined;
|
||||
user = (await USERS.find({
|
||||
username
|
||||
})).shift();
|
||||
})).shift()?.load();
|
||||
|
||||
if (!user) {
|
||||
return Response.json({
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { EVENT, EVENTS } from '../../../../../../models/event.ts';
|
||||
import { FSDB_COLLECTION } from '@andyburke/fsdb';
|
||||
import { EVENT, get_events_collection_for_room } from '../../../../../../models/event.ts';
|
||||
import { ROOM, ROOMS } from '../../../../../../models/room.ts';
|
||||
import parse_body from '../../../../../../utils/bodyparser.ts';
|
||||
import * as CANNED_RESPONSES from '../../../../../../utils/canned_responses.ts';
|
||||
|
@ -29,7 +30,8 @@ PRECHECKS.GET = [get_session, get_user, require_user, async (_req: Request, meta
|
|||
}
|
||||
}];
|
||||
export async function GET(_req: Request, meta: Record<string, any>): Promise<Response> {
|
||||
const event: EVENT | null = await EVENTS.get(meta.params.event_id);
|
||||
const events: FSDB_COLLECTION<EVENT> = get_events_collection_for_room(meta.room.id);
|
||||
const event: EVENT | null = await events.get(meta.params.event_id);
|
||||
|
||||
if (!event) {
|
||||
return CANNED_RESPONSES.not_found();
|
||||
|
@ -76,7 +78,8 @@ export async function PUT(req: Request, meta: Record<string, any>): Promise<Resp
|
|||
const now = new Date().toISOString();
|
||||
|
||||
try {
|
||||
const event: EVENT | null = await EVENTS.get(meta.params.event_id);
|
||||
const events: FSDB_COLLECTION<EVENT> = get_events_collection_for_room(meta.room.id);
|
||||
const event: EVENT | null = await events.get(meta.params.event_id);
|
||||
|
||||
if (!event) {
|
||||
return CANNED_RESPONSES.not_found();
|
||||
|
@ -98,7 +101,7 @@ export async function PUT(req: Request, meta: Record<string, any>): Promise<Resp
|
|||
}
|
||||
};
|
||||
|
||||
await EVENTS.update(updated);
|
||||
await events.update(updated);
|
||||
return Response.json(updated, {
|
||||
status: 200
|
||||
});
|
||||
|
@ -147,12 +150,13 @@ PRECHECKS.DELETE = [
|
|||
}
|
||||
];
|
||||
export async function DELETE(_req: Request, meta: Record<string, any>): Promise<Response> {
|
||||
const event: EVENT | null = await EVENTS.get(meta.params.event_id);
|
||||
const events: FSDB_COLLECTION<EVENT> = get_events_collection_for_room(meta.room.id);
|
||||
const event: EVENT | null = await events.get(meta.params.event_id);
|
||||
if (!event) {
|
||||
return CANNED_RESPONSES.not_found();
|
||||
}
|
||||
|
||||
await EVENTS.delete(event);
|
||||
await events.delete(event);
|
||||
|
||||
return Response.json({
|
||||
deleted: true
|
||||
|
|
|
@ -2,8 +2,10 @@ import lurid from 'jsr:@andyburke/lurid';
|
|||
import { get_session, get_user, PRECHECK_TABLE, require_user } from '../../../../../utils/prechecks.ts';
|
||||
import { ROOM, ROOMS } from '../../../../../models/room.ts';
|
||||
import * as CANNED_RESPONSES from '../../../../../utils/canned_responses.ts';
|
||||
import { EVENT, EVENTS } from '../../../../../models/event.ts';
|
||||
import { EVENT, get_events_collection_for_room } from '../../../../../models/event.ts';
|
||||
import parse_body from '../../../../../utils/bodyparser.ts';
|
||||
import { FSDB_COLLECTION, FSDB_SEARCH_OPTIONS, WALK_ENTRY } from 'jsr:@andyburke/fsdb';
|
||||
import * as path from 'jsr:@std/path';
|
||||
|
||||
export const PRECHECKS: PRECHECK_TABLE = {};
|
||||
|
||||
|
@ -31,30 +33,80 @@ PRECHECKS.GET = [get_session, get_user, require_user, async (_req: Request, meta
|
|||
return CANNED_RESPONSES.permission_denied();
|
||||
}
|
||||
}];
|
||||
export async function GET(_req: Request, meta: Record<string, any>): Promise<Response> {
|
||||
const query: URLSearchParams = meta.query;
|
||||
const partial_id: string | undefined = query.get('partial_id')?.toLowerCase().trim();
|
||||
export async function GET(request: Request, meta: Record<string, any>): Promise<Response> {
|
||||
const events: FSDB_COLLECTION<EVENT> = get_events_collection_for_room(meta.room.id);
|
||||
|
||||
const has_partial_id = typeof partial_id === 'string' && partial_id.length >= 2;
|
||||
if (!has_partial_id) {
|
||||
const sorts = events.sorts;
|
||||
const sort_name: string = meta.query.sort ?? 'newest';
|
||||
const key = sort_name as keyof typeof sorts;
|
||||
const sort: any = sorts[key];
|
||||
if (!sort) {
|
||||
return Response.json({
|
||||
error: {
|
||||
message: 'You must specify a `partial_id` query parameter.',
|
||||
cause: 'missing_query_parameter'
|
||||
message: 'You must specify a sort: newest, oldest, latest, stalest',
|
||||
cause: 'invalid_sort'
|
||||
}
|
||||
}, {
|
||||
status: 400
|
||||
});
|
||||
}
|
||||
|
||||
const limit = Math.min(parseInt(query.get('limit') ?? '10'), 100);
|
||||
const events = await EVENTS.all({
|
||||
id_after: partial_id,
|
||||
limit
|
||||
});
|
||||
const options: FSDB_SEARCH_OPTIONS<EVENT> = {
|
||||
...(meta.query ?? {}),
|
||||
limit: Math.min(parseInt(meta.query?.limit ?? '10'), 1_000),
|
||||
sort,
|
||||
filter: (entry: WALK_ENTRY<EVENT>) => {
|
||||
const event_id = path.basename(entry.path).replace(/\.json$/i, '');
|
||||
|
||||
return Response.json(events, {
|
||||
status: 200
|
||||
if (meta.query.after_id && event_id <= meta.query.after_id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (meta.query.before_id && event_id >= meta.query.before_id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
const headers = {
|
||||
'Cache-Control': 'no-cache, must-revalidate'
|
||||
};
|
||||
|
||||
const results = (await events.all(options)).map((entry) => entry.load());
|
||||
|
||||
// long-polling support
|
||||
if (results.length === 0 && meta.query.wait) {
|
||||
return new Promise((resolve) => {
|
||||
function on_create(create_event: any) {
|
||||
results.push(create_event.item);
|
||||
clearTimeout(timeout);
|
||||
events.off('create', on_create);
|
||||
|
||||
return resolve(Response.json(results, {
|
||||
status: 200,
|
||||
headers
|
||||
}));
|
||||
}
|
||||
|
||||
const timeout = setTimeout(() => {
|
||||
events.off('create', on_create);
|
||||
return resolve(Response.json(results, {
|
||||
status: 200,
|
||||
headers
|
||||
}));
|
||||
}, 60_000); // 60 seconds
|
||||
events.on('create', on_create);
|
||||
request.signal.addEventListener('abort', () => {
|
||||
events.off('create', on_create);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return Response.json(results, {
|
||||
status: 200,
|
||||
headers
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -82,12 +134,15 @@ PRECHECKS.POST = [get_session, get_user, require_user, async (_req: Request, met
|
|||
}];
|
||||
export async function POST(req: Request, meta: Record<string, any>): Promise<Response> {
|
||||
try {
|
||||
const events: FSDB_COLLECTION<EVENT> = get_events_collection_for_room(meta.room.id);
|
||||
|
||||
const now = new Date().toISOString();
|
||||
|
||||
const body = await parse_body(req);
|
||||
const new_event: EVENT = {
|
||||
const event: EVENT = {
|
||||
type: 'unknown',
|
||||
...body,
|
||||
id: `${meta.params.room_id}:${lurid()}`,
|
||||
id: lurid(),
|
||||
creator_id: meta.user.id,
|
||||
timestamps: {
|
||||
created: now,
|
||||
|
@ -95,9 +150,9 @@ export async function POST(req: Request, meta: Record<string, any>): Promise<Res
|
|||
}
|
||||
};
|
||||
|
||||
await EVENTS.create(new_event);
|
||||
await events.create(event);
|
||||
|
||||
return Response.json(new_event, {
|
||||
return Response.json(event, {
|
||||
status: 201
|
||||
});
|
||||
} catch (error) {
|
||||
|
|
|
@ -134,11 +134,11 @@ export async function DELETE(_req: Request, meta: Record<string, any>): Promise<
|
|||
await PASSWORD_ENTRIES.delete(password_entry);
|
||||
}
|
||||
|
||||
const sessions = await SESSIONS.find({
|
||||
const session_entries = await SESSIONS.find({
|
||||
user_id
|
||||
});
|
||||
for (const session of sessions) {
|
||||
await SESSIONS.delete(session);
|
||||
for (const entry of session_entries) {
|
||||
await SESSIONS.delete(entry.load());
|
||||
}
|
||||
|
||||
await USERS.delete(user);
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
/* Dark mode default */
|
||||
:root {
|
||||
--bg: #121212;
|
||||
--text: #f0f0f0;
|
||||
--accent: #4caf50;
|
||||
--bg: #323232;
|
||||
--text: #efe;
|
||||
--accent: #fa0 ;
|
||||
--border-subtle: #555;
|
||||
--border-normal: #888;
|
||||
--border-highlight: #bbb;
|
||||
|
@ -13,7 +13,7 @@
|
|||
:root {
|
||||
--bg: #f0f0f0;
|
||||
--text: #121212;
|
||||
--accent: #4caf50;
|
||||
--accent: #c80;
|
||||
--border-subtle: #bbb;
|
||||
--border-normal: #888;
|
||||
--border-highlight: #555;
|
||||
|
@ -135,9 +135,8 @@ button {
|
|||
background: inherit;
|
||||
color: inherit;
|
||||
padding: 0.5rem;
|
||||
margin: 0 1rem;
|
||||
border: 1px solid var(--text);
|
||||
border-radius: 10%;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
button.primary {
|
||||
|
|
|
@ -150,7 +150,6 @@
|
|||
document.body.dataset.user = JSON.stringify(response.user);
|
||||
document.body.dataset.perms =
|
||||
response.user.permissions.join(":");
|
||||
console.dir({ response });
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -90,7 +90,7 @@
|
|||
|
||||
.tab-switch:checked + .tab-label {
|
||||
margin-top: 1px;
|
||||
border-bottom: 1px solid var(--border-highlight);
|
||||
border-bottom: 1px solid var(--accent);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
|
|
|
@ -71,6 +71,7 @@
|
|||
#talk #room-chat-entry-container form button {
|
||||
width: inherit;
|
||||
padding: inherit;
|
||||
margin: 0 1rem;
|
||||
}
|
||||
|
||||
#talk #room-chat-entry-container form textarea {
|
||||
|
|
|
@ -2,6 +2,7 @@ import { api, API_CLIENT } from '../../../utils/api.ts';
|
|||
import * as asserts from 'jsr:@std/assert';
|
||||
import { EPHEMERAL_SERVER, get_ephemeral_listen_server, get_new_user, set_user_permissions } from '../../helpers.ts';
|
||||
import { generateTotp } from '@stdext/crypto/totp';
|
||||
import { clear_room_events_cache } from '../../../models/event.ts';
|
||||
|
||||
Deno.test({
|
||||
name: 'API - ROOMS - Create',
|
||||
|
@ -72,6 +73,7 @@ Deno.test({
|
|||
|
||||
asserts.assert(new_room);
|
||||
} finally {
|
||||
clear_room_events_cache();
|
||||
if (test_server_info) {
|
||||
await test_server_info?.server?.stop();
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ import { api, API_CLIENT } from '../../../utils/api.ts';
|
|||
import * as asserts from 'jsr:@std/assert';
|
||||
import { EPHEMERAL_SERVER, get_ephemeral_listen_server, get_new_user, set_user_permissions } from '../../helpers.ts';
|
||||
import { generateTotp } from '@stdext/crypto/totp';
|
||||
import { clear_room_events_cache } from '../../../models/event.ts';
|
||||
|
||||
Deno.test({
|
||||
name: 'API - ROOMS - Delete',
|
||||
|
@ -48,6 +49,7 @@ Deno.test({
|
|||
|
||||
asserts.assert(deleted_room);
|
||||
} finally {
|
||||
clear_room_events_cache();
|
||||
if (test_server_info) {
|
||||
await test_server_info?.server?.stop();
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ import * as asserts from 'jsr:@std/assert';
|
|||
import { EPHEMERAL_SERVER, get_ephemeral_listen_server, get_new_user, set_user_permissions } from '../../../helpers.ts';
|
||||
import { api, API_CLIENT } from '../../../../utils/api.ts';
|
||||
import { generateTotp } from '@stdext/crypto/totp';
|
||||
import { clear_room_events_cache } from '../../../../models/event.ts';
|
||||
|
||||
Deno.test({
|
||||
name: 'API - ROOMS - EVENTS - Create',
|
||||
|
@ -113,6 +114,7 @@ Deno.test({
|
|||
|
||||
asserts.assert(event_from_other_user);
|
||||
} finally {
|
||||
clear_room_events_cache();
|
||||
if (test_server_info) {
|
||||
await test_server_info?.server?.stop();
|
||||
}
|
||||
|
|
121
tests/api/rooms/events/get_events.test.ts
Normal file
121
tests/api/rooms/events/get_events.test.ts
Normal file
|
@ -0,0 +1,121 @@
|
|||
import * as asserts from 'jsr:@std/assert';
|
||||
import { EPHEMERAL_SERVER, get_ephemeral_listen_server, get_new_user, set_user_permissions } from '../../../helpers.ts';
|
||||
import { api, API_CLIENT } from '../../../../utils/api.ts';
|
||||
import { generateTotp } from '@stdext/crypto/totp';
|
||||
import { clear_room_events_cache } from '../../../../models/event.ts';
|
||||
|
||||
Deno.test({
|
||||
name: 'API - ROOMS - EVENTS - Get',
|
||||
permissions: {
|
||||
env: true,
|
||||
read: true,
|
||||
write: true,
|
||||
net: true
|
||||
},
|
||||
fn: async () => {
|
||||
let test_server_info: EPHEMERAL_SERVER | null = null;
|
||||
try {
|
||||
test_server_info = await get_ephemeral_listen_server();
|
||||
const client: API_CLIENT = api({
|
||||
prefix: '/api',
|
||||
hostname: test_server_info.hostname,
|
||||
port: test_server_info.port
|
||||
});
|
||||
|
||||
const owner_info = await get_new_user(client);
|
||||
|
||||
await set_user_permissions(client, owner_info.user, owner_info.session, [...owner_info.user.permissions, 'rooms.create']);
|
||||
|
||||
const room = await client.fetch('/rooms', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'x-session_id': owner_info.session.id,
|
||||
'x-totp': await generateTotp(owner_info.session.secret)
|
||||
},
|
||||
json: {
|
||||
name: 'test get events room'
|
||||
}
|
||||
});
|
||||
|
||||
asserts.assert(room);
|
||||
|
||||
const NUM_INITIAL_EVENTS = 5;
|
||||
const events_initial_batch: any[] = [];
|
||||
for (let i = 0; i < NUM_INITIAL_EVENTS; ++i) {
|
||||
const event = await client.fetch(`/rooms/${room.id}/events`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'x-session_id': owner_info.session.id,
|
||||
'x-totp': await generateTotp(owner_info.session.secret)
|
||||
},
|
||||
json: {
|
||||
type: 'test',
|
||||
data: {
|
||||
i
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
asserts.assert(event);
|
||||
events_initial_batch.push(event);
|
||||
}
|
||||
|
||||
asserts.assertEquals(events_initial_batch.length, NUM_INITIAL_EVENTS);
|
||||
|
||||
const other_user_info = await get_new_user(client);
|
||||
|
||||
const events_from_server = await client.fetch(`/rooms/${room.id}/events`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'x-session_id': other_user_info.session.id,
|
||||
'x-totp': await generateTotp(other_user_info.session.secret)
|
||||
}
|
||||
});
|
||||
|
||||
asserts.assertEquals(events_from_server.length, NUM_INITIAL_EVENTS);
|
||||
|
||||
const newest_event = events_from_server[0];
|
||||
asserts.assert(newest_event);
|
||||
|
||||
const long_poll_request_promise = client.fetch(`/rooms/${room.id}/events?wait=true&after_id=${newest_event.id}`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'x-session_id': other_user_info.session.id,
|
||||
'x-totp': await generateTotp(other_user_info.session.secret)
|
||||
}
|
||||
});
|
||||
|
||||
const wait_and_then_create_an_event = new Promise((resolve) => {
|
||||
setTimeout(async () => {
|
||||
await client.fetch(`/rooms/${room.id}/events`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'x-session_id': owner_info.session.id,
|
||||
'x-totp': await generateTotp(owner_info.session.secret)
|
||||
},
|
||||
json: {
|
||||
type: 'test',
|
||||
data: {
|
||||
i: 12345
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
resolve(undefined);
|
||||
}, 2_000);
|
||||
});
|
||||
|
||||
await Promise.all([long_poll_request_promise, wait_and_then_create_an_event]).then((values) => {
|
||||
const long_polled_events = values.shift();
|
||||
asserts.assert(Array.isArray(long_polled_events));
|
||||
asserts.assertEquals(long_polled_events.length, 1);
|
||||
asserts.assertEquals(long_polled_events[0].data?.i, 12345);
|
||||
});
|
||||
} finally {
|
||||
clear_room_events_cache();
|
||||
if (test_server_info) {
|
||||
await test_server_info?.server?.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
|
@ -2,6 +2,7 @@ import * as asserts from 'jsr:@std/assert';
|
|||
import { EPHEMERAL_SERVER, get_ephemeral_listen_server, get_new_user, set_user_permissions } from '../../../helpers.ts';
|
||||
import { api, API_CLIENT } from '../../../../utils/api.ts';
|
||||
import { generateTotp } from '@stdext/crypto/totp';
|
||||
import { clear_room_events_cache } from '../../../../models/event.ts';
|
||||
|
||||
Deno.test({
|
||||
name: 'API - ROOMS - EVENTS - Update',
|
||||
|
@ -235,6 +236,7 @@ Deno.test({
|
|||
|
||||
asserts.assertEquals(delete_owner_event_response.deleted, true);
|
||||
} finally {
|
||||
clear_room_events_cache();
|
||||
if (test_server_info) {
|
||||
await test_server_info?.server?.stop();
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ import * as asserts from 'jsr:@std/assert';
|
|||
import { EPHEMERAL_SERVER, get_ephemeral_listen_server, get_new_user, set_user_permissions } from '../../../helpers.ts';
|
||||
import { api, API_CLIENT } from '../../../../utils/api.ts';
|
||||
import { generateTotp } from '@stdext/crypto/totp';
|
||||
import { clear_room_events_cache } from '../../../../models/event.ts';
|
||||
|
||||
Deno.test({
|
||||
name: 'API - ROOMS - EVENTS - Update (APPEND_ONLY_EVENTS)',
|
||||
|
@ -159,6 +160,7 @@ Deno.test({
|
|||
} finally {
|
||||
Deno.env.delete('APPEND_ONLY_EVENTS');
|
||||
|
||||
clear_room_events_cache();
|
||||
if (test_server_info) {
|
||||
await test_server_info?.server?.stop();
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ import { api, API_CLIENT } from '../../../utils/api.ts';
|
|||
import * as asserts from 'jsr:@std/assert';
|
||||
import { EPHEMERAL_SERVER, get_ephemeral_listen_server, get_new_user, set_user_permissions } from '../../helpers.ts';
|
||||
import { generateTotp } from '@stdext/crypto/totp';
|
||||
import { clear_room_events_cache } from '../../../models/event.ts';
|
||||
|
||||
Deno.test({
|
||||
name: 'API - ROOMS - Update',
|
||||
|
@ -91,6 +92,7 @@ Deno.test({
|
|||
asserts.assertEquals(updated_by_other_user_room.topic, 'this is a newer topic');
|
||||
asserts.assertEquals(updated_by_other_user_room.permissions.write, [user_info.user.id, other_user_info.user.id]);
|
||||
} finally {
|
||||
clear_room_events_cache();
|
||||
if (test_server_info) {
|
||||
await test_server_info?.server?.stop();
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue