diff --git a/README.md b/README.md index 5e5ab4e..36bccb7 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ Bringing the BBS back. These are in no particular order. Pull requests updating this section welcome for feature discussions. -- [ ] should everything be an event in a room? +- [ ] should everything be an event in a zone? - [X] get a first-pass podman/docker setup up - [X] sign up - [X] check for logged in user session @@ -21,7 +21,7 @@ feature discussions. - [X] logout button - [ ] profile editing - [X] avatar uploads -- [X] chat rooms +- [X] chat zones - [X] chat messages - [ ] chat message actions - [X] '...' button to show actions @@ -66,6 +66,7 @@ feature discussions. - [ ] hide control - [ ] inline image support - [X] inline images + - [ ] fullscreen images on click to show largest version - [ ] hide control - [ ] custom emoji support - [ ] upload custom gif emoji @@ -75,7 +76,7 @@ feature discussions. - [ ] if web notifications are enabled, emit on events - [ ] ability to mute - [ ] users - - [ ] rooms + - [ ] zones - [ ] tags (#tags?) - [ ] admin panel - [ ] add invite code generation diff --git a/models/event.ts b/models/event.ts index c7f8165..42c9fe9 100644 --- a/models/event.ts +++ b/models/event.ts @@ -13,8 +13,8 @@ import { FSDB_INDEXER_SYMLINKS } from '@andyburke/fsdb/indexers'; * * @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 + * @property {string} [parent_id] - optional parent event id * @property {string[]} [tags] - optional event tags * @property {Record} [data] - optional data payload of the event * @property {TIMESTAMPS} timestamps - timestamps that will be set by the server @@ -23,6 +23,7 @@ export type EVENT = { id: string; creator_id: string; type: string; + parent_id?: string; tags?: string[]; data?: Record; timestamps: { @@ -31,16 +32,16 @@ export type EVENT = { }; }; -type ROOM_EVENT_CACHE_ENTRY = { +type ZONE_EVENT_CACHE_ENTRY = { collection: FSDB_COLLECTION; eviction_timeout: number; }; -const ROOM_EVENTS: Record = {}; -export function get_events_collection_for_room(room_id: string): FSDB_COLLECTION { - ROOM_EVENTS[room_id] = ROOM_EVENTS[room_id] ?? { +const ZONE_EVENTS: Record = {}; +export function get_events_collection_for_zone(zone_id: string): FSDB_COLLECTION { + ZONE_EVENTS[zone_id] = ZONE_EVENTS[zone_id] ?? { collection: new FSDB_COLLECTION({ - name: `rooms/${room_id.slice(0, 14)}/${room_id.slice(0, 34)}/${room_id}/events`, + name: `zones/${zone_id.slice(0, 14)}/${zone_id.slice(0, 34)}/${zone_id}/events`, id_field: 'id', organize: by_lurid, indexers: { @@ -64,22 +65,22 @@ export function get_events_collection_for_room(room_id: string): FSDB_COLLECTION eviction_timeout: 0 }; - if (ROOM_EVENTS[room_id].eviction_timeout) { - clearTimeout(ROOM_EVENTS[room_id].eviction_timeout); + if (ZONE_EVENTS[zone_id].eviction_timeout) { + clearTimeout(ZONE_EVENTS[zone_id].eviction_timeout); } - ROOM_EVENTS[room_id].eviction_timeout = setTimeout(() => { - delete ROOM_EVENTS[room_id]; + ZONE_EVENTS[zone_id].eviction_timeout = setTimeout(() => { + delete ZONE_EVENTS[zone_id]; }, 60_000 * 5); - return ROOM_EVENTS[room_id].collection; + return ZONE_EVENTS[zone_id].collection; } -export function clear_room_events_cache() { - for (const [room_id, cached] of Object.entries(ROOM_EVENTS)) { +export function clear_zone_events_cache() { + for (const [zone_id, cached] of Object.entries(ZONE_EVENTS)) { if (cached.eviction_timeout) { clearTimeout(cached.eviction_timeout); } - delete ROOM_EVENTS[room_id]; + delete ZONE_EVENTS[zone_id]; } } diff --git a/models/room.ts b/models/zone.ts similarity index 60% rename from models/room.ts rename to models/zone.ts index 2d1db71..36ae5f9 100644 --- a/models/room.ts +++ b/models/zone.ts @@ -3,29 +3,29 @@ import { FSDB_COLLECTION } from '@andyburke/fsdb'; import { FSDB_INDEXER_SYMLINKS } from '@andyburke/fsdb/indexers'; /** - * @typedef {object} ROOM_PERMISSIONS - * @property {string[]} read a list of user_ids with read permission for the room - * @property {string[]} write a list of user_ids with write permission for the room - * @property {string[]} read_events a list of user_ids with read_events permission for this room - * @property {string[]} write_events a list of user_ids with write_events permission for this room + * @typedef {object} ZONE_PERMISSIONS + * @property {string[]} read a list of user_ids with read permission for the zone + * @property {string[]} write a list of user_ids with write permission for the zone + * @property {string[]} read_events a list of user_ids with read_events permission for this zone + * @property {string[]} write_events a list of user_ids with write_events permission for this zone */ /** - * ROOM + * ZONE * * @property {string} id - lurid (stable) * @property {string} name - channel name (max 64 characters, unique, unstable) - * @property {string} creator_id - user id of the room creator - * @property {ROOM_PERMISSIONS} permissions - permissions setup for the room - * @property {string} [icon_url] - optional url for room icon - * @property {string} [topic] - optional topic for the room - * @property {string} [rules] - optional room rules (Markdown/text) - * @property {string[]} [tags] - optional tags for the room - * @property {Record} [meta] - optional metadata about the room + * @property {string} creator_id - user id of the zone creator + * @property {ZONE_PERMISSIONS} permissions - permissions setup for the zone + * @property {string} [icon_url] - optional url for zone icon + * @property {string} [topic] - optional topic for the zone + * @property {string} [rules] - optional zone rules (Markdown/text) + * @property {string[]} [tags] - optional tags for the zone + * @property {Record} [meta] - optional metadata about the zone * @property {Record} [emojis] - optional emojis table, eg: { 'rofl': 🤣, 'blap': 'https://somewhere.someplace/image.jpg' } */ -export type ROOM = { +export type ZONE = { id: string; name: string; creator_id: string; @@ -48,37 +48,37 @@ export type ROOM = { }; }; -export const ROOMS = new FSDB_COLLECTION({ - name: 'rooms', +export const ZONES = new FSDB_COLLECTION({ + name: 'zones', id_field: 'id', organize: by_lurid, indexers: { - creator_id: new FSDB_INDEXER_SYMLINKS({ + creator_id: new FSDB_INDEXER_SYMLINKS({ name: 'creator_id', field: 'creator_id', to_many: true, organize: by_lurid }), - name: new FSDB_INDEXER_SYMLINKS({ + name: new FSDB_INDEXER_SYMLINKS({ name: 'name', - get_values_to_index: (room) => [room.name.toLowerCase()], + get_values_to_index: (zone) => [zone.name.toLowerCase()], organize: by_character }), - tags: new FSDB_INDEXER_SYMLINKS({ + tags: new FSDB_INDEXER_SYMLINKS({ name: 'tags', - get_values_to_index: (room): string[] => { - return (room.tags ?? []).map((tag) => tag.toLowerCase()); + get_values_to_index: (zone): string[] => { + return (zone.tags ?? []).map((tag) => tag.toLowerCase()); }, to_many: true, organize: by_character }), - topic: new FSDB_INDEXER_SYMLINKS({ + topic: new FSDB_INDEXER_SYMLINKS({ name: 'topic', - get_values_to_index: (room): string[] => { - return (room.topic ?? '').split(/\W/); + get_values_to_index: (zone): string[] => { + return (zone.topic ?? '').split(/\W/); }, to_many: true, organize: by_character diff --git a/public/api/rooms/:room_id/README.md b/public/api/rooms/:room_id/README.md deleted file mode 100644 index d265c79..0000000 --- a/public/api/rooms/:room_id/README.md +++ /dev/null @@ -1,23 +0,0 @@ -# /api/rooms/:room_id - -Interact with a specific room. - -## GET /api/rooms/:room_id - -Get the room specified by `:room_id`. - -## PUT /api/rooms/:room_id - -Update the rooms specified by `:room_id`. - -Eg: - -``` -{ - name?: string; -} -``` - -## DELETE /api/rooms/:room_id - -Delete the room specified by `:room_id`. diff --git a/public/api/rooms/:room_id/events/README.md b/public/api/rooms/:room_id/events/README.md deleted file mode 100644 index ac03a50..0000000 --- a/public/api/rooms/:room_id/events/README.md +++ /dev/null @@ -1,15 +0,0 @@ -# /api/rooms/:room_id/events - -Interact with a events for a room. - -## GET /api/rooms/:room_id/events - -Get events for the given room. - -## PUT /api/rooms/:room_id/events/:event_id - -Update an event. - -## DELETE /api/rooms/:room_id/events/:event_id - -Delete an event. diff --git a/public/api/rooms/README.md b/public/api/rooms/README.md deleted file mode 100644 index c46829a..0000000 --- a/public/api/rooms/README.md +++ /dev/null @@ -1,28 +0,0 @@ -# /api/rooms - -Interact with rooms. - -## POST /api/rooms - -Create a new room. - -``` -export type ROOM = { - id: string; // unique id for this room - name: string; // the name of the room (max 128 characters) - icon_url?: string; // optional url for a room icon - topic?: string; // optional room topic - tags: string[]; // a list of tags for the room - meta: Record; - limits: { - users: number; - user_messages_per_minute: number; - }; - creator_id: string; // user_id of the room creator - emojis: Record; // either: string: emoji eg: { 'rofl: 🤣, ... } or { 'rofl': 🤣, 'blap': 'https://somewhere.someplace/image.jpg' } -}; -``` - -## GET /api/rooms - -Get rooms. diff --git a/public/api/users/index.ts b/public/api/users/index.ts index a8439e1..fcc43f7 100644 --- a/public/api/users/index.ts +++ b/public/api/users/index.ts @@ -13,7 +13,7 @@ const DEFAULT_USER_PERMISSIONS: string[] = [ 'files.write.own', 'self.read', 'self.write', - 'rooms.read', + 'zones.read', 'users.read' ]; diff --git a/public/api/zones/:zone_id/README.md b/public/api/zones/:zone_id/README.md new file mode 100644 index 0000000..3027438 --- /dev/null +++ b/public/api/zones/:zone_id/README.md @@ -0,0 +1,23 @@ +# /api/zones/:zone_id + +Interact with a specific zone. + +## GET /api/zones/:zone_id + +Get the zone specified by `:zone_id`. + +## PUT /api/zones/:zone_id + +Update the zones specified by `:zone_id`. + +Eg: + +``` +{ + name?: string; +} +``` + +## DELETE /api/zones/:zone_id + +Delete the zone specified by `:zone_id`. diff --git a/public/api/rooms/:room_id/events/:event_id/README.md b/public/api/zones/:zone_id/events/:event_id/README.md similarity index 100% rename from public/api/rooms/:room_id/events/:event_id/README.md rename to public/api/zones/:zone_id/events/:event_id/README.md diff --git a/public/api/rooms/:room_id/events/:event_id/index.ts b/public/api/zones/:zone_id/events/:event_id/index.ts similarity index 65% rename from public/api/rooms/:room_id/events/:event_id/index.ts rename to public/api/zones/:zone_id/events/:event_id/index.ts index 827175a..f6693d8 100644 --- a/public/api/rooms/:room_id/events/:event_id/index.ts +++ b/public/api/zones/:zone_id/events/:event_id/index.ts @@ -1,36 +1,36 @@ 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 { EVENT, get_events_collection_for_zone } from '../../../../../../models/event.ts'; +import { ZONE, ZONES } from '../../../../../../models/zone.ts'; import parse_body from '../../../../../../utils/bodyparser.ts'; import * as CANNED_RESPONSES from '../../../../../../utils/canned_responses.ts'; import { get_session, get_user, PRECHECK_TABLE, require_user } from '../../../../../../utils/prechecks.ts'; export const PRECHECKS: PRECHECK_TABLE = {}; -// GET /api/rooms/:room_id/events/:id - Get an event +// GET /api/zones/:zone_id/events/:id - Get an event PRECHECKS.GET = [get_session, get_user, require_user, async (_req: Request, meta: Record): Promise => { - const room_id: string = meta.params?.room_id?.toLowerCase().trim() ?? ''; + const zone_id: string = meta.params?.zone_id?.toLowerCase().trim() ?? ''; // lurid is 49 chars as we use them, eg: "also-play-flow-want-form-wide-thus-work-burn-same" - const room: ROOM | null = room_id.length === 49 ? await ROOMS.get(room_id) : null; + const zone: ZONE | null = zone_id.length === 49 ? await ZONES.get(zone_id) : null; - if (!room) { + if (!zone) { return CANNED_RESPONSES.not_found(); } - meta.room = room; - const room_is_public = room.permissions.read.length === 0; - const user_has_read_for_room = room_is_public || room.permissions.read.includes(meta.user.id); - const room_has_public_events = user_has_read_for_room && (room.permissions.read_events.length === 0); - const user_has_read_events_for_room = user_has_read_for_room && - (room_has_public_events || room.permissions.read_events.includes(meta.user.id)); + meta.zone = zone; + const zone_is_public = zone.permissions.read.length === 0; + const user_has_read_for_zone = zone_is_public || zone.permissions.read.includes(meta.user.id); + const zone_has_public_events = user_has_read_for_zone && (zone.permissions.read_events.length === 0); + const user_has_read_events_for_zone = user_has_read_for_zone && + (zone_has_public_events || zone.permissions.read_events.includes(meta.user.id)); - if (!user_has_read_events_for_room) { + if (!user_has_read_events_for_zone) { return CANNED_RESPONSES.permission_denied(); } }]; export async function GET(_req: Request, meta: Record): Promise { - const events: FSDB_COLLECTION = get_events_collection_for_room(meta.room.id); + const events: FSDB_COLLECTION = get_events_collection_for_zone(meta.zone.id); const event: EVENT | null = await events.get(meta.params.event_id); if (!event) { @@ -42,7 +42,7 @@ export async function GET(_req: Request, meta: Record): Promise): Promise => { - const room_id: string = meta.params?.room_id?.toLowerCase().trim() ?? ''; + const zone_id: string = meta.params?.zone_id?.toLowerCase().trim() ?? ''; // lurid is 49 chars as we use them, eg: "also-play-flow-want-form-wide-thus-work-burn-same" - const room: ROOM | null = room_id.length === 49 ? await ROOMS.get(room_id) : null; + const zone: ZONE | null = zone_id.length === 49 ? await ZONES.get(zone_id) : null; - if (!room) { + if (!zone) { return CANNED_RESPONSES.not_found(); } - meta.room = room; - const room_is_public: boolean = meta.room.permissions.read.length === 0; - const user_has_read_for_room = room_is_public || meta.room.permissions.read.includes(meta.user.id); - const room_events_are_publicly_writable = meta.room.permissions.write_events.length === 0; - const user_has_write_events_for_room = user_has_read_for_room && - (room_events_are_publicly_writable || meta.room.permissions.write_events.includes(meta.user.id)); + meta.zone = zone; + const zone_is_public: boolean = meta.zone.permissions.read.length === 0; + const user_has_read_for_zone = zone_is_public || meta.zone.permissions.read.includes(meta.user.id); + const zone_events_are_publicly_writable = meta.zone.permissions.write_events.length === 0; + const user_has_write_events_for_zone = user_has_read_for_zone && + (zone_events_are_publicly_writable || meta.zone.permissions.write_events.includes(meta.user.id)); - if (!user_has_write_events_for_room) { + if (!user_has_write_events_for_zone) { return CANNED_RESPONSES.permission_denied(); } } @@ -78,7 +78,7 @@ export async function PUT(req: Request, meta: Record): Promise = get_events_collection_for_room(meta.room.id); + const events: FSDB_COLLECTION = get_events_collection_for_zone(meta.zone.id); const event: EVENT | null = await events.get(meta.params.event_id); if (!event) { @@ -117,7 +117,7 @@ export async function PUT(req: Request, meta: Record): Promise): Promise => { - const room_id: string = meta.params?.room_id?.toLowerCase().trim() ?? ''; + const zone_id: string = meta.params?.zone_id?.toLowerCase().trim() ?? ''; // lurid is 49 chars as we use them, eg: "also-play-flow-want-form-wide-thus-work-burn-same" - const room: ROOM | null = room_id.length === 49 ? await ROOMS.get(room_id) : null; + const zone: ZONE | null = zone_id.length === 49 ? await ZONES.get(zone_id) : null; - if (!room) { + if (!zone) { return CANNED_RESPONSES.not_found(); } - meta.room = room; - const room_is_public: boolean = meta.room.permissions.read.length === 0; - const user_has_read_for_room = room_is_public || meta.room.permissions.read.includes(meta.user.id); - const room_events_are_publicly_writable = meta.room.permissions.write_events.length === 0; - const user_has_write_events_for_room = user_has_read_for_room && - (room_events_are_publicly_writable || meta.room.permissions.write_events.includes(meta.user.id)); + meta.zone = zone; + const zone_is_public: boolean = meta.zone.permissions.read.length === 0; + const user_has_read_for_zone = zone_is_public || meta.zone.permissions.read.includes(meta.user.id); + const zone_events_are_publicly_writable = meta.zone.permissions.write_events.length === 0; + const user_has_write_events_for_zone = user_has_read_for_zone && + (zone_events_are_publicly_writable || meta.zone.permissions.write_events.includes(meta.user.id)); - if (!user_has_write_events_for_room) { + if (!user_has_write_events_for_zone) { return CANNED_RESPONSES.permission_denied(); } } ]; export async function DELETE(_req: Request, meta: Record): Promise { - const events: FSDB_COLLECTION = get_events_collection_for_room(meta.room.id); + const events: FSDB_COLLECTION = get_events_collection_for_zone(meta.zone.id); const event: EVENT | null = await events.get(meta.params.event_id); if (!event) { return CANNED_RESPONSES.not_found(); diff --git a/public/api/zones/:zone_id/events/README.md b/public/api/zones/:zone_id/events/README.md new file mode 100644 index 0000000..af072f7 --- /dev/null +++ b/public/api/zones/:zone_id/events/README.md @@ -0,0 +1,15 @@ +# /api/zones/:zone_id/events + +Interact with a events for a zone. + +## GET /api/zones/:zone_id/events + +Get events for the given zone. + +## PUT /api/zones/:zone_id/events/:event_id + +Update an event. + +## DELETE /api/zones/:zone_id/events/:event_id + +Delete an event. diff --git a/public/api/rooms/:room_id/events/index.ts b/public/api/zones/:zone_id/events/index.ts similarity index 77% rename from public/api/rooms/:room_id/events/index.ts rename to public/api/zones/:zone_id/events/index.ts index be82b48..4597b0a 100644 --- a/public/api/rooms/:room_id/events/index.ts +++ b/public/api/zones/:zone_id/events/index.ts @@ -1,40 +1,40 @@ import lurid from '@andyburke/lurid'; import { get_session, get_user, PRECHECK_TABLE, require_user } from '../../../../../utils/prechecks.ts'; -import { ROOM, ROOMS } from '../../../../../models/room.ts'; +import { ZONE, ZONES } from '../../../../../models/zone.ts'; import * as CANNED_RESPONSES from '../../../../../utils/canned_responses.ts'; -import { EVENT, get_events_collection_for_room } from '../../../../../models/event.ts'; +import { EVENT, get_events_collection_for_zone } from '../../../../../models/event.ts'; import parse_body from '../../../../../utils/bodyparser.ts'; import { FSDB_COLLECTION, FSDB_SEARCH_OPTIONS, WALK_ENTRY } from '@andyburke/fsdb'; import * as path from '@std/path'; export const PRECHECKS: PRECHECK_TABLE = {}; -// GET /api/rooms/:room_id/events - get room events +// GET /api/zones/:zone_id/events - get zone events // query parameters: // partial_id: the partial id subset you would like to match (remember, lurids are lexigraphically sorted) PRECHECKS.GET = [get_session, get_user, require_user, async (_req: Request, meta: Record): Promise => { - const room_id: string = meta.params?.room_id?.toLowerCase().trim() ?? ''; + const zone_id: string = meta.params?.zone_id?.toLowerCase().trim() ?? ''; // lurid is 49 chars as we use them, eg: "also-play-flow-want-form-wide-thus-work-burn-same" - const room: ROOM | null = room_id.length === 49 ? await ROOMS.get(room_id) : null; + const zone: ZONE | null = zone_id.length === 49 ? await ZONES.get(zone_id) : null; - if (!room) { + if (!zone) { return CANNED_RESPONSES.not_found(); } - meta.room = room; - const room_is_public: boolean = meta.room.permissions.read.length === 0; - const user_has_read_for_room = room_is_public || meta.room.permissions.read.includes(meta.user.id); - const room_events_are_public = meta.room.permissions.read_events.length === 0; - const user_has_read_events_for_room = user_has_read_for_room && - (room_events_are_public || meta.room.permissions.read_events.includes(meta.user.id)); + meta.zone = zone; + const zone_is_public: boolean = meta.zone.permissions.read.length === 0; + const user_has_read_for_zone = zone_is_public || meta.zone.permissions.read.includes(meta.user.id); + const zone_events_are_public = meta.zone.permissions.read_events.length === 0; + const user_has_read_events_for_zone = user_has_read_for_zone && + (zone_events_are_public || meta.zone.permissions.read_events.includes(meta.user.id)); - if (!user_has_read_events_for_room) { + if (!user_has_read_events_for_zone) { return CANNED_RESPONSES.permission_denied(); } }]; export async function GET(request: Request, meta: Record): Promise { - const events: FSDB_COLLECTION = get_events_collection_for_room(meta.room.id); + const events: FSDB_COLLECTION = get_events_collection_for_zone(meta.zone.id); const sorts = events.sorts; const sort_name: string = meta.query.sort ?? 'newest'; @@ -126,31 +126,31 @@ export async function GET(request: Request, meta: Record): Promise< }); } -// POST /api/rooms/:room_id/events - Create an event +// POST /api/zones/:zone_id/events - Create an event PRECHECKS.POST = [get_session, get_user, require_user, async (_req: Request, meta: Record): Promise => { - const room_id: string = meta.params?.room_id?.toLowerCase().trim() ?? ''; + const zone_id: string = meta.params?.zone_id?.toLowerCase().trim() ?? ''; // lurid is 49 chars as we use them, eg: "also-play-flow-want-form-wide-thus-work-burn-same" - const room: ROOM | null = room_id.length === 49 ? await ROOMS.get(room_id) : null; + const zone: ZONE | null = zone_id.length === 49 ? await ZONES.get(zone_id) : null; - if (!room) { + if (!zone) { return CANNED_RESPONSES.not_found(); } - meta.room = room; - const room_is_public: boolean = meta.room.permissions.read.length === 0; - const user_has_read_for_room = room_is_public || meta.room.permissions.read.includes(meta.user.id); - const room_events_are_publicly_writable = meta.room.permissions.write_events.length === 0; - const user_has_write_events_for_room = user_has_read_for_room && - (room_events_are_publicly_writable || meta.room.permissions.write_events.includes(meta.user.id)); + meta.zone = zone; + const zone_is_public: boolean = meta.zone.permissions.read.length === 0; + const user_has_read_for_zone = zone_is_public || meta.zone.permissions.read.includes(meta.user.id); + const zone_events_are_publicly_writable = meta.zone.permissions.write_events.length === 0; + const user_has_write_events_for_zone = user_has_read_for_zone && + (zone_events_are_publicly_writable || meta.zone.permissions.write_events.includes(meta.user.id)); - if (!user_has_write_events_for_room) { + if (!user_has_write_events_for_zone) { return CANNED_RESPONSES.permission_denied(); } }]; export async function POST(req: Request, meta: Record): Promise { try { - const events: FSDB_COLLECTION = get_events_collection_for_room(meta.room.id); + const events: FSDB_COLLECTION = get_events_collection_for_zone(meta.zone.id); const now = new Date().toISOString(); diff --git a/public/api/rooms/:room_id/index.ts b/public/api/zones/:zone_id/index.ts similarity index 63% rename from public/api/rooms/:room_id/index.ts rename to public/api/zones/:zone_id/index.ts index 94a65a2..9708110 100644 --- a/public/api/rooms/:room_id/index.ts +++ b/public/api/zones/:zone_id/index.ts @@ -1,50 +1,50 @@ import { get_session, get_user, PRECHECK_TABLE, require_user } from '../../../../utils/prechecks.ts'; import parse_body from '../../../../utils/bodyparser.ts'; import * as CANNED_RESPONSES from '../../../../utils/canned_responses.ts'; -import { ROOM, ROOMS } from '../../../../models/room.ts'; +import { ZONE, ZONES } from '../../../../models/zone.ts'; export const PRECHECKS: PRECHECK_TABLE = {}; -// GET /api/rooms/:id - Get a room +// GET /api/zones/:id - Get a zone PRECHECKS.GET = [get_session, get_user, require_user, async (_req: Request, meta: Record): Promise => { - const room_id: string = meta.params?.room_id?.toLowerCase().trim() ?? ''; + const zone_id: string = meta.params?.zone_id?.toLowerCase().trim() ?? ''; // lurid is 49 chars as we use them, eg: "also-play-flow-want-form-wide-thus-work-burn-same" - const room: ROOM | null = room_id.length === 49 ? await ROOMS.get(room_id) : null; + const zone: ZONE | null = zone_id.length === 49 ? await ZONES.get(zone_id) : null; - if (!room) { + if (!zone) { return CANNED_RESPONSES.not_found(); } - meta.room = room; - const room_is_public = room.permissions.read.length === 0; - const user_has_read_for_room = room_is_public || room.permissions.read.includes(meta.user.id); + meta.zone = zone; + const zone_is_public = zone.permissions.read.length === 0; + const user_has_read_for_zone = zone_is_public || zone.permissions.read.includes(meta.user.id); - if (!user_has_read_for_room) { + if (!user_has_read_for_zone) { return CANNED_RESPONSES.permission_denied(); } }]; export function GET(_req: Request, meta: Record): Response { - return Response.json(meta.room, { + return Response.json(meta.zone, { status: 200 }); } -// PUT /api/rooms/:id - Update room +// PUT /api/zones/:id - Update zone PRECHECKS.PUT = [get_session, get_user, require_user, async (_req: Request, meta: Record): Promise => { - const room_id: string = meta.params?.room_id?.toLowerCase().trim() ?? ''; + const zone_id: string = meta.params?.zone_id?.toLowerCase().trim() ?? ''; // lurid is 49 chars as we use them, eg: "also-play-flow-want-form-wide-thus-work-burn-same" - const room: ROOM | null = room_id.length === 49 ? await ROOMS.get(room_id) : null; + const zone: ZONE | null = zone_id.length === 49 ? await ZONES.get(zone_id) : null; - if (!room) { + if (!zone) { return CANNED_RESPONSES.not_found(); } - meta.room = room; - const user_has_write_for_room = room.permissions.write.includes(meta.user.id); + meta.zone = zone; + const user_has_write_for_zone = zone.permissions.write.includes(meta.user.id); - if (!user_has_write_for_room) { + if (!user_has_write_for_zone) { return CANNED_RESPONSES.permission_denied(); } }]; @@ -54,16 +54,16 @@ export async function PUT(req: Request, meta: Record): Promise): Promise): Promise => { - const room_id: string = meta.params?.room_id?.toLowerCase().trim() ?? ''; + const zone_id: string = meta.params?.zone_id?.toLowerCase().trim() ?? ''; // lurid is 49 chars as we use them, eg: "also-play-flow-want-form-wide-thus-work-burn-same" - const room: ROOM | null = room_id.length === 49 ? await ROOMS.get(room_id) : null; + const zone: ZONE | null = zone_id.length === 49 ? await ZONES.get(zone_id) : null; - if (!room) { + if (!zone) { return CANNED_RESPONSES.not_found(); } - meta.room = room; - const user_has_write_for_room = room.permissions.write.includes(meta.user.id); + meta.zone = zone; + const user_has_write_for_zone = zone.permissions.write.includes(meta.user.id); - if (!user_has_write_for_room) { + if (!user_has_write_for_zone) { return CANNED_RESPONSES.permission_denied(); } } ]; export async function DELETE(_req: Request, meta: Record): Promise { - await ROOMS.delete(meta.room); + await ZONES.delete(meta.zone); return Response.json({ deleted: true diff --git a/public/api/zones/README.md b/public/api/zones/README.md new file mode 100644 index 0000000..72509d7 --- /dev/null +++ b/public/api/zones/README.md @@ -0,0 +1,28 @@ +# /api/zones + +Interact with zones. + +## POST /api/zones + +Create a new zone. + +``` +export type ZONE = { + id: string; // unique id for this zone + name: string; // the name of the zone (max 128 characters) + icon_url?: string; // optional url for a zone icon + topic?: string; // optional zone topic + tags: string[]; // a list of tags for the zone + meta: Record; + limits: { + users: number; + user_messages_per_minute: number; + }; + creator_id: string; // user_id of the zone creator + emojis: Record; // either: string: emoji eg: { 'rofl: 🤣, ... } or { 'rofl': 🤣, 'blap': 'https://somewhere.someplace/image.jpg' } +}; +``` + +## GET /api/zones + +Get zones. diff --git a/public/api/rooms/index.ts b/public/api/zones/index.ts similarity index 70% rename from public/api/rooms/index.ts rename to public/api/zones/index.ts index 2366ab0..34aa8f7 100644 --- a/public/api/rooms/index.ts +++ b/public/api/zones/index.ts @@ -3,40 +3,40 @@ import parse_body from '../../../utils/bodyparser.ts'; import { get_session, get_user, require_user } from '../../../utils/prechecks.ts'; import * as CANNED_RESPONSES from '../../../utils/canned_responses.ts'; import { PRECHECK_TABLE } from '../../../utils/prechecks.ts'; -import { ROOM, ROOMS } from '../../../models/room.ts'; +import { ZONE, ZONES } from '../../../models/zone.ts'; import { WALK_ENTRY } from '@andyburke/fsdb'; export const PRECHECKS: PRECHECK_TABLE = {}; -// GET /api/rooms - get rooms +// GET /api/zones - get zones PRECHECKS.GET = [get_session, get_user, require_user, (_req: Request, meta: Record): Response | undefined => { - const can_read_rooms = meta.user.permissions.includes('rooms.read'); + const can_read_zones = meta.user.permissions.includes('zones.read'); - if (!can_read_rooms) { + if (!can_read_zones) { return CANNED_RESPONSES.permission_denied(); } }]; export async function GET(_req: Request, meta: Record): Promise { const limit = Math.min(parseInt(meta.query.limit ?? '100'), 100); - const rooms = (await ROOMS.all({ + const zones = (await ZONES.all({ limit, - filter: (entry: WALK_ENTRY) => { - // we push our event collections into the rooms, and fsdb + filter: (entry: WALK_ENTRY) => { + // we push our event collections into the zones, and fsdb // doesn't yet filter that out in its all() logic return entry.path.indexOf('/events/') === -1; } - })).map((room_entry) => room_entry.load()); + })).map((zone_entry) => zone_entry.load()); - return Response.json(rooms, { + return Response.json(zones, { status: 200 }); } -// POST /api/rooms - Create a room +// POST /api/zones - Create a zone PRECHECKS.POST = [get_session, get_user, require_user, (_req: Request, meta: Record): Response | undefined => { - const can_create_rooms = meta.user.permissions.includes('rooms.create'); + const can_create_zones = meta.user.permissions.includes('zones.create'); - if (!can_create_rooms) { + if (!can_create_zones) { return CANNED_RESPONSES.permission_denied(); } }]; @@ -49,8 +49,8 @@ export async function POST(req: Request, meta: Record): Promise): Promise 64) { return Response.json({ error: { - cause: 'invalid_room_name', - message: 'Room names must be 64 characters or fewer.' + cause: 'invalid_zone_name', + message: 'zone names must be 64 characters or fewer.' } }, { status: 400 @@ -70,21 +70,21 @@ export async function POST(req: Request, meta: Record): Promise): Promise li.room a:before { +.zone-list > li.zone a:before { position: absolute; left: -1.75rem; top: 0; @@ -18,7 +18,7 @@ color: var(--text); } -.room-list > li.room a { +.zone-list > li.zone a { position: relative; display: block; width: 100%; @@ -30,7 +30,7 @@ text-decoration: none; } -.room-list > li.room.active a { +.zone-list > li.zone.active a { color: var(--accent); } @@ -56,11 +56,11 @@ line-height: 2rem; } -#chat #room-chat-container { +#chat #zone-chat-container { position: relative; } -#chat #room-chat-content { +#chat #zone-chat-content { overflow-y: scroll; position: absolute; top: 0; @@ -70,7 +70,7 @@ padding: 0.5rem; } -#chat #room-chat-entry-container { +#chat #zone-chat-entry-container { position: absolute; bottom: 0; left: 0; @@ -79,7 +79,7 @@ padding: 1rem; } -#chat #room-chat-entry-container form { +#chat #zone-chat-entry-container form { position: absolute; top: 0; left: 0; @@ -90,13 +90,13 @@ padding: 0.75rem; } -#chat #room-chat-entry-container form input[type="file"] { +#chat #zone-chat-entry-container form input[type="file"] { opacity: 0; display: none; } -#chat #room-chat-entry-container form button, -#chat #room-chat-entry-container form label { +#chat #zone-chat-entry-container form button, +#chat #zone-chat-entry-container form label { position: relative; top: inherit; font-size: inherit; @@ -110,7 +110,7 @@ border: 1px solid var(--text); } -#chat #room-chat-entry-container form textarea { +#chat #zone-chat-entry-container form textarea { width: 100%; flex-grow: 1; background: inherit; @@ -163,27 +163,43 @@ #chat .message-container .message-actions-container { position: absolute; - top: 0.5rem; - right: 0.5rem; - display: none; /* flex; */ - flex-direction: row; + top: 0.1rem; + right: 0.1rem; + display: flex; + flex-direction: row-reverse; + border: 1px solid; + border-color: rgba(0, 0, 0, 0); + border-radius: var(--border-radius); + color: rgb(from var(--text) r g b / 0.7); + z-index: 10; +} + +#chat .message-container .message-actions-container:has(input[type="checkbox"]:checked) { + background: rgb(from var(--bg) r g b / 0.9); + border-color: var(--border-subtle); + z-index: 11; } #chat .message-container .message-actions-container .message-action { + border: none; opacity: 0; transition: 0.2s ease-in-out; width: 0; - height: 4rem; + align-content: center; + height: 3.5rem; overflow: hidden; - order: -1; cursor: pointer; - margin-right: 1.25rem; - padding-top: 0.25rem; + margin-right: 0; + padding: 0.25rem 0 0 0; text-align: center; + text-wrap: nowrap; } #chat .message-container .message-actions-container label { cursor: pointer; + width: 2rem; + text-align: right; + padding-top: 0.5rem; } #chat .message-container .message-actions-container input[type="checkbox"] { @@ -197,7 +213,8 @@ input[type="checkbox"]:checked ~ .message-action { opacity: 1; - width: 4rem; + width: 3.25rem; + margin-right: 1.25rem; } #chat .message-container .message-actions-container .message-action .action-name { @@ -356,7 +373,7 @@ rotate: 180deg; } - #chat #room-chat-container { + #chat #zone-chat-container { position: absolute; top: 0; left: 0; @@ -364,13 +381,13 @@ bottom: 0; } - #chat #room-chat-container #room-chat-entry-container, - #chat #room-chat-container #room-chat-entry-container form { + #chat #zone-chat-container #zone-chat-entry-container, + #chat #zone-chat-container #zone-chat-entry-container form { padding: 0.25rem; } - #chat #room-chat-container #room-chat-entry-container form button, - #chat #room-chat-container #room-chat-entry-container form label { + #chat #zone-chat-container #zone-chat-entry-container form button, + #chat #zone-chat-container #zone-chat-entry-container form label { margin: 0 0.5rem; } diff --git a/public/tabs/chat/chat.html b/public/tabs/chat/chat.html index 62c73d1..a408f4a 100644 --- a/public/tabs/chat/chat.html +++ b/public/tabs/chat/chat.html @@ -22,21 +22,21 @@ -
+