gmnifaq

faq engine for gemini with full text search
git clone https://git.clttr.info/gmnifaq.git
Log (Feed) | Files | Refs (Tags) | README | LICENSE

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:
MREADME.md | 24+++++++++++++++++++-----
MTODO.md | 6++++--
Mdata/data.sqlite.example | 0
Afaqs.pl | 120+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mindex.pl | 16+++++-----------
Mtags.pl | 33+++++++++++++++------------------
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"); }