2022-01-19 19:51:08 +00:00
package sqlite
import (
2022-01-20 21:29:58 +00:00
"database/sql"
2022-01-19 19:51:08 +00:00
"embed"
2022-02-03 16:44:08 +00:00
"encoding/json"
"fmt"
2022-01-19 19:51:08 +00:00
"github.com/ChronosX88/yans/internal/config"
"github.com/ChronosX88/yans/internal/models"
2022-01-20 21:29:58 +00:00
"github.com/ChronosX88/yans/internal/utils"
"github.com/dlclark/regexp2"
2022-01-19 19:51:08 +00:00
"github.com/jmoiron/sqlx"
2022-01-20 21:29:58 +00:00
"github.com/mattn/go-sqlite3"
2022-01-19 19:51:08 +00:00
_ "github.com/mattn/go-sqlite3"
"github.com/pressly/goose/v3"
2022-02-03 16:44:08 +00:00
"strings"
2022-01-19 19:51:08 +00:00
)
//go:embed migrations/*.sql
var migrations embed . FS
type SQLiteBackend struct {
db * sqlx . DB
}
2022-01-20 21:29:58 +00:00
func regexHelper ( re , s string ) ( bool , error ) {
return regexp2 . MustCompile ( re , regexp2 . None ) . MatchString ( s )
}
2022-01-19 19:51:08 +00:00
func NewSQLiteBackend ( cfg config . SQLiteBackendConfig ) ( * SQLiteBackend , error ) {
2022-01-20 21:29:58 +00:00
sql . Register ( "sqlite3_with_regexp" ,
& sqlite3 . SQLiteDriver {
ConnectHook : func ( conn * sqlite3 . SQLiteConn ) error {
return conn . RegisterFunc ( "regexp" , regexHelper , true )
} ,
} )
db , err := sqlx . Open ( "sqlite3_with_regexp" , cfg . Path )
2022-01-19 19:51:08 +00:00
if err != nil {
return nil , err
}
goose . SetBaseFS ( migrations )
if err := goose . SetDialect ( "sqlite3" ) ; err != nil {
return nil , err
}
if err := goose . Up ( db . DB , "migrations" ) ; err != nil {
return nil , err
}
return & SQLiteBackend {
db : db ,
} , nil
}
func ( sb * SQLiteBackend ) ListGroups ( ) ( [ ] models . Group , error ) {
var groups [ ] models . Group
return groups , sb . db . Select ( & groups , "SELECT * FROM groups" )
}
2022-01-20 21:29:58 +00:00
func ( sb * SQLiteBackend ) ListGroupsByPattern ( pattern string ) ( [ ] models . Group , error ) {
var groups [ ] models . Group
w , err := utils . ParseWildmat ( pattern )
if err != nil {
return nil , err
}
r , err := w . ToRegex ( )
if err != nil {
return nil , err
}
return groups , sb . db . Select ( & groups , "SELECT * FROM groups WHERE group_name REGEXP ?" , r . String ( ) )
}
2022-02-03 17:39:08 +00:00
func ( sb * SQLiteBackend ) GetArticlesCount ( g * models . Group ) ( int , error ) {
2022-01-19 19:51:08 +00:00
var count int
2022-01-19 21:44:27 +00:00
return count , sb . db . Get ( & count , "SELECT COUNT(*) FROM articles_to_groups WHERE group_id = ?" , g . ID )
}
2022-02-03 17:39:08 +00:00
func ( sb * SQLiteBackend ) GetGroupHighWaterMark ( g * models . Group ) ( int , error ) {
2022-01-19 21:44:27 +00:00
var waterMark int
2022-04-14 23:21:35 +00:00
return waterMark , sb . db . Get ( & waterMark , "SELECT COALESCE(max(article_number), 0) FROM articles_to_groups WHERE group_id = ?" , g . ID )
2022-01-19 21:44:27 +00:00
}
2022-02-03 17:39:08 +00:00
func ( sb * SQLiteBackend ) GetGroupLowWaterMark ( g * models . Group ) ( int , error ) {
2022-01-19 21:44:27 +00:00
var waterMark int
2022-04-14 23:21:35 +00:00
return waterMark , sb . db . Get ( & waterMark , "SELECT COALESCE(min(article_number), 0) FROM articles_to_groups WHERE group_id = ?" , g . ID )
2022-01-19 19:51:08 +00:00
}
2022-01-25 16:27:58 +00:00
func ( sb * SQLiteBackend ) GetGroup ( groupName string ) ( models . Group , error ) {
var group models . Group
return group , sb . db . Get ( & group , "SELECT * FROM groups WHERE group_name = ?" , groupName )
}
2022-01-25 21:29:30 +00:00
func ( sb * SQLiteBackend ) GetNewGroupsSince ( timestamp int64 ) ( [ ] models . Group , error ) {
var groups [ ] models . Group
2022-02-03 16:44:08 +00:00
return groups , sb . db . Select ( & groups , "SELECT * FROM groups WHERE created_at > datetime(?, 'unixepoch')" , timestamp )
}
func ( sb * SQLiteBackend ) SaveArticle ( a models . Article , groups [ ] string ) error {
res , err := sb . db . Exec ( "INSERT INTO articles (header, body, thread) VALUES (?, ?, ?)" , a . HeaderRaw , a . Body , a . Thread )
articleID , err := res . LastInsertId ( )
if err != nil {
return err
}
var groupIDs [ ] int
for _ , v := range groups {
v = strings . TrimSpace ( v )
g , err := sb . GetGroup ( v )
if err != nil {
if err == sql . ErrNoRows {
return fmt . Errorf ( "no such newsgroup" )
} else {
return err
}
}
groupIDs = append ( groupIDs , g . ID )
}
for _ , v := range groupIDs {
2022-04-18 23:50:31 +00:00
_ , err = sb . db . Exec ( "INSERT INTO articles_to_groups (article_id, article_number, group_id) VALUES (?, (SELECT ifnull(max(article_number)+1, 1) FROM articles_to_groups WHERE group_id = ?), ?)" , articleID , v , v )
2022-02-03 16:44:08 +00:00
if err != nil {
return err
}
}
2022-04-12 00:29:22 +00:00
// save attachments into db
for _ , v := range a . Attachments {
_ , err = sb . db . Exec ( "INSERT INTO attachments_articles_mapping (article_id, content_type, attachment_id) VALUES (?, ?, ?)" , articleID , v . ContentType , v . FileName )
if err != nil {
return err
}
}
2022-02-03 16:44:08 +00:00
return err
}
func ( sb * SQLiteBackend ) GetArticle ( messageID string ) ( models . Article , error ) {
var a models . Article
2022-02-05 10:51:50 +00:00
if err := sb . db . Get ( & a , "SELECT * FROM articles WHERE json_extract(articles.header, '$.Message-Id[0]') = ?" , messageID ) ; err != nil {
return a , err
}
2022-02-05 16:03:54 +00:00
if err := sb . db . Get ( & a . ArticleNumber , "SELECT article_number FROM articles_to_groups WHERE article_id = ?" , a . ID ) ; err != nil {
return a , err
}
2022-04-12 00:29:22 +00:00
if err := sb . db . Select ( & a . Attachments , "SELECT content_type, attachment_id FROM attachments_articles_mapping WHERE article_id = ?" , a . ID ) ; err != nil {
return a , err
}
2022-02-05 10:51:50 +00:00
return a , json . Unmarshal ( [ ] byte ( a . HeaderRaw ) , & a . Header )
}
func ( sb * SQLiteBackend ) GetArticleByNumber ( g * models . Group , num int ) ( models . Article , error ) {
var a models . Article
if err := sb . db . Get ( & a , "SELECT articles.* FROM articles INNER JOIN articles_to_groups atg on atg.article_id = articles.id WHERE atg.article_number = ? AND atg.group_id = ?" , num , g . ID ) ; err != nil {
2022-02-03 16:44:08 +00:00
return a , err
}
2022-02-05 16:03:54 +00:00
a . ArticleNumber = num
2022-04-12 00:29:22 +00:00
if err := sb . db . Select ( & a . Attachments , "SELECT content_type, attachment_id FROM attachments_articles_mapping WHERE article_id = ?" , a . ID ) ; err != nil {
return a , err
}
2022-02-03 16:44:08 +00:00
return a , json . Unmarshal ( [ ] byte ( a . HeaderRaw ) , & a . Header )
2022-01-25 21:29:30 +00:00
}
2022-02-03 17:39:08 +00:00
func ( sb * SQLiteBackend ) GetArticleNumbers ( g * models . Group , low , high int64 ) ( [ ] int64 , error ) {
var numbers [ ] int64
if high == 0 && low == 0 {
2022-02-03 18:24:40 +00:00
if err := sb . db . Select ( & numbers , "SELECT article_number FROM articles_to_groups WHERE group_id = ?" , g . ID ) ; err != nil {
2022-02-03 17:39:08 +00:00
return nil , err
}
} else if low == - 1 && high != 0 {
2022-02-03 18:24:40 +00:00
if err := sb . db . Select ( & numbers , "SELECT article_number FROM articles_to_groups WHERE group_id = ? AND article_number = ?" , g . ID , high ) ; err != nil {
2022-02-03 17:39:08 +00:00
return nil , err
}
} else if low != 0 && high == - 1 {
2022-02-03 18:24:40 +00:00
if err := sb . db . Select ( & numbers , "SELECT article_number FROM articles_to_groups WHERE group_id = ? AND article_number > ?" , g . ID , low ) ; err != nil {
2022-02-03 17:39:08 +00:00
return nil , err
}
} else if low == - 1 && high == - 1 {
return nil , nil
} else {
2022-02-03 18:24:40 +00:00
if err := sb . db . Select ( & numbers , "SELECT article_number FROM articles_to_groups WHERE group_id = ? AND article_number > ? AND article_number < ?" , g . ID , low , high ) ; err != nil {
2022-02-03 17:39:08 +00:00
return nil , err
}
}
return numbers , nil
}
2022-02-05 15:42:23 +00:00
2022-02-05 16:19:43 +00:00
func ( sb * SQLiteBackend ) GetLastArticleByNum ( g * models . Group , a * models . Article ) ( models . Article , error ) {
var lastArticle models . Article
2022-02-05 17:25:38 +00:00
if err := sb . db . Get ( & lastArticle , "SELECT articles.* FROM articles INNER JOIN articles_to_groups atg on atg.article_id = articles.id WHERE atg.article_number < ? AND atg.group_id = ? ORDER BY atg.article_number DESC LIMIT 1" , a . ArticleNumber , g . ID ) ; err != nil {
2022-02-05 16:19:43 +00:00
return lastArticle , err
}
2022-02-05 17:25:38 +00:00
if err := sb . db . Get ( & lastArticle . ArticleNumber , "SELECT article_number FROM articles_to_groups WHERE article_id = ?" , lastArticle . ID ) ; err != nil {
2022-02-05 16:19:43 +00:00
return lastArticle , err
}
2022-02-05 17:25:38 +00:00
return lastArticle , json . Unmarshal ( [ ] byte ( lastArticle . HeaderRaw ) , & lastArticle . Header )
2022-02-05 16:19:43 +00:00
}
2022-02-05 16:23:20 +00:00
func ( sb * SQLiteBackend ) GetNextArticleByNum ( g * models . Group , a * models . Article ) ( models . Article , error ) {
2022-02-05 17:25:38 +00:00
var nextArticle models . Article
if err := sb . db . Get ( & nextArticle , "SELECT articles.* FROM articles INNER JOIN articles_to_groups atg on atg.article_id = articles.id WHERE atg.article_number > ? AND atg.group_id = ? ORDER BY atg.article_number LIMIT 1" , a . ArticleNumber , g . ID ) ; err != nil {
return nextArticle , err
2022-02-05 16:23:20 +00:00
}
2022-02-05 17:25:38 +00:00
if err := sb . db . Get ( & nextArticle . ArticleNumber , "SELECT article_number FROM articles_to_groups WHERE article_id = ?" , nextArticle . ID ) ; err != nil {
return nextArticle , err
2022-02-05 16:23:20 +00:00
}
2022-02-05 17:25:38 +00:00
return nextArticle , json . Unmarshal ( [ ] byte ( nextArticle . HeaderRaw ) , & nextArticle . Header )
2022-02-05 16:23:20 +00:00
}
2022-02-05 18:31:22 +00:00
func ( sb * SQLiteBackend ) GetArticlesByRange ( g * models . Group , low , high int64 ) ( [ ] models . Article , error ) {
var articles [ ] models . Article
if err := sb . db . Select ( & articles , "SELECT articles.* FROM articles INNER JOIN articles_to_groups atg on atg.article_id = articles.id WHERE atg.article_number >= ? AND atg.article_number <= ? AND atg.group_id = ? ORDER BY atg.article_number" , low , high , g . ID ) ; err != nil {
return nil , err
}
for i := 0 ; i < len ( articles ) ; i ++ {
if err := sb . db . Get ( & articles [ i ] . ArticleNumber , "SELECT article_number FROM articles_to_groups WHERE article_id = ?" , articles [ i ] . ID ) ; err != nil {
return nil , err
}
if err := json . Unmarshal ( [ ] byte ( articles [ i ] . HeaderRaw ) , & articles [ i ] . Header ) ; err != nil {
return nil , err
}
}
return articles , nil
}
2022-02-05 15:42:23 +00:00
func ( sb * SQLiteBackend ) GetNewArticlesSince ( timestamp int64 ) ( [ ] string , error ) {
var articleIds [ ] string
return articleIds , sb . db . Select ( & articleIds , "SELECT json_extract(articles.header, '$.Message-Id[0]') FROM articles WHERE created_at > datetime(?, 'unixepoch')" , timestamp )
}
2022-04-14 21:31:03 +00:00
func ( sb * SQLiteBackend ) GetNewThreads ( g * models . Group , perPage int , pageNum int ) ( [ ] int , error ) {
var numbers [ ] int
return numbers , sb . db . Select ( & numbers , "SELECT atg.article_number FROM articles INNER JOIN articles_to_groups atg on atg.article_id = articles.id WHERE atg.group_id = ? AND articles.thread IS NULL ORDER BY articles.created_at DESC LIMIT ? OFFSET ?" , g . ID , perPage , perPage * pageNum )
}
2022-04-15 02:45:40 +00:00
func ( sb * SQLiteBackend ) GetThread ( g * models . Group , threadNum int ) ( [ ] int , error ) {
var numbers [ ] int
return numbers , sb . db . Select ( & numbers , "SELECT atg.article_number FROM articles INNER JOIN articles_to_groups atg on atg.article_id = articles.id WHERE atg.group_id = ? AND articles.thread = json_extract((SELECT articles.header from articles INNER JOIN articles_to_groups a on articles.id = a.article_id WHERE a.group_id = ? AND a.article_number = ?), '$.Message-Id[0]') ORDER BY articles.created_at" , g . ID , g . ID , threadNum )
}