summary refs log tree commit diff
diff options
context:
space:
mode:
authorJune McEnroe <june@causal.agency>2020-07-09 15:41:32 -0400
committerJune McEnroe <june@causal.agency>2020-07-09 15:41:32 -0400
commit8954be6eb8c3cc0a286a84cac55ca72ff25157bf (patch)
tree2c5e6967fe17509f350e5c66b1ea09d0517fc0b0
parentReturn basic 404 and 405 (diff)
downloadscooper-8954be6eb8c3cc0a286a84cac55ca72ff25157bf.tar.gz
scooper-8954be6eb8c3cc0a286a84cac55ca72ff25157bf.zip
Implement basic networks list
-rw-r--r--scooper.c129
1 files changed, 112 insertions, 17 deletions
diff --git a/scooper.c b/scooper.c
index 291abb0..afbbda5 100644
--- a/scooper.c
+++ b/scooper.c
@@ -25,6 +25,7 @@
 #include <unistd.h>
 
 #include <kcgi.h>
+#include <kcgihtml.h>
 #include <sqlite3.h>
 
 #define SQL(...) #__VA_ARGS__
@@ -33,8 +34,18 @@ enum { DatabaseVersion = 4 };
 
 static sqlite3 *db;
 
+static struct {
+	sqlite3_stmt *networks;
+	sqlite3_stmt *contexts;
+	sqlite3_stmt *events;
+	sqlite3_stmt *search;
+} stmt;
+
 static void dbClose(void) {
-	// TODO: Finalize statements.
+	if (stmt.networks) sqlite3_finalize(stmt.networks);
+	if (stmt.contexts) sqlite3_finalize(stmt.contexts);
+	if (stmt.events) sqlite3_finalize(stmt.events);
+	if (stmt.search) sqlite3_finalize(stmt.search);
 	sqlite3_close(db);
 }
 
@@ -76,31 +87,115 @@ static const struct kvalid Keys[KeysLen] = {
 #undef X
 };
 
-static enum kcgi_err fail(struct kreq *req, enum khttp status) {
-	return khttp_head(req, kresps[KRESP_STATUS], "%s", khttps[status])
-		|| khttp_head(
-			req, kresps[KRESP_CONTENT_TYPE], "%s", kmimetypes[KMIME_TEXT_PLAIN]
-		)
+static enum kcgi_err head(struct kreq *req, enum khttp http, enum kmime mime) {
+	return khttp_head(req, kresps[KRESP_STATUS], "%s", khttps[http])
+		|| khttp_head(req, kresps[KRESP_CONTENT_TYPE], "%s", kmimetypes[mime]);
+}
+
+static enum kcgi_err fail(struct kreq *req, enum khttp http) {
+	return head(req, http, KMIME_TEXT_PLAIN)
 		|| khttp_body(req)
-		|| khttp_puts(req, khttps[status])
+		|| khttp_puts(req, khttps[http])
 		|| khttp_putc(req, '\n');
 }
 
+static const char *stylesheet;
+
+static enum kcgi_err htmlHead(struct khtmlreq *html, const char *title) {
+	enum kcgi_err error = khtml_elem(html, KELEM_DOCTYPE)
+		|| khtml_attr(html, KELEM_META, KATTR_CHARSET, "utf-8", KATTR__MAX)
+		|| khtml_elem(html, KELEM_TITLE)
+		|| khtml_puts(html, title)
+		|| khtml_closeelem(html, 1);
+	if (error) return error;
+	if (stylesheet) {
+		error = khtml_attr(
+			html, KELEM_LINK,
+			KATTR_REL, "stylesheet",
+			KATTR_HREF, stylesheet,
+			KATTR__MAX
+		);
+		if (error) return error;
+	}
+	return khtml_elem(html, KELEM_H1)
+		|| khtml_puts(html, title)
+		|| khtml_closeelem(html, 1);
+}
+
+static const char *NetworksQuery = SQL(
+	SELECT DISTINCT network
+	FROM contexts
+	ORDER BY network;
+);
+
+static enum kcgi_err networks(struct kreq *req) {
+	struct khtmlreq html;
+	enum kcgi_err error = head(req, KHTTP_200, KMIME_TEXT_HTML)
+		|| khttp_body(req)
+		|| khtml_open(&html, req, KHTML_PRETTY)
+		|| htmlHead(&html, "Networks")
+		|| khtml_elem(&html, KELEM_UL);
+	if (error) goto fail;
+
+	int result;
+	while (SQLITE_ROW == (result = sqlite3_step(stmt.networks))) {
+		const char *network = sqlite3_column_text(stmt.networks, 0);
+		char *href = khttp_urlpart(
+			NULL, NULL, Pages[Contexts], Keys[Network].name, network, NULL
+		);
+		if (!href) err(EX_OSERR, "khttp_urlpart");
+
+		error = khtml_elem(&html, KELEM_LI)
+			|| khtml_attr(&html, KELEM_A, KATTR_HREF, href, KATTR__MAX)
+			|| khtml_puts(&html, network)
+			|| khtml_closeelem(&html, 2);
+		free(href);
+		if (error) goto fail;
+	}
+	if (result != SQLITE_DONE) errx(EX_SOFTWARE, "%s", sqlite3_errmsg(db));
+
+	error = khtml_close(&html);
+
+fail:
+	sqlite3_reset(stmt.networks);
+	return error;
+}
+
+static enum kcgi_err contexts(struct kreq *req) {
+	return KCGI_OK;
+}
+
+static enum kcgi_err events(struct kreq *req) {
+	return KCGI_OK;
+}
+
+static enum kcgi_err search(struct kreq *req) {
+	return KCGI_OK;
+}
+
 static enum kcgi_err request(struct kreq *req) {
 	if (req->method != KMETHOD_HEAD && req->method != KMETHOD_GET) {
 		return fail(req, KHTTP_405);
 	}
-	if (req->mime != KMIME_TEXT_HTML || req->page == PagesLen) {
-		return fail(req, KHTTP_404);
+	switch (req->page) {
+		case Networks: return networks(req);
+		case Contexts: return contexts(req);
+		case Events:   return events(req);
+		case Search:   return search(req);
+		default:       return fail(req, KHTTP_404);
 	}
+}
 
-	return KCGI_OK;
+static void prepare(sqlite3_stmt **stmt, const char *query) {
+	int error = sqlite3_prepare_v3(
+		db, query, -1, SQLITE_PREPARE_PERSISTENT, stmt, NULL
+	);
+	if (error) errx(EX_SOFTWARE, "%s: %s", sqlite3_errmsg(db), query);
 }
 
 int main(int argc, char *argv[]) {
 	bool fastCGI = false;
 	bool public = false;
-	const char *stylesheet = NULL;
 
 	for (int opt; 0 < (opt = getopt(argc, argv, "fps:"));) {
 		switch (opt) {
@@ -116,22 +211,22 @@ int main(int argc, char *argv[]) {
 	if (error) errx(EX_NOINPUT, "%s: %s", argv[optind], sqlite3_errmsg(db));
 	atexit(dbClose);
 
-	sqlite3_stmt *stmt;
+	sqlite3_stmt *check;
 	error = sqlite3_prepare_v2(
-		db, SQL(PRAGMA user_version;), -1, &stmt, NULL
+		db, SQL(PRAGMA user_version;), -1, &check, NULL
 	);
 	if (error) errx(EX_SOFTWARE, "%s", sqlite3_errmsg(db));
 
-	error = sqlite3_step(stmt);
+	error = sqlite3_step(check);
 	if (error != SQLITE_ROW) errx(EX_SOFTWARE, "%s", sqlite3_errmsg(db));
 
-	int version = sqlite3_column_int(stmt, 0);
+	int version = sqlite3_column_int(check, 0);
 	if (version != DatabaseVersion) {
 		errx(EX_DATAERR, "unsupported database version %d", version);
 	}
-	sqlite3_finalize(stmt);
+	sqlite3_finalize(check);
 
-	// TODO: Prepare all statements with persist flag.
+	prepare(&stmt.networks, NetworksQuery);
 	
 	if (fastCGI) {
 		struct kfcgi *fcgi;