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 e3616bfde048241b5ea2c2e0266283ba725a0894
Author: René Wagner <rwagner@rw-net.de>
Date:   Wed, 18 Nov 2020 21:33:19 +0100

initial commit of current wip

Diffstat:
A.gitignore | 2++
ALICENSE | 29+++++++++++++++++++++++++++++
AREADME.md | 17+++++++++++++++++
Adata/data.sqlite.example | 0
Agmnifaq.conf.example | 2++
Aindex.pl | 97+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atags.pl | 105+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
7 files changed, 252 insertions(+), 0 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -0,0 +1,2 @@ +data/data.sqlite +gmnifaq.conf diff --git a/LICENSE b/LICENSE @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2018-2020, René Wagner +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md @@ -0,0 +1,17 @@ +# gmnifaq + +gmnifaq is going to be a simple, self-hostable FAQ-engine for the [gemini protocol](gemini://gemini.circumlunar.space) + +## planned features + +- support for tags +- searching +- view by tag +- view all + +## requirements + +- gemini server with cgi enabled +- Perl >= 5.28 +- SQLite + diff --git a/data/data.sqlite.example b/data/data.sqlite.example Binary files differ. diff --git a/gmnifaq.conf.example b/gmnifaq.conf.example @@ -0,0 +1,2 @@ +$sitename = 'gemini faq'; +$siteintro = 'Welcome to gmnifaq, the simple cgi engine for your gemini capsule'; diff --git a/index.pl b/index.pl @@ -0,0 +1,97 @@ +#!/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; + +# 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 +); + +my $sitename = 'gmnifaq'; +my $siteintro = 'Welcome to gmnifaq!'; + +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, '# 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, footer(); + +write_response('SUCCESS', 'text/gemini', @body); + +exit; + +sub header +{ + my $dbh = DBI->connect($dsn, '', '', { RaiseError => 1 }) or die $DBI::errstr; + + 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); +} + +sub footer +{ + return ('', '=> 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/tags.pl b/tags.pl @@ -0,0 +1,105 @@ +#!/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 +); + +my $sitename; +my $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, '# 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, tags(); +push @body, footer(); + +write_response('SUCCESS', 'text/gemini', @body); + +exit; + +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'); + $stmt->execute(); + + my $rows = $stmt->fetchall_arrayref; + $dbh->disconnect(); + + if ( !scalar @$rows ) { + push @body, 'No tags found!'; + } + else { + foreach (@$rows) + { + push @tags, sprintf("=> tags.pl?%s %s (%d entrys)", @$_[0], @$_[0], @$_[1]); + } + } + return @tags; +} + +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; +}