diff --git a/CHANGELOG.md b/CHANGELOG.md index 29c1f245..34ca4aee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -111,17 +111,6 @@ path/server, for example, then you can simply update the path in the config for * If compiling `matrix-media-repo`, note that new external dependencies are required. See [the docs](https://docs.t2bot.io/matrix-media-repo/installing/method/compilation.html). * Docker images already contain these dependencies. * Datastores no longer use the `enabled` flag set on them. Use `forKinds: []` instead to disable a datastore's usage. -* Some admin endpoints for purging media, quarantining media, and background task information now require additional path components. - ```diff - -POST /_matrix/media/unstable/admin/purge//?access_token=your_access_token - +POST /_matrix/media/unstable/admin/purge/media//?access_token=your_access_token - - -POST /_matrix/media/unstable/admin/quarantine//?access_token=your_access_token - +POST /_matrix/media/unstable/admin/quarantine/media//?access_token=your_access_token - - -GET /_matrix/media/unstable/admin/tasks/ - +GET /_matrix/media/unstable/admin/task/ - ``` * Per-user upload quotas now do not allow users to exceed the maximum values, even by 1 byte. Previously, users could exceed the limits by a little bit. * Updated to Go 1.19, then Go 1.20 in the same release cycle. * New CGO dependencies are required. See [docs.t2bot.io](https://docs.t2bot.io/matrix-media-repo/installing/method/compilation.html) for details. diff --git a/api/_routers/00-install-params.go b/api/_routers/00-install-params.go index 47c48d9a..11b42f33 100644 --- a/api/_routers/00-install-params.go +++ b/api/_routers/00-install-params.go @@ -1,6 +1,7 @@ package _routers import ( + "context" "errors" "net/http" "regexp" @@ -27,3 +28,22 @@ func GetParam(name string, r *http.Request) string { } return p.ByName(name) } + +func ForceSetParam(name string, val string, r *http.Request) *http.Request { + params := httprouter.ParamsFromContext(r.Context()) + wasSet := false + for _, p := range params { + if p.Key == name { + p.Value = val + wasSet = true + break + } + } + if !wasSet { + params = append(params, httprouter.Param{ + Key: name, + Value: val, + }) + } + return r.WithContext(context.WithValue(r.Context(), httprouter.ParamsKey, params)) +} diff --git a/api/branched_route.go b/api/branched_route.go new file mode 100644 index 00000000..dde80e22 --- /dev/null +++ b/api/branched_route.go @@ -0,0 +1,50 @@ +package api + +import ( + "net/http" + "strings" + + "github.com/turt2live/matrix-media-repo/api/_routers" +) + +type branch struct { + string + http.Handler +} + +type splitBranch struct { + segments []string + handler http.Handler +} + +func branchedRoute(branches []branch) http.Handler { + sbranches := make([]splitBranch, len(branches)) + for i, b := range branches { + sbranches[i] = splitBranch{ + segments: strings.Split(b.string, "/"), + handler: b.Handler, + } + } + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + catchAll := _routers.GetParam("branch", r) + if catchAll[0] == '/' { + catchAll = catchAll[1:] + } + params := strings.Split(catchAll, "/") + for _, b := range sbranches { + if b.segments[0][0] == ':' || b.segments[0] == params[0] { + if len(b.segments) != len(params) { + continue + } + for i, s := range b.segments { + if s[0] == ':' { + r = _routers.ForceSetParam(s[1:], params[i], r) + } + } + b.handler.ServeHTTP(w, r) + return + } + } + notFoundFn(w, r) + }) +} diff --git a/api/routes.go b/api/routes.go index 1c47cb74..808646dd 100644 --- a/api/routes.go +++ b/api/routes.go @@ -62,21 +62,27 @@ func buildRoutes() http.Handler { // All admin routes are unstable only purgeRemoteRoute := makeRoute(_routers.RequireRepoAdmin(custom.PurgeRemoteMedia), "purge_remote_media", counter) + purgeBranch := branchedRoute([]branch{ + {"remote", purgeRemoteRoute}, + {"old", makeRoute(_routers.RequireRepoAdmin(custom.PurgeOldMedia), "purge_old_media", counter)}, + {"quarantined", makeRoute(_routers.RequireAccessToken(custom.PurgeQuarantined), "purge_quarantined", counter)}, + {"user/:userId", makeRoute(_routers.RequireAccessToken(custom.PurgeUserMedia), "purge_user_media", counter)}, + {"room/:roomId", makeRoute(_routers.RequireAccessToken(custom.PurgeRoomMedia), "purge_room_media", counter)}, + {"server/:serverName", makeRoute(_routers.RequireAccessToken(custom.PurgeDomainMedia), "purge_domain_media", counter)}, + {":server/:mediaId", purgeOneRoute}, + }) + register([]string{"POST"}, PrefixMedia, "admin/purge/*branch", mxUnstable, router, purgeBranch) register([]string{"POST"}, PrefixMedia, "admin/purge_remote", mxUnstable, router, purgeRemoteRoute) - register([]string{"POST"}, PrefixMedia, "admin/purge/remote", mxUnstable, router, purgeRemoteRoute) register([]string{"POST"}, PrefixClient, "admin/purge_media_cache", mxUnstable, router, purgeRemoteRoute) // synapse compat - register([]string{"POST"}, PrefixMedia, "admin/purge/media/:server/:mediaId", mxUnstable, router, purgeOneRoute) - register([]string{"POST"}, PrefixMedia, "admin/purge/old", mxUnstable, router, makeRoute(_routers.RequireRepoAdmin(custom.PurgeOldMedia), "purge_old_media", counter)) - register([]string{"POST"}, PrefixMedia, "admin/purge/quarantined", mxUnstable, router, makeRoute(_routers.RequireAccessToken(custom.PurgeQuarantined), "purge_quarantined", counter)) - register([]string{"POST"}, PrefixMedia, "admin/purge/user/:userId", mxUnstable, router, makeRoute(_routers.RequireAccessToken(custom.PurgeUserMedia), "purge_user_media", counter)) - register([]string{"POST"}, PrefixMedia, "admin/purge/room/:roomId", mxUnstable, router, makeRoute(_routers.RequireAccessToken(custom.PurgeRoomMedia), "purge_room_media", counter)) - register([]string{"POST"}, PrefixMedia, "admin/purge/server/:serverName", mxUnstable, router, makeRoute(_routers.RequireAccessToken(custom.PurgeDomainMedia), "purge_domain_media", counter)) quarantineRoomRoute := makeRoute(_routers.RequireAccessToken(custom.QuarantineRoomMedia), "quarantine_room", counter) - register([]string{"POST"}, PrefixMedia, "admin/quarantine/room/:roomId", mxUnstable, router, quarantineRoomRoute) + quarantineBranch := branchedRoute([]branch{ + {"room/:roomId", quarantineRoomRoute}, + {"user/:userId", makeRoute(_routers.RequireAccessToken(custom.QuarantineUserMedia), "quarantine_user", counter)}, + {"server/:serverName", makeRoute(_routers.RequireAccessToken(custom.QuarantineDomainMedia), "quarantine_domain", counter)}, + {":server/:mediaId", makeRoute(_routers.RequireAccessToken(custom.QuarantineMedia), "quarantine_media", counter)}, + }) + register([]string{"POST"}, PrefixMedia, "admin/quarantine/*branch", mxUnstable, router, quarantineBranch) register([]string{"POST"}, PrefixClient, "admin/quarantine_media/:roomId", mxUnstable, router, quarantineRoomRoute) // synapse compat - register([]string{"POST"}, PrefixMedia, "admin/quarantine/user/:userId", mxUnstable, router, makeRoute(_routers.RequireAccessToken(custom.QuarantineUserMedia), "quarantine_user", counter)) - register([]string{"POST"}, PrefixMedia, "admin/quarantine/server/:serverName", mxUnstable, router, makeRoute(_routers.RequireAccessToken(custom.QuarantineDomainMedia), "quarantine_domain", counter)) - register([]string{"POST"}, PrefixMedia, "admin/quarantine/media/:server/:mediaId", mxUnstable, router, makeRoute(_routers.RequireAccessToken(custom.QuarantineMedia), "quarantine_media", counter)) register([]string{"GET"}, PrefixMedia, "admin/datastores/:datastoreId/size_estimate", mxUnstable, router, makeRoute(_routers.RequireRepoAdmin(custom.GetDatastoreStorageEstimate), "get_storage_estimate", counter)) register([]string{"POST"}, PrefixMedia, "admin/datastores/:sourceDsId/transfer_to/:targetDsId", mxUnstable, router, makeRoute(_routers.RequireRepoAdmin(custom.MigrateBetweenDatastores), "datastore_transfer", counter)) register([]string{"GET"}, PrefixMedia, "admin/datastores", mxUnstable, router, makeRoute(_routers.RequireRepoAdmin(custom.GetDatastores), "list_datastores", counter)) @@ -85,9 +91,12 @@ func buildRoutes() http.Handler { register([]string{"GET"}, PrefixMedia, "admin/usage/:serverName/users", mxUnstable, router, makeRoute(_routers.RequireRepoAdmin(custom.GetUserUsage), "user_usage", counter)) register([]string{"GET"}, PrefixMedia, "admin/usage/:serverName/users-stats", mxUnstable, router, synUserStatsRoute) register([]string{"GET"}, PrefixMedia, "admin/usage/:serverName/uploads", mxUnstable, router, makeRoute(_routers.RequireRepoAdmin(custom.GetUploadsUsage), "uploads_usage", counter)) - register([]string{"GET"}, PrefixMedia, "admin/task/:taskId", mxUnstable, router, makeRoute(_routers.RequireRepoAdmin(custom.GetTask), "get_background_task", counter)) - register([]string{"GET"}, PrefixMedia, "admin/tasks/all", mxUnstable, router, makeRoute(_routers.RequireRepoAdmin(custom.ListAllTasks), "list_all_background_tasks", counter)) - register([]string{"GET"}, PrefixMedia, "admin/tasks/unfinished", mxUnstable, router, makeRoute(_routers.RequireRepoAdmin(custom.ListUnfinishedTasks), "list_unfinished_background_tasks", counter)) + tasksBranch := branchedRoute([]branch{ + {"all", makeRoute(_routers.RequireRepoAdmin(custom.ListAllTasks), "list_all_background_tasks", counter)}, + {"unfinished", makeRoute(_routers.RequireRepoAdmin(custom.ListUnfinishedTasks), "list_unfinished_background_tasks", counter)}, + {":taskId", makeRoute(_routers.RequireRepoAdmin(custom.GetTask), "get_background_task", counter)}, + }) + register([]string{"GET"}, PrefixMedia, "admin/tasks/*branch", mxUnstable, router, tasksBranch) register([]string{"POST"}, PrefixMedia, "admin/user/:userId/export", mxUnstable, router, makeRoute(_routers.RequireAccessToken(custom.ExportUserData), "export_user_data", counter)) register([]string{"POST"}, PrefixMedia, "admin/server/:serverName/export", mxUnstable, router, makeRoute(_routers.RequireAccessToken(custom.ExportServerData), "export_server_data", counter)) register([]string{"GET"}, PrefixMedia, "admin/export/:exportId/view", mxUnstable, router, makeRoute(_routers.OptionalAccessToken(custom.ViewExport), "view_export", counter)) diff --git a/docs/admin.md b/docs/admin.md index 96219454..58df5da1 100644 --- a/docs/admin.md +++ b/docs/admin.md @@ -46,7 +46,7 @@ This will delete all media that has previously been quarantined, local or remote #### Purge individual record -URL: `POST /_matrix/media/unstable/admin/purge/media//?access_token=your_access_token` +URL: `POST /_matrix/media/unstable/admin/purge//?access_token=your_access_token` **Note**: Prior to v1.3, this endpoint did not require the `/media` component, but does now. @@ -90,7 +90,7 @@ This API is unique in that it can allow administrators of configured homeservers #### Quarantine a specific record -URL: `POST /_matrix/media/unstable/admin/quarantine/media//?access_token=your_access_token` +URL: `POST /_matrix/media/unstable/admin/quarantine//?access_token=your_access_token` **Note**: Prior to v1.3, this endpoint did not require the `/media` component, but does now. @@ -367,7 +367,7 @@ The response is a list of all unfinished tasks: #### Getting information on a specific task -URL: `GET /_matrix/media/unstable/admin/task/` +URL: `GET /_matrix/media/unstable/admin/tasks/` **Note**: Prior to v1.3, this endpoint was "tasks" (plural). It is now singular.