changeset 25:f4e8a7920b94

pasterd: implement search, closes #2474 @30m
author David Demelier <markand@malikania.fr>
date Fri, 07 Feb 2020 13:23:48 +0100
parents 6702a87420d1
children 6a45977c82b4
files database.c database.h http.c themes/minimal/404.html themes/minimal/500.html themes/minimal/header.html themes/minimal/search.html themes/siimple/header.html themes/siimple/search.html
diffstat 9 files changed, 222 insertions(+), 1 deletions(-) [+]
line wrap: on
line diff
--- a/database.c	Thu Feb 06 21:48:32 2020 +0100
+++ b/database.c	Fri Feb 07 13:23:48 2020 +0100
@@ -92,6 +92,23 @@
 	"\n"
 	"END TRANSACTION";
 
+static const char *sql_search =
+	"SELECT uuid\n"
+	"     , title\n"
+	"     , author\n"
+	"     , language\n"
+	"     , code\n"
+	"     , strftime('%s', date) AS date\n"
+	"     , visible\n"
+	"     , duration\n"
+	"  FROM paste\n"
+	" WHERE title like ?\n"
+	"   AND author like ?\n"
+	"   AND language like ?\n"
+	"   AND visible = 1\n"
+	" ORDER BY date DESC\n"
+	" LIMIT ?\n";
+
 /* sqlite3 use const unsigned char *. */
 static char *
 dup(const unsigned char *s)
@@ -177,7 +194,6 @@
 	sqlite3_finalize(stmt);
 	*max = i;
 
-
 	return true;
 
 sqlite_err:
@@ -279,6 +295,61 @@
 	return false;
 }
 
+bool
+database_search(struct paste *pastes,
+                size_t *max,
+                const char *title,
+                const char *author,
+                const char *language)
+{
+	assert(pastes);
+	assert(max);
+
+	sqlite3_stmt *stmt = NULL;
+
+	log_debug("database: searching title=%s, author=%s, language=%s",
+	    title    ? title    : "",
+	    author   ? author   : "",
+	    language ? language : "");
+
+	memset(pastes, 0, *max * sizeof (struct paste));
+
+	/* Select everything if not specified. */
+	title    = title    ? title    : "%";
+	author   = author   ? author   : "%";
+	language = language ? language : "%";
+
+	if (sqlite3_prepare(db, sql_search, -1, &stmt, NULL) != SQLITE_OK)
+		goto sqlite_err;
+	if (sqlite3_bind_text(stmt, 1, title, -1, NULL) != SQLITE_OK)
+		goto sqlite_err;
+	if (sqlite3_bind_text(stmt, 2, author, -1, NULL) != SQLITE_OK)
+		goto sqlite_err;
+	if (sqlite3_bind_text(stmt, 3, language, -1, NULL) != SQLITE_OK)
+		goto sqlite_err;
+	if (sqlite3_bind_int64(stmt, 4, *max) != SQLITE_OK)
+		goto sqlite_err;
+
+	size_t i = 0;
+
+	for (; i < *max && sqlite3_step(stmt) == SQLITE_ROW; ++i)
+		convert(stmt, &pastes[i]);
+
+	log_debug("database: found %zu pastes", i);
+	sqlite3_finalize(stmt);
+	*max = i;
+
+	return true;
+
+sqlite_err:
+	log_warn("database: error (search): %s\n", sqlite3_errmsg(db));
+
+	if (stmt)
+		sqlite3_finalize(stmt);
+
+	return (*max = 0);
+}
+
 void
 database_clear(void)
 {
--- a/database.h	Thu Feb 06 21:48:32 2020 +0100
+++ b/database.h	Fri Feb 07 13:23:48 2020 +0100
@@ -36,6 +36,13 @@
 bool
 database_insert(struct paste *paste);
 
+bool
+database_search(struct paste *pastes,
+                size_t *max,
+                const char *title,
+                const char *author,
+                const char *language);
+
 void
 database_clear(void);
 
--- a/http.c	Thu Feb 06 21:48:32 2020 +0100
+++ b/http.c	Fri Feb 07 13:23:48 2020 +0100
@@ -43,6 +43,7 @@
 static void page_fork(struct kreq *);
 static void page_paste(struct kreq *);
 static void page_download(struct kreq *);
+static void page_search(struct kreq *);
 static void page_static(struct kreq *);
 
 enum page {
@@ -51,6 +52,7 @@
 	PAGE_FORK,
 	PAGE_PASTE,
 	PAGE_DOWNLOAD,
+	PAGE_SEARCH,
 	PAGE_STATIC,
 	PAGE_LAST       /* Not used. */
 };
@@ -61,6 +63,7 @@
 	[PAGE_FORK]     = "fork",
 	[PAGE_PASTE]    = "paste",
 	[PAGE_DOWNLOAD] = "download",
+	[PAGE_SEARCH]   = "search",
 	[PAGE_STATIC]   = "static"
 };
 
@@ -70,6 +73,7 @@
 	[PAGE_FORK]     = page_fork,
 	[PAGE_PASTE]    = page_paste,
 	[PAGE_DOWNLOAD] = page_download,
+	[PAGE_SEARCH]   = page_search,
 	[PAGE_STATIC]   = page_static
 };
 
@@ -789,6 +793,88 @@
 }
 
 static void
+page_search_get(struct kreq *req)
+{
+	/* We re-use the /new form with an empty paste. */
+	struct tmpl_paste data = {
+		.req = req
+	};
+	const struct ktemplate kt = {
+		.key    = tmpl_new_keywords,
+		.keysz  = 6,
+		.cb     = tmpl_new,
+		.arg    = &data
+	};
+
+	page(req, &kt, KHTTP_200, "search.html");
+}
+
+static void
+page_search_post(struct kreq *req)
+{
+	struct tmpl_index data = {
+		.req    = req,
+		.count  = 10
+	};
+
+	const char *title = NULL;
+	const char *author = NULL;
+	const char *language = NULL;
+
+	for (size_t i = 0; i < req->fieldsz; ++i) {
+		const char *key = req->fields[i].key;
+		const char *val = req->fields[i].val;
+
+		if (strcmp(key, "title") == 0)
+			title = val;
+		else if (strcmp(key, "author") == 0)
+			author = val;
+		else if (strcmp(key, "language") == 0)
+			language = val;
+	}
+
+	/* Sets to null if they are empty. */
+	if (title && strlen(title) == 0)
+		title = NULL;
+	if (author && strlen(author) == 0)
+		author = NULL;
+	if (language && strlen(language) == 0)
+		language = NULL;
+
+	if (!database_search(data.pastes, &data.count, title, author, language))
+		page(req, NULL, KHTTP_500, "500.html");
+	else {
+		struct ktemplate kt = {
+			.key    = tmpl_index_keywords,
+			.keysz  = 1,
+			.arg    = &data,
+			.cb     = tmpl_index
+		};
+
+		page(req, &kt, KHTTP_200, "index.html");
+	}
+
+	for (size_t i = 0; i < data.count; ++i)
+		paste_finish(&data.pastes[i]);
+}
+
+static void
+page_search(struct kreq *req)
+{
+	switch (req->method) {
+	case KMETHOD_GET:
+		page_search_get(req);
+		break;
+	case KMETHOD_POST:
+		page_search_post(req);
+		break;
+	default:
+		page(req, NULL, KHTTP_400, "400.html");
+		break;
+	}
+}
+
+static void
 page_static_get(struct kreq *req)
 {
 	struct stat st;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/minimal/404.html	Fri Feb 07 13:23:48 2020 +0100
@@ -0,0 +1,1 @@
+<h1>Not found</h1>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/minimal/500.html	Fri Feb 07 13:23:48 2020 +0100
@@ -0,0 +1,1 @@
+<h1>Internal server error</h1>
--- a/themes/minimal/header.html	Thu Feb 06 21:48:32 2020 +0100
+++ b/themes/minimal/header.html	Fri Feb 07 13:23:48 2020 +0100
@@ -7,5 +7,7 @@
 
 	<h1>Menu</h1>
 	<ul>
+		<li><a href="/">Home</a></li>
 		<li><a href="/new">New</a></li>
+		<li><a href="/search">Search</a></li>
 	</ul>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/minimal/search.html	Fri Feb 07 13:23:48 2020 +0100
@@ -0,0 +1,26 @@
+	<h1>Search pastes</h1>
+
+	<form action="/search" method="post">
+		<table>
+			<tr>
+				<td>Title</td>
+				<td><input name="title" type="text" placeholder="Title" /></td>
+			</tr>
+
+			<tr>
+				<td>Author</td>
+				<td><input name="author" type="text" placeholder="Author" /></td>
+			</tr>
+
+			<tr>
+				<td>Language</td>
+				<td>
+					<select name="language">
+						@@languages@@
+					</select>
+				</td>
+			</tr>
+		</table>
+
+		<button>Submit</button>
+	</form>
--- a/themes/siimple/header.html	Thu Feb 06 21:48:32 2020 +0100
+++ b/themes/siimple/header.html	Fri Feb 07 13:23:48 2020 +0100
@@ -14,6 +14,7 @@
 			<div class="siimple-navbar-subtitle">simple paste service</div>
 			<div class="siimple--float-right">
 				<a class="siimple-navbar-item" href="/new">Create</a>
+				<a class="siimple-navbar-item" href="/search">Search</a>
 			</div>
 		</div>
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/siimple/search.html	Fri Feb 07 13:23:48 2020 +0100
@@ -0,0 +1,26 @@
+	<h1>Search pastes</h1>
+
+	<div class="siimple-form">
+		<form action="/search" method="post">
+			<div class="siimple-field">
+				<label class="siimple-field-label">Title</label>
+				<input class="siimple-input" name="title" type="text" placeholder="Title" />
+			</div>
+
+			<div class="siimple-field">
+				<label class="siimple-field-label">Author</label>
+				<input class="siimple-input" name="author" type="text" placeholder="Author" />
+			</div>
+
+			<div class="siimple-field">
+				<label class="siimple-field-label">Language</label>
+				<select class="siimple-select" name="language">
+					@@languages@@
+				</select>
+			</div>
+
+			<div class="siimple-form-field">
+				<input type="submit" class="siimple-btn siimple-btn--blue"></input>
+			</div>
+		</form>
+	</div>