diff options
| author | Xavier Del Campo Romero <xavi92@disroot.org> | 2025-09-22 17:32:44 +0200 |
|---|---|---|
| committer | Xavier Del Campo Romero <xavi92@disroot.org> | 2026-02-13 09:57:39 +0100 |
| commit | 78bf2fe4a5bf37514f6dfd203ef969da0bf40c2e (patch) | |
| tree | 33f9440b8ee0fa7a3b3ad033616d722d2101bb4d /ep_index.c | |
| parent | 107a2e43d54f9a42fb85b00b83cb0d9abb194680 (diff) | |
Diffstat (limited to 'ep_index.c')
| -rw-r--r-- | ep_index.c | 432 |
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; +} |
