diff --git a/CHANGELOG.md b/CHANGELOG.md
index a03c18cc..b7138a84 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* Targets with kind 'url' can provide a direct URL to a PKGBUILD instead of
providing a Git repository
* CLI commands for searching the AUR & directly adding packages
+* HTTP routes for removing packages, arch-repos & repos
### Changed
diff --git a/docs/api/source/includes/_repository.md b/docs/api/source/includes/_repository.md
index fbbc329a..ff17f719 100644
--- a/docs/api/source/includes/_repository.md
+++ b/docs/api/source/includes/_repository.md
@@ -93,3 +93,87 @@ other already present arch-repos.
Parameter | Description
--------- | -----------
repo | Repository to publish package to
+
+## Remove package from arch-repo
+
+
+
+```shell
+curl \
+ -H 'X-Api-Key: secret' \
+ -XDELETE \
+ https://example.com/vieter/x86_64/mike
+```
+
+This endpoint allows you to remove a package from a given arch-repo.
+
+### HTTP Request
+
+`DELETE /:repo/:arch/:pkg`
+
+### URL Parameters
+
+Parameter | Description
+--------- | -----------
+repo | Repository to delete package from
+arch | Specific arch-repo to remove package from
+pkg | Name of package to remove (without any version information)
+
+## Remove arch-repo
+
+
+
+```shell
+curl \
+ -H 'X-Api-Key: secret' \
+ -XDELETE \
+ https://example.com/vieter/x86_64
+```
+
+This endpoint allows removing an entire arch-repo.
+
+### HTTP Request
+
+`DELETE /:repo/:arch`
+
+### URL Parameters
+
+Parameter | Description
+--------- | -----------
+repo | Repository to delete arch-repo from
+arch | Specific architecture to remove
+
+## Remove repo
+
+
+
+```shell
+curl \
+ -H 'X-Api-Key: secret' \
+ -XDELETE \
+ https://example.com/vieter
+```
+
+This endpoint allows removing an entire repo.
+
+### HTTP Request
+
+`DELETE /:repo`
+
+### URL Parameters
+
+Parameter | Description
+--------- | -----------
+repo | Repository to delete
diff --git a/src/repo/repo.v b/src/repo/add.v
similarity index 77%
rename from src/repo/repo.v
rename to src/repo/add.v
index c4b85c0b..0985c7a6 100644
--- a/src/repo/repo.v
+++ b/src/repo/add.v
@@ -154,51 +154,3 @@ fn (r &RepoGroupManager) add_pkg_in_arch_repo(repo string, arch string, pkg &pac
return true
}
-
-// remove_pkg_from_arch_repo removes a package from an arch-repo's database. It
-// returns false if the package wasn't present in the database. It also
-// optionally re-syncs the repo archives.
-fn (r &RepoGroupManager) remove_pkg_from_arch_repo(repo string, arch string, pkg_name string, sync bool) ?bool {
- repo_dir := os.join_path(r.repos_dir, repo, arch)
-
- // If the repository doesn't exist yet, the result is automatically false
- if !os.exists(repo_dir) {
- return false
- }
-
- // We iterate over every directory in the repo dir
- // TODO filter so we only check directories
- for d in os.ls(repo_dir)? {
- // Because a repository only allows a single version of each package,
- // we need only compare whether the name of the package is the same,
- // not the version.
- name := d.split('-')#[..-2].join('-')
-
- if name == pkg_name {
- // We lock the mutex here to prevent other routines from creating a
- // new archive while we remove an entry
- lock r.mutex {
- os.rmdir_all(os.join_path_single(repo_dir, d))?
- }
-
- // Also remove the package archive
- repo_pkg_dir := os.join_path(r.pkg_dir, repo, arch)
-
- archives := os.ls(repo_pkg_dir)?.filter(it.split('-')#[..-3].join('-') == name)
-
- for archive_name in archives {
- full_path := os.join_path_single(repo_pkg_dir, archive_name)
- os.rm(full_path)?
- }
-
- // Sync the db archives if requested
- if sync {
- r.sync(repo, arch)?
- }
-
- return true
- }
- }
-
- return false
-}
diff --git a/src/repo/remove.v b/src/repo/remove.v
new file mode 100644
index 00000000..add921cb
--- /dev/null
+++ b/src/repo/remove.v
@@ -0,0 +1,85 @@
+module repo
+
+import os
+
+// remove_pkg_from_arch_repo removes a package from an arch-repo's database. It
+// returns false if the package wasn't present in the database. It also
+// optionally re-syncs the repo archives.
+pub fn (r &RepoGroupManager) remove_pkg_from_arch_repo(repo string, arch string, pkg_name string, sync bool) ?bool {
+ repo_dir := os.join_path(r.repos_dir, repo, arch)
+
+ // If the repository doesn't exist yet, the result is automatically false
+ if !os.exists(repo_dir) {
+ return false
+ }
+
+ // We iterate over every directory in the repo dir
+ // TODO filter so we only check directories
+ for d in os.ls(repo_dir)? {
+ // Because a repository only allows a single version of each package,
+ // we need only compare whether the name of the package is the same,
+ // not the version.
+ name := d.split('-')#[..-2].join('-')
+
+ if name == pkg_name {
+ // We lock the mutex here to prevent other routines from creating a
+ // new archive while we remove an entry
+ lock r.mutex {
+ os.rmdir_all(os.join_path_single(repo_dir, d))?
+ }
+
+ // Also remove the package archive
+ repo_pkg_dir := os.join_path(r.pkg_dir, repo, arch)
+
+ archives := os.ls(repo_pkg_dir)?.filter(it.split('-')#[..-3].join('-') == name)
+
+ for archive_name in archives {
+ full_path := os.join_path_single(repo_pkg_dir, archive_name)
+ os.rm(full_path)?
+ }
+
+ // Sync the db archives if requested
+ if sync {
+ r.sync(repo, arch)?
+ }
+
+ return true
+ }
+ }
+
+ return false
+}
+
+// remove_arch_repo removes an arch-repo & its packages.
+pub fn (r &RepoGroupManager) remove_arch_repo(repo string, arch string) ?bool {
+ repo_dir := os.join_path(r.repos_dir, repo, arch)
+
+ // If the repository doesn't exist yet, the result is automatically false
+ if !os.exists(repo_dir) {
+ return false
+ }
+
+ os.rmdir_all(repo_dir)?
+
+ pkg_dir := os.join_path(r.pkg_dir, repo, arch)
+ os.rmdir_all(pkg_dir)?
+
+ return true
+}
+
+// remove_repo removes a repo & its packages.
+pub fn (r &RepoGroupManager) remove_repo(repo string) ?bool {
+ repo_dir := os.join_path_single(r.repos_dir, repo)
+
+ // If the repository doesn't exist yet, the result is automatically false
+ if !os.exists(repo_dir) {
+ return false
+ }
+
+ os.rmdir_all(repo_dir)?
+
+ pkg_dir := os.join_path_single(r.pkg_dir, repo)
+ os.rmdir_all(pkg_dir)?
+
+ return true
+}
diff --git a/src/server/routes.v b/src/server/repo.v
similarity index 100%
rename from src/server/routes.v
rename to src/server/repo.v
diff --git a/src/server/repo_remove.v b/src/server/repo_remove.v
new file mode 100644
index 00000000..642f26f2
--- /dev/null
+++ b/src/server/repo_remove.v
@@ -0,0 +1,77 @@
+module server
+
+import web
+import net.http
+import response { new_response }
+
+// delete_package tries to remove the given package.
+['/:repo/:arch/:pkg'; delete]
+fn (mut app App) delete_package(repo string, arch string, pkg string) web.Result {
+ if !app.is_authorized() {
+ return app.json(http.Status.unauthorized, new_response('Unauthorized.'))
+ }
+
+ res := app.repo.remove_pkg_from_arch_repo(repo, arch, pkg, true) or {
+ app.lerror('Error while deleting package: $err.msg()')
+
+ return app.json(http.Status.internal_server_error, new_response('Failed to delete package.'))
+ }
+
+ if res {
+ app.linfo("Removed package '$pkg' from '$repo/$arch'")
+
+ return app.json(http.Status.ok, new_response('Package removed.'))
+ } else {
+ app.linfo("Tried removing package '$pkg' from '$repo/$arch', but it doesn't exist.")
+
+ return app.json(http.Status.not_found, new_response('Package not found.'))
+ }
+}
+
+// delete_arch_repo tries to remove the given arch-repo.
+['/:repo/:arch'; delete]
+fn (mut app App) delete_arch_repo(repo string, arch string) web.Result {
+ if !app.is_authorized() {
+ return app.json(http.Status.unauthorized, new_response('Unauthorized.'))
+ }
+
+ res := app.repo.remove_arch_repo(repo, arch) or {
+ app.lerror('Error while deleting arch-repo: $err.msg()')
+
+ return app.json(http.Status.internal_server_error, new_response('Failed to delete arch-repo.'))
+ }
+
+ if res {
+ app.linfo("Removed '$repo/$arch'")
+
+ return app.json(http.Status.ok, new_response('Arch-repo removed.'))
+ } else {
+ app.linfo("Tried removing '$repo/$arch', but it doesn't exist.")
+
+ return app.json(http.Status.not_found, new_response('Arch-repo not found.'))
+ }
+}
+
+// delete_repo tries to remove the given repo.
+['/:repo'; delete]
+fn (mut app App) delete_repo(repo string) web.Result {
+ if !app.is_authorized() {
+ return app.json(http.Status.unauthorized, new_response('Unauthorized.'))
+ }
+
+ res := app.repo.remove_repo(repo) or {
+ app.lerror('Error while deleting repo: $err.msg()')
+
+ return app.json(http.Status.internal_server_error, new_response('Failed to delete repo.'))
+ }
+
+ if res {
+ app.linfo("Removed '$repo'")
+
+ return app.json(http.Status.ok, new_response('Repo removed.'))
+ } else {
+ app.linfo("Tried removing '$repo', but it doesn't exist.")
+
+ return app.json(http.Status.not_found, new_response('Repo not found.'))
+ }
+}