Coverage for an_website / endpoints / endpoints.py: 96.000%

25 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-04-24 17:35 +0000

1# This program is free software: you can redistribute it and/or modify 

2# it under the terms of the GNU Affero General Public License as 

3# published by the Free Software Foundation, either version 3 of the 

4# License, or (at your option) any later version. 

5# 

6# This program is distributed in the hope that it will be useful, 

7# but WITHOUT ANY WARRANTY; without even the implied warranty of 

8# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

9# GNU Affero General Public License for more details. 

10# 

11# You should have received a copy of the GNU Affero General Public License 

12# along with this program. If not, see <https://www.gnu.org/licenses/>. 

13 

14"""Show a list of all API endpoints.""" 

15 

16from typing import ClassVar, cast 

17 

18import orjson as json 

19 

20from .. import ORJSON_OPTIONS 

21from ..utils.request_handler import APIRequestHandler, HTMLRequestHandler 

22from ..utils.utils import ModuleInfo, Permission, name_to_id 

23 

24 

25def get_module_info() -> ModuleInfo: 

26 """Create and return the ModuleInfo for this module.""" 

27 return ModuleInfo( 

28 handlers=( 

29 ("/endpunkte", Endpoints), 

30 ("/api/endpunkte", EndpointsAPI), 

31 ), 

32 name="API-Endpunkte", 

33 description="Alle API-Endpunkte unserer Webseite", 

34 path="/endpunkte", 

35 aliases={ 

36 "/endpoints": "/endpunkte", 

37 "/api": "/api/endpunkte", 

38 "/api/endpoints": "/api/endpunkte", 

39 }, 

40 keywords=("API", "Endpoints", "Endpunkte"), 

41 ) 

42 

43 

44class Endpoints(HTMLRequestHandler): 

45 """Endpoint page request handler.""" 

46 

47 async def get(self, *, head: bool = False) -> None: 

48 """Handle a GET request.""" 

49 if head: 

50 return 

51 return await self.render( 

52 "pages/endpoints.html", 

53 endpoints=self.get_endpoints(), 

54 name_to_id=name_to_id, 

55 ) 

56 

57 def get_endpoints( 

58 self, 

59 ) -> list[dict[str, str | list[dict[str, str | list[str]]]]]: 

60 """Get a list of all API endpoints and return it.""" 

61 endpoints: list[dict[str, str | list[dict[str, str | list[str]]]]] = [] 

62 for module_info in self.settings["MODULE_INFOS"]: 

63 api_paths: list[dict[str, str | list[str]]] = [ 

64 { 

65 "path": handler[0], 

66 "methods": methods, 

67 "content_types": list(handler[1].POSSIBLE_CONTENT_TYPES), 

68 } 

69 for handler in module_info.handlers 

70 if handler[0].startswith("/api/") 

71 if issubclass(handler[1], APIRequestHandler) 

72 if ( 

73 methods := [ 

74 method.upper() 

75 for method in handler[1].get_allowed_methods() 

76 if ( 

77 perm := getattr( 

78 getattr(handler[1], method.lower()), 

79 "required_perms", 

80 None, 

81 ) 

82 ) 

83 is None 

84 or self.is_authorized(cast(Permission, perm)) 

85 ] 

86 ) 

87 if methods != ["OPTIONS"] 

88 ] 

89 if len(api_paths) > 0: 

90 endpoints.append( 

91 { 

92 "name": module_info.name, 

93 "description": module_info.description, 

94 "endpoints": api_paths, 

95 } 

96 ) 

97 return endpoints 

98 

99 

100class EndpointsAPI(APIRequestHandler, Endpoints): 

101 """Show a list of all API endpoints.""" 

102 

103 POSSIBLE_CONTENT_TYPES: ClassVar[tuple[str, ...]] = ( 

104 APIRequestHandler.POSSIBLE_CONTENT_TYPES + ("application/x-ndjson",) 

105 ) 

106 

107 async def get(self, *, head: bool = False) -> None: 

108 """Handle a GET request.""" 

109 if self.content_type == "application/x-ndjson": 

110 await self.finish( 

111 b"\n".join( 

112 json.dumps(endpoint, option=ORJSON_OPTIONS) 

113 for endpoint in self.get_endpoints() 

114 ) 

115 ) 

116 else: 

117 await self.finish(self.dump(self.get_endpoints()))