2022-08-25 15:10:08 +02:00
#!/usr/bin/python3
import sqlite3
2023-08-21 10:43:25 +02:00
from flask import Flask , make_response , request , render_template , g , send_from_directory
2022-08-25 15:10:08 +02:00
from flask_paginate import Pagination , get_page_parameter
2023-08-21 14:56:12 +02:00
from flask_assets import Environment
2022-08-27 12:48:40 +02:00
import confuse
2023-08-16 10:28:20 +02:00
import re
2022-08-25 15:10:08 +02:00
2023-08-26 12:32:18 +02:00
DATABASE = " file:../data/diffs.db?mode=ro "
2022-08-27 12:48:40 +02:00
CONFIG_FILE = " ../data/config.yaml "
2023-08-21 10:38:13 +02:00
config = confuse . Configuration ( " headline " , __name__ )
2022-08-27 12:48:40 +02:00
config . set_file ( CONFIG_FILE )
2022-08-25 15:10:08 +02:00
app = Flask ( __name__ )
2023-08-21 14:56:12 +02:00
assets = Environment ( app )
assets . register ( " css " , " main.css " , output = " dist/main. %(version)s .css " )
assets . url_expire = False
2022-08-25 15:10:08 +02:00
def get_db ( ) :
2023-08-21 10:38:13 +02:00
db = getattr ( g , " _database " , None )
if db is None :
2023-08-26 12:32:18 +02:00
db = g . _database = sqlite3 . connect ( DATABASE , uri = True )
2023-08-21 10:38:13 +02:00
db . row_factory = sqlite3 . Row
return db
2022-08-25 15:10:08 +02:00
2023-08-16 10:28:20 +02:00
2022-08-25 15:10:08 +02:00
@app.teardown_appcontext
def close_connection ( exception ) :
2023-08-21 10:38:13 +02:00
db = getattr ( g , " _database " , None )
if db is not None :
db . close ( )
2022-08-25 15:10:08 +02:00
2023-08-16 10:28:20 +02:00
def websearch_to_fts_query ( search : str ) :
2023-08-21 10:38:13 +02:00
"""
Converts web searches into fts queries :
' this is " a test " ' - > ' " this " OR " is " OR " a test " '
"""
return " OR " . join (
[
' " ' + m . group ( 0 ) + ' " '
for m in re . finditer ( r ' (?<= " )[^ " ]+(?= " )|[^ \ s " ]+ ' , search )
]
)
2023-08-25 10:33:06 +02:00
def get_feeds ( ) :
return [
{
" rss_source " : str ( conf [ " rss_source " ] ) ,
" unique_tag " : str ( conf [ " unique_tag " ] ) ,
" feed_name " : str ( conf [ " name " ] ) ,
}
for conf in config [ " feeds " ]
]
2023-08-21 10:38:13 +02:00
@app.route ( " / " )
2022-08-25 15:10:08 +02:00
def index ( ) :
2023-08-21 10:38:13 +02:00
db = get_db ( ) . cursor ( )
search = request . args . get ( " search " , type = str , default = " " )
query = websearch_to_fts_query ( search ) if search else None
2023-08-25 10:33:06 +02:00
selected_feeds = request . args . getlist ( " feeds[] " )
2023-08-21 10:38:13 +02:00
2023-08-26 12:32:18 +02:00
sql_select = " SELECT * FROM diffs "
sql_count = " SELECT count(*) FROM diffs "
2023-08-21 10:38:13 +02:00
2023-08-26 12:32:18 +02:00
if query :
sql_part_query = f " JOIN (SELECT rowid FROM diffs_fts( { query } )) filter ON filter.rowid = diffs.diff_id "
sql_select = sql_select + sql_part_query
sql_count = sql_count + sql_part_query
if selected_feeds :
feeds = str ( selected_feeds ) . strip ( " [] " )
sql_part_feeds = f " WHERE feed_name in ( { feeds } ) "
sql_select = sql_select + sql_part_feeds
sql_count = sql_count + sql_part_feeds
2023-08-21 10:38:13 +02:00
# flask-paginate
page = request . args . get ( get_page_parameter ( ) , type = int , default = 1 )
2023-08-26 12:32:18 +02:00
db . execute ( sql_count )
diff_count = db . fetchall ( ) [ 0 ] [ 0 ]
2023-08-21 10:38:13 +02:00
pagination = Pagination (
page = page , total = diff_count , record_name = " diffs " , css_framework = " bootstrap5 "
)
page_skip = pagination . skip
per_page = pagination . per_page
2023-08-26 12:32:18 +02:00
# Create and execute final query after getting page info
sql_part_pagination = f " ORDER BY diff_id DESC LIMIT { per_page } OFFSET { page_skip } "
sql_select = sql_select + sql_part_pagination
print ( sql_select )
db . execute ( sql_select )
# This would be a cleaner way to do it, but I have no clue how to make it work. Giving multiple feeds to the query is just seemingly impossible in sqlite.
# What about switching to Elasticsearch? :)
# if selected_feeds and query:
# feeds = str(selected_feeds).strip("[]")
# db.execute(
# "SELECT * FROM diffs JOIN (SELECT rowid FROM diffs_fts(?)) filter ON filter.rowid = diffs.diff_id WHERE feed_name IN (?) ORDER BY diff_id DESC LIMIT ? OFFSET ?",
# (query, feeds, per_page, page_skip),
# )
# elif query:
# db.execute(
# "SELECT * FROM diffs JOIN (SELECT rowid FROM diffs_fts(?)) filter ON filter.rowid = diffs.diff_id ORDER BY diff_id DESC LIMIT ? OFFSET ?",
# (query, per_page, page_skip),
# )
# elif selected_feeds:
# feeds = str(selected_feeds).strip("[]").replace("'", '"')
# print(feeds)
# db.execute(
# f"SELECT * FROM diffs WHERE feed_name IN ({','.join(['?']*len(selected_feeds))}) ORDER BY diff_id DESC LIMIT ? OFFSET ?",
# (selected_feeds, per_page, page_skip),
# )
# else:
# db.execute(
# "SELECT * FROM diffs ORDER BY diff_id DESC LIMIT ? OFFSET ?",
# (per_page, page_skip),
# )
2023-08-21 10:38:13 +02:00
diffs = db . fetchall ( )
2023-08-21 10:43:25 +02:00
html = render_template (
2023-08-21 10:56:04 +02:00
" index.html " ,
2023-08-21 10:38:13 +02:00
diffs = diffs ,
page = page ,
pagination = pagination ,
diff_count = diff_count ,
search = search ,
2023-08-25 10:33:06 +02:00
feeds = get_feeds ( ) ,
selected_feeds = selected_feeds ,
2023-08-21 10:38:13 +02:00
)
2022-08-27 12:48:40 +02:00
2023-08-21 10:43:25 +02:00
res = make_response ( html )
res . cache_control . max_age = 60
res . cache_control . public = True
return res
2022-08-27 12:48:40 +02:00
2023-08-17 11:19:12 +02:00
@app.route ( " /article/<path:article_id> " )
def article_detail ( article_id : str ) :
2023-08-21 10:38:13 +02:00
db = get_db ( ) . cursor ( )
db . execute ( " SELECT * FROM diffs WHERE article_id = ? " , ( article_id , ) )
result = db . fetchall ( )
2023-08-21 10:56:04 +02:00
if len ( result ) == 0 :
return make_response ( render_template ( " not_found.html " ) , 404 )
2023-08-21 10:38:13 +02:00
article_url = result [ 0 ] [ " article_url " ]
2023-08-21 10:56:04 +02:00
2023-08-21 10:38:13 +02:00
return render_template (
" article_detail.html " ,
article_id = article_id ,
article_url = article_url ,
diffs = result ,
)
@app.route ( " /about " )
2022-08-27 12:48:40 +02:00
def about ( ) :
2023-08-21 10:38:13 +02:00
return render_template ( " about.html " )
2022-08-27 12:48:40 +02:00
2023-08-21 10:38:13 +02:00
@app.route ( " /feeds " )
2022-08-27 12:48:40 +02:00
def feed_list ( ) :
2023-08-25 10:33:06 +02:00
return render_template ( " feeds.html " , feeds = get_feeds ( ) )
2023-08-21 10:38:13 +02:00
@app.route ( " /robots.txt " )
2022-08-27 13:20:44 +02:00
def static_from_root ( ) :
2023-08-21 10:38:13 +02:00
return send_from_directory ( app . static_folder or " static " , request . path [ 1 : ] )
2022-08-27 13:20:44 +02:00
2022-08-25 15:10:08 +02:00
if __name__ == " __main__ " :
2023-08-21 10:38:13 +02:00
app . run ( host = " 0.0.0.0 " )