commit 2204be2ba369d22740ed26bf4f5e982bd0746ba8
parent 36090048851cdf7e07ac53209d78429cb3a5f157
Author: Jacob R. Edwards <jacob@jacobedwards.org>
Date: Sat, 2 Mar 2024 16:09:51 -0800
Add backend to main page
The time when the timer was started is now stored in the database
and it is displayed in a time element as the duration in HH:MM:SS
format. main.js was updated to work for the new main page aswell.
Diffstat:
M | scripts/main.js | | | 24 | +++++++++++++----------- |
M | timekeeper.c | | | 154 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------- |
2 files changed, 137 insertions(+), 41 deletions(-)
diff --git a/scripts/main.js b/scripts/main.js
@@ -1,16 +1,18 @@
-counter = document.querySelector("#counter")
+start = document.querySelector("#start")
+let starttime = Math.trunc(Date.parse(start.dateTime) / 1000)
-function updatetime() {
- counter.textContent = String(Number(counter.textContent) + 1)
- if (!document.hidden)
- setTimeout(updatetime, 1000)
+function sn(n) {
+ return String(n).padStart(2, "0")
}
-<!--
-document.addEventListener("visibilitychange", () => {
- if (!document.hidden) {
- updatetime()
-});
--->
+function updatetime() {
+ let elapsed = Math.trunc(Date.now() / 1000) - starttime
+ let mn = Math.trunc(elapsed / 60)
+ let sc = elapsed % 60
+ let hr = Math.trunc(mn / 60)
+ mn = mn % 60
+ start.textContent = sn(hr) + ':' + sn(mn) + ':' + sn(sc)
+ setTimeout(updatetime, 1000)
+}
updatetime()
diff --git a/timekeeper.c b/timekeeper.c
@@ -9,8 +9,10 @@
#include <pwd.h>
#include <stdarg.h>
#include <stdint.h>
+#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <time.h>
#include <unistd.h>
#include <kcgi.h>
@@ -32,6 +34,8 @@ enum Field {
KeyHash, /* Authentication hash */
KeyCreate, /* Whether to create a new user in login page */
KeyDelete, /* Delete account in account page */
+ KeyStart,
+ KeyStop,
KeyMax
};
@@ -46,11 +50,15 @@ enum Page {
};
enum StmtID {
- StmtInit,
+ StmtInitUsers,
+ StmtInitUserdata,
StmtAddUser,
+ StmtAddUserdata,
StmtGetUserByHash,
StmtGetUserByName,
StmtDeleteUser,
+ StmtStartTime,
+ StmtGetStartTime,
StmtMax
};
@@ -108,24 +116,15 @@ enum LoginStatus {
int
initdb(struct pagedata *pd)
{
- size_t stmtid;
- struct sqlbox_parmset *res;
-
- if (!(stmtid = sqlbox_prepare_bind(pd->db, pd->dbid, StmtInit, 0, NULL, 0)))
+ if (sqlbox_exec(pd->db, pd->dbid, StmtInitUsers, 0, NULL, 0) ||
+ sqlbox_exec(pd->db, pd->dbid, StmtInitUserdata, 0, NULL, 0))
return 1;
- if (!(res = sqlbox_step(pd->db, stmtid)))
- err(1, "step");
- if (!sqlbox_finalise(pd->db, stmtid))
- err(1, "finalize");
-
return 0;
}
int
adduser(struct pagedata *pd, char *name, char *key)
{
- size_t stmtid;
- struct sqlbox_parmset *res;
char *salt, *hash;
salt = bcrypt_gensalt(8);
@@ -138,11 +137,8 @@ adduser(struct pagedata *pd, char *name, char *key)
{ .sparm = name, .type = SQLBOX_PARM_STRING }
};
- if (!(stmtid = sqlbox_prepare_bind(pd->db, pd->dbid, StmtAddUser, Len(p), p, 0)))
- return 1;
- if (!(res = sqlbox_step(pd->db, stmtid)))
- return 1;
- if (!sqlbox_finalise(pd->db, stmtid))
+ if (sqlbox_exec(pd->db, pd->dbid, StmtAddUser, Len(p), p, 0) != SQLBOX_CODE_OK ||
+ sqlbox_exec(pd->db, pd->dbid, StmtAddUserdata, 1, p, 0) != SQLBOX_CODE_OK)
return 1;
return 0;
}
@@ -181,7 +177,7 @@ getuser(struct pagedata *pd, char *field, char *value)
err(1, "step");
if (!res->psz && res->code == SQLBOX_CODE_OK) {
- kutil_info(NULL, NULL, "%s: %s not found", value, field);
+ kutil_info(&pd->req, NULL, "getuser: %s: %s not found", value, field);
sqlbox_finalise(pd->db, stmtid);
return NULL;
}
@@ -528,27 +524,119 @@ pageaccount(struct pagedata *pd)
return khtml_close(&pd->html);
}
+time_t
+setstart(struct pagedata *pd, char *hash, time_t time)
+{
+ struct sqlbox_parm ps[] = {
+ { .iparm = time, .type = SQLBOX_PARM_INT },
+ { .sparm = hash, .type = SQLBOX_PARM_STRING },
+ };
+
+ return sqlbox_exec(pd->db, pd->dbid, StmtStartTime,
+ Len(ps), ps, 0) != SQLBOX_CODE_OK;
+}
+
+time_t
+getstart(struct pagedata *pd, char *hash)
+{
+ size_t stmtid;
+ struct sqlbox_parm p = {
+ .sparm = hash, .type = SQLBOX_PARM_STRING
+ };
+ struct sqlbox_parmset *r;
+ time_t time;
+
+ stmtid = sqlbox_prepare_bind(pd->db, pd->dbid, StmtGetStartTime, 1, &p, 0);
+ if (stmtid == 0)
+ err(1, "prepare bind");
+
+ r = sqlbox_step(pd->db, stmtid);
+ if (!r || r->code != SQLBOX_CODE_OK)
+ err(1, "step");
+
+ if (r->psz > 1)
+ err(1, "too many rows");
+
+ if (r->psz == 1 && (r->ps[0].type != SQLBOX_PARM_INT &&
+ r->ps[0].type != SQLBOX_PARM_NULL))
+ err(1, "invalid type");
+
+ time = r->psz == 1 ? r->ps[0].iparm : 0;
+ kutil_info(NULL,NULL,"gettime %lld %zu", time, r->psz);
+
+ if (!sqlbox_finalise(pd->db, stmtid))
+ err(1, "finalise");
+ return time;
+}
+
enum kcgi_err
pagemain(struct pagedata *pd)
{
enum kcgi_err status;
struct user *user;
+ time_t start;
+ time_t elapsed;
+ char datetime[25];
+ int hr, mn, sc;
+ struct tm *tm;
user = sitegetlogin(pd);
if (!user)
return errorpage(pd, KHTTP_401);
- if ((status = khttp_head(&pd->req, kresps[KRESP_STATUS], "%s", khttps[KHTTP_200])) != KCGI_OK ||
- (status = khttp_head(&pd->req, kresps[KRESP_CONTENT_TYPE], "%s", kmimetypes[KMIME_TEXT_HTML])) != KCGI_OK ||
- (status = khttp_body(&pd->req)))
+ if (pd->req.fieldmap[KeyStart] && setstart(pd, user->hash, time(NULL)))
+ return KCGI_SYSTEM;
+
+ status = starthtmldoc(pd, KHTTP_200);
+ if (status != KCGI_OK)
+ return status;
+
+ start = getstart(pd, user->hash);
+
+ if ((status = htmlwithin(pd, KELEM_H1, "Timekeeper")) != KCGI_OK ||
+ (status = khtml_elem(&pd->html, KELEM_FORM)) != KCGI_OK ||
+ (status = khtml_attr(&pd->html, KELEM_INPUT,
+ KATTR_TYPE, "hidden",
+ KATTR_NAME, pd->keys[start ? KeyStop : KeyStart].name,
+ KATTR_VALUE, "yes",
+ KATTR__MAX)) != KCGI_OK ||
+ (status = khtml_attr(&pd->html, KELEM_INPUT,
+ KATTR_TYPE, "submit",
+ KATTR_VALUE, start ? "Stop Time" : "Start Time", KATTR__MAX)) != KCGI_OK ||
+ (status = khtml_closeelem(&pd->html, 1)) != KCGI_OK)
return status;
- khttp_puts(&pd->req, "<!doctype html><html><head><title>Main</title></head><body><h1>Main</h1>"
- "<noscript><a href=\".\">Reload</a></noscript>"
- "<p id=\"counter\">0</p>"
- "<script src=\"scripts/main.js\"></script>"
- "</body></html>");
+
+ if (start) {
+ elapsed = time(NULL) - start;
+ mn = elapsed / 60;
+ sc = elapsed % 60;
+ hr = mn / 60;
+ mn = mn % 60;
+ tm = gmtime(&start);
+ if (!tm)
+ err(1, "gmtime");
+ if (strftime(datetime, sizeof(datetime), "%FT%T+0000", tm) >= (int)sizeof(datetime))
+ err(1, "strftime");
+ if ((status = khtml_attr(&pd->html, KELEM_TIME,
+ KATTR_ID, "start", KATTR_DATETIME, datetime,
+ KATTR__MAX)) != KCGI_OK ||
+ (status = khtml_printf(&pd->html, "%.2d:%.2d:%.2d", hr, mn, sc)) != KCGI_OK ||
+ (status = khtml_closeelem(&pd->html, 1)) != KCGI_OK)
+ return status;
+
+ if ((status = khtml_elem(&pd->html, KELEM_NOSCRIPT)) != KCGI_OK ||
+ (status = khtml_attr(&pd->html, KELEM_A,
+ KATTR_HREF, pages[PageMain], KATTR__MAX)) != KCGI_OK ||
+ (status = htmlwithin(pd, KELEM_BUTTON, "Reload")) != KCGI_OK ||
+ (status = khtml_closeelem(&pd->html, 2)) != KCGI_OK ||
+ (status = khtml_attr(&pd->html, KELEM_SCRIPT,
+ KATTR_SRC, "scripts/main.js", KATTR__MAX)) != KCGI_OK ||
+ (status = khtml_closeelem(&pd->html, 1)) != KCGI_OK)
+ return status;
+ }
+
freeuser(user);
- return KCGI_OK;
+ return khtml_close(&pd->html);
}
int
@@ -561,7 +649,9 @@ main(void)
[KeyPassword] = { kvalid_pass, "password" },
[KeyHash] = { kvalid_stringne, "hash" },
[KeyCreate] = { kvalid_stringne, "create" },
- [KeyDelete] = { kvalid_stringne, "delete" }
+ [KeyDelete] = { kvalid_stringne, "delete" },
+ [KeyStart] = { kvalid_stringne, "start" },
+ [KeyStop] = { kvalid_stringne, "stop" }
};
struct pagedata pd = {
.keys = keys
@@ -571,11 +661,15 @@ main(void)
{ .fname = "/tmp/timekeeper.db", .mode = SQLBOX_SRC_RWC }
};
struct sqlbox_pstmt pstmts[] = {
- [StmtInit] = { .stmt = "CREATE TABLE IF NOT EXISTS users (hash TEXT PRIMARY KEY, name TEXT UNIQUE)" },
+ [StmtInitUsers] = { .stmt = "CREATE TABLE IF NOT EXISTS users (hash TEXT PRIMARY KEY, name TEXT UNIQUE)" },
[StmtAddUser] = { .stmt = "INSERT INTO users (hash, name) VALUES (?, ?)" },
[StmtGetUserByName] = { .stmt = "SELECT * FROM users WHERE name IS ?" },
[StmtGetUserByHash] = { .stmt = "SELECT * FROM users WHERE hash IS ?" },
- [StmtDeleteUser] = { .stmt = "DELETE FROM users WHERE hash IS ?" }
+ [StmtDeleteUser] = { .stmt = "DELETE FROM users WHERE hash IS ?" },
+ [StmtInitUserdata] = { .stmt = "CREATE TABLE IF NOT EXISTS userdata (hash TEXT PRIMARY KEY, start INTEGER)" },
+ [StmtAddUserdata] = { .stmt = "INSERT INTO userdata (hash) VALUES (?)" },
+ [StmtStartTime] = { .stmt = "UPDATE userdata SET start = ? WHERE hash IS ?" },
+ [StmtGetStartTime] = { .stmt = "SELECT start FROM userdata WHERE hash IS ?" }
};
enum kcgi_err (*pagefunctions[])(struct pagedata *) = {
[PageIndex] = pageindex,