From 891a206116dd33b1e4640d6a9867a1a897ce7b21 Mon Sep 17 00:00:00 2001 From: Jef Roosens Date: Sun, 1 May 2022 22:47:00 +0200 Subject: [PATCH] feat(server): partially migrated repos API to sqlite --- .woodpecker/.build.yml | 2 +- src/db/db.v | 19 +++++++ src/db/git.v | 98 ++++++++++++++++++++++++++++++++++++ src/server/git.v | 109 ++++++++++++++++++++++------------------- src/server/server.v | 5 ++ 5 files changed, 181 insertions(+), 52 deletions(-) create mode 100644 src/db/db.v create mode 100644 src/db/git.v diff --git a/.woodpecker/.build.yml b/.woodpecker/.build.yml index b41a39d..f9cab00 100644 --- a/.woodpecker/.build.yml +++ b/.woodpecker/.build.yml @@ -23,7 +23,7 @@ pipeline: image: 'chewingbever/vlang:latest' pull: true environment: - - LDFLAGS=-lz -lbz2 -llzma -lexpat -lzstd -llz4 -static + - LDFLAGS=-lz -lbz2 -llzma -lexpat -lzstd -llz4 -lsqlite3 -static commands: # Apparently this -D is *very* important - CFLAGS='-DGC_THREADS=1' make prod diff --git a/src/db/db.v b/src/db/db.v new file mode 100644 index 0000000..b62fa3b --- /dev/null +++ b/src/db/db.v @@ -0,0 +1,19 @@ +module db + +import sqlite + +struct VieterDb { + conn sqlite.DB +} + +pub fn init(db_path string) ?VieterDb { + conn := sqlite.connect(db_path) ? + + sql conn { + create table GitRepo + } + + return VieterDb{ + conn: conn + } +} diff --git a/src/db/git.v b/src/db/git.v new file mode 100644 index 0000000..fca46c6 --- /dev/null +++ b/src/db/git.v @@ -0,0 +1,98 @@ +module db + +struct GitRepoArch { +pub: + id int [primary; sql: serial] + repo_id int + value string +} + +pub struct GitRepo { +pub mut: + id int [optional; primary; sql: serial] + // URL of the Git repository + url string [nonull] + // Branch of the Git repository to use + branch string [nonull] + // Which repo the builder should publish packages to + repo string [nonull] + // Cron schedule describing how frequently to build the repo. + schedule string [optional] + // On which architectures the package is allowed to be built. In reality, + // this controls which builders will periodically build the image. + arch []GitRepoArch [fkey: 'repo_id'] +} + +// patch_from_params patches a GitRepo from a map[string]string, usually +// provided from a web.App's params +pub fn (mut r GitRepo) patch_from_params(params map[string]string) { + $for field in GitRepo.fields { + if field.name in params { + $if field.typ is string { + r.$(field.name) = params[field.name] + // This specific type check is needed for the compiler to ensure + // our types are correct + } $else $if field.typ is []GitRepoArch { + r.$(field.name) = params[field.name].split(',').map(GitRepoArch{ value: it }) + } + } + } +} + +// repo_from_params creates a GitRepo from a map[string]string, usually +// provided from a web.App's params +pub fn git_repo_from_params(params map[string]string) ?GitRepo { + mut repo := GitRepo{} + + // If we're creating a new GitRepo, we want all fields to be present before + // "patching". + $for field in GitRepo.fields { + if field.name !in params && !field.attrs.contains('optional') { + return error('Missing parameter: ${field.name}.') + } + } + repo.patch_from_params(params) + + return repo +} + +pub fn (db &VieterDb) get_git_repos() []GitRepo { + res := sql db.conn { + select from GitRepo + } + + return res +} + +pub fn (db &VieterDb) get_git_repo(repo_id int) ?GitRepo { + res := sql db.conn { + select from GitRepo where id == repo_id + } + + // If a select statement fails, it returns a zeroed object. By + // checking one of the required fields, we can see whether the query + // returned a result or not. + if res.url == '' { + return none + } + + return res +} + +pub fn (db &VieterDb) add_git_repo(repo GitRepo) { + sql db.conn { + insert repo into GitRepo + } +} + +pub fn (db &VieterDb) delete_git_repo(repo_id int) { + sql db.conn { + delete from GitRepo where id == repo_id + } +} + +pub fn (db &VieterDb) update_git_repo(repo GitRepo) { + /* sql db.conn { */ + /* update GitRepo set repo */ + /* } */ +} diff --git a/src/server/git.v b/src/server/git.v index c136d98..6485eca 100644 --- a/src/server/git.v +++ b/src/server/git.v @@ -5,6 +5,7 @@ import git import net.http import rand import response { new_data_response, new_response } +import db const repos_file = 'repos.json' @@ -15,37 +16,39 @@ fn (mut app App) get_repos() web.Result { return app.json(http.Status.unauthorized, new_response('Unauthorized.')) } - repos := rlock app.git_mutex { - git.read_repos(app.conf.repos_file) or { - app.lerror('Failed to read repos file: $err.msg()') + repos := app.db.get_git_repos() + // repos := rlock app.git_mutex { + // git.read_repos(app.conf.repos_file) or { + // app.lerror('Failed to read repos file: $err.msg()') - return app.status(http.Status.internal_server_error) - } - } + // return app.status(http.Status.internal_server_error) + // } + //} return app.json(http.Status.ok, new_data_response(repos)) } // get_single_repo returns the information for a single repo. ['/api/repos/:id'; get] -fn (mut app App) get_single_repo(id string) web.Result { +fn (mut app App) get_single_repo(id int) web.Result { if !app.is_authorized() { return app.json(http.Status.unauthorized, new_response('Unauthorized.')) } - repos := rlock app.git_mutex { - git.read_repos(app.conf.repos_file) or { - app.lerror('Failed to read repos file.') + // repos := rlock app.git_mutex { + // git.read_repos(app.conf.repos_file) or { + // app.lerror('Failed to read repos file.') - return app.status(http.Status.internal_server_error) - } - } + // return app.status(http.Status.internal_server_error) + // } + //} - if id !in repos { - return app.not_found() - } + // if id !in repos { + // return app.not_found() + //} - repo := repos[id] + // repo := repos[id] + repo := app.db.get_git_repo(id) or { return app.not_found() } return app.json(http.Status.ok, new_data_response(repo)) } @@ -65,62 +68,66 @@ fn (mut app App) post_repo() web.Result { params['arch'] = app.conf.default_arch } - new_repo := git.repo_from_params(params) or { + new_repo := db.git_repo_from_params(params) or { return app.json(http.Status.bad_request, new_response(err.msg())) } - id := rand.uuid_v4() + app.db.add_git_repo(new_repo) - mut repos := rlock app.git_mutex { - git.read_repos(app.conf.repos_file) or { - app.lerror('Failed to read repos file.') + // id := rand.uuid_v4() - return app.status(http.Status.internal_server_error) - } - } + // mut repos := rlock app.git_mutex { + // git.read_repos(app.conf.repos_file) or { + // app.lerror('Failed to read repos file.') - // We need to check for duplicates - for _, repo in repos { - if repo == new_repo { - return app.json(http.Status.bad_request, new_response('Duplicate repository.')) - } - } + // return app.status(http.Status.internal_server_error) + // } + //} + // repos := app.db.get_git_repos() - repos[id] = new_repo + //// We need to check for duplicates + // for _, repo in repos { + // if repo == new_repo { + // return app.json(http.Status.bad_request, new_response('Duplicate repository.')) + // } + //} - lock app.git_mutex { - git.write_repos(app.conf.repos_file, &repos) or { - return app.status(http.Status.internal_server_error) - } - } + // repos[id] = new_repo + + // lock app.git_mutex { + // git.write_repos(app.conf.repos_file, &repos) or { + // return app.status(http.Status.internal_server_error) + // } + //} return app.json(http.Status.ok, new_response('Repo added successfully.')) } // delete_repo removes a given repo from the server's list. ['/api/repos/:id'; delete] -fn (mut app App) delete_repo(id string) web.Result { +fn (mut app App) delete_repo(id int) web.Result { if !app.is_authorized() { return app.json(http.Status.unauthorized, new_response('Unauthorized.')) } - mut repos := rlock app.git_mutex { - git.read_repos(app.conf.repos_file) or { - app.lerror('Failed to read repos file.') + /* mut repos := rlock app.git_mutex { */ + /* git.read_repos(app.conf.repos_file) or { */ + /* app.lerror('Failed to read repos file.') */ - return app.status(http.Status.internal_server_error) - } - } + /* return app.status(http.Status.internal_server_error) */ + /* } */ + /* } */ - if id !in repos { - return app.not_found() - } + /* if id !in repos { */ + /* return app.not_found() */ + /* } */ - repos.delete(id) + /* repos.delete(id) */ + app.db.delete_git_repo(id) - lock app.git_mutex { - git.write_repos(app.conf.repos_file, &repos) or { return app.server_error(500) } - } +/* lock app.git_mutex { */ +/* git.write_repos(app.conf.repos_file, &repos) or { return app.server_error(500) } */ +/* } */ return app.json(http.Status.ok, new_response('Repo removed successfully.')) } diff --git a/src/server/server.v b/src/server/server.v index c4317c5..751ea9c 100644 --- a/src/server/server.v +++ b/src/server/server.v @@ -5,6 +5,7 @@ import os import log import repo import util +import db const port = 8000 @@ -16,6 +17,7 @@ pub mut: repo repo.RepoGroupManager [required; web_global] // This is used to claim the file lock on the repos file git_mutex shared util.Dummy + db db.VieterDb } // server starts the web server & starts listening for requests @@ -53,9 +55,12 @@ pub fn server(conf Config) ? { util.exit_with_message(1, 'Failed to create download directory.') } + db := db.init('test.db') or { util.exit_with_message(1, 'Failed to initialize database.') } + web.run(&App{ logger: logger conf: conf repo: repo + db: db }, server.port) }