commit ed9009e79d88ebe5eeb1ce78cedd0d1bb4fe5dd8
parent fc9c20994275edb9dbc6d1b5e97d53785643c021
Author: René Wagner <rwagner@rw-net.de>
Date: Fri, 20 Nov 2020 21:35:16 +0100
implement initial faq display
Diffstat:
6 files changed, 163 insertions(+), 36 deletions(-)
diff --git a/README.md b/README.md
@@ -1,19 +1,33 @@
# gmnifaq
-gmnifaq is going to be a simple, self-hostable FAQ-engine for the [gemini protocol](gemini://gemini.circumlunar.space)
+gmnifaq is going to be a simple, self-hostable FAQ-engine for the [gemini protocol](gemini://gemini.circumlunar.space).
Visit the [demo](gemini://gmnspc.clttr.info).
+## already implemented
+
+- tags
+- questions
+ - all
+ - by tag
+
## planned features
-- support for tags
- searching
-- view by tag
-- view all
+- admin site with auth
+
+# installation
+
+- setup your geminiserver with cgi enable
+- `git clone` the repo to the directory
+- rename the files
+ - `gmnifaq.conf.example` to `gmnifaq.conf`
+ - `data/data.sqlite.example` to `data/data.sqlite`
+- enter your FAQs into the db (with SQLiteStudio or similar) - only till admin site is finished
## requirements
-- gemini server with cgi enabled
+- gemini server with cgi enabled (like gmnisrv or stargazer)
- Perl >= 5.28
- URI::Encode
- SQLite
diff --git a/TODO.md b/TODO.md
@@ -1,9 +1,11 @@
# initial todo
- implement display of question
- - all
- - by tag
- single question
- implement search
- check soundslike search
- URI:encode
- built admin interface
+- consolidate things
+ - header generation
+ - footer generation
+ - config slurp
diff --git a/data/data.sqlite.example b/data/data.sqlite.example
Binary files differ.
diff --git a/faqs.pl b/faqs.pl
@@ -0,0 +1,120 @@
+#!/usr/bin/perl
+# Copyright René Wagner 2020
+# licenced under BSD 3-Clause licence
+# https://git.sr.ht/~rwa/gmni-perl-cgi-demo
+
+use strict;
+use DBI;
+#use URI::Encode qw(uri_encode uri_decode);
+# define return codes
+our %RC = (
+ 'INPUT', 10,
+ 'SENSITIVE_INPUT', 11,
+ 'SUCCESS', 20,
+ 'TEMPORARY_REDIRECT', 30,
+ 'PERMANENT_REDIRECT', 31,
+ 'TEMPORARY_FAILURE', 40,
+ 'SERVER_UNAVAILABLE', 41,
+ 'CGI_ERROR', 42,
+ 'PROXY_ERROR', 43,
+ 'SLOW_DOWN', 44,
+ 'PERMANENT_FAILURE', 50,
+ 'NOT_FOUND', 51,
+ 'GONE', 52,
+ 'PROXY_REQUEST_REFUSE', 53,
+ 'BAD_REQUEST', 59,
+ 'CLIENT_CERT_REQUIRED', 60,
+ 'CERT_NOT_AUTHORISED', 61,
+ 'CERT_NOT_VALID', 62
+);
+
+our $sitename;
+our $siteintro;
+
+my $dsn = "DBI:SQLite:dbname=data/data.sqlite";
+
+# enable UTF-8 mode for everything
+use utf8;
+binmode STDOUT, ':utf8';
+binmode STDERR, ':utf8';
+
+if (!defined($ENV{'SERVER_PROTOCOL'}) || $ENV{'SERVER_PROTOCOL'} ne 'GEMINI') {
+ write_response('CGI_ERROR', 'CGI execution error', undef);
+}
+
+if ( -f './gmnifaq.conf' ) { do './gmnifaq.conf'; }
+
+if ( !-f 'data/data.sqlite' ) { write_response('PERMANENT_FAILURE', 'Permanent failure', undef) };
+
+my @body = ();
+push @body, header();
+push @body, faqs();
+push @body, footer();
+
+write_response('SUCCESS', 'text/gemini', @body);
+
+exit;
+
+sub sql
+{
+ my $query = $ENV{'QUERY_STRING'};
+
+ if ( $query eq '' ) {
+ return "SELECT * FROM questions q ORDER BY question;";
+ }
+
+ if ( $query =~ /tag=([0-9]+)/i ) {
+ return "SELECT q.* FROM questions q JOIN tags_questions tq ON q.id = tq.q_id WHERE tq.t_id = $1";
+ }
+
+ write_response('INTERNAL_SERVER_ERROR', 'CGI execution error', undef);
+}
+
+sub faqs
+{
+ my $dbh = DBI->connect($dsn, '', '', { RaiseError => 1 }) or die $DBI::errstr;
+
+ my @return;
+ my $stmt = $dbh->prepare(sql());
+ $stmt->execute();
+
+ my $rows = $stmt->fetchall_arrayref;
+ $dbh->disconnect();
+
+ if ( !scalar @$rows ) {
+ push @return, 'No faqs found!';
+ }
+ else {
+ foreach (@$rows) {
+ push @return, sprintf("### %s", @$_[1]);
+ push @return, '';
+ push @return, @$_[2];
+ push @return, '';
+ }
+ }
+ return @return;
+}
+
+sub header
+{
+ return ( '# FAQs on '. $sitename, '');
+}
+
+sub footer
+{
+ return ('', '=> index.pl Home', '=> https://git.sr.ht/~rwa/gmnifaq powered by gmnifaq');
+}
+
+sub write_response
+{
+ my ($returncode, $meta, @content) = @_;
+
+ if (!defined($RC{$returncode})) { die "Unknown response code!"; }
+
+ printf("%d %s\r\n", $RC{$returncode}, ($meta eq '') ? $returncode : $meta);
+ foreach (@content) {
+ print("$_\r\n");
+ }
+
+ exit;
+}
diff --git a/index.pl b/index.pl
@@ -28,8 +28,8 @@ our %RC = (
'CERT_NOT_VALID', 62
);
-my $sitename = 'gmnifaq';
-my $siteintro = 'Welcome to gmnifaq!';
+our $sitename = 'gmnifaq';
+our $siteintro = 'Welcome to gmnifaq!';
my $dsn = "DBI:SQLite:dbname=data/data.sqlite";
@@ -48,17 +48,12 @@ if ( -f './gmnifaq.conf' ) { do './gmnifaq.conf'; }
if ( !-f 'data/data.sqlite' ) { write_response('PERMANENT_FAILURE', 'Permanent failure', undef) };
my @body = ();
-push @body, '# Welcome to '. $sitename;
-push @body, '';
-push @body, $siteintro;
-push @body, '';
push @body, header();
-push @body, '';
push @body, '## Meta';
push @body, '';
push @body, 'Search';
push @body, '=> tags.pl Tags';
-push @body, 'View all';
+push @body, '=> faqs.pl View all';
push @body, footer();
write_response('SUCCESS', 'text/gemini', @body);
@@ -73,7 +68,7 @@ sub header
my $faqcount = $dbh->selectrow_array("SELECT count(id) from questions");
$dbh->disconnect();
- return sprintf('We are currently serving %d FAQs categorized with %d tags!', $faqcount, $tagcount);
+ return ('# Welcome to '. $sitename, '', $siteintro, '', sprintf('We are currently serving %d FAQs categorized with %d tags!', $faqcount, $tagcount), '');
}
sub footer
@@ -88,8 +83,7 @@ sub write_response
if (!defined($RC{$returncode})) { die "Unknown response code!"; }
printf("%d %s\r\n", $RC{$returncode}, ($meta eq '') ? $returncode : $meta);
- foreach (@content)
- {
+ foreach (@content) {
print("$_\r\n");
}
diff --git a/tags.pl b/tags.pl
@@ -28,8 +28,8 @@ our %RC = (
'CERT_NOT_VALID', 62
);
-my $sitename;
-my $siteintro;
+our $sitename;
+our $siteintro;
my $dsn = "DBI:SQLite:dbname=data/data.sqlite";
@@ -38,8 +38,7 @@ use utf8;
binmode STDOUT, ':utf8';
binmode STDERR, ':utf8';
-if (!defined($ENV{'SERVER_PROTOCOL'}) || $ENV{'SERVER_PROTOCOL'} ne 'GEMINI')
-{
+if (!defined($ENV{'SERVER_PROTOCOL'}) || $ENV{'SERVER_PROTOCOL'} ne 'GEMINI') {
write_response('CGI_ERROR', 'CGI execution error', undef);
}
@@ -48,12 +47,7 @@ if ( -f './gmnifaq.conf' ) { do './gmnifaq.conf'; }
if ( !-f 'data/data.sqlite' ) { write_response('PERMANENT_FAILURE', 'Permanent failure', undef) };
my @body = ();
-push @body, '# Welcome to '. $sitename;
-push @body, '';
-push @body, 'Select a tag to browse the questions associated with this tag.';
-push @body, '';
-push @body, '## Tags';
-push @body, '';
+push @body, header();
push @body, tags();
push @body, footer();
@@ -66,27 +60,31 @@ sub tags
my $dbh = DBI->connect($dsn, '', '', { RaiseError => 1 }) or die $DBI::errstr;
my @tags;
- my $stmt = $dbh->prepare('SELECT name, count(t_id) FROM tags LEFT JOIN tags_questions ON tags_questions.t_id = tags.id GROUP BY t_id');
+ my $stmt = $dbh->prepare('SELECT id, name, count(t_id) FROM tags LEFT JOIN tags_questions ON tags_questions.t_id = tags.id GROUP BY t_id');
$stmt->execute();
my $rows = $stmt->fetchall_arrayref;
$dbh->disconnect();
if ( !scalar @$rows ) {
- push @body, 'No tags found!';
+ push @tags, 'No tags found!';
}
else {
- foreach (@$rows)
- {
- push @tags, sprintf("=> tags.pl?%s %s (%d entrys)", @$_[0], @$_[0], @$_[1]);
+ foreach (@$rows) {
+ push @tags, sprintf("=> faqs.pl?tag=%d %s (%d entrys)", @$_[0], @$_[1], @$_[2]);
}
}
return @tags;
}
+sub header
+{
+ return ('# Welcome to '. $sitename, '', 'Select a tag to browse the questions associated with this tag.', '', '## Tags', '');
+}
+
sub footer
{
- return ('', '=> index.pl [Home]', '=> https://git.sr.ht/~rwa/gmnifaq powered by gmnifaq');
+ return ('', '=> index.pl Home', '=> https://git.sr.ht/~rwa/gmnifaq powered by gmnifaq');
}
sub write_response
@@ -96,8 +94,7 @@ sub write_response
if (!defined($RC{$returncode})) { die "Unknown response code!"; }
printf("%d %s\r\n", $RC{$returncode}, ($meta eq '') ? $returncode : $meta);
- foreach (@content)
- {
+ foreach (@content) {
print("$_\r\n");
}