diff --git a/README.md b/README.md

index 9ad328058e50995621b5be586f6aa5d93bab2258..f60e15b66132ff99d1805b54ed925ce882379f5b 100644

--- 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

index 9e12f2eb042ad51af1b9ad837eec8fe699dee61b..8ad3088b909987c9a99a7aa465d68d05fff7dc53 100644

--- 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

index d8c4059aa1104544e7399cafb62f38c9b210290e..a1480e5b5f170772dbe223cdc1a859996ed4f9ad 100644

Binary files a/data/data.sqlite.example and b/data/data.sqlite.example differ

diff --git a/faqs.pl b/faqs.pl

new file mode 100755

index 0000000000000000000000000000000000000000..5626eda5b201e7925d37480bc5d7140ebe660263

--- /dev/null

+++ 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

index d7865c082337d027e890cf73f9ebb8fbc7c91d58..91136f409b5ddaa5a280715f4ae387992167d157 100755

--- a/index.pl

+++ b/index.pl

@@ -28,8 +28,8 @@ 'CERT_NOT_AUTHORISED', 61,

'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 '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 @@ my $tagcount = $dbh->selectrow_array("SELECT count(id) from tags");

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 @@

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

index 87405788d7c7506edf9d023c2b199d06fbf64030..8c82b415093755e44535367a475639fcfac91094 100755

--- a/tags.pl

+++ b/tags.pl

@@ -28,8 +28,8 @@ 'CERT_NOT_AUTHORISED', 61,

'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 '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 @@ {

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 @@

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");

}