/************************************************************************
A basic site-search script intended for use with s2cgi and an sqlite3
FTS5-indexed database of the site content.
************************************************************************/
affirm typeinfo(iscontainer cgi);
// cgi.config.scrubExceptions = false;
const s2 = s2,
cgi = cgi,
cliFlags = (s2.ARGV ? s2.ARGV.flags : 0) ||| {prototype:null}
;
const app = {
sqlite3: cgi.sqlite3,
config:{
dbFile: cliFlags['search-db'] ||| __FILEDIR+'fts-pages.sqlite3',
maxQueryLength: 100 /* try to discourage hypothetical "massive query attacks" */
},
db: proc x(){
x.$ && return x.$;
affirm typeinfo(isobject this.sqlite3);
return x.$ = new this.sqlite3(this.config.dbFile,'r');
},
search:proc(q){
if(!q || q.#>this.config.maxQueryLength) return false;
const s = this.db()
.prepare(
//"select round(abs(rank),2) rank, uri uri from fts where content match ?1 or uri match ?1 order by rank DESC limit 15"
/**
It turns out that we can give a match in the URI
a higher rank than the content :) (significant
for uri /gaming/painting when searching for
"painting") ==> bm25(mtime, uri, content). */
"select uri uri, round(abs(rank),2) rank from fts where fts match ?1 and rank match 'bm25(0.0,100.0,1.0)' order by rank desc limit 15"
).bind(1,q),
r = [];
for(var v; v=s.stepObject(); r[]=v);
s.finalize();
return r;
}
};
const out = s2.require(['s2cgi/basicJsonOutputHandler']).0;
cgi.request.pathList && return out.tooMuchPath();
const q = cgi.getGET('q');
out.main({q, results:app.search(q)});
app.db().close();
|