diff --git a/CHANGELOG.md b/CHANGELOG.md index 113dc21..bb0c517 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,12 +17,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * CLI commands for searching the AUR & directly adding packages * HTTP routes for removing packages, arch-repos & repos * All endpoints serving files now support HTTP byte range requests -* Better CLI UX - * When adding targets, the ID of the created target is returned - * The `-r` flag only shows raw data of action - * When adding a target, only ID is shown and not surrounding text - * Tabled output returns a tab-separated list (easy to script using - `cut`) ### Changed @@ -33,12 +27,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 repository will be cloned with the default branch * Build containers now explicitely set the PATH variable * Refactor of web framework -* API endpoints now return id of newly created entries -* Repo POST requests now return information on published package -* `api` can no longer be used as a repository name -* CLI client now allows setting values to an empty value -* API endpoints now return id of newly created entries -* Repo POST requests now return information on published package * `api` can no longer be used as a repository name * CLI client now allows setting values to an empty value diff --git a/src/client/logs.v b/src/client/logs.v index b414245..b52c3d0 100644 --- a/src/client/logs.v +++ b/src/client/logs.v @@ -39,7 +39,7 @@ pub fn (c &Client) get_build_log_content(id int) ?string { } // add_build_log adds a new build log to the server. -pub fn (c &Client) add_build_log(target_id int, start_time time.Time, end_time time.Time, arch string, exit_code int, content string) ?Response { +pub fn (c &Client) add_build_log(target_id int, start_time time.Time, end_time time.Time, arch string, exit_code int, content string) ?Response { params := { 'target': target_id.str() 'startTime': start_time.unix_time().str() @@ -48,7 +48,7 @@ pub fn (c &Client) add_build_log(target_id int, start_time time.Time, end_time t 'exitCode': exit_code.str() } - data := c.send_request_with_body(Method.post, '/api/v1/logs', params, content)? + data := c.send_request_with_body(Method.post, '/api/v1/logs', params, content)? return data } diff --git a/src/client/targets.v b/src/client/targets.v index c5e44fe..f5258a4 100644 --- a/src/client/targets.v +++ b/src/client/targets.v @@ -49,9 +49,9 @@ pub struct NewTarget { } // add_target adds a new target to the server. -pub fn (c &Client) add_target(t NewTarget) ?Response { +pub fn (c &Client) add_target(t NewTarget) ?Response { params := models.params_from(t) - data := c.send_request(Method.post, '/api/v1/targets', params)? + data := c.send_request(Method.post, '/api/v1/targets', params)? return data } diff --git a/src/console/console.v b/src/console/console.v index caf4cca..7d782ba 100644 --- a/src/console/console.v +++ b/src/console/console.v @@ -5,11 +5,6 @@ import strings import cli import os -// tabbed_table returns a simple textual table, with tabs as separators. -pub fn tabbed_table(data [][]string) string { - return data.map(it.join('\t')).join('\n') -} - // pretty_table converts a list of string data into a pretty table. Many thanks // to @hungrybluedev in the Vlang Discord for providing this code! // https://ptb.discord.com/channels/592103645835821068/592106336838352923/970278787143045192 diff --git a/src/console/logs/logs.v b/src/console/logs/logs.v index 41830c2..0f023bc 100644 --- a/src/console/logs/logs.v +++ b/src/console/logs/logs.v @@ -133,9 +133,7 @@ pub fn cmd() cli.Command { ] } - raw := cmd.flags.get_bool('raw')? - - list(conf, filter, raw)? + list(conf, filter)? } }, cli.Command{ @@ -169,31 +167,27 @@ pub fn cmd() cli.Command { } // print_log_list prints a list of logs. -fn print_log_list(logs []BuildLog, raw bool) ? { +fn print_log_list(logs []BuildLog) ? { data := logs.map([it.id.str(), it.target_id.str(), it.start_time.local().str(), it.exit_code.str()]) - if raw { - println(console.tabbed_table(data)) - } else { - println(console.pretty_table(['id', 'target', 'start time', 'exit code'], data)?) - } + println(console.pretty_table(['id', 'target', 'start time', 'exit code'], data)?) } // list prints a list of all build logs. -fn list(conf Config, filter BuildLogFilter, raw bool) ? { +fn list(conf Config, filter BuildLogFilter) ? { c := client.new(conf.address, conf.api_key) logs := c.get_build_logs(filter)?.data - print_log_list(logs, raw)? + print_log_list(logs)? } // list prints a list of all build logs for a given target. -fn list_for_target(conf Config, target_id int, raw bool) ? { +fn list_for_target(conf Config, target_id int) ? { c := client.new(conf.address, conf.api_key) logs := c.get_build_logs_for_target(target_id)?.data - print_log_list(logs, raw)? + print_log_list(logs)? } // info print the detailed info for a given build log. diff --git a/src/console/targets/targets.v b/src/console/targets/targets.v index 5640011..66d48fb 100644 --- a/src/console/targets/targets.v +++ b/src/console/targets/targets.v @@ -60,9 +60,7 @@ pub fn cmd() cli.Command { filter.repo = repo } - raw := cmd.flags.get_bool('raw')? - - list(conf, filter, raw)? + list(conf, filter)? } }, cli.Command{ @@ -94,9 +92,7 @@ pub fn cmd() cli.Command { branch: cmd.flags.get_string('branch') or { '' } } - raw := cmd.flags.get_bool('raw')? - - add(conf, t, raw)? + add(conf, t)? } }, cli.Command{ @@ -197,28 +193,20 @@ pub fn cmd() cli.Command { // ID. If multiple or none are found, an error is raised. // list prints out a list of all repositories. -fn list(conf Config, filter TargetFilter, raw bool) ? { +fn list(conf Config, filter TargetFilter) ? { c := client.new(conf.address, conf.api_key) repos := c.get_targets(filter)? data := repos.map([it.id.str(), it.kind, it.url, it.repo]) - if raw { - println(console.tabbed_table(data)) - } else { - println(console.pretty_table(['id', 'kind', 'url', 'repo'], data)?) - } + println(console.pretty_table(['id', 'kind', 'url', 'repo'], data)?) } // add adds a new repository to the server's list. -fn add(conf Config, t &NewTarget, raw bool) ? { +fn add(conf Config, t &NewTarget) ? { c := client.new(conf.address, conf.api_key) res := c.add_target(t)? - if raw { - println(res.data) - } else { - println('Target added with id $res.data') - } + println(res.message) } // remove removes a repository from the server's list. diff --git a/src/db/logs.v b/src/db/logs.v index 923dde2..af5f53c 100644 --- a/src/db/logs.v +++ b/src/db/logs.v @@ -79,14 +79,10 @@ pub fn (db &VieterDb) get_build_log(id int) ?BuildLog { } // add_build_log inserts the given BuildLog into the database. -pub fn (db &VieterDb) add_build_log(log BuildLog) int { +pub fn (db &VieterDb) add_build_log(log BuildLog) { sql db.conn { insert log into BuildLog } - - inserted_id := db.conn.last_id() as int - - return inserted_id } // delete_build_log delete the BuildLog with the given ID from the database. diff --git a/src/db/targets.v b/src/db/targets.v index a705ebb..9102033 100644 --- a/src/db/targets.v +++ b/src/db/targets.v @@ -38,14 +38,10 @@ pub fn (db &VieterDb) get_target(target_id int) ?Target { } // add_target inserts the given target into the database. -pub fn (db &VieterDb) add_target(repo Target) int { +pub fn (db &VieterDb) add_target(repo Target) { sql db.conn { insert repo into Target } - - inserted_id := db.conn.last_id() as int - - return inserted_id } // delete_target deletes the target with the given id from the database. diff --git a/src/main.v b/src/main.v index 0e98bd2..4ade930 100644 --- a/src/main.v +++ b/src/main.v @@ -24,13 +24,6 @@ fn main() { global: true default_value: [os.expand_tilde_to_home('~/.vieterrc')] }, - cli.Flag{ - flag: cli.FlagType.bool - name: 'raw' - abbrev: 'r' - description: 'Only output minimal information (no formatted tables, etc.)' - global: true - }, ] commands: [ server.cmd(), diff --git a/src/repo/add.v b/src/repo/add.v index 608ca50..0985c7a 100644 --- a/src/repo/add.v +++ b/src/repo/add.v @@ -23,9 +23,8 @@ pub: pub struct RepoAddResult { pub: - name string - version string - archs []string + added bool [required] + pkg &package.Pkg [required] } // new creates a new RepoGroupManager & creates the directories as needed @@ -54,10 +53,10 @@ pub fn (r &RepoGroupManager) add_pkg_from_path(repo string, pkg_path string) ?Re return error('Failed to read package file: $err.msg()') } - archs := r.add_pkg_in_repo(repo, pkg)? + added := r.add_pkg_in_repo(repo, pkg)? // If the add was successful, we move the file to the packages directory - for arch in archs { + for arch in added { repo_pkg_path := os.real_path(os.join_path(r.pkg_dir, repo, arch)) dest_path := os.join_path_single(repo_pkg_path, pkg.filename()) @@ -72,9 +71,8 @@ pub fn (r &RepoGroupManager) add_pkg_from_path(repo string, pkg_path string) ?Re os.rm(pkg_path)? return RepoAddResult{ - name: pkg.info.name - version: pkg.info.version - archs: archs + added: added.len > 0 + pkg: &pkg } } @@ -89,9 +87,11 @@ fn (r &RepoGroupManager) add_pkg_in_repo(repo string, pkg &package.Pkg) ?[]strin // A package not of arch 'any' can be handled easily by adding it to the // respective repo if pkg.info.arch != 'any' { - r.add_pkg_in_arch_repo(repo, pkg.info.arch, pkg)? - - return [pkg.info.arch] + if r.add_pkg_in_arch_repo(repo, pkg.info.arch, pkg)? { + return [pkg.info.arch] + } else { + return [] + } } mut arch_repos := []string{} @@ -113,22 +113,25 @@ fn (r &RepoGroupManager) add_pkg_in_repo(repo string, pkg &package.Pkg) ?[]strin arch_repos << r.default_arch } - // Add the package to each found architecture - // NOTE: if any of these fail, the function fails. This means the user does - // not know which arch-repositories did succeed in adding the package, if - // any. + mut added := []string{} + + // We add the package to each repository. If any of the repositories + // return true, the result of the function is also true. for arch in arch_repos { - r.add_pkg_in_arch_repo(repo, arch, pkg)? + if r.add_pkg_in_arch_repo(repo, arch, pkg)? { + added << arch + } } - return arch_repos + return added } // add_pkg_in_arch_repo is the function that actually adds a package to a given // arch-repo. It records the package's data in the arch-repo's desc & files // files, and afterwards updates the db & files archives to reflect these -// changes. -fn (r &RepoGroupManager) add_pkg_in_arch_repo(repo string, arch string, pkg &package.Pkg) ? { +// changes. The function returns false if the package was already present in +// the repo, and true otherwise. +fn (r &RepoGroupManager) add_pkg_in_arch_repo(repo string, arch string, pkg &package.Pkg) ?bool { pkg_dir := os.join_path(r.repos_dir, repo, arch, '$pkg.info.name-$pkg.info.version') // Remove the previous version of the package, if present @@ -148,4 +151,6 @@ fn (r &RepoGroupManager) add_pkg_in_arch_repo(repo string, arch string, pkg &pac } r.sync(repo, arch)? + + return true } diff --git a/src/server/api_logs.v b/src/server/api_logs.v index 287755a..021c1ac 100644 --- a/src/server/api_logs.v +++ b/src/server/api_logs.v @@ -19,7 +19,7 @@ fn (mut app App) v1_get_logs() web.Result { } logs := app.db.get_build_logs(filter) - return app.json(.ok, new_data_response(logs)) + return app.json(http.Status.ok, new_data_response(logs)) } // v1_get_single_log returns the build log with the given id. @@ -27,7 +27,7 @@ fn (mut app App) v1_get_logs() web.Result { fn (mut app App) v1_get_single_log(id int) web.Result { log := app.db.get_build_log(id) or { return app.not_found() } - return app.json(.ok, new_data_response(log)) + return app.json(http.Status.ok, new_data_response(log)) } // v1_get_log_content returns the actual build log file for the given id. @@ -95,8 +95,7 @@ fn (mut app App) v1_post_log() web.Result { exit_code: exit_code } - // id of newly created log - log_id := app.db.add_build_log(log) + app.db.add_build_log(log) repo_logs_dir := os.join_path(app.conf.data_dir, logs_dir_name, target_id.str(), arch) @@ -123,5 +122,5 @@ fn (mut app App) v1_post_log() web.Result { return app.status(http.Status.length_required) } - return app.json(.ok, new_data_response(log_id)) + return app.json(http.Status.ok, new_response('Logs added successfully.')) } diff --git a/src/server/api_targets.v b/src/server/api_targets.v index 6f284af..c9e7963 100644 --- a/src/server/api_targets.v +++ b/src/server/api_targets.v @@ -14,7 +14,7 @@ fn (mut app App) v1_get_targets() web.Result { } repos := app.db.get_targets(filter) - return app.json(.ok, new_data_response(repos)) + return app.json(http.Status.ok, new_data_response(repos)) } // v1_get_single_target returns the information for a single target. @@ -22,7 +22,7 @@ fn (mut app App) v1_get_targets() web.Result { fn (mut app App) v1_get_single_target(id int) web.Result { repo := app.db.get_target(id) or { return app.not_found() } - return app.json(.ok, new_data_response(repo)) + return app.json(http.Status.ok, new_data_response(repo)) } // v1_post_target creates a new target from the provided query string. @@ -45,9 +45,9 @@ fn (mut app App) v1_post_target() web.Result { return app.json(http.Status.bad_request, new_response('Invalid kind.')) } - id := app.db.add_target(new_repo) + app.db.add_target(new_repo) - return app.json(http.Status.ok, new_data_response(id)) + return app.json(http.Status.ok, new_response('Repo added successfully.')) } // v1_delete_target removes a given target from the server's list. @@ -55,7 +55,7 @@ fn (mut app App) v1_post_target() web.Result { fn (mut app App) v1_delete_target(id int) web.Result { app.db.delete_target(id) - return app.status(.ok) + return app.json(http.Status.ok, new_response('Repo removed successfully.')) } // v1_patch_target updates a target's data with the given query params. @@ -69,5 +69,5 @@ fn (mut app App) v1_patch_target(id int) web.Result { app.db.update_target_archs(id, arch_objs) } - return app.status(.ok) + return app.json(http.Status.ok, new_response('Repo updated successfully.')) } diff --git a/src/server/repo.v b/src/server/repo.v index 06ab72e..abfc631 100644 --- a/src/server/repo.v +++ b/src/server/repo.v @@ -6,7 +6,8 @@ import repo import time import rand import util -import web.response { new_data_response, new_response } +import net.http +import web.response { new_response } // healthcheck just returns a string, but can be used to quickly check if the // server is still responsive. @@ -70,7 +71,7 @@ fn (mut app App) put_package(repo string) web.Result { util.reader_to_file(mut app.reader, length.int(), pkg_path) or { app.lwarn("Failed to upload '$pkg_path'") - return app.status(.internal_server_error) + return app.json(http.Status.internal_server_error, new_response('Failed to upload file.')) } sw.stop() @@ -79,7 +80,7 @@ fn (mut app App) put_package(repo string) web.Result { app.lwarn('Tried to upload package without specifying a Content-Length.') // length required - return app.status(.length_required) + return app.status(http.Status.length_required) } res := app.repo.add_pkg_from_path(repo, pkg_path) or { @@ -87,10 +88,18 @@ fn (mut app App) put_package(repo string) web.Result { os.rm(pkg_path) or { app.lerror("Failed to remove download '$pkg_path': $err.msg()") } - return app.status(.internal_server_error) + return app.json(http.Status.internal_server_error, new_response('Failed to add package.')) } - app.linfo("Added '$res.name-$res.version' to '$repo (${res.archs.join(',')})'.") + if !res.added { + os.rm(pkg_path) or { app.lerror("Failed to remove download '$pkg_path': $err.msg()") } - return app.json(.ok, new_data_response(res)) + app.lwarn("Duplicate package '$res.pkg.full_name()' in repo '$repo'.") + + return app.json(http.Status.bad_request, new_response('File already exists.')) + } + + app.linfo("Added '$res.pkg.full_name()' to repo '$repo ($res.pkg.info.arch)'.") + + return app.json(http.Status.ok, new_response('Package added successfully.')) } diff --git a/src/server/repo_remove.v b/src/server/repo_remove.v index 694f085..fdc40e8 100644 --- a/src/server/repo_remove.v +++ b/src/server/repo_remove.v @@ -1,6 +1,8 @@ module server import web +import net.http +import web.response { new_response } // delete_package tries to remove the given package. ['/:repo/:arch/:pkg'; auth; delete] @@ -8,17 +10,17 @@ fn (mut app App) delete_package(repo string, arch string, pkg string) web.Result res := app.repo.remove_pkg_from_arch_repo(repo, arch, pkg, true) or { app.lerror('Error while deleting package: $err.msg()') - return app.status(.internal_server_error) + 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.status(.ok) + 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.status(.not_found) + return app.json(http.Status.not_found, new_response('Package not found.')) } } @@ -28,17 +30,17 @@ fn (mut app App) delete_arch_repo(repo string, arch string) web.Result { res := app.repo.remove_arch_repo(repo, arch) or { app.lerror('Error while deleting arch-repo: $err.msg()') - return app.status(.internal_server_error) + return app.json(http.Status.internal_server_error, new_response('Failed to delete arch-repo.')) } if res { - app.linfo("Removed arch-repo '$repo/$arch'") + app.linfo("Removed '$repo/$arch'") - return app.status(.ok) + 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.status(.not_found) + return app.json(http.Status.not_found, new_response('Arch-repo not found.')) } } @@ -48,16 +50,16 @@ fn (mut app App) delete_repo(repo string) web.Result { res := app.repo.remove_repo(repo) or { app.lerror('Error while deleting repo: $err.msg()') - return app.status(.internal_server_error) + return app.json(http.Status.internal_server_error, new_response('Failed to delete repo.')) } if res { - app.linfo("Removed repo '$repo'") + app.linfo("Removed '$repo'") - return app.status(.ok) + return app.json(http.Status.ok, new_response('Repo removed.')) } else { app.linfo("Tried removing '$repo', but it doesn't exist.") - return app.status(.not_found) + return app.json(http.Status.not_found, new_response('Repo not found.')) } }