aboutsummaryrefslogtreecommitdiff
path: root/ep_index.c
diff options
context:
space:
mode:
Diffstat (limited to 'ep_index.c')
-rw-r--r--ep_index.c432
1 files changed, 432 insertions, 0 deletions
diff --git a/ep_index.c b/ep_index.c
new file mode 100644
index 0000000..d533220
--- /dev/null
+++ b/ep_index.c
@@ -0,0 +1,432 @@
+/*
+ * nanobbs, a tiny forums software.
+ * Copyright (C) 2025-2026 Xavier Del Campo Romero
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#define _POSIX_C_SOURCE 200809L
+
+#include "endpoints.h"
+#include "auth.h"
+#include "defs.h"
+#include "db.h"
+#include "form.h"
+#include "op.h"
+#include <dynstr.h>
+#include <libweb/http.h>
+#include <libweb/html.h>
+#include <sqlite3.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+
+struct category
+{
+ sqlite3 *db;
+ struct op *op;
+ struct html_node *root, *body, *section_ul, *div;
+};
+
+static void free_category(struct category *const c)
+{
+ int error;
+
+ if (!c)
+ return;
+ else if ((error = sqlite3_close(c->db)) != SQLITE_OK)
+ fprintf(stderr, "%s: sqlite3_close: %s\n", __func__,
+ sqlite3_errstr(error));
+
+ html_node_free(c->root);
+ free(c);
+}
+
+static int section(sqlite3_stmt *const stmt,
+ const struct http_payload *const pl, struct http_response *const r,
+ void *const user, void *const args)
+{
+ int ret = -1;
+ struct db_section dbs = {0};
+ struct html_node *div, *ul, *a, *p;
+ struct dynstr d;
+ struct category *const c = args;
+
+ dynstr_init(&d);
+
+ if (db_section(c->db, stmt, &dbs))
+ {
+ fprintf(stderr, "%s: db_section failed\n", __func__);
+ goto end;
+ }
+ else if (dynstr_append(&d, "/view/%d/%d", dbs.catid, dbs.id))
+ {
+ fprintf(stderr, "%s: dynstr_append failed\n", __func__);
+ goto end;
+ }
+ else if (!(ul = html_node_add_child(c->div, "ul")))
+ {
+ fprintf(stderr, "%s: html_node_add_child %s failed\n", __func__, "ul");
+ goto end;
+ }
+ else if (!(div = html_node_add_child(ul, "div")))
+ {
+ fprintf(stderr, "%s: html_node_add_child %s failed\n", __func__, "ul");
+ goto end;
+ }
+ else if (!(a = html_node_add_child(div, "a")))
+ {
+ fprintf(stderr, "%s: html_node_add_child %s failed\n", __func__, "a");
+ goto end;
+ }
+ else if (html_node_add_attr(a, "href", d.str))
+ {
+ fprintf(stderr, "%s: html_node_add_attr failed\n", __func__);
+ goto end;
+ }
+ else if (html_node_set_value(a, dbs.name))
+ {
+ fprintf(stderr, "%s: html_node_set_value failed\n", __func__);
+ goto end;
+ }
+ else if (!(p = html_node_add_child(div, "p")))
+ {
+ fprintf(stderr, "%s: html_node_add_child %s failed\n", __func__, "p");
+ goto end;
+ }
+ else if (html_node_set_value(p, dbs.desc))
+ {
+ fprintf(stderr, "%s: html_node_set_value failed\n", __func__);
+ goto end;
+ }
+
+ ret = 0;
+
+end:
+ db_section_free(&dbs);
+ dynstr_free(&d);
+ return ret;
+}
+
+static int end_section(const struct http_payload *const p,
+ struct http_response *const r, void *const user, void *const args)
+{
+ struct category *const c = args;
+
+ op_resume(c->op, r);
+ return 0;
+}
+
+static void rollback(void *const args)
+{
+ struct category *const c = args;
+
+ if (!c)
+ return;
+
+ db_rollback(c->db);
+ free_category(c);
+}
+
+static int category( sqlite3_stmt *const stmt,
+ const struct http_payload *const p, struct http_response *const r,
+ void *const user, void *const args)
+{
+ int ret = -1;
+ unsigned long long id;
+ char *name = NULL;
+ struct html_node *div, *ul, *a;
+ struct dynstr d, expr;
+ struct category *const c = args;
+ sqlite3 *const db = c->db;
+
+ dynstr_init(&d);
+ dynstr_init(&expr);
+
+ if (db_biguint(db, stmt, "id", &id))
+ {
+ fprintf(stderr, "%s: failed to get id\n", __func__);
+ goto end;
+ }
+ else if (!(name = db_str(db, stmt, "name")))
+ {
+ fprintf(stderr, "%s: failed to get name\n", __func__);
+ goto end;
+ }
+ else if (dynstr_append(&d, "/view/%d", id))
+ {
+ fprintf(stderr, "%s: dynstr_append failed\n", __func__);
+ goto end;
+ }
+ else if (!(div = html_node_add_child(c->body, "div")))
+ {
+ fprintf(stderr, "%s: html_node_add_child %s failed\n", __func__, "div");
+ goto end;
+ }
+ else if (!(ul = html_node_add_child(div, "ul")))
+ {
+ fprintf(stderr, "%s: html_node_add_child %s failed\n", __func__, "ul");
+ goto end;
+ }
+ else if (!(a = html_node_add_child(ul, "a")))
+ {
+ fprintf(stderr, "%s: html_node_add_child %s failed\n", __func__, "a");
+ goto end;
+ }
+ else if (html_node_add_attr(a, "href", d.str))
+ {
+ fprintf(stderr, "%s: html_node_add_attr failed\n", __func__);
+ goto end;
+ }
+ else if (html_node_set_value(a, name))
+ {
+ fprintf(stderr, "%s: html_node_set_value failed\n", __func__);
+ goto end;
+ }
+ else if (dynstr_append(&expr, "SELECT * FROM sections WHERE catid = %d "
+ "ORDER BY id ASC;", id))
+ {
+ fprintf(stderr, "%s: dynstr_append failed\n", __func__);
+ goto end;
+ }
+
+ const struct op_cfg op =
+ {
+ .end = end_section,
+ .row = section,
+ .error = rollback,
+ .args = c
+ };
+
+ if (!op_run(db, expr.str, &op, r))
+ {
+ fprintf(stderr, "%s: op_run failed\n", __func__);
+ goto end;
+ }
+
+ c->div = div;
+ ret = 0;
+
+end:
+ free(name);
+ dynstr_free(&expr);
+ dynstr_free(&d);
+ return ret;
+}
+
+static int end_category(const struct http_payload *const p,
+ struct http_response *const r, void *const user, void *const args)
+{
+ struct category *const c = args;
+ struct dynstr d;
+ const int error = sqlite3_close(c->db);
+
+ c->db = NULL;
+ dynstr_init(&d);
+
+ if (error != SQLITE_OK)
+ {
+ fprintf(stderr, "%s: sqlite3_close: %s\n", __func__,
+ sqlite3_errstr(error));
+ goto failure;
+ }
+ else if (form_footer(c->body, p->resource))
+ {
+ fprintf(stderr, "%s: form_footer failed\n", __func__);
+ goto failure;
+ }
+ else if (dynstr_append(&d, "%s", DOCTYPE_TAG))
+ {
+ fprintf(stderr, "%s: dynstr_append failed\n", __func__);
+ goto failure;
+ }
+ else if (html_serialize(c->root, &d))
+ {
+ fprintf(stderr, "%s: html_serialize failed\n", __func__);
+ goto failure;
+ }
+
+ *r = (const struct http_response)
+ {
+ .status = HTTP_STATUS_OK,
+ .buf.rw = d.str,
+ .free = free,
+ .n = d.len
+ };
+
+ free_category(c);
+ return 0;
+
+failure:
+ dynstr_free(&d);
+ return -1;
+}
+
+static int commit(const struct http_payload *const p,
+ struct http_response *const r, void *const user, void *const args)
+{
+ struct category *const c = args;
+ const struct op_cfg ocfg =
+ {
+ .end = end_category,
+ .error = rollback,
+ .args = c
+ };
+
+ if (!op_run(c->db, "COMMIT;", &ocfg, r))
+ {
+ fprintf(stderr, "%s: op_run failed\n", __func__);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int begin_tr(const struct http_payload *const p,
+ struct http_response *const r, void *const user, void *const args)
+{
+ static const char query[] = "SELECT * FROM categories ORDER BY id ASC;";
+ struct category *const c = args;
+ const struct op_cfg ocfg =
+ {
+ .row = category,
+ .end = commit,
+ .args = c
+ };
+
+ if (!(c->op = op_run(c->db, query, &ocfg, r)))
+ {
+ fprintf(stderr, "%s: op_run failed\n", __func__);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int setup(const struct http_payload *const pl,
+ struct http_response *const r, void *const user, sqlite3 *const db,
+ const struct auth_user *const u)
+{
+ int ret = -1, error;
+ char *userdup = NULL;
+ const char *const username = u ? u->username : NULL;
+ struct html_node *root = NULL, *body, *div, *h2, *ul;
+ struct category *c = NULL;
+
+ if (u && u->role <= AUTH_ROLE_BANNED)
+ {
+ ret = form_unauthorized("Banned account", r);
+ goto failure;
+ }
+ else if (!(root = html_node_alloc("html")))
+ {
+ fprintf(stderr, "%s: html_node_alloc failed\n", __func__);
+ goto failure;
+ }
+ else if (form_head(root))
+ {
+ fprintf(stderr, "%s: form_head failed\n", __func__);
+ goto failure;
+ }
+ else if (!(body = html_node_add_child(root, "body")))
+ {
+ fprintf(stderr, "%s: html_node_add_child failed\n", __func__);
+ goto failure;
+ }
+ else if (form_login(body, username))
+ {
+ fprintf(stderr, "%s: form_login failed\n", __func__);
+ goto failure;
+ }
+ else if (u && u->role >= AUTH_ROLE_MOD && form_category(body))
+ {
+ fprintf(stderr, "%s: form_category failed\n", __func__);
+ goto failure;
+ }
+ else if (!(div = html_node_add_child(body, "div"))
+ || !(h2 = html_node_add_child(div, "h2"))
+ || !(ul = html_node_add_child(body, "ul")))
+ {
+ fprintf(stderr, "%s: html_node_add_child failed\n", __func__);
+ goto failure;
+ }
+ else if (html_node_set_value(h2, "Categories"))
+ {
+ fprintf(stderr, "%s: html_node_set_value failed\n", __func__);
+ goto failure;
+ }
+ else if (!(c = malloc(sizeof *c)))
+ {
+ fprintf(stderr, "%s: malloc(3): %s\n", __func__, strerror(errno));
+ goto failure;
+ }
+
+ const struct op_cfg ocfg =
+ {
+ .error = rollback,
+ .end = begin_tr,
+ .args = c
+ };
+
+ if (!op_run(db, "BEGIN TRANSACTION", &ocfg, r))
+ {
+ fprintf(stderr, "%s: op_run failed\n", __func__);
+ goto failure;
+ }
+
+ *c = (const struct category)
+ {
+ .section_ul = ul,
+ .body = body,
+ .root = root,
+ .db = db
+ };
+
+ return 0;
+
+failure:
+
+ if ((error = sqlite3_close(db)) != SQLITE_OK)
+ fprintf(stderr, "%s: sqlite3_close: %s\n", __func__,
+ sqlite3_errstr(error));
+
+ free(c);
+ free(userdup);
+ html_node_free(root);
+ return ret;
+}
+
+int ep_index(const struct http_payload *const p, struct http_response *const r,
+ void *const user)
+{
+ const int n = auth_validate(p, r, user, setup);
+
+ if (n < 0)
+ fprintf(stderr, "%s: auth_validate failed\n", __func__);
+ else if (n)
+ {
+ const struct cfg *const cfg = user;
+ sqlite3 *db;
+
+ if (db_open(cfg->dir, &db))
+ {
+ fprintf(stderr, "%s: db_open failed\n", __func__);
+ return -1;
+ }
+
+ return setup(p, r, user, db, NULL);
+ }
+
+ return n;
+}