@@ -326,6 +326,116 @@ class ThreadService extends BaseService {
326326 res . json ( { } ) ;
327327 }
328328 } ) . attach ( router ) ;
329+
330+ Endpoint ( {
331+ route : '/read/:uid' ,
332+ methods : [ 'GET' ] ,
333+ mw : [ configurable_auth ( ) ] ,
334+ handler : async ( req , res ) => {
335+ const uid = req . params . uid ;
336+
337+ if ( ! is_valid_uuid ( uid ) ) {
338+ throw APIError . create ( 'field_invalid' , null , {
339+ key : 'uid' ,
340+ expected : 'uuid' ,
341+ got : whatis ( uid ) ,
342+ } ) ;
343+ }
344+
345+
346+ const actor = Context . get ( 'actor' ) ;
347+
348+ // Check read permission
349+ {
350+ const permission = PermissionUtil . join ( 'thread' , uid , 'read' ) ;
351+ const svc_permission = this . services . get ( 'permission' ) ;
352+ const reading = await svc_permission . scan ( actor , permission ) ;
353+ const options = PermissionUtil . reading_to_options ( reading ) ;
354+ if ( options . length <= 0 ) {
355+ throw APIError . create ( 'permission_denied' , null , {
356+ permission,
357+ } ) ;
358+ }
359+ }
360+
361+ const thread = await this . get_thread ( { uid } ) ;
362+
363+ res . json ( this . client_safe_thread ( thread ) ) ;
364+ }
365+ } ) . attach ( router ) ;
366+
367+ Endpoint ( {
368+ route : '/list/:uid/:page' ,
369+ methods : [ 'POST' ] ,
370+ mw : [ configurable_auth ( ) ] ,
371+ handler : async ( req , res ) => {
372+ const uid = req . params . uid ;
373+
374+ if ( ! is_valid_uuid ( uid ) ) {
375+ throw APIError . create ( 'field_invalid' , null , {
376+ key : 'uid' ,
377+ expected : 'uuid' ,
378+ got : whatis ( uid ) ,
379+ } ) ;
380+ }
381+
382+ const actor = Context . get ( 'actor' ) ;
383+
384+ // Check list permission
385+ {
386+ const permission = PermissionUtil . join ( 'thread' , uid , 'list' ) ;
387+ const svc_permission = this . services . get ( 'permission' ) ;
388+ const reading = await svc_permission . scan ( actor , permission ) ;
389+ const options = PermissionUtil . reading_to_options ( reading ) ;
390+ if ( options . length <= 0 ) {
391+ throw APIError . create ( 'permission_denied' , null , {
392+ permission,
393+ } ) ;
394+ }
395+ }
396+
397+ const page = Number ( req . params . page ) ;
398+ const validate_positive_integer = ( key , value ) => {
399+ if ( whatis ( value ) !== 'number' ) {
400+ throw APIError . create ( 'field_invalid' , null , {
401+ key,
402+ expected : 'number' ,
403+ got : whatis ( value ) ,
404+ } ) ;
405+ }
406+ if ( value < 0 || ! Number . isInteger ( value ) ) {
407+ throw APIError . create ( 'field_invalid' , null , {
408+ key,
409+ expected : 'positive integer' ,
410+ got : value ,
411+ } ) ;
412+ }
413+ }
414+ validate_positive_integer ( 'page' , page ) ;
415+
416+ if ( req . body . limit !== undefined ) {
417+ validate_positive_integer ( 'limit' , req . body . limit ) ;
418+ }
419+
420+ const limit = Math . min ( 100 , req . body . limit ?? 50 ) ;
421+ const offset = page * limit ;
422+
423+ const threads = await this . db . read (
424+ "SELECT * FROM `thread` WHERE parent_uid=? LIMIT ?,?" ,
425+ [ uid , offset , limit ]
426+ ) ;
427+
428+ res . json ( threads . map ( this . client_safe_thread ) ) ;
429+ }
430+ } ) . attach ( router ) ;
431+ }
432+
433+ client_safe_thread ( thread ) {
434+ return {
435+ uid : thread . uid ,
436+ parent : thread . parent_uid ,
437+ text : thread . text ,
438+ } ;
329439 }
330440
331441 async get_thread ( { uid } ) {
0 commit comments