1+ using AttribDoc . Attributes ;
2+ using Bunkum . Core ;
3+ using Bunkum . Core . Endpoints ;
4+ using Bunkum . Core . Responses ;
5+ using Bunkum . Listener . Protocol ;
6+ using Refresh . Core . Configuration ;
7+ using Refresh . Core . Types . Categories ;
8+ using Refresh . Core . Types . Data ;
9+ using Refresh . Database ;
10+ using Refresh . Database . Models . Authentication ;
11+ using Refresh . Database . Models . Levels ;
12+ using Refresh . Database . Models . Users ;
13+ using Refresh . Database . Query ;
14+ using Refresh . Interfaces . APIv3 . Documentation . Attributes ;
15+ using Refresh . Interfaces . APIv3 . Endpoints . ApiTypes ;
16+ using Refresh . Interfaces . APIv3 . Endpoints . ApiTypes . Errors ;
17+ using Refresh . Interfaces . APIv3 . Endpoints . DataTypes . Response . Categories ;
18+ using Refresh . Interfaces . APIv3 . Endpoints . DataTypes . Response . Levels ;
19+ using Refresh . Interfaces . APIv3 . Endpoints . DataTypes . Response . Users ;
20+ using Refresh . Interfaces . APIv3 . Extensions ;
21+
22+ namespace Refresh . Interfaces . APIv3 . Endpoints ;
23+
24+ public class CategoryApiEndpoints : EndpointGroup
25+ {
26+ [ ApiV3Endpoint ( "levels" ) , Authentication ( false ) ]
27+ [ ClientCacheResponse ( 1800 ) ] // cache for half an hour
28+ [ DocSummary ( "Retrieves a list of categories you can use to search levels" ) ]
29+ [ DocQueryParam ( "includePreviews" , "If true, a single level will be added to each category representing a level from that category. False by default." ) ]
30+ [ DocError ( typeof ( ApiValidationError ) , "The boolean 'includePreviews' could not be parsed by the server." ) ]
31+ public ApiListResponse < ApiLevelCategoryResponse > GetLevelCategories ( RequestContext context , CategoryService categories ,
32+ DataContext dataContext )
33+ {
34+ bool result = bool . TryParse ( context . QueryString . Get ( "includePreviews" ) ?? "false" , out bool includePreviews ) ;
35+ if ( ! result ) return ApiValidationError . BooleanParseError ;
36+
37+ IEnumerable < ApiLevelCategoryResponse > resp ;
38+
39+ // ReSharper disable once ConvertIfStatementToConditionalTernaryExpression
40+ if ( includePreviews ) resp = ApiLevelCategoryResponse . FromOldList ( categories . LevelCategories , context , dataContext ) ;
41+ else resp = ApiLevelCategoryResponse . FromOldList ( categories . LevelCategories , dataContext ) ;
42+
43+ return new ApiListResponse < ApiLevelCategoryResponse > ( resp ) ;
44+ }
45+
46+ [ ApiV3Endpoint ( "levels/{route}" ) , Authentication ( false ) ]
47+ [ DocSummary ( "Retrieves a list of levels from a category" ) ]
48+ [ DocError ( typeof ( ApiNotFoundError ) , "The level category cannot be found" ) ]
49+ [ DocUsesPageData ]
50+ [ DocQueryParam ( "game" , "Filters levels to a specific game version. Allowed values: lbp1-3, vita, psp, beta" ) ]
51+ [ DocQueryParam ( "seed" , "The random seed to use for randomization. Uses 0 if not specified." ) ]
52+ [ DocQueryParam ( "players" , "Filters levels to those accommodating the specified number of players." ) ]
53+ [ DocQueryParam ( "username" , "If set, certain categories like 'hearted' or 'byUser' will return the levels of " +
54+ "the user with this username instead of your own. Optional." ) ]
55+ public ApiListResponse < ApiGameLevelResponse > GetLevels ( RequestContext context , CategoryService categories , GameUser ? user ,
56+ [ DocSummary ( "The name of the category you'd like to retrieve levels from. " +
57+ "Make a request to /levels to see a list of available categories" ) ]
58+ string route , DataContext dataContext )
59+ {
60+ if ( string . IsNullOrWhiteSpace ( route ) )
61+ {
62+ return new ApiError ( "You didn't specify a route. " +
63+ "You probably meant to use the `/levels` endpoint and left a trailing slash in the URL." , NotFound ) ;
64+ }
65+
66+ ( int skip , int count ) = context . GetPageData ( ) ;
67+
68+ DatabaseList < GameLevel > ? list = categories . LevelCategories
69+ . FirstOrDefault ( c => c . ApiRoute . StartsWith ( route ) ) ?
70+ . Fetch ( context , skip , count , dataContext , new LevelFilterSettings ( context , TokenGame . Website ) , user ) ;
71+
72+ if ( list == null ) return ApiNotFoundError . Instance ;
73+
74+ DatabaseList < ApiGameLevelResponse > levels = DatabaseListExtensions . FromOldList < ApiGameLevelResponse , GameLevel > ( list , dataContext ) ;
75+ return levels ;
76+ }
77+
78+ [ ApiV3Endpoint ( "users" ) , Authentication ( false ) ]
79+ [ ClientCacheResponse ( 1800 ) ] // cache for half an hour
80+ [ DocSummary ( "Retrieves a list of categories you can use to search users. Returns an empty list if the instance doesn't allow showing online users." ) ]
81+ [ DocQueryParam ( "includePreviews" , "If true, a single user will be added to each category representing a user from that category. False by default." ) ]
82+ [ DocError ( typeof ( ApiValidationError ) , "The boolean 'includePreviews' could not be parsed by the server." ) ]
83+ public ApiListResponse < ApiUserCategoryResponse > GetUserCategories ( RequestContext context , CategoryService categories ,
84+ DataContext dataContext , GameServerConfig config )
85+ {
86+ bool result = bool . TryParse ( context . QueryString . Get ( "includePreviews" ) ?? "false" , out bool includePreviews ) ;
87+ if ( ! result ) return ApiValidationError . BooleanParseError ;
88+
89+ if ( ! config . PermitShowingOnlineUsers ) return new ApiListResponse < ApiUserCategoryResponse > ( [ ] ) ;
90+ IEnumerable < ApiUserCategoryResponse > resp ;
91+
92+ if ( includePreviews ) resp = ApiUserCategoryResponse . FromOldList ( categories . UserCategories , context , dataContext ) ;
93+ else resp = ApiUserCategoryResponse . FromOldList ( categories . UserCategories , dataContext ) ;
94+
95+ return new ApiListResponse < ApiUserCategoryResponse > ( resp ) ;
96+ }
97+
98+ [ ApiV3Endpoint ( "users/{route}" ) , Authentication ( false ) ]
99+ [ DocSummary ( "Retrieves a list of users from a category." ) ]
100+ [ DocError ( typeof ( ApiNotFoundError ) , "The user category cannot be found, or the instance does not allow showing online users." ) ]
101+ [ DocUsesPageData ]
102+ [ DocQueryParam ( "username" , "If set, certain categories like 'hearted' will return the related users of " +
103+ "the user with this username instead of your own. Optional." ) ]
104+ public Response GetUsers ( RequestContext context , CategoryService categories , GameUser ? user ,
105+ [ DocSummary ( "The name of the category you'd like to retrieve users from. " +
106+ "Make a request to /users to see a list of available categories" ) ]
107+ string route , DataContext dataContext , GameServerConfig config )
108+ {
109+ // Bunkum usually routes users/me requests to here aswell, so use this hack to serve those requests properly.
110+ if ( route == "me" )
111+ {
112+ if ( user == null ) return ApiAuthenticationError . NotAuthenticated ; // Error documented in UserApiEndpoints.GetMyUser()
113+ return new Response ( new ApiResponse < ApiExtendedGameUserResponse > ( ApiExtendedGameUserResponse . FromOld ( user , dataContext ) ! ) , ContentType . Json ) ;
114+ }
115+
116+ if ( string . IsNullOrWhiteSpace ( route ) )
117+ {
118+ return new ApiError ( "You didn't specify a route. " +
119+ "You probably meant to use the `/users` endpoint and left a trailing slash in the URL." , NotFound ) ;
120+ }
121+
122+ if ( ! config . PermitShowingOnlineUsers ) return ApiNotFoundError . Instance ;
123+ ( int skip , int count ) = context . GetPageData ( ) ;
124+
125+ DatabaseList < GameUser > ? list = categories . UserCategories
126+ . FirstOrDefault ( c => c . ApiRoute . StartsWith ( route ) ) ?
127+ . Fetch ( context , skip , count , dataContext , user ) ;
128+
129+ if ( list == null ) return ApiNotFoundError . Instance ;
130+
131+ ApiListResponse < ApiGameUserResponse > users = DatabaseListExtensions . FromOldList < ApiGameUserResponse , GameUser > ( list , dataContext ) ;
132+ return new Response ( users , ContentType . Json ) ;
133+ }
134+ }
0 commit comments