forked from vieter-v/vieter
				
			Initial part of repos API (SEGFAULTS) [CI SKIP]
							parent
							
								
									92ad0c51eb
								
							
						
					
					
						commit
						e13252d368
					
				|  | @ -18,3 +18,6 @@ test/ | |||
| 
 | ||||
| # V compiler directory | ||||
| v/ | ||||
| 
 | ||||
| # gdb log file | ||||
| gdb.txt | ||||
|  |  | |||
							
								
								
									
										13
									
								
								Makefile
								
								
								
								
							
							
						
						
									
										13
									
								
								Makefile
								
								
								
								
							|  | @ -16,7 +16,17 @@ vieter: $(SOURCES) | |||
| .PHONY: debug | ||||
| debug: dvieter | ||||
| dvieter: $(SOURCES) | ||||
| 	$(V) -keepc -cg -cc gcc -o dvieter $(SRC_DIR) | ||||
| 	$(V_PATH) -showcc -keepc -cg -o dvieter $(SRC_DIR) | ||||
| 
 | ||||
| .PHONY: gdb | ||||
| gdb: dvieter | ||||
| 	 VIETER_API_KEY=test \
 | ||||
| 		VIETER_DOWNLOAD_DIR=data/downloads \
 | ||||
| 		VIETER_REPO_DIR=data/repo \
 | ||||
| 		VIETER_PKG_DIR=data/pkgs \
 | ||||
| 		VIETER_LOG_LEVEL=DEBUG \
 | ||||
| 		VIETER_REPOS_FILE=data/repos.json \
 | ||||
| 		gdb --args ./dvieter | ||||
| 
 | ||||
| # Optimised production build
 | ||||
| .PHONY: prod | ||||
|  | @ -39,6 +49,7 @@ run: vieter | |||
| 		VIETER_REPO_DIR=data/repo \
 | ||||
| 		VIETER_PKG_DIR=data/pkgs \
 | ||||
| 		VIETER_LOG_LEVEL=DEBUG \
 | ||||
| 		VIETER_REPOS_FILE=data/repos.json \
 | ||||
| 		./vieter server | ||||
| 
 | ||||
| .PHONY: run-prod | ||||
|  |  | |||
|  | @ -6,7 +6,7 @@ import rand | |||
| import time | ||||
| import os | ||||
| import json | ||||
| import git | ||||
| import server | ||||
| import env | ||||
| 
 | ||||
| const container_build_dir = '/build' | ||||
|  | @ -17,7 +17,7 @@ fn build() ? { | |||
| 	// Read in the repos from a json file | ||||
| 	filename := os.join_path_single(conf.repo_dir, 'repos.json') | ||||
| 	txt := os.read_file(filename) ? | ||||
| 	repos := json.decode([]git.GitRepo, txt) ? | ||||
| 	repos := json.decode([]server.GitRepo, txt) ? | ||||
| 
 | ||||
| 	mut commands := [ | ||||
| 		// Update repos & install required packages | ||||
|  |  | |||
|  | @ -17,6 +17,7 @@ pub: | |||
| 	download_dir string | ||||
| 	api_key      string | ||||
| 	repo_dir     string | ||||
| 	repos_file string | ||||
| } | ||||
| 
 | ||||
| pub struct BuildConfig { | ||||
|  | @ -42,7 +43,10 @@ fn get_env_var(field_name string) ?string { | |||
| 		return error('Only one of $env_var_name or $env_file_name can be defined.') | ||||
| 	} | ||||
| 
 | ||||
| 	// If it's the env var itself, we return it | ||||
| 	// If it's the env var itself, we return it. | ||||
| 	// I'm pretty sure this also prevents variable ending in _FILE (e.g. | ||||
| 	// VIETER_LOG_FILE) from being mistakingely read as an _FILE suffixed env | ||||
| 	// var. | ||||
| 	if env_var != '' { | ||||
| 		return env_var | ||||
| 	} | ||||
|  |  | |||
|  | @ -1,7 +0,0 @@ | |||
| module git | ||||
| 
 | ||||
| pub struct GitRepo { | ||||
| pub: | ||||
| 	url    string [required] | ||||
| 	branch string [required] | ||||
| } | ||||
|  | @ -2,17 +2,12 @@ module repo | |||
| 
 | ||||
| import os | ||||
| import package | ||||
| 
 | ||||
| // Dummy struct to work around the fact that you can only share structs, maps & | ||||
| // arrays | ||||
| pub struct Dummy { | ||||
| 	x int | ||||
| } | ||||
| import util | ||||
| 
 | ||||
| // This struct manages a single repository. | ||||
| pub struct Repo { | ||||
| mut: | ||||
| 	mutex shared Dummy | ||||
| 	mutex shared util.Dummy | ||||
| pub: | ||||
| 	// Where to store repository files | ||||
| 	repo_dir string [required] | ||||
|  |  | |||
|  | @ -0,0 +1,100 @@ | |||
| module server | ||||
| 
 | ||||
| import web | ||||
| import os | ||||
| import json | ||||
| 
 | ||||
| const repos_file = 'repos.json' | ||||
| 
 | ||||
| pub struct GitRepo { | ||||
| pub: | ||||
| 	url    string [required] | ||||
| 	branch string [required] | ||||
| } | ||||
| 
 | ||||
| fn read_repos(path string) ?[]GitRepo { | ||||
| 	if !os.exists(path) { | ||||
| 		mut f := os.create(path) ? | ||||
| 
 | ||||
| 		defer { | ||||
| 			f.close() | ||||
| 		} | ||||
| 
 | ||||
| 		f.write_string('{}') ? | ||||
| 
 | ||||
| 		return [] | ||||
| 	} | ||||
| 
 | ||||
| 	content := os.read_file(path) ? | ||||
| 	return json.decode([]GitRepo, content) | ||||
| } | ||||
| 
 | ||||
| fn write_repos(path string, repos []GitRepo) ? { | ||||
| 	mut f := os.create(path) ? | ||||
| 
 | ||||
| 	defer { | ||||
| 		f.close() | ||||
| 	} | ||||
| 
 | ||||
| 	dump(repos) | ||||
| 	value := json.encode(repos) | ||||
| 	f.write_string(value) ? | ||||
| } | ||||
| 
 | ||||
| ['/api/repos'; get] | ||||
| pub fn (mut app App) get_repos() web.Result { | ||||
| 	if !app.is_authorized() { | ||||
| 		return app.text('Unauthorized.') | ||||
| 	} | ||||
| 
 | ||||
| 	repos := rlock app.git_mutex { | ||||
| 		read_repos(app.conf.repos_file) or { | ||||
| 			app.lerror('Failed to read repos file.') | ||||
| 
 | ||||
| 			return app.server_error(500) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return app.json(repos) | ||||
| } | ||||
| 
 | ||||
| ['/api/repos'; post] | ||||
| pub fn (mut app App) post_repo() web.Result { | ||||
| 	if !app.is_authorized() { | ||||
| 		return app.text('Unauthorized.') | ||||
| 	} | ||||
| 
 | ||||
| 	if !('url' in app.query && 'branch' in app.query) { | ||||
| 		return app.server_error(400) | ||||
| 	} | ||||
| 
 | ||||
| 	new_repo := GitRepo{ | ||||
| 		url: app.query['url'] | ||||
| 		branch: app.query['branch'] | ||||
| 	} | ||||
| 
 | ||||
| 	mut repos := rlock app.git_mutex { | ||||
| 		read_repos(app.conf.repos_file) or { | ||||
| 			app.lerror('Failed to read repos file.') | ||||
| 
 | ||||
| 			return app.server_error(500) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// We need to check for duplicates | ||||
| 	for r in repos { | ||||
| 		if r == new_repo { | ||||
| 			return app.text('Duplicate repository.') | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	repos << new_repo | ||||
| 
 | ||||
| 	lock app.git_mutex { | ||||
| 		write_repos(app.conf.repos_file, repos) or { | ||||
| 			return app.server_error(500) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return app.ok('Repo added successfully.') | ||||
| } | ||||
|  | @ -7,25 +7,6 @@ import time | |||
| import rand | ||||
| import util | ||||
| 
 | ||||
| const prefixes = ['B', 'KB', 'MB', 'GB'] | ||||
| 
 | ||||
| // pretty_bytes converts a byte count to human-readable version | ||||
| fn pretty_bytes(bytes int) string { | ||||
| 	mut i := 0 | ||||
| 	mut n := f32(bytes) | ||||
| 
 | ||||
| 	for n >= 1024 { | ||||
| 		i++ | ||||
| 		n /= 1024 | ||||
| 	} | ||||
| 
 | ||||
| 	return '${n:.2}${server.prefixes[i]}' | ||||
| } | ||||
| 
 | ||||
| fn is_pkg_name(s string) bool { | ||||
| 	return s.contains('.pkg') | ||||
| } | ||||
| 
 | ||||
| // healthcheck just returns a string, but can be used to quickly check if the | ||||
| // server is still responsive. | ||||
| ['/health'; get] | ||||
|  | @ -65,7 +46,7 @@ fn (mut app App) put_package() web.Result { | |||
| 			pkg_path = os.join_path_single(app.conf.download_dir, rand.uuid_v4()) | ||||
| 		} | ||||
| 
 | ||||
| 		app.ldebug("Uploading $length bytes (${pretty_bytes(length.int())}) to '$pkg_path'.") | ||||
| 		app.ldebug("Uploading $length bytes (${util.pretty_bytes(length.int())}) to '$pkg_path'.") | ||||
| 
 | ||||
| 		// This is used to time how long it takes to upload a file | ||||
| 		mut sw := time.new_stopwatch(time.StopWatchOptions{ auto_start: true }) | ||||
|  |  | |||
|  | @ -12,9 +12,11 @@ const port = 8000 | |||
| struct App { | ||||
| 	web.Context | ||||
| pub: | ||||
| 	conf env.ServerConfig [required: web_global] | ||||
| 	conf env.ServerConfig [required; web_global] | ||||
| pub mut: | ||||
| 	repo repo.Repo [required; web_global] | ||||
| 	// This is used to claim the file lock on the repos file | ||||
| 	git_mutex shared util.Dummy | ||||
| } | ||||
| 
 | ||||
| pub fn server() ? { | ||||
|  |  | |||
							
								
								
									
										22
									
								
								src/util.v
								
								
								
								
							
							
						
						
									
										22
									
								
								src/util.v
								
								
								
								
							|  | @ -7,6 +7,14 @@ import crypto.sha256 | |||
| 
 | ||||
| const reader_buf_size = 1_000_000 | ||||
| 
 | ||||
| const prefixes = ['B', 'KB', 'MB', 'GB'] | ||||
| 
 | ||||
| // Dummy struct to work around the fact that you can only share structs, maps & | ||||
| // arrays | ||||
| pub struct Dummy { | ||||
| 	x int | ||||
| } | ||||
| 
 | ||||
| [noreturn] | ||||
| pub fn exit_with_message(code int, msg string) { | ||||
| 	eprintln(msg) | ||||
|  | @ -67,3 +75,17 @@ pub fn hash_file(path &string) ?(string, string) { | |||
| 
 | ||||
| 	return md5sum.checksum().hex(), sha256sum.checksum().hex() | ||||
| } | ||||
| 
 | ||||
| // pretty_bytes converts a byte count to human-readable version | ||||
| pub fn pretty_bytes(bytes int) string { | ||||
| 	mut i := 0 | ||||
| 	mut n := f32(bytes) | ||||
| 
 | ||||
| 	for n >= 1024 { | ||||
| 		i++ | ||||
| 		n /= 1024 | ||||
| 	} | ||||
| 
 | ||||
| 	return '${n:.2}${util.prefixes[i]}' | ||||
| } | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue