diff --git a/.woodpecker/.build.yml b/.woodpecker/.build.yml index 16e5a69..e68c4c9 100644 --- a/.woodpecker/.build.yml +++ b/.woodpecker/.build.yml @@ -2,6 +2,8 @@ matrix: PLATFORM: - linux/amd64 - linux/arm64 + # I just don't have a performant enough runner for this platform + # - linux/arm/v7 # These checks already get performed on the feature branches platform: ${PLATFORM} diff --git a/CHANGELOG.md b/CHANGELOG.md index 754f04e..e1daaec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,9 +9,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Changed -* Better environment variable support +* Better config system + * Support for both a config file & environment variables * Each env var can now be provided from a file by appending it with `_FILE` & passing the path to the file as value +* Revamped web framework + * All routes now return proper JSON where applicable & the correct status + codes ## Added diff --git a/src/build/build.v b/src/build/build.v index 934627f..c42c98d 100644 --- a/src/build/build.v +++ b/src/build/build.v @@ -3,9 +3,7 @@ module build import docker import encoding.base64 import time -import net.http import git -import json const container_build_dir = '/build' @@ -63,11 +61,7 @@ fn create_build_image() ?string { fn build(conf Config) ? { // We get the repos list from the Vieter instance - mut req := http.new_request(http.Method.get, '$conf.address/api/repos', '') ? - req.add_custom_header('X-Api-Key', conf.api_key) ? - - res := req.do() ? - repos := json.decode([]git.GitRepo, res.text) ? + repos := git.get_repos(conf.address, conf.api_key) ? // No point in doing work if there's no repos present if repos.len == 0 { @@ -77,7 +71,7 @@ fn build(conf Config) ? { // First, we create a base image which has updated repos n stuff image_id := create_build_image() ? - for repo in repos { + for _, repo in repos { // TODO what to do with PKGBUILDs that build multiple packages? commands := [ 'git clone --single-branch --depth 1 --branch $repo.branch $repo.url repo', diff --git a/src/git/cli.v b/src/git/cli.v index 17fa984..4a066d5 100644 --- a/src/git/cli.v +++ b/src/git/cli.v @@ -2,7 +2,6 @@ module git import cli import env -import net.http struct Config { address string [required] @@ -28,25 +27,25 @@ pub fn cmd() cli.Command { cli.Command{ name: 'add' required_args: 2 - usage: 'url branch' + usage: 'url branch arch...' description: 'Add a new repository.' execute: fn (cmd cli.Command) ? { config_file := cmd.flags.get_string('config-file') ? conf := env.load(config_file) ? - add(conf, cmd.args[0], cmd.args[1]) ? + add(conf, cmd.args[0], cmd.args[1], cmd.args[2..]) ? } }, cli.Command{ name: 'remove' - required_args: 2 - usage: 'url branch' - description: 'Remove a repository.' + required_args: 1 + usage: 'id' + description: 'Remove a repository that matches the given ID prefix.' execute: fn (cmd cli.Command) ? { config_file := cmd.flags.get_string('config-file') ? conf := env.load(config_file) ? - remove(conf, cmd.args[0], cmd.args[1]) ? + remove(conf, cmd.args[0]) ? } }, ] @@ -54,30 +53,41 @@ pub fn cmd() cli.Command { } fn list(conf Config) ? { - mut req := http.new_request(http.Method.get, '$conf.address/api/repos', '') ? - req.add_custom_header('X-API-Key', conf.api_key) ? + repos := get_repos(conf.address, conf.api_key) ? - res := req.do() ? - - println(res.text) + for id, details in repos { + println('${id[..8]}\t$details.url\t$details.branch\t$details.arch') + } } -fn add(conf Config, url string, branch string) ? { - mut req := http.new_request(http.Method.post, '$conf.address/api/repos?url=$url&branch=$branch', - '') ? - req.add_custom_header('X-API-Key', conf.api_key) ? +fn add(conf Config, url string, branch string, arch []string) ? { + res := add_repo(conf.address, conf.api_key, url, branch, arch) ? - res := req.do() ? - - println(res.text) + println(res.message) } -fn remove(conf Config, url string, branch string) ? { - mut req := http.new_request(http.Method.delete, '$conf.address/api/repos?url=$url&branch=$branch', - '') ? - req.add_custom_header('X-API-Key', conf.api_key) ? +fn remove(conf Config, id_prefix string) ? { + repos := get_repos(conf.address, conf.api_key) ? - res := req.do() ? + mut to_remove := []string{} - println(res.text) + for id, _ in repos { + if id.starts_with(id_prefix) { + to_remove << id + } + } + + if to_remove.len == 0 { + eprintln('No repo found for given prefix.') + exit(1) + } + + if to_remove.len > 1 { + eprintln('Multiple repos found for given prefix.') + exit(1) + } + + res := remove_repo(conf.address, conf.api_key, to_remove[0]) ? + + println(res.message) } diff --git a/src/git/client.v b/src/git/client.v new file mode 100644 index 0000000..97fe9fb --- /dev/null +++ b/src/git/client.v @@ -0,0 +1,39 @@ +module git + +import json +import response { Response } +import net.http + +// get_repos returns the current list of repos. +pub fn get_repos(address string, api_key string) ?map[string]GitRepo { + mut req := http.new_request(http.Method.get, '$address/api/repos', '') ? + req.add_custom_header('X-API-Key', api_key) ? + + res := req.do() ? + data := json.decode(Response, res.text) ? + + return data.data +} + +// add_repo adds a new repo to the server. +pub fn add_repo(address string, api_key string, url string, branch string, arch []string) ?Response { + mut req := http.new_request(http.Method.post, '$address/api/repos?url=$url&branch=$branch&arch=${arch.join(',')}', + '') ? + req.add_custom_header('X-API-Key', api_key) ? + + res := req.do() ? + data := json.decode(Response, res.text) ? + + return data +} + +// remove_repo removes the repo with the given ID from the server. +pub fn remove_repo(address string, api_key string, id string) ?Response { + mut req := http.new_request(http.Method.delete, '$address/api/repos/$id', '') ? + req.add_custom_header('X-API-Key', api_key) ? + + res := req.do() ? + data := json.decode(Response, res.text) ? + + return data +} diff --git a/src/git/git.v b/src/git/git.v index 913bc39..c5390b6 100644 --- a/src/git/git.v +++ b/src/git/git.v @@ -4,13 +4,34 @@ import os import json pub struct GitRepo { -pub: - url string [required] - branch string [required] +pub mut: + // URL of the Git repository + url string + // Branch of the Git repository to use + branch string + // On which architectures the package is allowed to be built. In reality, + // this controls which builders will periodically build the image. + arch []string } -// read_repos reads the given JSON file & parses it as a list of Git repos -pub fn read_repos(path string) ?[]GitRepo { +// 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 []string { + r.$(field.name) = params[field.name].split(',') + } + } + } +} + +// read_repos reads the provided path & parses it into a map of GitRepo's. +pub fn read_repos(path string) ?map[string]GitRepo { if !os.exists(path) { mut f := os.create(path) ? @@ -18,18 +39,19 @@ pub fn read_repos(path string) ?[]GitRepo { f.close() } - f.write_string('[]') ? + f.write_string('{}') ? - return [] + return {} } content := os.read_file(path) ? - res := json.decode([]GitRepo, content) ? + res := json.decode(map[string]GitRepo, content) ? + return res } -// write_repos writes a list of repositories back to a given file -pub fn write_repos(path string, repos []GitRepo) ? { +// write_repos writes a map of GitRepo's back to disk given the provided path. +pub fn write_repos(path string, repos &map[string]GitRepo) ? { mut f := os.create(path) ? defer { @@ -39,3 +61,20 @@ pub fn write_repos(path string, repos []GitRepo) ? { value := json.encode(repos) f.write_string(value) ? } + +// repo_from_params creates a GitRepo from a map[string]string, usually +// provided from a web.App's params +pub fn 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 { + return error('Missing parameter: ${field.name}.') + } + } + repo.patch_from_params(params) + + return repo +} diff --git a/src/response.v b/src/response.v new file mode 100644 index 0000000..a06a589 --- /dev/null +++ b/src/response.v @@ -0,0 +1,34 @@ +module response + +pub struct Response { +pub: + message string + data T +} + +// new_response constructs a new Response object with the given message +// & an empty data field. +pub fn new_response(message string) Response { + return Response{ + message: message + data: '' + } +} + +// new_data_response constructs a new Response object with the given data +// & an empty message field. +pub fn new_data_response(data T) Response { + return Response{ + message: '' + data: data + } +} + +// new_full_response constructs a new Response object with the given +// message & data. +pub fn new_full_response(message string, data T) Response { + return Response{ + message: message + data: data + } +} diff --git a/src/server/git.v b/src/server/git.v index 3ec8eeb..2a682d8 100644 --- a/src/server/git.v +++ b/src/server/git.v @@ -2,92 +2,140 @@ module server import web import git +import net.http +import rand +import response { new_data_response, new_response } const repos_file = 'repos.json' ['/api/repos'; get] fn (mut app App) get_repos() web.Result { if !app.is_authorized() { - return app.text('Unauthorized.') + 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') + + return app.status(http.Status.internal_server_error) + } + } + + return app.json(http.Status.ok, new_data_response(repos)) +} + +['/api/repos/:id'; get] +fn (mut app App) get_single_repo(id string) 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.') - return app.server_error(500) + return app.status(http.Status.internal_server_error) } } - return app.json(repos) + if id !in repos { + return app.not_found() + } + + repo := repos[id] + + return app.json(http.Status.ok, new_data_response(repo)) } ['/api/repos'; post] fn (mut app App) post_repo() web.Result { if !app.is_authorized() { - return app.text('Unauthorized.') + return app.json(http.Status.unauthorized, new_response('Unauthorized.')) } - if !('url' in app.query && 'branch' in app.query) { - return app.server_error(400) + new_repo := git.repo_from_params(app.query) or { + return app.json(http.Status.bad_request, new_response(err.msg)) } - new_repo := git.GitRepo{ - url: app.query['url'] - branch: app.query['branch'] - } + id := rand.uuid_v4() mut repos := rlock app.git_mutex { git.read_repos(app.conf.repos_file) or { app.lerror('Failed to read repos file.') - return app.server_error(500) + return app.status(http.Status.internal_server_error) } } // We need to check for duplicates - for r in repos { - if r == new_repo { - return app.text('Duplicate repository.') + for _, repo in repos { + if repo == new_repo { + return app.json(http.Status.bad_request, new_response('Duplicate repository.')) } } - repos << new_repo + repos[id] = new_repo lock app.git_mutex { - git.write_repos(app.conf.repos_file, repos) or { return app.server_error(500) } + git.write_repos(app.conf.repos_file, &repos) or { + return app.status(http.Status.internal_server_error) + } } - return app.ok('Repo added successfully.') + return app.json(http.Status.ok, new_response('Repo added successfully.')) } -['/api/repos'; delete] -fn (mut app App) delete_repo() web.Result { +['/api/repos/:id'; delete] +fn (mut app App) delete_repo(id string) web.Result { if !app.is_authorized() { - return app.text('Unauthorized.') - } - - if !('url' in app.query && 'branch' in app.query) { - return app.server_error(400) - } - - repo_to_remove := git.GitRepo{ - url: app.query['url'] - branch: app.query['branch'] + 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.') - return app.server_error(500) + return app.status(http.Status.internal_server_error) } } - filtered := repos.filter(it != repo_to_remove) - lock app.git_mutex { - git.write_repos(app.conf.repos_file, filtered) or { return app.server_error(500) } + if id !in repos { + return app.not_found() } - return app.ok('Repo removed successfully.') + repos.delete(id) + + 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.')) +} + +['/api/repos/:id'; patch] +fn (mut app App) patch_repo(id string) 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.') + + return app.status(http.Status.internal_server_error) + } + } + + if id !in repos { + return app.not_found() + } + + repos[id].patch_from_params(app.query) + + 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 updated successfully.')) } diff --git a/src/server/routes.v b/src/server/routes.v index 0f697f9..55f6f12 100644 --- a/src/server/routes.v +++ b/src/server/routes.v @@ -7,12 +7,13 @@ import time import rand import util import net.http +import response { new_response } // healthcheck just returns a string, but can be used to quickly check if the // server is still responsive. ['/health'; get] pub fn (mut app App) healthcheck() web.Result { - return app.text('Healthy') + return app.json(http.Status.ok, new_response('Healthy.')) } ['/:repo/:arch/:filename'; get; head] @@ -36,7 +37,7 @@ fn (mut app App) get_repo_file(repo string, arch string, filename string) web.Re // Scuffed way to respond to HEAD requests if app.req.method == http.Method.head { if os.exists(full_path) { - return app.ok('') + return app.status(http.Status.ok) } return app.not_found() @@ -48,7 +49,7 @@ fn (mut app App) get_repo_file(repo string, arch string, filename string) web.Re ['/:repo/publish'; post] fn (mut app App) put_package(repo string) web.Result { if !app.is_authorized() { - return app.text('Unauthorized.') + return app.json(http.Status.unauthorized, new_response('Unauthorized.')) } mut pkg_path := '' @@ -69,14 +70,16 @@ 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.text('Failed to upload file.') + return app.json(http.Status.internal_server_error, new_response('Failed to upload file.')) } sw.stop() app.ldebug("Upload of '$pkg_path' completed in ${sw.elapsed().seconds():.3}s.") } else { app.lwarn('Tried to upload package without specifying a Content-Length.') - return app.text("Content-Type header isn't set.") + + // length required + return app.status(http.Status.length_required) } res := app.repo.add_pkg_from_path(repo, pkg_path) or { @@ -84,7 +87,7 @@ 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.text('Failed to add package.') + return app.json(http.Status.internal_server_error, new_response('Failed to add package.')) } if !res.added { @@ -92,10 +95,10 @@ fn (mut app App) put_package(repo string) web.Result { app.lwarn("Duplicate package '$res.pkg.full_name()' in repo '$repo ($res.pkg.info.arch)'.") - return app.text('File already exists.') + 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.text('Package added successfully.') + return app.json(http.Status.ok, new_response('Package added successfully.')) } diff --git a/src/web/web.v b/src/web/web.v index ad647f2..000c6a6 100644 --- a/src/web/web.v +++ b/src/web/web.v @@ -12,9 +12,6 @@ import time import json import log -// A type which don't get filtered inside templates -pub type RawHtml = string - // A dummy structure that returns from routes to indicate that you actually sent something to a user [noinit] pub struct Result {} @@ -141,8 +138,8 @@ pub const ( // It has fields for the query, form, files. pub struct Context { mut: - content_type string = 'text/plain' - status string = '200 OK' + content_type string = 'text/plain' + status http.Status = http.Status.ok pub: // HTTP Request req http.Request @@ -186,24 +183,14 @@ struct Route { path string } -// Defining this method is optional. -// init_server is called at server start. -// You can use it for initializing globals. -pub fn (ctx Context) init_server() { - eprintln('init_server() has been deprecated, please init your web app in `fn main()`') -} - // Defining this method is optional. // before_request is called before every request (aka middleware). // Probably you can use it for check user session cookie or add header. pub fn (ctx Context) before_request() {} -pub struct Cookie { - name string - value string - expires time.Time - secure bool - http_only bool +// send_string +fn send_string(mut conn net.TcpConn, s string) ? { + conn.write(s.bytes()) ? } // send_response_to_client sends a response to the client @@ -225,34 +212,27 @@ pub fn (mut ctx Context) send_response_to_client(mimetype string, res string) bo text: res } resp.set_version(.v1_1) - resp.set_status(http.status_from_int(ctx.status.int())) + resp.set_status(ctx.status) send_string(mut ctx.conn, resp.bytestr()) or { return false } return true } -// html HTTP_OK with s as payload with content-type `text/html` -pub fn (mut ctx Context) html(s string) Result { - ctx.send_response_to_client('text/html', s) - return Result{} -} +// text responds to a request with some plaintext. +pub fn (mut ctx Context) text(status http.Status, s string) Result { + ctx.status = status -// text HTTP_OK with s as payload with content-type `text/plain` -pub fn (mut ctx Context) text(s string) Result { ctx.send_response_to_client('text/plain', s) + return Result{} } // json HTTP_OK with json_s as payload with content-type `application/json` -pub fn (mut ctx Context) json(j T) Result { +pub fn (mut ctx Context) json(status http.Status, j T) Result { + ctx.status = status + json_s := json.encode(j) ctx.send_response_to_client('application/json', json_s) - return Result{} -} -// json_pretty Response HTTP_OK with a pretty-printed JSON result -pub fn (mut ctx Context) json_pretty(j T) Result { - json_s := json.encode_pretty(j) - ctx.send_response_to_client('application/json', json_s) return Result{} } @@ -302,7 +282,7 @@ pub fn (mut ctx Context) file(f_path string) Result { header: header.join(web.headers_close) } resp.set_version(.v1_1) - resp.set_status(http.status_from_int(ctx.status.int())) + resp.set_status(ctx.status) send_string(mut ctx.conn, resp.bytestr()) or { return Result{} } mut buf := []byte{len: 1_000_000} @@ -328,10 +308,10 @@ pub fn (mut ctx Context) file(f_path string) Result { return Result{} } -// ok Response HTTP_OK with s as payload -pub fn (mut ctx Context) ok(s string) Result { - ctx.send_response_to_client(ctx.content_type, s) - return Result{} +// status responds with an empty textual response, essentially only returning +// the given status code. +pub fn (mut ctx Context) status(status http.Status) Result { + return ctx.text(status, '') } // server_error Response a server error @@ -361,64 +341,7 @@ pub fn (mut ctx Context) redirect(url string) Result { // not_found Send an not_found response pub fn (mut ctx Context) not_found() Result { - if ctx.done { - return Result{} - } - ctx.done = true - send_string(mut ctx.conn, web.http_404.bytestr()) or {} - return Result{} -} - -// set_cookie Sets a cookie -pub fn (mut ctx Context) set_cookie(cookie Cookie) { - mut cookie_data := []string{} - mut secure := if cookie.secure { 'Secure;' } else { '' } - secure += if cookie.http_only { ' HttpOnly' } else { ' ' } - cookie_data << secure - if cookie.expires.unix > 0 { - cookie_data << 'expires=$cookie.expires.utc_string()' - } - data := cookie_data.join(' ') - ctx.add_header('Set-Cookie', '$cookie.name=$cookie.value; $data') -} - -// set_content_type Sets the response content type -pub fn (mut ctx Context) set_content_type(typ string) { - ctx.content_type = typ -} - -// set_cookie_with_expire_date Sets a cookie with a `expire_data` -pub fn (mut ctx Context) set_cookie_with_expire_date(key string, val string, expire_date time.Time) { - ctx.add_header('Set-Cookie', '$key=$val; Secure; HttpOnly; expires=$expire_date.utc_string()') -} - -// get_cookie Gets a cookie by a key -pub fn (ctx &Context) get_cookie(key string) ?string { // TODO refactor - mut cookie_header := ctx.get_header('cookie') - if cookie_header == '' { - cookie_header = ctx.get_header('Cookie') - } - cookie_header = ' ' + cookie_header - // println('cookie_header="$cookie_header"') - // println(ctx.req.header) - cookie := if cookie_header.contains(';') { - cookie_header.find_between(' $key=', ';') - } else { - cookie_header.find_between(' $key=', '\r') - } - if cookie != '' { - return cookie.trim_space() - } - return error('Cookie not found') -} - -// set_status Sets the response status -pub fn (mut ctx Context) set_status(code int, desc string) { - if code < 100 || code > 599 { - ctx.status = '500 Internal Server Error' - } else { - ctx.status = '$code $desc' - } + return ctx.status(http.Status.not_found) } // add_header Adds an header to the response with key and val @@ -560,12 +483,6 @@ fn handle_conn(mut conn net.TcpConn, mut app T, routes map[string]Route) { // Calling middleware... app.before_request() - // Static handling - if serve_if_static(mut app, url) { - // successfully served a static file - return - } - // Route matching $for method in T.methods { $if method.return_type is Result { @@ -661,83 +578,6 @@ fn route_matches(url_words []string, route_words []string) ?[]string { return params } -// serve_if_static checks if request is for a static file and serves it -// returns true if we served a static file, false otherwise -[manualfree] -fn serve_if_static(mut app T, url urllib.URL) bool { - // TODO: handle url parameters properly - for now, ignore them - static_file := app.static_files[url.path] - mime_type := app.static_mime_types[url.path] - if static_file == '' || mime_type == '' { - return false - } - data := os.read_file(static_file) or { - send_string(mut app.conn, web.http_404.bytestr()) or {} - return true - } - app.send_response_to_client(mime_type, data) - unsafe { data.free() } - return true -} - -// scan_static_directory makes a static route for each file in a directory -fn (mut ctx Context) scan_static_directory(directory_path string, mount_path string) { - files := os.ls(directory_path) or { panic(err) } - if files.len > 0 { - for file in files { - full_path := os.join_path(directory_path, file) - if os.is_dir(full_path) { - ctx.scan_static_directory(full_path, mount_path + '/' + file) - } else if file.contains('.') && !file.starts_with('.') && !file.ends_with('.') { - ext := os.file_ext(file) - // Rudimentary guard against adding files not in mime_types. - // Use serve_static directly to add non-standard mime types. - if ext in web.mime_types { - ctx.serve_static(mount_path + '/' + file, full_path) - } - } - } - } -} - -// handle_static Handles a directory static -// If `root` is set the mount path for the dir will be in '/' -pub fn (mut ctx Context) handle_static(directory_path string, root bool) bool { - if ctx.done || !os.exists(directory_path) { - return false - } - dir_path := directory_path.trim_space().trim_right('/') - mut mount_path := '' - if dir_path != '.' && os.is_dir(dir_path) && !root { - // Mount point hygene, "./assets" => "/assets". - mount_path = '/' + dir_path.trim_left('.').trim('/') - } - ctx.scan_static_directory(dir_path, mount_path) - return true -} - -// mount_static_folder_at - makes all static files in `directory_path` and inside it, available at http://server/mount_path -// For example: suppose you have called .mount_static_folder_at('/var/share/myassets', '/assets'), -// and you have a file /var/share/myassets/main.css . -// => That file will be available at URL: http://server/assets/main.css . -pub fn (mut ctx Context) mount_static_folder_at(directory_path string, mount_path string) bool { - if ctx.done || mount_path.len < 1 || mount_path[0] != `/` || !os.exists(directory_path) { - return false - } - dir_path := directory_path.trim_right('/') - ctx.scan_static_directory(dir_path, mount_path[1..]) - return true -} - -// serve_static Serves a file static -// `url` is the access path on the site, `file_path` is the real path to the file, `mime_type` is the file type -pub fn (mut ctx Context) serve_static(url string, file_path string) { - ctx.static_files[url] = file_path - // ctx.static_mime_types[url] = mime_type - ext := os.file_ext(file_path) - ctx.static_mime_types[url] = web.mime_types[ext] -} - // ip Returns the ip address from the current user pub fn (ctx &Context) ip() string { mut ip := ctx.req.header.get(.x_forwarded_for) or { '' } @@ -760,16 +600,6 @@ pub fn (mut ctx Context) error(s string) { ctx.form_error = s } -// not_found Returns an empty result -pub fn not_found() Result { - return Result{} -} - -// send_string -fn send_string(mut conn net.TcpConn, s string) ? { - conn.write(s.bytes()) ? -} - // filter Do not delete. // It used by `vlib/v/gen/c/str_intp.v:130` for string interpolation inside web templates // TODO: move it to template render diff --git a/vieter.toml b/vieter.toml index fb05d6f..f3904b6 100644 --- a/vieter.toml +++ b/vieter.toml @@ -3,7 +3,7 @@ api_key = "test" download_dir = "data/downloads" data_dir = "data" pkg_dir = "data/pkgs" -# log_level = "DEBUG" +log_level = "DEBUG" repos_file = "data/repos.json" address = "http://localhost:8000"