Skip to content

Commit 6396165

Browse files
committed
chore(api): clean up viewset technical debt and optimize routing
1 parent 7f1ff44 commit 6396165

3 files changed

Lines changed: 88 additions & 126 deletions

File tree

rootfs/api/tests/test_perm.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ def test_non_admin_cannot_transfer_app(self):
166166
f'/v2/apps/{app_id}',
167167
{'workspace': 'testws08'},
168168
)
169-
self.assertEqual(response.status_code, 400, response.data)
169+
self.assertEqual(response.status_code, 403, response.data)
170170
self.assertEqual(
171171
str(response.data['detail']),
172172
'you must be an admin of the current workspace',

rootfs/api/urls.py

Lines changed: 20 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,22 @@
88
from api import views
99

1010

11-
router = DefaultRouter(trailing_slash=False)
11+
class OptionalSlashRouter(DefaultRouter):
12+
"""Router that accepts both trailing-slash and no-trailing-slash URLs."""
13+
14+
def __init__(self, *args, **kwargs):
15+
super().__init__(*args, **kwargs)
16+
self.trailing_slash = '/?'
17+
18+
19+
router = OptionalSlashRouter()
20+
router.register(r'workspaces', views.WorkspaceViewSet, basename='workspace')
21+
router.register(r'apps', views.AppViewSet, basename='app')
22+
router.register(r'keys', views.KeyViewSet, basename='key')
23+
router.register(r'tokens', views.TokenViewSet, basename='token')
24+
router.register(r'limits/specs', views.LimitSpecViewSet, basename='limitspec')
25+
router.register(r'limits/plans', views.LimitPlanViewSet, basename='limitplan')
26+
1227
extra = getattr(settings, setting_name('TRAILING_SLASH'), True) and '/' or ''
1328

1429
# Add the generated REST URLs and login/logout endpoint
@@ -17,14 +32,7 @@
1732
re_path(r'auth/login/?$', views.AuthLoginView.as_view({"post": "login"})),
1833
re_path(r'auth/token/?$', views.AuthTokenView.as_view({"post": "token"})),
1934
re_path(r'auth/token/(?P<key>[-_\w]+)/?$', views.AuthTokenView.as_view({"get": "token"})),
20-
# Workspaces management URLs (keep workspaces prefix, no user prefix)
21-
re_path(r'^workspaces/?$',
22-
views.WorkspaceViewSet.as_view({'get': 'list', 'post': 'create'}),
23-
name='workspace_list'),
24-
re_path(r'^workspaces/(?P<name>[-_\w]+)/?$',
25-
views.WorkspaceViewSet.as_view(
26-
{'get': 'retrieve', 'patch': 'partial_update', 'delete': 'destroy'}),
27-
name='workspace_detail'),
35+
# Workspace sub-resources (members, invitations)
2836
re_path(r'^workspaces/(?P<name>[-_\w]+)/members/?$',
2937
views.WorkspaceMemberViewSet.as_view({'get': 'list'}),
3038
name='workspace_member_list'),
@@ -38,16 +46,6 @@
3846
re_path(r'^workspaces/(?P<name>[-_\w]+)/invitations/(?P<uid>[-_\w]+)/?$',
3947
views.WorkspaceInvitationViewSet.as_view({'get': 'retrieve', 'delete': 'destroy'}),
4048
name='workspace_invitation_detail'),
41-
# limits
42-
re_path(
43-
r'^limits/specs/?$',
44-
views.LimitSpecViewSet.as_view({'get': 'list'})),
45-
re_path(
46-
r'^limits/plans/?$',
47-
views.LimitPlanViewSet.as_view({'get': 'list'})),
48-
re_path(
49-
r'^limits/plans/(?P<id>[-.\w]+)/?$',
50-
views.LimitPlanViewSet.as_view({'get': 'retrieve'})),
5149
# application release components
5250
re_path(
5351
r"^apps/(?P<id>{})/build/?$".format(settings.APP_URL_REGEX),
@@ -112,7 +110,7 @@
112110
# application services
113111
re_path(
114112
r"^apps/(?P<id>{})/services/?$".format(settings.APP_URL_REGEX),
115-
views.ServiceViewSet.as_view({'post': 'create_or_update',
113+
views.ServiceViewSet.as_view({'post': 'upsert',
116114
'get': 'list', 'delete': 'destroy'})),
117115
# application settings
118116
re_path(
@@ -164,24 +162,6 @@
164162
re_path(
165163
r'^apps/(?P<id>{})/certs/?$'.format(settings.APP_URL_REGEX),
166164
views.CertificateViewSet.as_view({'get': 'list', 'post': 'create'})),
167-
# application actions
168-
re_path(
169-
r"^apps/(?P<id>{})/run/?$".format(settings.APP_URL_REGEX),
170-
views.AppViewSet.as_view({'post': 'run'})),
171-
# apps base endpoint
172-
re_path(
173-
r"^apps/(?P<id>{})/?$".format(settings.APP_URL_REGEX),
174-
views.AppViewSet.as_view({'get': 'retrieve', 'patch': 'transfer', 'delete': 'destroy'})),
175-
re_path(
176-
r'^apps/?$',
177-
views.AppViewSet.as_view({'get': 'list', 'post': 'create'})),
178-
# key
179-
re_path(
180-
r'^keys/(?P<id>.+)/?$',
181-
views.KeyViewSet.as_view({'get': 'retrieve', 'delete': 'destroy'})),
182-
re_path(
183-
r'^keys/?$',
184-
views.KeyViewSet.as_view({'get': 'list', 'post': 'create'})),
185165
# hooks
186166
re_path(
187167
r'^hooks/keys/(?P<id>{})/(?P<username>[\w.@+-]+)/?$'.format(settings.APP_URL_REGEX),
@@ -206,7 +186,7 @@
206186
re_path(
207187
r"^apps/(?P<id>{})/gateways/?$".format(settings.APP_URL_REGEX),
208188
views.GatewayViewSet.as_view(
209-
{'post': 'create_or_update', 'get': 'list', 'delete': 'destroy'})),
189+
{'post': 'upsert', 'get': 'list', 'delete': 'destroy'})),
210190
# routes
211191
re_path(
212192
r"^apps/(?P<id>{})/routes/(?P<name>{})?/?$".format(
@@ -224,7 +204,7 @@
224204
re_path(
225205
r"^apps/(?P<id>{})/routes/(?P<name>{})/rules/?$".format(
226206
settings.APP_URL_REGEX, settings.NAME_REGEX),
227-
views.RouteViewSet.as_view({'get': 'get', 'put': 'set'})),
207+
views.RouteRulesViewSet.as_view({'get': 'retrieve', 'put': 'update'})),
228208
re_path(
229209
r'^apps/(?P<id>{})/metrics/?$'.format(settings.APP_URL_REGEX),
230210
views.MetricView.as_view({'get': 'metric'})),
@@ -245,11 +225,6 @@
245225
re_path(
246226
r'^prometheus/(?P<workspace>[-\w]+)/(?P<path>.+)/?$',
247227
views.PrometheusProxyView.as_view()),
248-
# tokens
249-
re_path(r'^tokens/?$', views.TokenViewSet.as_view({'get': 'list'})),
250-
re_path(
251-
r"^tokens/(?P<pk>[-_\w]+)/?$",
252-
views.TokenViewSet.as_view({'get': 'retrieve', 'delete': 'destroy'})),
253228
]
254229

255230
metric_urlpatterns = [

0 commit comments

Comments
 (0)