changeset 22:dd078aea5d02

misc: use project/worker name as primary key
author David Demelier <markand@malikania.fr>
date Thu, 21 Jul 2022 20:23:22 +0200
parents ec30e1b078a9
children 2cb228f23f53
files .hgignore Makefile lib/apic.c lib/apic.h lib/db.c lib/db.h lib/strlcpy.c lib/strtonum.c lib/types.c lib/types.h lib/util.h man/scid.8.in scictl/scictl.c scid/page-api-projects.c scid/page-api-todo.c scid/page-api-workers.c sql/init.sql sql/job-add.sql sql/job-todo.sql sql/jobresult-add.sql sql/project-add.sql sql/project-find-id.sql sql/project-find.sql sql/project-list.sql sql/project-save.sql sql/project-update.sql sql/worker-add.sql sql/worker-find-id.sql sql/worker-find.sql sql/worker-list.sql sql/worker-save.sql
diffstat 31 files changed, 584 insertions(+), 675 deletions(-) [+]
line wrap: on
line diff
--- a/.hgignore	Tue Jul 19 22:45:44 2022 +0200
+++ b/.hgignore	Thu Jul 21 20:23:22 2022 +0200
@@ -21,6 +21,7 @@
 
 # executables.
 ^bcc$
+^scictl/scictl$
 ^scid/scid$
 ^sciworkerd/sciworkerd$
 
--- a/Makefile	Tue Jul 19 22:45:44 2022 +0200
+++ b/Makefile	Thu Jul 21 20:23:22 2022 +0200
@@ -26,6 +26,7 @@
                         lib/db.c                        \
                         lib/log.c                       \
                         lib/strlcpy.c                   \
+                        lib/strtonum.c                  \
                         lib/types.c                     \
                         lib/util.c
 LIBSCI_OBJS=            ${LIBSCI_SRCS:.c=.o}
@@ -35,15 +36,12 @@
                         sql/job-add.sql                 \
                         sql/job-todo.sql                \
                         sql/jobresult-add.sql           \
-                        sql/project-add.sql             \
-                        sql/project-find-id.sql         \
                         sql/project-find.sql            \
                         sql/project-list.sql            \
-                        sql/project-update.sql          \
-                        sql/worker-add.sql              \
+                        sql/project-save.sql            \
                         sql/worker-find.sql             \
-                        sql/worker-find-id.sql          \
-                        sql/worker-list.sql
+                        sql/worker-list.sql             \
+                        sql/worker-save.sql
 SQL_OBJS=               ${SQL_SRCS:.sql=.h}
 
 SCICTL=                 scictl/scictl
--- a/lib/apic.c	Tue Jul 19 22:45:44 2022 +0200
+++ b/lib/apic.c	Thu Jul 21 20:23:22 2022 +0200
@@ -19,7 +19,7 @@
 struct converter {
 	void *data;
 	size_t datasz;
-	int (*unpack)(void *, size_t, json_t *);
+	ssize_t (*unpack)(void *, size_t, json_t *);
 	json_t *(*pack)(const void *, size_t);
 };
 
@@ -133,6 +133,101 @@
 	return ret;
 }
 
+static ssize_t
+get(struct apic *req, const struct converter *cv, const char *fmt, ...)
+{
+	va_list ap;
+	ssize_t ret;
+
+	va_start(ap, fmt);
+	ret = perform(req, NULL, fmt, ap);
+	va_end(ap);
+
+	if (ret < 0)
+		return -1;
+	if (!req->doc || (!json_is_object(req->doc) && !json_is_array(req->doc)))
+		return snprintf(req->error, sizeof (req->error), "invalid JSON document received"), -1;
+	if ((ret = cv->unpack(cv->data, cv->datasz, req->doc)) < 0)
+		return snprintf(req->error, sizeof (req->error), "%s", strerror(errno));
+
+	return ret;
+}
+
+static int
+create(struct apic *req, const struct converter *cv, const char *fmt, ...)
+{
+	va_list ap;
+	int ret;
+	json_t *doc;
+	char *body;
+
+	memset(req, 0, sizeof (*req));
+
+	if (!(doc = cv->pack(cv->data, cv->datasz)))
+		return snprintf(req->error, sizeof (req->error), "%s", strerror(errno));
+	if (!(body = json_dumps(doc, JSON_COMPACT))) {
+		json_decref(doc);
+		return snprintf(req->error, sizeof (req->error), "%s", strerror(errno));
+	}
+
+	va_start(ap, fmt);
+	ret = perform(req, body, fmt, ap);
+	va_end(ap);
+
+	json_decref(doc);
+	free(body);
+
+	return 0;
+}
+
+static json_t *
+wrap_job_to(const void *data, size_t datasz)
+{
+	return job_to(data, datasz);
+}
+
+static ssize_t
+wrap_job_from(void *data, size_t datasz, json_t *doc)
+{
+	return job_from(data, datasz, doc);
+}
+
+static json_t *
+wrap_jobresult_to(const void *data, size_t datasz)
+{
+	return jobresult_to(data, datasz);
+}
+
+static ssize_t
+wrap_project_from(void *data, size_t datasz, json_t *doc)
+{
+	return project_from(data, datasz, doc);
+}
+
+static json_t *
+wrap_project_to(const void *data, size_t datasz)
+{
+	return project_to(data, datasz);
+}
+
+static ssize_t
+wrap_worker_from(void *data, size_t datasz, json_t *doc)
+{
+	return worker_from(data, datasz, doc);
+}
+
+static json_t *
+wrap_worker_to(const void *data, size_t datasz)
+{
+	return worker_to(data, datasz);
+}
+
+static ssize_t
+wrap_jobresult_from(void *data, size_t datasz, json_t *doc)
+{
+	return jobresult_from(data, datasz, doc);
+}
+
 int
 apic_get(struct apic *req, const char *fmt, ...)
 {
@@ -171,105 +266,6 @@
 	return ret;
 }
 
-static int
-get(struct apic *req, const struct converter *cv, const char *fmt, ...)
-{
-	va_list ap;
-	int ret;
-
-	va_start(ap, fmt);
-	ret = perform(req, NULL, fmt, ap);
-	va_end(ap);
-
-	memset(req, 0, sizeof (*req));
-
-	if (ret < 0)
-		return -1;
-	if (!req->doc || !json_is_object(req->doc))
-		return snprintf(req->error, sizeof (req->error), "invalid JSON object received"), -1;
-	if (cv->unpack(cv->data, cv->datasz, req->doc) < 0)
-		return snprintf(req->error, sizeof (req->error), "%s", strerror(errno));
-
-	return 0;
-}
-
-static int
-create(struct apic *req, const struct converter *cv, const char *fmt, ...)
-{
-	va_list ap;
-	int ret;
-	json_t *doc;
-	char *body;
-
-	memset(req, 0, sizeof (*req));
-
-	if (!(doc = cv->pack(cv->data, cv->datasz)))
-		return snprintf(req->error, sizeof (req->error), "%s", strerror(errno));
-	if (!(body = json_dumps(doc, JSON_COMPACT))) {
-		json_decref(doc);
-		return snprintf(req->error, sizeof (req->error), "%s", strerror(errno));
-	}
-
-	va_start(ap, fmt);
-	ret = perform(req, body, fmt, ap);
-	va_end(ap);
-	json_decref(doc);
-	free(body);
-
-	if (cv->unpack(cv->data, cv->datasz, req->doc) < 0)
-		return snprintf(req->error, sizeof (req->error), "%s", strerror(errno));
-
-	return 0;
-}
-
-static json_t *
-wrap_job_to(const void *data, size_t datasz)
-{
-	return job_to(data, datasz);
-}
-
-static int
-wrap_job_from(void *data, size_t datasz, json_t *doc)
-{
-	return job_from(data, datasz, doc);
-}
-
-static json_t *
-wrap_jobresult_to(const void *data, size_t datasz)
-{
-	return jobresult_to(data, datasz);
-}
-
-static int
-wrap_project_from(void *data, size_t datasz, json_t *doc)
-{
-	return project_from(data, datasz, doc);
-}
-
-static json_t *
-wrap_project_to(const void *data, size_t datasz)
-{
-	return project_to(data, datasz);
-}
-
-static int
-wrap_worker_from(void *data, size_t datasz, json_t *doc)
-{
-	return worker_from(data, datasz, doc);
-}
-
-static json_t *
-wrap_worker_to(const void *data, size_t datasz)
-{
-	return worker_to(data, datasz);
-}
-
-static int
-wrap_jobresult_from(void *data, size_t datasz, json_t *doc)
-{
-	return jobresult_from(data, datasz, doc);
-}
-
 int
 apic_job_add(struct apic *req, struct job *job)
 {
@@ -287,7 +283,7 @@
 }
 
 ssize_t
-apic_job_todo(struct apic *req, struct job *jobs, size_t jobsz, int worker_id)
+apic_job_todo(struct apic *req, struct job *jobs, size_t jobsz, intmax_t worker_id)
 {
 	assert(req);
 	assert(jobs);
@@ -298,7 +294,7 @@
 		.unpack = wrap_job_from
 	};
 
-	return get(req, &cv, "api/v1/jobs/%d", worker_id);
+	return get(req, &cv, "api/v1/jobs/%jd", worker_id);
 }
 
 int
@@ -318,7 +314,7 @@
 }
 
 int
-apic_project_add(struct apic *req, struct project *project)
+apic_project_save(struct apic *req, struct project *project)
 {
 	assert(req);
 	assert(project);
@@ -365,7 +361,7 @@
 }
 
 int
-apic_project_find(struct apic *req, struct project *project)
+apic_project_find(struct apic *req, struct project *project, const char *name)
 {
 	assert(req);
 	assert(project);
@@ -376,26 +372,11 @@
 		.unpack = wrap_project_from
 	};
 
-	return get(req, &cv, "api/v1/projects/%s", project->name);
+	return get(req, &cv, "api/v1/projects/%s", name);
 }
 
 int
-apic_project_find_id(struct apic *req, struct project *project)
-{
-	assert(req);
-	assert(project);
-
-	struct converter cv = {
-		.data = project,
-		.datasz = 1,
-		.unpack = wrap_project_from
-	};
-
-	return get(req, &cv, "api/v1/projects/%jd", project->id);
-}
-
-int
-apic_worker_add(struct apic *req, struct worker *wk)
+apic_worker_save(struct apic *req, struct worker *wk)
 {
 	assert(req);
 	assert(wk);
@@ -426,7 +407,7 @@
 }
 
 int
-apic_worker_find(struct apic *req, struct worker *wk)
+apic_worker_find(struct apic *req, struct worker *wk, const char *name)
 {
 	assert(req);
 	assert(wk);
@@ -437,20 +418,16 @@
 		.unpack = wrap_worker_from
 	};
 
-	return get(req, &cv, "api/v1/workers/%s", wk->name);
+	return get(req, &cv, "api/v1/workers/%s", name);
 }
 
-int
-apic_worker_find_id(struct apic *req, struct worker *wk)
+void
+apic_finish(struct apic *req)
 {
 	assert(req);
-	assert(wk);
 
-	struct converter cv = {
-		.data = wk,
-		.datasz = 1,
-		.unpack = wrap_worker_from
-	};
+	if (req->doc)
+		json_decref(req->doc);
 
-	return get(req, &cv, "api/v1/workers/%jd", wk->id);
+	memset(req, 0, sizeof (*req));
 }
--- a/lib/apic.h	Tue Jul 19 22:45:44 2022 +0200
+++ b/lib/apic.h	Thu Jul 21 20:23:22 2022 +0200
@@ -1,6 +1,9 @@
 #ifndef SCI_APIC_H
 #define SCI_APIC_H
 
+#include <sys/types.h>
+#include <stdint.h>
+
 #include <jansson.h>
 
 #include "config.h"
@@ -44,37 +47,28 @@
 apic_job_add(struct apic *, struct job *);
 
 ssize_t
-apic_job_todo(struct apic *, struct job *, size_t, int);
+apic_job_todo(struct apic *, struct job *, size_t, intmax_t);
 
 int
 apic_jobresult_add(struct apic *, struct jobresult *);
 
 int
-apic_project_add(struct apic *, struct project *);
-
-int
-apic_project_update(struct apic *, struct project *);
+apic_project_save(struct apic *, struct project *);
 
 ssize_t
 apic_project_list(struct apic *, struct project *, size_t);
 
 int
-apic_project_find(struct apic *, struct project *);
+apic_project_find(struct apic *, struct project *, const char *);
 
 int
-apic_project_find_id(struct apic *, struct project *);
-
-int
-apic_worker_add(struct apic *, struct worker *);
+apic_worker_save(struct apic *, struct worker *);
 
 ssize_t
 apic_worker_list(struct apic *, struct worker *, size_t);
 
 int
-apic_worker_find(struct apic *, struct worker *);
-
-int
-apic_worker_find_id(struct apic *, struct worker *);
+apic_worker_find(struct apic *, struct worker *, const char *);
 
 void
 apic_finish(struct apic *);
--- a/lib/db.c	Tue Jul 19 22:45:44 2022 +0200
+++ b/lib/db.c	Thu Jul 21 20:23:22 2022 +0200
@@ -33,15 +33,12 @@
 #include "sql/job-add.h"
 #include "sql/job-todo.h"
 #include "sql/jobresult-add.h"
-#include "sql/project-add.h"
-#include "sql/project-update.h"
 #include "sql/project-find.h"
-#include "sql/project-find-id.h"
 #include "sql/project-list.h"
-#include "sql/worker-add.h"
+#include "sql/project-save.h"
 #include "sql/worker-find.h"
-#include "sql/worker-find-id.h"
 #include "sql/worker-list.h"
+#include "sql/worker-save.h"
 
 #define CHAR(v) (const char *)(v)
 
@@ -59,11 +56,10 @@
 {
 	struct project *project = data;
 
-	project->id = sqlite3_column_int(stmt, 0);
-	project->name = util_strdup(CHAR(sqlite3_column_text(stmt, 1)));
-	project->desc = util_strdup(CHAR(sqlite3_column_text(stmt, 2)));
-	project->url = util_strdup(CHAR(sqlite3_column_text(stmt, 3)));
-	project->script = util_strdup(CHAR(sqlite3_column_text(stmt, 4)));
+	project->name = util_strdup(CHAR(sqlite3_column_text(stmt, 0)));
+	project->desc = util_strdup(CHAR(sqlite3_column_text(stmt, 1)));
+	project->url = util_strdup(CHAR(sqlite3_column_text(stmt, 2)));
+	project->script = util_strdup(CHAR(sqlite3_column_text(stmt, 3)));
 }
 
 static void
@@ -71,9 +67,8 @@
 {
 	struct worker *w = data;
 
-	w->id = sqlite3_column_int(stmt, 0);
-	w->name = util_strdup(CHAR(sqlite3_column_text(stmt, 1)));
-	w->desc = util_strdup(CHAR(sqlite3_column_text(stmt, 2)));
+	w->name = util_strdup(CHAR(sqlite3_column_text(stmt, 0)));
+	w->desc = util_strdup(CHAR(sqlite3_column_text(stmt, 1)));
 }
 
 static void
@@ -81,9 +76,9 @@
 {
 	struct job *job = data;
 
-	job->id = sqlite3_column_int(stmt, 0);
+	job->id = sqlite3_column_int64(stmt, 0);
 	job->tag = util_strdup(CHAR(sqlite3_column_text(stmt, 1)));
-	job->project_id = sqlite3_column_int(stmt, 2);
+	job->project_name = util_strdup(CHAR(sqlite3_column_text(stmt, 2)));
 }
 
 static void
@@ -211,19 +206,10 @@
 }
 
 int
-db_project_add(struct project *p)
+db_project_save(struct project *p)
 {
-	return (p->id = insert(CHAR(sql_project_add), "ssss", p->name, p->desc,
-	    p->url, p->script)) < 0 ? -1 : 0;
-}
-
-int
-db_project_update(const struct project *p)
-{
-	assert(p);
-
-	return update(CHAR(sql_project_update), "ssssi", p->name, p->desc,
-	    p->url, p->script, p->id);
+	return insert(CHAR(sql_project_save), "ssss", p->name, p->desc,
+	    p->url, p->script) < 0 ? -1 : 0;
 }
 
 ssize_t
@@ -253,24 +239,11 @@
 }
 
 int
-db_project_find_id(struct project *project, intmax_t id)
-{
-	struct list sel = {
-		.unpack = project_unpacker,
-		.data = project,
-		.datasz = 1,
-		.elemwidth = sizeof (*project),
-	};
-
-	return list(&sel, CHAR(sql_project_find_id), "i", id) == 1 ? 0 : -1;
-}
-
-int
-db_worker_add(struct worker *wk)
+db_worker_save(struct worker *wk)
 {
 	assert(wk);
 
-	return (wk->id = insert(CHAR(sql_worker_add), "ss", wk->name, wk->desc)) < 0 ? -1 : 0;
+	return insert(CHAR(sql_worker_save), "ss", wk->name, wk->desc) < 0 ? -1 : 0;
 }
 
 ssize_t
@@ -302,29 +275,16 @@
 }
 
 int
-db_worker_find_id(struct worker *wk, intmax_t id)
-{
-	struct list sel = {
-		.unpack = worker_unpacker,
-		.data = wk,
-		.datasz = 1,
-		.elemwidth = sizeof (*wk),
-	};
-
-	return list(&sel, CHAR(sql_worker_find_id), "i", id) == 1 ? 0 : -1;
-}
-
-int
 db_job_add(struct job *job)
 {
 	assert(job);
 
 	return (job->id = insert(CHAR(sql_job_add),
-	    "si", job->tag, job->project_id)) < 0 ? -1 : 0;
+	    "ss", job->tag, job->project_name)) < 0 ? -1 : 0;
 }
 
 ssize_t
-db_job_todo(struct job *jobs, size_t jobsz, int worker_id)
+db_job_todo(struct job *jobs, size_t jobsz, const char *worker)
 {
 	assert(jobs);
 
@@ -335,7 +295,7 @@
 		.elemwidth = sizeof (*jobs),
 	};
 
-	return list(&sel, CHAR(sql_job_todo), "iiz", worker_id, worker_id, jobsz);
+	return list(&sel, CHAR(sql_job_todo), "ssz", worker, worker, jobsz);
 }
 
 int
@@ -343,8 +303,8 @@
 {
 	assert(r);
 
-	return (r->id = insert(CHAR(sql_jobresult_add), "iiis", r->job_id,
-	    r->worker_id, r->exitcode, r->log)) < 0 ? -1 : 0;
+	return (r->id = insert(CHAR(sql_jobresult_add), "jsis", r->job_id,
+	    r->worker_name, r->exitcode, r->log)) < 0 ? -1 : 0;
 }
 
 void
--- a/lib/db.h	Tue Jul 19 22:45:44 2022 +0200
+++ b/lib/db.h	Thu Jul 21 20:23:22 2022 +0200
@@ -35,16 +35,13 @@
 db_job_add(struct job *);
 
 ssize_t
-db_job_todo(struct job *, size_t, int);
+db_job_todo(struct job *, size_t, const char *);
 
 int
 db_jobresult_add(struct jobresult *);
 
 int
-db_project_add(struct project *);
-
-int
-db_project_update(const struct project *);
+db_project_save(struct project *);
 
 ssize_t
 db_project_list(struct project *, size_t);
@@ -53,10 +50,7 @@
 db_project_find(struct project *, const char *);
 
 int
-db_project_find_id(struct project *, intmax_t);
-
-int
-db_worker_add(struct worker *);
+db_worker_save(struct worker *);
 
 ssize_t
 db_worker_list(struct worker *, size_t);
@@ -64,9 +58,6 @@
 int
 db_worker_find(struct worker *, const char *);
 
-int
-db_worker_find_id(struct worker *, intmax_t);
-
 void
 db_finish(void);
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/strlcpy.c	Thu Jul 21 20:23:22 2022 +0200
@@ -0,0 +1,49 @@
+/*	$OpenBSD: strlcpy.c,v 1.16 2019/01/25 00:19:25 millert Exp $	*/
+
+/*
+ * Copyright (c) 1998, 2015 Todd C. Miller <millert@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+
+/*
+ * Copy string src to buffer dst of size dsize.  At most dsize-1
+ * chars will be copied.  Always NUL terminates (unless dsize == 0).
+ * Returns strlen(src); if retval >= dsize, truncation occurred.
+ */
+size_t
+util_strlcpy(char *dst, const char *src, size_t dsize)
+{
+	const char *osrc = src;
+	size_t nleft = dsize;
+
+	/* Copy as many bytes as will fit. */
+	if (nleft != 0) {
+		while (--nleft != 0) {
+			if ((*dst++ = *src++) == '\0')
+				break;
+		}
+	}
+
+	/* Not enough room in dst, add NUL and traverse rest of src. */
+	if (nleft == 0) {
+		if (dsize != 0)
+			*dst = '\0';		/* NUL-terminate dst */
+		while (*src++)
+			;
+	}
+
+	return(src - osrc - 1);	/* count does not include NUL */
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/strtonum.c	Thu Jul 21 20:23:22 2022 +0200
@@ -0,0 +1,65 @@
+/*	$OpenBSD: strtonum.c,v 1.8 2015/09/13 08:31:48 guenther Exp $	*/
+
+/*
+ * Copyright (c) 2004 Ted Unangst and Todd Miller
+ * All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <errno.h>
+#include <limits.h>
+#include <stdlib.h>
+
+#define	INVALID		1
+#define	TOOSMALL	2
+#define	TOOLARGE	3
+
+long long
+util_strtonum(const char *numstr, long long minval, long long maxval,
+    const char **errstrp)
+{
+	long long ll = 0;
+	int error = 0;
+	char *ep;
+	struct errval {
+		const char *errstr;
+		int err;
+	} ev[4] = {
+		{ NULL,		0 },
+		{ "invalid",	EINVAL },
+		{ "too small",	ERANGE },
+		{ "too large",	ERANGE },
+	};
+
+	ev[0].err = errno;
+	errno = 0;
+	if (minval > maxval) {
+		error = INVALID;
+	} else {
+		ll = strtoll(numstr, &ep, 10);
+		if (numstr == ep || *ep != '\0')
+			error = INVALID;
+		else if ((ll == LLONG_MIN && errno == ERANGE) || ll < minval)
+			error = TOOSMALL;
+		else if ((ll == LLONG_MAX && errno == ERANGE) || ll > maxval)
+			error = TOOLARGE;
+	}
+	if (errstrp != NULL)
+		*errstrp = ev[error].errstr;
+	errno = ev[error].err;
+	if (error)
+		ll = 0;
+
+	return (ll);
+}
--- a/lib/types.c	Tue Jul 19 22:45:44 2022 +0200
+++ b/lib/types.c	Thu Jul 21 20:23:22 2022 +0200
@@ -28,9 +28,9 @@
 static inline json_t *
 job_packer(const struct job *job)
 {
-	return json_pack("{si si ss}",
+	return json_pack("{si ss ss}",
 	    "id",               job->id,
-	    "project_id",       job->project_id,
+	    "project_name",     job->project_name,
 	    "tag",              job->tag
 	);
 }
@@ -38,14 +38,16 @@
 static inline int
 job_unpacker(struct job *job, json_t *doc)
 {
-	const int ret = json_unpack(doc, "{si si ss}",
+	const int ret = json_unpack(doc, "{si ss ss}",
 	    "id",               &job->id,
-	    "project_id",       &job->project_id,
+	    "project_name",     &job->project_name,
 	    "tag",              &job->tag
 	);
 
-	if (ret == 0)
+	if (ret == 0) {
+		job->project_name = util_strdup(job->project_name);
 		job->tag = util_strdup(job->tag);
+	}
 
 	return ret;
 }
@@ -53,10 +55,10 @@
 static inline json_t *
 jobresult_packer(const struct jobresult *res)
 {
-	return json_pack("{si si si si ss}",
+	return json_pack("{si si ss si ss}",
 	    "id",               res->id,
 	    "job_id",           res->job_id,
-	    "worker_id",        res->worker_id,
+	    "worker_name",      res->worker_name,
 	    "exitcode",         res->exitcode,
 	    "log",              res->log
 	);
@@ -65,16 +67,18 @@
 static inline int
 jobresult_unpacker(struct jobresult *res, json_t *doc)
 {
-	const int ret = json_unpack(doc, "{si si si si ss}",
+	const int ret = json_unpack(doc, "{si si ss si ss}",
 	    "id",               &res->id,
 	    "job_id",           &res->job_id,
-	    "worker_id",        &res->worker_id,
+	    "worker_name",      &res->worker_name,
 	    "exitcode",         &res->exitcode,
 	    "log",              &res->log
 	);
 
-	if (ret == 0)
+	if (ret == 0) {
+		res->worker_name = util_strdup(res->worker_name);
 		res->log = util_strdup(res->log);
+	}
 
 	return ret;
 }
@@ -82,8 +86,7 @@
 static inline json_t *
 worker_packer(const struct worker *w)
 {
-	return json_pack("{si ss ss}",
-	    "id",               w->id,
+	return json_pack("{ss ss}",
 	    "name",             w->name,
 	    "desc",             w->desc
 	);
@@ -92,8 +95,7 @@
 static inline int
 worker_unpacker(struct worker *w, json_t *doc)
 {
-	const int ret = json_unpack(doc, "{si ss ss}",
-	    "id",               &w->id,
+	const int ret = json_unpack(doc, "{ss ss}",
 	    "name",             &w->name,
 	    "desc",             &w->desc
 	);
@@ -109,8 +111,7 @@
 static inline json_t *
 project_packer(struct project *p)
 {
-	return json_pack("{si ss ss ss ss}",
-	    "id",               p->id,
+	return json_pack("{ss ss ss ss}",
 	    "name",             p->name,
 	    "desc",             p->desc,
 	    "url",              p->url,
@@ -121,8 +122,7 @@
 static inline int
 project_unpacker(struct project *p, json_t *doc)
 {
-	const int ret = json_unpack(doc, "{si ss ss ss ss}",
-	    "id",               &p->id,
+	const int ret = json_unpack(doc, "{ss ss ss ss}",
 	    "name",             &p->name,
 	    "desc",             &p->desc,
 	    "url",              &p->url,
@@ -197,6 +197,14 @@
 	return from(jobs, jobsz, sizeof (*jobs), doc, (unpacker)job_unpacker);
 }
 
+void
+job_finish(struct job *job)
+{
+	assert(job);
+
+	free(job->tag);
+}
+
 json_t *
 jobresult_to(const struct jobresult *res, size_t resz)
 {
@@ -214,6 +222,14 @@
 	return from(res, resz, sizeof (*res), doc, (unpacker)jobresult_unpacker);
 }
 
+void
+jobresult_finish(struct jobresult *res)
+{
+	assert(res);
+
+	free(res->log);
+}
+
 json_t *
 worker_to(const struct worker *w, size_t wsz)
 {
@@ -231,6 +247,15 @@
 	return from(w, wsz, sizeof (*w), doc, (unpacker)worker_unpacker);
 }
 
+void
+worker_finish(struct worker *w)
+{
+	assert(w);
+
+	free(w->name);
+	free(w->desc);
+}
+
 json_t *
 project_to(const struct project *proj, size_t projsz)
 {
@@ -247,3 +272,14 @@
 
 	return from(proj, projsz, sizeof (*proj), doc, (unpacker)project_unpacker);
 }
+
+void
+project_finish(struct project *proj)
+{
+	assert(proj);
+
+	free(proj->name);
+	free(proj->desc);
+	free(proj->url);
+	free(proj->script);
+}
--- a/lib/types.h	Tue Jul 19 22:45:44 2022 +0200
+++ b/lib/types.h	Thu Jul 21 20:23:22 2022 +0200
@@ -27,26 +27,24 @@
 
 struct job {
 	intmax_t id;
-	int project_id;
+	char *project_name;
 	char *tag;
 };
 
 struct jobresult {
 	intmax_t id;
 	int job_id;
-	int worker_id;
+	char *worker_name;
 	int exitcode;
 	char *log;
 };
 
 struct worker {
-	intmax_t id;
 	char *name;
 	char *desc;
 };
 
 struct project {
-	intmax_t id;
 	char *name;
 	char *desc;
 	char *url;
--- a/lib/util.h	Tue Jul 19 22:45:44 2022 +0200
+++ b/lib/util.h	Thu Jul 21 20:23:22 2022 +0200
@@ -69,4 +69,7 @@
 void
 util_die(const char *, ...);
 
+long long
+util_strtonum(const char *, long long, long long, const char **);
+
 #endif /* !SCI_UTIL_H */
--- a/man/scid.8.in	Tue Jul 19 22:45:44 2022 +0200
+++ b/man/scid.8.in	Thu Jul 21 20:23:22 2022 +0200
@@ -23,37 +23,36 @@
 .\" SYNOPSIS
 .Sh SYNOPSIS
 .Nm
+.Op Fl f
 .Op Fl d Ar database-file
-.Op Fl s Ar socket-file
 .\" DESCRIPTION
 .Sh DESCRIPTION
 The
 .Nm
-program is the main controller daemon responsible of the database access. It
-opens it, creates if does not exist and then update its content.
+program is the main controller daemon responsible of the database access and
+web content. It opens it, creates if does not exist and then update its
+content.
+.Pp
+After that, it opens a CGI/FastCGI service to get HTTP requests either from a
+web browser or through the dedicated
+.Xr scictl 1
+utility.
 .Pp
 It is part of the
 .Xr sci 7
 continuous integration framework.
 .Pp
-It also opens a UNIX socket connection on
-.Pa @VARDIR@/run/sci.sock
-by default unless option
-.Fl s
-has been specified, it will opens it on
-.Ar database-file
-argument.
-.Pp
-Clients connect to
-.Nm
-and make requests to create, update or list projects, workers and jobs. This can
-either be done using
-.Nm scictl
-utility or the HTTP interface using
-.Nm sciwebd .
+Available options:
+.Bl -tag
+.It Fl f
+Runs as FastCGI process.
+.It Fl d Ar database-file
+Use path specified
+.Pa database-file
+as alternative database location.
+.El
 .\" SEE ALSO
 .Sh SEE ALSO
 .Xr sci 7 ,
 .Xr scictl 8 ,
-.Xr sciwebd 8 ,
 .Xr sciworkerd 8
--- a/scictl/scictl.c	Tue Jul 19 22:45:44 2022 +0200
+++ b/scictl/scictl.c	Thu Jul 21 20:23:22 2022 +0200
@@ -16,64 +16,81 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
-#include <err.h>
+#include <limits.h>
+#include <errno.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
-#include <stdnoreturn.h>
 #include <unistd.h>
 
+#include "apic.h"
 #include "config.h"
-#include "req.h"
 #include "types.h"
 #include "util.h"
 
-noreturn static void
+static void
 usage(void)
 {
-	fprintf(stderr, "usage: %s [-s sock] command [args...]\n", getprogname());
+	fprintf(stderr, "usage: %s [-u baseurl] command [args...]\n", getprogname());
 	exit(1);
 }
 
-noreturn static void
+static void
 help(void)
 {
-	fprintf(stderr, "usage: %s job-add project tag\n", getprogname());
-	fprintf(stderr, "       %s job-todo worker\n", getprogname());
-	fprintf(stderr, "       %s jobresult-add id worker exitcode console\n", getprogname());
-	fprintf(stderr, "       %s project-add name desc url script\n", getprogname());
-	fprintf(stderr, "       %s project-info name\n", getprogname());
-	fprintf(stderr, "       %s project-list\n", getprogname());
-	fprintf(stderr, "       %s project-update name key value\n", getprogname());
-	fprintf(stderr, "       %s worker-add name desc\n", getprogname());
-	fprintf(stderr, "       %s worker-list\n", getprogname());
+	fprintf(stderr, "usage: scictl job-add project tag\n");
+	fprintf(stderr, "       scictl job-todo worker\n");
+	fprintf(stderr, "       scictl jobresult-add id worker exitcode console\n");
+	fprintf(stderr, "       scictl project-add name desc url script\n");
+	fprintf(stderr, "       scictl project-info name\n");
+	fprintf(stderr, "       scictl project-list\n");
+	fprintf(stderr, "       scictl project-update name key value\n");
+	fprintf(stderr, "       scictl worker-add name desc\n");
+	fprintf(stderr, "       scictl worker-list\n");
 	exit(0);
 }
 
+static inline void
+replace(char **str, const char *new)
+{
+	free(*str);
+	*str = util_strdup(new);
+}
+
+long long
+toint(const char *s)
+{
+	long long v;
+	const char *err;
+
+	v = util_strtonum(s, 0, LLONG_MAX, &err);
+
+	if (err)
+		util_die("abort: %s\n", err);
+
+	return v;
+}
+
 static char *
 readfile(const char *path)
 {
 	FILE *fp, *str;
-	char buf[BUFSIZ], *console;
-	size_t nr;
+	char buf[BUFSIZ], *console = NULL;
+	size_t nr, consolesz = 0;
 
 	if (strcmp(path, "-") == 0)
 		fp = stdin;
 	else if (!(fp = fopen(path, "r")))
-		err(1, "%s", path);
+		util_die("abort: %s: %s\n", path, strerror(errno));
 
-	console = util_calloc(1, SCI_MSG_MAX);
-
-	if (!(str = fmemopen(console, SCI_MSG_MAX, "w")))
-		err(1, "fmemopen");
+	if (!(str = open_memstream(&console, &consolesz)))
+		util_die("abort: open_memstream: %s\n", strerror(errno));
 
 	while ((nr = fread(buf, 1, sizeof (buf), fp)) > 0)
 		fwrite(buf, 1, nr, str);
 
-	if ((ferror(fp) && !feof(fp)) || (ferror(str) && !feof(str))) {
-		free(console);
-		console = NULL;
-	}
+	if ((ferror(fp) && !feof(fp)) || (ferror(str) && !feof(str)))
+		util_die("abort: %s\n", strerror(errno));
 
 	fclose(str);
 	fclose(fp);
@@ -87,285 +104,218 @@
 	return fwrite(s, w, n, data);
 }
 
-static json_t *
-parse(const char *data)
-{
-	json_t *doc;
-	json_error_t err;
-
-	if (!(json_loads(doc, 0, &err)))
-		die("abort: unable to parse JSON: %s\n", err.text);
-
-	return doc;
-}
-
-static json_t *
-get(const char *url)
-{
-	CURL *curl;
-	CURLcode code;
-	FILE *fp;
-	char buf[HTTP_BUF_MAX];
-	long ret;
-
-	if (!(fp = fmemopen(buf, sizeof (buf), "w")))
-		die("abort: %s", strerror(errno));
-
-	curl = curl_easy_init();
-	curl_easy_setopt(curl, CURLOPT_URL, url);
-	curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, extract);
-	curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp);
-	curl_easy_setopt(curl, CURLOPT_TIMEOUT, 3L);
-	code = curl_easy_perform(curl);
-
-	if (code != CURLE_OK)
-		die("abort: %s", curl_easy_strerror(code));
-
-	curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &ret);
-
-	if (ret != 200)
-		die("abort: HTTP %ld\n", ret);
-
-	curl_easy_cleanup(curl);
-	fclose(fp);
-
-	return parse(buf);
-}
-
-static struct req
+static void
 cmd_job_add(int argc, char **argv)
 {
 	struct job job = {0};
-	struct project project = {0};
+	struct apic req;
+
+	if (argc < 3)
+		usage();
+
+	job.project_name = util_strdup(argv[1]);
+	job.tag = util_strdup(argv[2]);
+
+	if (apic_job_add(&req, &job) < 0)
+		util_die("abort: %s\n", req.error);
+
+	apic_finish(&req);
+	job_finish(&job);
+}
+
+static void
+cmd_job_todo(int argc, char **argv)
+{
+	struct job jobs[SCI_JOB_LIST_MAX] = {0};
+	size_t jobsz;
+	struct apic req;
 
 	if (argc < 2)
 		usage();
 
-	project.name = argv[0];
-
-	if (_project_find(&project, argv[0])).status)
-		return rp;
-
-	job.project_id = project.id;
-	job.tag = argv[1];
-
-	rj = req_job_add(&job);
-	req_finish(&rp);
-
-	return rj;
-}
-
-static struct req
-cmd_job_todo(int argc, char **argv)
-{
-	struct project projects[SCI_PROJECT_MAX] = {0};
-	struct job jobs[SCI_JOB_LIST_MAX] = {0};
-	struct req rp, rj;
-	size_t projectsz = UTIL_SIZE(projects), jobsz = UTIL_SIZE(jobs);
-
-	if (argc < 1)
-		usage();
-
-	/* First retrieve projects for a better listing. */
-	if ((rp = req_project_list(projects, &projectsz)).status)
-		return rp;
-
-	if ((rj = req_job_todo(jobs, &jobsz, argv[0])).status) {
-		req_finish(&rp);
-		return rj;
-	}
+	if ((jobsz = apic_job_todo(&req, jobs, UTIL_SIZE(jobs), toint(argv[1]))))
+		util_die("abort: %s\n", req.error);
 
 	for (size_t i = 0; i < jobsz; ++i) {
-		const char *project = "unknown";
-
-		/* Find project if exists (it should). */
-		for (size_t p = 0; p < projectsz; ++p) {
-			if (projects[p].id == jobs[i].project_id) {
-				project = projects[p].name;
-				break;
-			}
-		}
-
-		printf("%-16s%d\n", "id:", jobs[i].id);
+		printf("%-16s%jd\n", "id:", jobs[i].id);
 		printf("%-16s%s\n", "tag:", jobs[i].tag);
-		printf("%-16s%s\n", "project:", project);
+		printf("%-16s%s\n", "project:", jobs[i].project_name);
 
 		if (i + 1 < jobsz)
 			printf("\n");
+
+		job_finish(&jobs[i]);
 	}
 
-	req_finish(&rp);
-	
-	return rj;
+	apic_finish(&req);
 }
 
-static struct req
+static void
 cmd_jobresult_add(int argc, char **argv)
 {
 	struct jobresult res = {0};
-	struct worker wk = {0};
-	struct req rw, rj;
-	char *log;
+	struct apic req;
+
+	if (argc < 5)
+		usage();
+
+	res.job_id = toint(argv[1]);
+	res.worker_name = util_strdup(argv[2]);
+	res.exitcode = toint(argv[3]);
+	res.log = readfile(argv[4]);
+
+	if (apic_jobresult_add(&req, &res) < 0)
+		util_die("abort: unable to add job result: %s\n", req.error);
+
+	apic_finish(&req);
+	jobresult_finish(&res);
+}
+
+static void
+cmd_project_add(int argc, char **argv)
+{
+	struct project pc = {0};
+	struct apic req;
 
 	if (argc < 5)
 		usage();
 
-	/* Find worker id. */
-	if ((rw = req_worker_find(&wk, argv[1])).status)
-		return rw;
+	pc.name = util_strdup(argv[1]);
+	pc.desc = util_strdup(argv[2]);
+	pc.url = util_strdup(argv[3]);
+	pc.script = readfile(argv[4]);
 
-	res.job_id = strtoll(argv[0], NULL, 10);
-	res.exitcode = strtoll(argv[2], NULL, 10);
-	res.worker_id = wk.id;
-	res.log = log = readfile(argv[3]);
-	rj = req_jobresult_add(&res);
+	if (apic_project_save(&req, &pc) < 0)
+		util_die("abort: unable to create project: %s\n", req.error);
 
-	free(log);
-	req_finish(&rw);
-
-	return rj;
+	apic_finish(&req);
+	project_finish(&pc);
 }
 
-static struct req
-cmd_project_add(int argc, char **argv)
+static void
+cmd_project_update(int argc, char **argv)
 {
-	struct project pc = {0};
-	struct req res;
-	char *script;
+	struct project pc;
+	struct apic req;
 
 	if (argc < 4)
 		usage();
 
-	pc.name = argv[0];
-	pc.desc = argv[1];
-	pc.url = argv[2];
-	pc.script = script = readfile(argv[3]);
-	res = req_project_add(&pc);
+	if (apic_project_find(&req, &pc, argv[1]) < 0)
+		util_die("abort: unable to find project: %s\n", req.error);
 
-	free(script);
+	if (strcmp(argv[2], "name") == 0)
+		replace(&pc.name, argv[3]);
+	else if (strcmp(argv[2], "desc") == 0)
+		replace(&pc.desc, argv[3]);
+	else if (strcmp(argv[2], "url") == 0)
+		replace(&pc.url, argv[3]);
+	else if (strcmp(argv[2], "script") == 0)
+		replace(&pc.script, readfile(argv[3]));
 
-	return res;
+	if (apic_project_save(&req, &pc) < 0)
+		util_die("abort: unable to save project: %s\n", req.error);
+
+	apic_finish(&req);
+	project_finish(&pc);
 }
 
-static struct req
-cmd_project_update(int argc, char **argv)
-{
-	struct project pc;
-	struct req rget, rsend;
-	char *script = NULL;
-
-	if (argc < 3)
-		help();
-
-	if ((rget = req_project_find(&pc, argv[0])).status)
-		return rget;
-
-	if (strcmp(argv[1], "name") == 0)
-		pc.name = argv[2];
-	else if (strcmp(argv[1], "desc") == 0)
-		pc.desc = argv[2];
-	else if (strcmp(argv[1], "url") == 0)
-		pc.url = argv[2];
-	else if (strcmp(argv[1], "script") == 0)
-		pc.script = script = readfile(argv[2]);
-
-	rsend = req_project_update(&pc);
-
-	req_finish(&rget);
-	free(script);
-
-	return rsend;
-}
-
-static struct req
+static void
 cmd_project_info(int argc, char **argv)
 {
 	struct project project = {0};
-	struct req req;
+	struct apic req;
 
-	if (argc < 1)
+	if (argc < 2)
 		usage();
-	if ((req = req_project_find(&project, argv[0])).status)
-		return req;
+	if (apic_project_find(&req, &project, argv[1]) < 0)
+		util_die("abort: unable to find project: %s\n", req.error);
 
-	printf("%-16s%d\n", "id:", project.id);
 	printf("%-16s%s\n", "name:", project.name);
 	printf("%-16s%s\n", "desc:", project.desc);
 	printf("%-16s%s\n", "url:", project.url);
 	printf("\n");
 	printf("%s", project.script);
 
-	return req;
+	apic_finish(&req);
+	project_finish(&project);
 }
 
-static struct req
+static void
 cmd_project_list(int argc, char **argv)
 {
 	(void)argc;
 	(void)argv;
 
 	struct project projects[SCI_PROJECT_MAX] = {0};
-	struct req req;
-	size_t projectsz = UTIL_SIZE(projects);
+	struct apic req;
+	ssize_t projectsz;
 
-	if ((req = req_project_list(projects, &projectsz)).status)
-		return req;
+	if ((projectsz = apic_project_list(&req, projects, UTIL_SIZE(projects))) < 0)
+		util_die("abort: unable to list projects: %s\n", req.error);
 
 	for (size_t i = 0; i < projectsz; ++i) {
-		printf("%-16s%d\n", "id:", projects[i].id);
 		printf("%-16s%s\n", "name:", projects[i].name);
 		printf("%-16s%s\n", "desc:", projects[i].desc);
 		printf("%-16s%s\n", "url:", projects[i].url);
 
 		if (i + 1 < projectsz)
 			printf("\n");
+
+		project_finish(&projects[i]);
 	}
 
-	return req;
+	apic_finish(&req);
 }
 
-static struct req
+static void
 cmd_worker_add(int argc, char **argv)
 {
 	struct worker wk = {0};
+	struct apic req;
 
-	if (argc < 2)
+	if (argc < 3)
 		usage();
 
-	wk.name = argv[0];
-	wk.desc = argv[1];
+	wk.name = util_strdup(argv[1]);
+	wk.desc = util_strdup(argv[2]);
 
-	return req_worker_add(&wk);
+	if (apic_worker_save(&req, &wk) < 0)
+		util_die("abort: unable to save worker: %s\n", req.error);
+
+	worker_finish(&wk);
+	apic_finish(&req);
 }
 
-static struct req
+static void
 cmd_worker_list(int argc, char **argv)
 {
 	(void)argc;
 	(void)argv;
 
 	struct worker wk[SCI_WORKER_MAX];
-	struct req req;
-	size_t wksz = UTIL_SIZE(wk);
+	struct apic req;
+	ssize_t wksz;
 
-	if ((req = req_worker_list(wk, &wksz)).status)
-		return req;
+	if ((wksz = apic_worker_list(&req, wk, UTIL_SIZE(wk))) < 0)
+		util_die("abort: unable to list worker: %s\n", req.error);
 
 	for (size_t i = 0; i < wksz; ++i) {
-		printf("%-16s%d\n", "id:", wk[i].id);
 		printf("%-16s%s\n", "name:", wk[i].name);
 		printf("%-16s%s\n", "desc:", wk[i].desc);
 
 		if (i + 1 < wksz)
 			printf("\n");
+
+		worker_finish(&wk[i]);
 	}
 
-	return req;
+	apic_finish(&req);
 }
 
 static struct {
 	const char *name;
-	struct req (*exec)(int, char **);
+	void (*exec)(int, char **);
 } commands[] = {
 	{ "job-add",            cmd_job_add             },
 	{ "job-todo",           cmd_job_todo            },
@@ -382,14 +332,21 @@
 int
 main(int argc, char **argv)
 {
-	int ch, cmdfound = 0;
+	int ch;
 
-	setprogname("scictl");
+	opterr = 0;
+	setenv("POSIXLY_CORRECT", "1", 1);
 
-	while ((ch = getopt(argc, argv, "s:")) != -1) {
+	while ((ch = getopt(argc, argv, "u:")) != -1) {
 		switch (ch) {
-		case 's':
-			req_set_path(optarg);
+		case 'u':
+			util_strlcpy(apiconf.baseurl, optarg, sizeof (apiconf.baseurl));
+			break;
+		case '?':
+			util_die("abort: invalid option: %c\n", ch);
+			break;
+		case ':':
+			util_die("abort: missing value for option %c\n", ch);
 			break;
 		default:
 			break;
@@ -399,26 +356,19 @@
 	argc -= optind;
 	argv += optind;
 
-	if (argc <= 0)
+	optind = 1;
+
+	if (argc < 1)
 		usage();
 	if (strcmp(argv[0], "help") == 0)
 		help();
 
 	for (size_t i = 0; commands[i].name; ++i) {
-		struct req res;
-
 		if (strcmp(commands[i].name, argv[0]) == 0) {
-			res = commands[i].exec(--argc, ++argv);
-			cmdfound = 1;
-
-			if (res.status)
-				warnx("%s", json_string_value(json_object_get(res.msg, "error")));
-
-			req_finish(&res);
-			break;
+			commands[i].exec(argc, argv);
+			return 0;
 		}
 	}
 
-	if (!cmdfound)
-		errx(1, "abort: command %s not found", argv[0]);
+	util_die("abort: invalid command: %s\n", argv[0]);
 }
--- a/scid/page-api-projects.c	Tue Jul 19 22:45:44 2022 +0200
+++ b/scid/page-api-projects.c	Thu Jul 21 20:23:22 2022 +0200
@@ -53,7 +53,7 @@
 		log_warn("api/post: invalid JSON input: %s", err.text);
 	else if (project_from(&res, 1, doc) < 0)
 		log_warn("api/post: failed to decode parameters");
-	else if (db_project_add(&res) < 0)
+	else if (db_project_save(&res) < 0)
 		log_warn("api/post: database save error");
 	else
 		ret = 0;
@@ -96,19 +96,6 @@
 }
 
 static void
-get_one_id(struct kreq *r, intmax_t id)
-{
-	struct project project = {0};
-
-	if (db_project_find_id(&project, id) < 0)
-		page(r, NULL, KHTTP_500, KMIME_APP_JSON, NULL);
-	else {
-		push(r, &project);
-		project_finish(&project);
-	}
-}
-
-static void
 get_all(struct kreq *r)
 {
 	struct project projects[SCI_PROJECT_MAX] = {0};
@@ -129,21 +116,18 @@
 }
 
 /*
- * GET /api/v1/projects[/<name|id>]
- * ------------------------------
+ * GET /api/v1/projects[/<name>]
+ * -----------------------------
  *
  * Retrieve one project or a list of projects depending on the presence of
- * <name> or <id> parameter.
+ * <name> parameter.
  */
 static void
 get(struct kreq *r)
 {
-	char name[128];
-	intmax_t id;
+	char name[128] = {0};
 
-	if (sscanf(r->path, "v1/projects/%jd", &id) == 1)
-		get_one_id(r, id);
-	else if (sscanf(r->path, "v1/projects/%127s", name) == 1)
+	if (sscanf(r->path, "v1/projects/%127s", name) == 1)
 		get_one(r, name);
 	else
 		get_all(r);
--- a/scid/page-api-todo.c	Tue Jul 19 22:45:44 2022 +0200
+++ b/scid/page-api-todo.c	Thu Jul 21 20:23:22 2022 +0200
@@ -76,7 +76,7 @@
 
 /*
  * GET /api/v1/todo/<worker-name>
- * ----------------
+ * ------------------------------
  *
  * Retrieve a list of jobs to perform for this worker name.
  */
@@ -85,14 +85,8 @@
 {
 	struct job jobs[SCI_JOB_LIST_MAX];
 	ssize_t jobsz;
-	struct worker wk = {0};
 
-	if (db_worker_find(&wk, util_basename(r->path)) < 0) {
-		page(r, NULL, KHTTP_404, KMIME_APP_JSON, NULL);
-		return;
-	}
-
-	if ((jobsz = db_job_todo(jobs, UTIL_SIZE(jobs), wk.id)) < 0)
+	if ((jobsz = db_job_todo(jobs, UTIL_SIZE(jobs), util_basename(r->path))) < 0)
 		page(r, NULL, KHTTP_500, KMIME_APP_JSON, NULL);
 	else {
 		khttp_head(r, kresps[KRESP_CONTENT_TYPE], "%s", kmimetypes[KMIME_APP_JSON]);
@@ -107,7 +101,7 @@
 }
 
 void
-page_api_v1_jobs(struct kreq *r)
+page_api_v1_todo(struct kreq *r)
 {
 	assert(r);
 
--- a/scid/page-api-workers.c	Tue Jul 19 22:45:44 2022 +0200
+++ b/scid/page-api-workers.c	Thu Jul 21 20:23:22 2022 +0200
@@ -35,7 +35,7 @@
 		log_warn("api/post: invalid JSON input: %s", err.text);
 	else if (worker_from(&res, 1, doc) < 0)
 		log_warn("api/post: failed to decode parameters");
-	else if (db_worker_add(&res) < 0)
+	else if (db_worker_save(&res) < 0)
 		log_warn("api/post: database save error");
 	else
 		ret = 0;
@@ -78,19 +78,6 @@
 }
 
 static void
-get_one_id(struct kreq *r, intmax_t id)
-{
-	struct worker worker = {0};
-
-	if (db_worker_find_id(&worker, id) < 0)
-		page(r, NULL, KHTTP_500, KMIME_APP_JSON, NULL);
-	else {
-		push(r, &worker);
-		worker_finish(&worker);
-	}
-}
-
-static void
 get_all(struct kreq *r)
 {
 	struct worker workers[SCI_PROJECT_MAX] = {0};
@@ -111,21 +98,18 @@
 }
 
 /*
- * GET /api/v1/workers[/<name|id>]
- * ------------------------------
+ * GET /api/v1/workers[/<name>]
+ * ----------------------------
  *
  * Retrieve one worker or a list of workers depending on the presence of
- * <name> or <id> parameter.
+ * <name> parameter.
  */
 static void
 get(struct kreq *r)
 {
-	char name[128];
-	int id;
+	char name[128] = {0};
 
-	if (sscanf(r->path, "v1/workers/%d", &id) == 1)
-		get_one_id(r, id);
-	else if (sscanf(r->path, "v1/workers/%127s", name) == 1)
+	if (sscanf(r->path, "v1/workers/%127s", name) == 1)
 		get_one(r, name);
 	else
 		get_all(r);
--- a/sql/init.sql	Tue Jul 19 22:45:44 2022 +0200
+++ b/sql/init.sql	Thu Jul 21 20:23:22 2022 +0200
@@ -17,7 +17,7 @@
 --
 
 CREATE TABLE IF NOT EXISTS project(
-	`name` TEXT NOT NULL UNIQUE,
+	`name` TEXT NOT NULL PRIMARY KEY,
 	`desc` TEXT NOT NULL,
 	`url` TEXT NOT NULL,
 	`script` TEXT NOT NULL,
@@ -25,20 +25,20 @@
 );
 
 CREATE TABLE IF NOT EXISTS worker(
-	`name` TEXT NOT NULL UNIQUE,
+	`name` TEXT NOT NULL PRIMARY KEY,
 	`desc` TEXT NOT NULL,
 	`date` INTEGER NOT NULL DEFAULT (strftime('%s', 'now'))
 );
 
 CREATE TABLE IF NOT EXISTS job(
 	`tag` TEXT NOT NULL UNIQUE,
-	`project_id` INTEGER NOT NULL REFERENCES project (id),
+	`project_name` INTEGER NOT NULL REFERENCES project (name),
 	`date` INTEGER NOT NULL DEFAULT (strftime('%s', 'now'))
 );
 
 CREATE TABLE IF NOT EXISTS jobresult(
 	`job_id` INTEGER NOT NULL REFERENCES job (id),
-	`worker_id` INTEGER NOT NULL REFERENCES worker (id),
+	`worker_name` INTEGER NOT NULL REFERENCES worker (name),
 	`exitcode` INTEGER DEFAULT 0,
 	`console` TEXT DEFAULT NULL,
 	`date` INTEGER NOT NULL DEFAULT (strftime('%s', 'now'))
--- a/sql/job-add.sql	Tue Jul 19 22:45:44 2022 +0200
+++ b/sql/job-add.sql	Thu Jul 21 20:23:22 2022 +0200
@@ -18,5 +18,5 @@
 
 INSERT INTO job(
   `tag`,
-  `project_id`
+  `project_name`
 ) VALUES (?, ?)
--- a/sql/job-todo.sql	Tue Jul 19 22:45:44 2022 +0200
+++ b/sql/job-todo.sql	Thu Jul 21 20:23:22 2022 +0200
@@ -23,17 +23,17 @@
 --
 SELECT `job`.`rowid`
      , `job`.`tag`
-     , `job`.`project_id`
+     , `job`.`project_name`
   FROM `job`
  WHERE `job`.`rowid`
 NOT IN (
        SELECT `jobresult`.`job_id`
          FROM `jobresult`
-        WHERE `jobresult`.`worker_id` = ?
+        WHERE `jobresult`.`worker_name` = ?
        )
    AND `job`.`date` >= (
                  SELECT `worker`.`date`
                    FROM `worker`
-                  WHERE `worker`.`rowid` = ?
+                  WHERE `worker`.`name` = ?
                  )
  LIMIT ?
--- a/sql/jobresult-add.sql	Tue Jul 19 22:45:44 2022 +0200
+++ b/sql/jobresult-add.sql	Thu Jul 21 20:23:22 2022 +0200
@@ -18,7 +18,7 @@
 
 INSERT INTO jobresult(
 	`job_id`,
-	`worker_id`,
+	`worker_name`,
 	`exitcode`,
 	`console`
 ) VALUES (?, ?, ?, ?)
--- a/sql/project-add.sql	Tue Jul 19 22:45:44 2022 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,24 +0,0 @@
---
--- project-add.sql -- create a new project
---
--- Copyright (c) 2021 David Demelier <markand@malikania.fr>
---
--- Permission to use, copy, modify, and/or distribute this software for any
--- purpose with or without fee is hereby granted, provided that the above
--- copyright notice and this permission notice appear in all copies.
---
--- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
--- WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
--- MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
--- ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
--- WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
--- ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
--- OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
---
-
-INSERT INTO project(
-	`name`,
-	`desc`,
-	`url`,
-	`script`
-) VALUES (?, ?, ?, ?)
--- a/sql/project-find-id.sql	Tue Jul 19 22:45:44 2022 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,23 +0,0 @@
---
--- project-find-id.sql -- find project by id
---
--- Copyright (c) 2021 David Demelier <markand@malikania.fr>
---
--- Permission to use, copy, modify, and/or distribute this software for any
--- purpose with or without fee is hereby granted, provided that the above
--- copyright notice and this permission notice appear in all copies.
---
--- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
--- WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
--- MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
--- ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
--- WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
--- ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
--- OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
---
-
-SELECT rowid
-     , *
-  FROM `project`
- WHERE `rowid` = ?
- LIMIT 1
--- a/sql/project-find.sql	Tue Jul 19 22:45:44 2022 +0200
+++ b/sql/project-find.sql	Thu Jul 21 20:23:22 2022 +0200
@@ -16,8 +16,7 @@
 -- OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 --
 
-SELECT rowid
-     , *
+SELECT *
   FROM `project`
  WHERE `name` = ?
  LIMIT 1
--- a/sql/project-list.sql	Tue Jul 19 22:45:44 2022 +0200
+++ b/sql/project-list.sql	Thu Jul 21 20:23:22 2022 +0200
@@ -16,7 +16,6 @@
 -- OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 --
 
-SELECT rowid
-     , *
+SELECT *
   FROM `project`
  LIMIT ?
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sql/project-save.sql	Thu Jul 21 20:23:22 2022 +0200
@@ -0,0 +1,24 @@
+--
+-- project-save.sql -- create a new project
+--
+-- Copyright (c) 2021 David Demelier <markand@malikania.fr>
+--
+-- Permission to use, copy, modify, and/or distribute this software for any
+-- purpose with or without fee is hereby granted, provided that the above
+-- copyright notice and this permission notice appear in all copies.
+--
+-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+-- WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+-- MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+-- ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+-- WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+-- ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+-- OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+--
+
+REPLACE INTO project(
+	`name`,
+	`desc`,
+	`url`,
+	`script`
+) VALUES (?, ?, ?, ?)
--- a/sql/project-update.sql	Tue Jul 19 22:45:44 2022 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,24 +0,0 @@
---
--- project-update.sql -- update existing project by id
---
--- Copyright (c) 2021 David Demelier <markand@malikania.fr>
---
--- Permission to use, copy, modify, and/or distribute this software for any
--- purpose with or without fee is hereby granted, provided that the above
--- copyright notice and this permission notice appear in all copies.
---
--- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
--- WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
--- MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
--- ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
--- WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
--- ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
--- OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
---
-
-UPDATE `project`
-   SET `name` = ?
-     , `desc` = ?
-     , `url` = ?
-     , `script` = ?
- WHERE `rowid` = ?
--- a/sql/worker-add.sql	Tue Jul 19 22:45:44 2022 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,22 +0,0 @@
---
--- worker-add -- create a new worker
---
--- Copyright (c) 2021 David Demelier <markand@malikania.fr>
---
--- Permission to use, copy, modify, and/or distribute this software for any
--- purpose with or without fee is hereby granted, provided that the above
--- copyright notice and this permission notice appear in all copies.
---
--- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
--- WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
--- MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
--- ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
--- WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
--- ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
--- OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
---
-
-INSERT INTO worker(
-	`name`,
-	`desc`
-) VALUES (?, ?)
--- a/sql/worker-find-id.sql	Tue Jul 19 22:45:44 2022 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,23 +0,0 @@
---
--- worker-find-id.sql -- find worker by id
---
--- Copyright (c) 2021 David Demelier <markand@malikania.fr>
---
--- Permission to use, copy, modify, and/or distribute this software for any
--- purpose with or without fee is hereby granted, provided that the above
--- copyright notice and this permission notice appear in all copies.
---
--- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
--- WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
--- MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
--- ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
--- WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
--- ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
--- OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
---
-
-SELECT rowid
-     , *
-  FROM `worker`
- WHERE `rowid` = ?
- LIMIT 1
--- a/sql/worker-find.sql	Tue Jul 19 22:45:44 2022 +0200
+++ b/sql/worker-find.sql	Thu Jul 21 20:23:22 2022 +0200
@@ -16,8 +16,7 @@
 -- OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 --
 
-SELECT rowid
-     , *
+SELECT *
   FROM `worker`
  WHERE `name` = ?
  LIMIT 1
--- a/sql/worker-list.sql	Tue Jul 19 22:45:44 2022 +0200
+++ b/sql/worker-list.sql	Thu Jul 21 20:23:22 2022 +0200
@@ -16,7 +16,6 @@
 -- OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 --
 
-SELECT rowid
-     , *
+SELECT *
   FROM `worker`
  LIMIT ?
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sql/worker-save.sql	Thu Jul 21 20:23:22 2022 +0200
@@ -0,0 +1,22 @@
+--
+-- worker-save -- create a new worker
+--
+-- Copyright (c) 2021 David Demelier <markand@malikania.fr>
+--
+-- Permission to use, copy, modify, and/or distribute this software for any
+-- purpose with or without fee is hereby granted, provided that the above
+-- copyright notice and this permission notice appear in all copies.
+--
+-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+-- WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+-- MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+-- ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+-- WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+-- ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+-- OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+--
+
+REPLACE INTO worker(
+	`name`,
+	`desc`
+) VALUES (?, ?)