forked from vieter-v/vieter
				
			feat(server): partially migrated repos API to sqlite
							parent
							
								
									03318586ed
								
							
						
					
					
						commit
						891a206116
					
				| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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 */
 | 
			
		||||
	/* } */
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										109
									
								
								src/server/git.v
								
								
								
								
							
							
						
						
									
										109
									
								
								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.'))
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue