prepare for multiple apps
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright (c) 2024-2026 Fabian Freund.
|
||||
*
|
||||
* This file is part of WebLibre
|
||||
* (see https://weblibre.eu).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:weblibre/features/web_feed/data/models/feed_author.dart';
|
||||
|
||||
class FeedAuthorsConverter extends TypeConverter<List<FeedAuthor>, String> {
|
||||
const FeedAuthorsConverter();
|
||||
|
||||
@override
|
||||
List<FeedAuthor> fromSql(String fromDb) {
|
||||
final authors = jsonDecode(fromDb) as List<dynamic>;
|
||||
return authors
|
||||
.map((author) => FeedAuthor.fromJson(author as Map<String, dynamic>))
|
||||
.toList();
|
||||
}
|
||||
|
||||
@override
|
||||
String toSql(List<FeedAuthor> value) {
|
||||
return jsonEncode(value.map((author) => author.toJson()).toList());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright (c) 2024-2026 Fabian Freund.
|
||||
*
|
||||
* This file is part of WebLibre
|
||||
* (see https://weblibre.eu).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:weblibre/features/web_feed/data/models/feed_category.dart';
|
||||
|
||||
class FeedCategoriesConverter
|
||||
extends TypeConverter<List<FeedCategory>, String> {
|
||||
const FeedCategoriesConverter();
|
||||
|
||||
@override
|
||||
List<FeedCategory> fromSql(String fromDb) {
|
||||
final categories = jsonDecode(fromDb) as List<dynamic>;
|
||||
return categories
|
||||
.map(
|
||||
(category) => FeedCategory.fromJson(category as Map<String, dynamic>),
|
||||
)
|
||||
.toList();
|
||||
}
|
||||
|
||||
@override
|
||||
String toSql(List<FeedCategory> value) {
|
||||
return jsonEncode(value.map((category) => category.toJson()).toList());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright (c) 2024-2026 Fabian Freund.
|
||||
*
|
||||
* This file is part of WebLibre
|
||||
* (see https://weblibre.eu).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
import 'package:weblibre/features/web_feed/data/database/definitions.drift.dart';
|
||||
|
||||
class FeedDataConverter extends JsonConverter<FeedData, Map<String, dynamic>> {
|
||||
const FeedDataConverter();
|
||||
|
||||
@override
|
||||
FeedData fromJson(Map<String, dynamic> json) {
|
||||
return FeedData.fromJson(json);
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson(FeedData object) {
|
||||
return object.toJson();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright (c) 2024-2026 Fabian Freund.
|
||||
*
|
||||
* This file is part of WebLibre
|
||||
* (see https://weblibre.eu).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:weblibre/features/web_feed/data/models/feed_link.dart';
|
||||
|
||||
class FeedLinksConverter extends TypeConverter<List<FeedLink>, String> {
|
||||
const FeedLinksConverter();
|
||||
|
||||
@override
|
||||
List<FeedLink> fromSql(String fromDb) {
|
||||
final links = jsonDecode(fromDb) as List<dynamic>;
|
||||
return links
|
||||
.map((link) => FeedLink.fromJson(link as Map<String, dynamic>))
|
||||
.toList();
|
||||
}
|
||||
|
||||
@override
|
||||
String toSql(List<FeedLink> value) {
|
||||
return jsonEncode(value.map((link) => link.toJson()).toList());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
/*
|
||||
* Copyright (c) 2024-2026 Fabian Freund.
|
||||
*
|
||||
* This file is part of WebLibre
|
||||
* (see https://weblibre.eu).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:weblibre/features/web_feed/data/database/daos/article.drift.dart';
|
||||
import 'package:weblibre/features/web_feed/data/database/database.dart';
|
||||
import 'package:weblibre/features/web_feed/data/database/definitions.drift.dart';
|
||||
import 'package:weblibre/features/web_feed/data/models/feed_article.dart';
|
||||
import 'package:weblibre/features/web_feed/data/models/feed_article_query_result.dart';
|
||||
|
||||
@DriftAccessor()
|
||||
class ArticleDao extends DatabaseAccessor<FeedDatabase> with $ArticleDaoMixin {
|
||||
ArticleDao(super.attachedDatabase);
|
||||
|
||||
Selectable<FeedArticle> getFeedArticles(Uri? url) {
|
||||
final select = db.articleView.select();
|
||||
|
||||
if (url != null) {
|
||||
select.where((article) => article.feedId.equalsValue(url));
|
||||
}
|
||||
|
||||
return select..orderBy([
|
||||
(row) => OrderingTerm(
|
||||
expression: coalesce([row.updated, row.created]),
|
||||
mode: OrderingMode.desc,
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
Selectable<FeedArticle> getUnprocessedArticles() {
|
||||
return db.articleView.select()..where(
|
||||
(article) =>
|
||||
(article.contentHtml.isNotNull() &
|
||||
(article.contentMarkdown.isNull() |
|
||||
article.contentPlain.isNull())) |
|
||||
(article.summaryHtml.isNotNull() &
|
||||
(article.summaryMarkdown.isNull() |
|
||||
article.summaryPlain.isNull())),
|
||||
);
|
||||
}
|
||||
|
||||
SingleOrNullSelectable<FeedArticle> getArticleById(String articleId) {
|
||||
return db.articleView.select()..where((row) => row.id.equals(articleId));
|
||||
}
|
||||
|
||||
Future<void> updateArticleContent(List<FeedArticle> articles) {
|
||||
return db.transaction(() async {
|
||||
await Future.wait(
|
||||
articles.map((newArticle) {
|
||||
final statement = db.article.update()
|
||||
..where((article) => article.id.equals(newArticle.id));
|
||||
|
||||
return statement.write(
|
||||
ArticleCompanion(
|
||||
summaryHtml: Value(newArticle.summaryHtml),
|
||||
summaryMarkdown: Value(newArticle.summaryMarkdown),
|
||||
summaryPlain: Value(newArticle.summaryPlain),
|
||||
contentHtml: Value(newArticle.contentHtml),
|
||||
contentMarkdown: Value(newArticle.contentMarkdown),
|
||||
contentPlain: Value(newArticle.contentPlain),
|
||||
),
|
||||
);
|
||||
}),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> upsertArticles(List<FeedArticle> articles) {
|
||||
return db.transaction(() async {
|
||||
await Future.wait(
|
||||
articles
|
||||
.map(
|
||||
(article) => db.article.insertOne(
|
||||
article,
|
||||
onConflict: DoUpdate(
|
||||
(old) {
|
||||
return ArticleCompanion(
|
||||
authors: Value(article.authors),
|
||||
contentHtml: Value(article.contentHtml),
|
||||
contentMarkdown: Value(article.contentMarkdown),
|
||||
contentPlain: Value(article.contentPlain),
|
||||
links: Value(article.links),
|
||||
summaryHtml: Value(article.summaryHtml),
|
||||
summaryMarkdown: Value(article.summaryMarkdown),
|
||||
summaryPlain: Value(article.summaryPlain),
|
||||
tags: Value(article.tags),
|
||||
title: Value(article.title),
|
||||
updated: Value(article.updated),
|
||||
);
|
||||
},
|
||||
where: (old) =>
|
||||
old.updated.isNotNull() &
|
||||
old.updated.isSmallerThanValue(
|
||||
article.updated ?? DateTime(0),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Future<int> updateArticleRead(String articleId, DateTime? read) {
|
||||
final statement = db.article.update()
|
||||
..where((article) => article.id.equals(articleId));
|
||||
|
||||
return statement.write(ArticleCompanion(lastRead: Value(read)));
|
||||
}
|
||||
|
||||
Selectable<(String, int)> getUnreadArticleCount() {
|
||||
final count = countAll();
|
||||
|
||||
final countByFeed = db.article.selectOnly()
|
||||
..addColumns([db.article.feedId, count])
|
||||
..where(
|
||||
db.article.lastRead.isNull() |
|
||||
(db.article.updated.isNotNull() &
|
||||
db.article.lastRead.isSmallerThan(db.article.lastRead)),
|
||||
)
|
||||
..groupBy([db.article.feedId]);
|
||||
|
||||
return countByFeed.map(
|
||||
(result) => (result.read(db.article.feedId)!, result.read(count)!),
|
||||
);
|
||||
}
|
||||
|
||||
Selectable<FeedArticleQueryResult> queryArticles({
|
||||
required String matchPrefix,
|
||||
required String matchSuffix,
|
||||
required String ellipsis,
|
||||
required int snippetLength,
|
||||
required String searchString,
|
||||
required Uri? feedId,
|
||||
int limit = 25,
|
||||
}) {
|
||||
final ftsQuery = db.buildFtsQuery(searchString);
|
||||
|
||||
if (ftsQuery.isNotEmpty) {
|
||||
return db.definitionsDrift.queryArticlesFullContent(
|
||||
feedId: feedId?.toString(),
|
||||
query: ftsQuery,
|
||||
snippetLength: snippetLength,
|
||||
beforeMatch: matchPrefix,
|
||||
afterMatch: matchSuffix,
|
||||
ellipsis: ellipsis,
|
||||
limit: limit,
|
||||
);
|
||||
} else {
|
||||
return db.definitionsDrift.queryArticlesBasic(
|
||||
feedId: feedId?.toString(),
|
||||
query: db.buildLikeQuery(searchString),
|
||||
limit: limit,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// dart format width=80
|
||||
// ignore_for_file: type=lint
|
||||
import 'package:drift/drift.dart' as i0;
|
||||
import 'package:weblibre/features/web_feed/data/database/database.dart' as i1;
|
||||
|
||||
mixin $ArticleDaoMixin on i0.DatabaseAccessor<i1.FeedDatabase> {
|
||||
ArticleDaoManager get managers => ArticleDaoManager(this);
|
||||
}
|
||||
|
||||
class ArticleDaoManager {
|
||||
final $ArticleDaoMixin _db;
|
||||
ArticleDaoManager(this._db);
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright (c) 2024-2026 Fabian Freund.
|
||||
*
|
||||
* This file is part of WebLibre
|
||||
* (see https://weblibre.eu).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:weblibre/features/web_feed/data/database/daos/feed.drift.dart';
|
||||
import 'package:weblibre/features/web_feed/data/database/database.dart';
|
||||
import 'package:weblibre/features/web_feed/data/database/definitions.drift.dart';
|
||||
|
||||
@DriftAccessor()
|
||||
class FeedDao extends DatabaseAccessor<FeedDatabase> with $FeedDaoMixin {
|
||||
FeedDao(super.attachedDatabase);
|
||||
|
||||
Selectable<FeedData> getFeeds() {
|
||||
return db.feed.select();
|
||||
}
|
||||
|
||||
SingleOrNullSelectable<FeedData> getFeed(Uri feedId) {
|
||||
return db.feed.select()..where((feed) => feed.url.equalsValue(feedId));
|
||||
}
|
||||
|
||||
Future<int> updateFeedFetched(Uri feedId, DateTime fetched) {
|
||||
final statement = db.feed.update()
|
||||
..where((feed) => feed.url.equalsValue(feedId));
|
||||
|
||||
return statement.write(FeedCompanion(lastFetched: Value(fetched)));
|
||||
}
|
||||
|
||||
Future<int> deleteFeed(Uri feedId) {
|
||||
return db.feed.deleteWhere((feed) => feed.url.equals(feedId.toString()));
|
||||
}
|
||||
|
||||
Future<int> upsertFeed(FeedData feedData) {
|
||||
return db.feed.insertOne(
|
||||
feedData,
|
||||
onConflict: DoUpdate((old) {
|
||||
return FeedCompanion(
|
||||
authors: Value(feedData.authors),
|
||||
title: Value(feedData.title),
|
||||
description: Value(feedData.description),
|
||||
tags: Value(feedData.tags),
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// dart format width=80
|
||||
// ignore_for_file: type=lint
|
||||
import 'package:drift/drift.dart' as i0;
|
||||
import 'package:weblibre/features/web_feed/data/database/database.dart' as i1;
|
||||
|
||||
mixin $FeedDaoMixin on i0.DatabaseAccessor<i1.FeedDatabase> {
|
||||
FeedDaoManager get managers => FeedDaoManager(this);
|
||||
}
|
||||
|
||||
class FeedDaoManager {
|
||||
final $FeedDaoMixin _db;
|
||||
FeedDaoManager(this._db);
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright (c) 2024-2026 Fabian Freund.
|
||||
*
|
||||
* This file is part of WebLibre
|
||||
* (see https://weblibre.eu).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:drift_dev/api/migrations_native.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:weblibre/features/search/domain/fts_tokenizer.dart';
|
||||
import 'package:weblibre/features/web_feed/data/database/daos/article.dart';
|
||||
import 'package:weblibre/features/web_feed/data/database/daos/feed.dart';
|
||||
import 'package:weblibre/features/web_feed/data/database/database.drift.dart';
|
||||
|
||||
@DriftDatabase(include: {'definitions.drift'}, daos: [ArticleDao, FeedDao])
|
||||
class FeedDatabase extends $FeedDatabase with TrigramQueryBuilderMixin {
|
||||
@override
|
||||
final int schemaVersion = 1;
|
||||
|
||||
@override
|
||||
final int ftsTokenLimit = 10;
|
||||
@override
|
||||
final int ftsMinTokenLength = 3;
|
||||
|
||||
@override
|
||||
MigrationStrategy get migration => MigrationStrategy(
|
||||
beforeOpen: (details) async {
|
||||
if (kDebugMode) {
|
||||
// This check pulls in a fair amount of code that's not needed
|
||||
// anywhere else, so we recommend only doing it in debug builds.
|
||||
await validateDatabaseSchema();
|
||||
}
|
||||
|
||||
await customStatement('PRAGMA foreign_keys = ON;');
|
||||
await definitionsDrift.optimizeFtsIndex();
|
||||
},
|
||||
);
|
||||
|
||||
FeedDatabase(super.e);
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
// dart format width=80
|
||||
// ignore_for_file: type=lint
|
||||
import 'package:drift/drift.dart' as i0;
|
||||
import 'package:weblibre/features/web_feed/data/database/definitions.drift.dart'
|
||||
as i1;
|
||||
import 'package:weblibre/features/web_feed/data/database/daos/article.dart'
|
||||
as i2;
|
||||
import 'package:weblibre/features/web_feed/data/database/database.dart' as i3;
|
||||
import 'package:weblibre/features/web_feed/data/database/daos/feed.dart' as i4;
|
||||
import 'package:drift/internal/modular.dart' as i5;
|
||||
import 'package:sqlite3/common.dart' as i6;
|
||||
|
||||
abstract class $FeedDatabase extends i0.GeneratedDatabase {
|
||||
$FeedDatabase(i0.QueryExecutor e) : super(e);
|
||||
$FeedDatabaseManager get managers => $FeedDatabaseManager(this);
|
||||
late final i1.Feed feed = i1.Feed(this);
|
||||
late final i1.Article article = i1.Article(this);
|
||||
late final i1.ArticleView articleView = i1.ArticleView(this);
|
||||
late final i1.ArticleFts articleFts = i1.ArticleFts(this);
|
||||
late final i2.ArticleDao articleDao = i2.ArticleDao(this as i3.FeedDatabase);
|
||||
late final i4.FeedDao feedDao = i4.FeedDao(this as i3.FeedDatabase);
|
||||
i1.DefinitionsDrift get definitionsDrift => i5.ReadDatabaseContainer(
|
||||
this,
|
||||
).accessor<i1.DefinitionsDrift>(i1.DefinitionsDrift.new);
|
||||
@override
|
||||
Iterable<i0.TableInfo<i0.Table, Object?>> get allTables =>
|
||||
allSchemaEntities.whereType<i0.TableInfo<i0.Table, Object?>>();
|
||||
@override
|
||||
List<i0.DatabaseSchemaEntity> get allSchemaEntities => [
|
||||
feed,
|
||||
article,
|
||||
articleView,
|
||||
i1.articleFeedId,
|
||||
articleFts,
|
||||
i1.articleAfterInsert,
|
||||
i1.articleAfterDelete,
|
||||
i1.articleAfterUpdate,
|
||||
];
|
||||
@override
|
||||
i0.StreamQueryUpdateRules get streamUpdateRules =>
|
||||
const i0.StreamQueryUpdateRules([
|
||||
i0.WritePropagation(
|
||||
on: i0.TableUpdateQuery.onTableName(
|
||||
'feed',
|
||||
limitUpdateKind: i0.UpdateKind.delete,
|
||||
),
|
||||
result: [i0.TableUpdate('article', kind: i0.UpdateKind.delete)],
|
||||
),
|
||||
i0.WritePropagation(
|
||||
on: i0.TableUpdateQuery.onTableName(
|
||||
'article',
|
||||
limitUpdateKind: i0.UpdateKind.insert,
|
||||
),
|
||||
result: [i0.TableUpdate('article_fts', kind: i0.UpdateKind.insert)],
|
||||
),
|
||||
i0.WritePropagation(
|
||||
on: i0.TableUpdateQuery.onTableName(
|
||||
'article',
|
||||
limitUpdateKind: i0.UpdateKind.delete,
|
||||
),
|
||||
result: [i0.TableUpdate('article_fts', kind: i0.UpdateKind.insert)],
|
||||
),
|
||||
i0.WritePropagation(
|
||||
on: i0.TableUpdateQuery.onTableName(
|
||||
'article',
|
||||
limitUpdateKind: i0.UpdateKind.update,
|
||||
),
|
||||
result: [i0.TableUpdate('article_fts', kind: i0.UpdateKind.insert)],
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
class $FeedDatabaseManager {
|
||||
final $FeedDatabase _db;
|
||||
$FeedDatabaseManager(this._db);
|
||||
i1.$FeedTableManager get feed => i1.$FeedTableManager(_db, _db.feed);
|
||||
i1.$ArticleTableManager get article =>
|
||||
i1.$ArticleTableManager(_db, _db.article);
|
||||
i1.$ArticleFtsTableManager get articleFts =>
|
||||
i1.$ArticleFtsTableManager(_db, _db.articleFts);
|
||||
}
|
||||
|
||||
extension DefineFunctions on i6.CommonDatabase {
|
||||
void defineFunctions({
|
||||
required String Function(int, String?) lexoRankNext,
|
||||
required String Function(int, String?) lexoRankPrevious,
|
||||
required String Function(String?, String?) lexoRankReorderAfter,
|
||||
required String Function(String?, String?) lexoRankReorderBefore,
|
||||
}) {
|
||||
createFunction(
|
||||
functionName: 'lexo_rank_next',
|
||||
argumentCount: const i6.AllowedArgumentCount(2),
|
||||
function: (args) {
|
||||
final arg0 = args[0] as int;
|
||||
final arg1 = args[1] as String?;
|
||||
return lexoRankNext(arg0, arg1);
|
||||
},
|
||||
);
|
||||
createFunction(
|
||||
functionName: 'lexo_rank_previous',
|
||||
argumentCount: const i6.AllowedArgumentCount(2),
|
||||
function: (args) {
|
||||
final arg0 = args[0] as int;
|
||||
final arg1 = args[1] as String?;
|
||||
return lexoRankPrevious(arg0, arg1);
|
||||
},
|
||||
);
|
||||
createFunction(
|
||||
functionName: 'lexo_rank_reorder_after',
|
||||
argumentCount: const i6.AllowedArgumentCount(2),
|
||||
function: (args) {
|
||||
final arg0 = args[0] as String?;
|
||||
final arg1 = args[1] as String?;
|
||||
return lexoRankReorderAfter(arg0, arg1);
|
||||
},
|
||||
);
|
||||
createFunction(
|
||||
functionName: 'lexo_rank_reorder_before',
|
||||
argumentCount: const i6.AllowedArgumentCount(2),
|
||||
function: (args) {
|
||||
final arg0 = args[0] as String?;
|
||||
final arg1 = args[1] as String?;
|
||||
return lexoRankReorderBefore(arg0, arg1);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
import 'package:weblibre/data/database/converters/uri.dart';
|
||||
import 'package:weblibre/features/web_feed/data/models/feed_article_query_result.dart';
|
||||
import 'package:weblibre/features/web_feed/data/database/converters/feed_authors.dart';
|
||||
import 'package:weblibre/features/web_feed/data/database/converters/feed_categories.dart';
|
||||
import 'package:weblibre/features/web_feed/data/database/converters/feed_links.dart';
|
||||
import 'package:weblibre/features/web_feed/data/models/feed_article.dart';
|
||||
|
||||
CREATE TABLE feed (
|
||||
url TEXT PRIMARY KEY NOT NULL MAPPED BY `const UriConverter()`,
|
||||
title TEXT,
|
||||
description TEXT,
|
||||
icon TEXT MAPPED BY `const UriConverterNullable()`,
|
||||
site_link TEXT MAPPED BY `const UriConverterNullable()`,
|
||||
authors TEXT MAPPED BY `const FeedAuthorsConverter()`,
|
||||
tags TEXT MAPPED BY `const FeedCategoriesConverter()`,
|
||||
last_fetched DATETIME
|
||||
);
|
||||
|
||||
CREATE TABLE article (
|
||||
id TEXT PRIMARY KEY NOT NULL,
|
||||
feed_id TEXT NOT NULL MAPPED BY `const UriConverter()` REFERENCES feed ("url") ON DELETE CASCADE,
|
||||
fetched DATETIME NOT NULL,
|
||||
created DATETIME,
|
||||
updated DATETIME,
|
||||
last_read DATETIME,
|
||||
title TEXT,
|
||||
authors TEXT MAPPED BY `const FeedAuthorsConverter()`,
|
||||
tags TEXT MAPPED BY `const FeedCategoriesConverter()`,
|
||||
links TEXT MAPPED BY `const FeedLinksConverter()`,
|
||||
summaryHtml TEXT,
|
||||
summaryMarkdown TEXT,
|
||||
summaryPlain TEXT,
|
||||
contentHtml TEXT,
|
||||
contentMarkdown TEXT,
|
||||
contentPlain TEXT
|
||||
) WITH FeedArticle;
|
||||
|
||||
CREATE VIEW article_view WITH FeedArticle AS
|
||||
SELECT
|
||||
a.*,
|
||||
f.icon,
|
||||
f.site_link
|
||||
FROM
|
||||
article a
|
||||
INNER JOIN
|
||||
feed f on f.url = a.feed_id;
|
||||
|
||||
CREATE INDEX article_feed_id ON article (feed_id);
|
||||
|
||||
CREATE VIRTUAL TABLE article_fts
|
||||
USING fts5(
|
||||
title,
|
||||
summaryPlain,
|
||||
contentPlain,
|
||||
content=article,
|
||||
tokenize="trigram"
|
||||
);
|
||||
|
||||
-- Triggers to keep the FTS index up to date.
|
||||
CREATE TRIGGER article_after_insert AFTER INSERT ON article BEGIN
|
||||
INSERT INTO
|
||||
article_fts(rowid, title, summaryPlain, contentPlain)
|
||||
VALUES (new.rowid, new.title, new.summaryPlain, new.contentPlain);
|
||||
END;
|
||||
CREATE TRIGGER article_after_delete AFTER DELETE ON article BEGIN
|
||||
INSERT INTO
|
||||
article_fts(article_fts, rowid, title, summaryPlain, contentPlain)
|
||||
VALUES('delete', old.rowid, old.title, old.summaryPlain, old.contentPlain);
|
||||
END;
|
||||
CREATE TRIGGER article_after_update AFTER UPDATE ON article BEGIN
|
||||
INSERT INTO
|
||||
article_fts(article_fts, rowid, title, summaryPlain, contentPlain)
|
||||
VALUES('delete', old.rowid, old.title, old.summaryPlain, old.contentPlain);
|
||||
INSERT INTO
|
||||
article_fts(rowid, title, summaryPlain, contentPlain)
|
||||
VALUES (new.rowid, new.title, new.summaryPlain, new.contentPlain);
|
||||
END;
|
||||
|
||||
optimizeFtsIndex:
|
||||
INSERT INTO article_fts(article_fts) VALUES ('optimize');
|
||||
|
||||
queryArticlesBasic(:feed_id AS TEXT OR NULL) WITH FeedArticleQueryResult:
|
||||
WITH weights AS (
|
||||
SELECT
|
||||
-- Customize these weights (higher = more important)
|
||||
1.0 as title_weight -- Title matches are most important
|
||||
)
|
||||
SELECT
|
||||
a.*,
|
||||
f.icon,
|
||||
(
|
||||
bm25(article_fts, weights.title_weight)
|
||||
) AS weighted_rank
|
||||
FROM article_fts fts
|
||||
INNER JOIN
|
||||
article a ON a.rowid = fts.rowid
|
||||
INNER JOIN
|
||||
feed f ON f.url = a.feed_id
|
||||
CROSS JOIN weights
|
||||
WHERE
|
||||
fts.title LIKE :query AND
|
||||
(:feed_id IS NULL OR a.feed_id = :feed_id)
|
||||
ORDER BY
|
||||
weighted_rank ASC,
|
||||
a.created DESC NULLS LAST
|
||||
LIMIT :limit;
|
||||
|
||||
queryArticlesFullContent(:feed_id AS TEXT OR NULL) WITH FeedArticleQueryResult:
|
||||
WITH weights AS (
|
||||
SELECT
|
||||
-- Customize these weights (higher = more important)
|
||||
10.0 as title_weight, -- Title matches are most important
|
||||
3.0 as summary_weight, -- Summary matches are quite important
|
||||
1.0 as content_weight -- Content matches are basic
|
||||
)
|
||||
SELECT
|
||||
a.*,
|
||||
f.icon,
|
||||
highlight(article_fts, 0, :beforeMatch, :afterMatch) AS title_highlight,
|
||||
snippet(article_fts, 1, :beforeMatch, :afterMatch, :ellipsis, :snippetLength) AS summary_snippet,
|
||||
snippet(article_fts, 2, :beforeMatch, :afterMatch, :ellipsis, :snippetLength) AS content_snippet,
|
||||
(
|
||||
bm25(article_fts, weights.title_weight, weights.summary_weight,
|
||||
weights.content_weight)
|
||||
) AS weighted_rank
|
||||
FROM article_fts(:query) fts
|
||||
INNER JOIN
|
||||
article a ON a.rowid = fts.rowid
|
||||
INNER JOIN
|
||||
feed f ON f.url = a.feed_id
|
||||
CROSS JOIN weights
|
||||
WHERE
|
||||
:feed_id IS NULL OR a.feed_id = :feed_id
|
||||
ORDER BY
|
||||
weighted_rank ASC,
|
||||
a.created DESC NULLS LAST
|
||||
LIMIT :limit;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,154 @@
|
||||
/*
|
||||
* Copyright (c) 2024-2026 Fabian Freund.
|
||||
*
|
||||
* This file is part of WebLibre
|
||||
* (see https://weblibre.eu).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import 'package:copy_with_extension/copy_with_extension.dart';
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:fast_equatable/fast_equatable.dart';
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
import 'package:weblibre/features/web_feed/data/database/definitions.drift.dart';
|
||||
import 'package:weblibre/features/web_feed/data/models/feed_author.dart';
|
||||
import 'package:weblibre/features/web_feed/data/models/feed_category.dart';
|
||||
import 'package:weblibre/features/web_feed/data/models/feed_link.dart';
|
||||
|
||||
part 'feed_article.g.dart';
|
||||
|
||||
@JsonSerializable()
|
||||
@CopyWith()
|
||||
class FeedArticle with FastEquatable implements Insertable<FeedArticle> {
|
||||
final String id;
|
||||
final Uri feedId;
|
||||
final DateTime fetched;
|
||||
final DateTime? created;
|
||||
final DateTime? updated;
|
||||
final DateTime? lastRead;
|
||||
final String? title;
|
||||
final List<FeedAuthor>? authors;
|
||||
final List<FeedCategory>? tags;
|
||||
final List<FeedLink>? links;
|
||||
final String? summaryHtml;
|
||||
final String? summaryMarkdown;
|
||||
final String? summaryPlain;
|
||||
final String? contentHtml;
|
||||
final String? contentMarkdown;
|
||||
final String? contentPlain;
|
||||
|
||||
//Derived by view from feed table, should not get inserted
|
||||
final Uri? icon;
|
||||
final Uri? siteLink;
|
||||
|
||||
FeedArticle({
|
||||
required this.id,
|
||||
required this.feedId,
|
||||
required this.fetched,
|
||||
this.created,
|
||||
this.updated,
|
||||
this.lastRead,
|
||||
this.title,
|
||||
this.authors,
|
||||
this.tags,
|
||||
this.links,
|
||||
this.summaryHtml,
|
||||
this.summaryMarkdown,
|
||||
this.summaryPlain,
|
||||
this.contentHtml,
|
||||
this.contentMarkdown,
|
||||
this.contentPlain,
|
||||
this.icon,
|
||||
this.siteLink,
|
||||
});
|
||||
|
||||
factory FeedArticle.fromJson(Map<String, dynamic> json) =>
|
||||
_$FeedArticleFromJson(json);
|
||||
|
||||
Map<String, dynamic> toJson() => _$FeedArticleToJson(this);
|
||||
|
||||
@override
|
||||
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||
final map = <String, Expression>{};
|
||||
map['id'] = Variable<String>(id);
|
||||
{
|
||||
map['feed_id'] = Variable<String>(Article.$converterfeedId.toSql(feedId));
|
||||
}
|
||||
map['fetched'] = Variable<DateTime>(fetched);
|
||||
if (!nullToAbsent || created != null) {
|
||||
map['created'] = Variable<DateTime>(created);
|
||||
}
|
||||
if (!nullToAbsent || updated != null) {
|
||||
map['updated'] = Variable<DateTime>(updated);
|
||||
}
|
||||
if (!nullToAbsent || lastRead != null) {
|
||||
map['last_read'] = Variable<DateTime>(lastRead);
|
||||
}
|
||||
if (!nullToAbsent || title != null) {
|
||||
map['title'] = Variable<String>(title);
|
||||
}
|
||||
if (!nullToAbsent || authors != null) {
|
||||
map['authors'] = Variable<String>(
|
||||
Article.$converterauthorsn.toSql(authors),
|
||||
);
|
||||
}
|
||||
if (!nullToAbsent || tags != null) {
|
||||
map['tags'] = Variable<String>(Article.$convertertagsn.toSql(tags));
|
||||
}
|
||||
if (!nullToAbsent || links != null) {
|
||||
map['links'] = Variable<String>(Article.$converterlinksn.toSql(links));
|
||||
}
|
||||
if (!nullToAbsent || summaryHtml != null) {
|
||||
map['summaryHtml'] = Variable<String>(summaryHtml);
|
||||
}
|
||||
if (!nullToAbsent || summaryMarkdown != null) {
|
||||
map['summaryMarkdown'] = Variable<String>(summaryMarkdown);
|
||||
}
|
||||
if (!nullToAbsent || summaryPlain != null) {
|
||||
map['summaryPlain'] = Variable<String>(summaryPlain);
|
||||
}
|
||||
if (!nullToAbsent || contentHtml != null) {
|
||||
map['contentHtml'] = Variable<String>(contentHtml);
|
||||
}
|
||||
if (!nullToAbsent || contentMarkdown != null) {
|
||||
map['contentMarkdown'] = Variable<String>(contentMarkdown);
|
||||
}
|
||||
if (!nullToAbsent || contentPlain != null) {
|
||||
map['contentPlain'] = Variable<String>(contentPlain);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object?> get hashParameters => [
|
||||
id,
|
||||
feedId,
|
||||
fetched,
|
||||
created,
|
||||
updated,
|
||||
lastRead,
|
||||
title,
|
||||
authors,
|
||||
tags,
|
||||
links,
|
||||
summaryHtml,
|
||||
summaryMarkdown,
|
||||
summaryPlain,
|
||||
contentHtml,
|
||||
contentMarkdown,
|
||||
contentPlain,
|
||||
icon,
|
||||
siteLink,
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,313 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'feed_article.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// CopyWithGenerator
|
||||
// **************************************************************************
|
||||
|
||||
abstract class _$FeedArticleCWProxy {
|
||||
FeedArticle id(String id);
|
||||
|
||||
FeedArticle feedId(Uri feedId);
|
||||
|
||||
FeedArticle fetched(DateTime fetched);
|
||||
|
||||
FeedArticle created(DateTime? created);
|
||||
|
||||
FeedArticle updated(DateTime? updated);
|
||||
|
||||
FeedArticle lastRead(DateTime? lastRead);
|
||||
|
||||
FeedArticle title(String? title);
|
||||
|
||||
FeedArticle authors(List<FeedAuthor>? authors);
|
||||
|
||||
FeedArticle tags(List<FeedCategory>? tags);
|
||||
|
||||
FeedArticle links(List<FeedLink>? links);
|
||||
|
||||
FeedArticle summaryHtml(String? summaryHtml);
|
||||
|
||||
FeedArticle summaryMarkdown(String? summaryMarkdown);
|
||||
|
||||
FeedArticle summaryPlain(String? summaryPlain);
|
||||
|
||||
FeedArticle contentHtml(String? contentHtml);
|
||||
|
||||
FeedArticle contentMarkdown(String? contentMarkdown);
|
||||
|
||||
FeedArticle contentPlain(String? contentPlain);
|
||||
|
||||
FeedArticle icon(Uri? icon);
|
||||
|
||||
FeedArticle siteLink(Uri? siteLink);
|
||||
|
||||
/// Creates a new instance with the provided field values.
|
||||
/// Passing `null` to a nullable field nullifies it, while `null` for a non-nullable field is ignored. To update a single field use `FeedArticle(...).copyWith.fieldName(value)`.
|
||||
///
|
||||
/// Example:
|
||||
/// ```dart
|
||||
/// FeedArticle(...).copyWith(id: 12, name: "My name")
|
||||
/// ```
|
||||
FeedArticle call({
|
||||
String id,
|
||||
Uri feedId,
|
||||
DateTime fetched,
|
||||
DateTime? created,
|
||||
DateTime? updated,
|
||||
DateTime? lastRead,
|
||||
String? title,
|
||||
List<FeedAuthor>? authors,
|
||||
List<FeedCategory>? tags,
|
||||
List<FeedLink>? links,
|
||||
String? summaryHtml,
|
||||
String? summaryMarkdown,
|
||||
String? summaryPlain,
|
||||
String? contentHtml,
|
||||
String? contentMarkdown,
|
||||
String? contentPlain,
|
||||
Uri? icon,
|
||||
Uri? siteLink,
|
||||
});
|
||||
}
|
||||
|
||||
/// Callable proxy for `copyWith` functionality.
|
||||
/// Use as `instanceOfFeedArticle.copyWith(...)` or call `instanceOfFeedArticle.copyWith.fieldName(value)` for a single field.
|
||||
class _$FeedArticleCWProxyImpl implements _$FeedArticleCWProxy {
|
||||
const _$FeedArticleCWProxyImpl(this._value);
|
||||
|
||||
final FeedArticle _value;
|
||||
|
||||
@override
|
||||
FeedArticle id(String id) => call(id: id);
|
||||
|
||||
@override
|
||||
FeedArticle feedId(Uri feedId) => call(feedId: feedId);
|
||||
|
||||
@override
|
||||
FeedArticle fetched(DateTime fetched) => call(fetched: fetched);
|
||||
|
||||
@override
|
||||
FeedArticle created(DateTime? created) => call(created: created);
|
||||
|
||||
@override
|
||||
FeedArticle updated(DateTime? updated) => call(updated: updated);
|
||||
|
||||
@override
|
||||
FeedArticle lastRead(DateTime? lastRead) => call(lastRead: lastRead);
|
||||
|
||||
@override
|
||||
FeedArticle title(String? title) => call(title: title);
|
||||
|
||||
@override
|
||||
FeedArticle authors(List<FeedAuthor>? authors) => call(authors: authors);
|
||||
|
||||
@override
|
||||
FeedArticle tags(List<FeedCategory>? tags) => call(tags: tags);
|
||||
|
||||
@override
|
||||
FeedArticle links(List<FeedLink>? links) => call(links: links);
|
||||
|
||||
@override
|
||||
FeedArticle summaryHtml(String? summaryHtml) =>
|
||||
call(summaryHtml: summaryHtml);
|
||||
|
||||
@override
|
||||
FeedArticle summaryMarkdown(String? summaryMarkdown) =>
|
||||
call(summaryMarkdown: summaryMarkdown);
|
||||
|
||||
@override
|
||||
FeedArticle summaryPlain(String? summaryPlain) =>
|
||||
call(summaryPlain: summaryPlain);
|
||||
|
||||
@override
|
||||
FeedArticle contentHtml(String? contentHtml) =>
|
||||
call(contentHtml: contentHtml);
|
||||
|
||||
@override
|
||||
FeedArticle contentMarkdown(String? contentMarkdown) =>
|
||||
call(contentMarkdown: contentMarkdown);
|
||||
|
||||
@override
|
||||
FeedArticle contentPlain(String? contentPlain) =>
|
||||
call(contentPlain: contentPlain);
|
||||
|
||||
@override
|
||||
FeedArticle icon(Uri? icon) => call(icon: icon);
|
||||
|
||||
@override
|
||||
FeedArticle siteLink(Uri? siteLink) => call(siteLink: siteLink);
|
||||
|
||||
@override
|
||||
/// Creates a new instance with the provided field values.
|
||||
/// Passing `null` to a nullable field nullifies it, while `null` for a non-nullable field is ignored. To update a single field use `FeedArticle(...).copyWith.fieldName(value)`.
|
||||
///
|
||||
/// Example:
|
||||
/// ```dart
|
||||
/// FeedArticle(...).copyWith(id: 12, name: "My name")
|
||||
/// ```
|
||||
FeedArticle call({
|
||||
Object? id = const $CopyWithPlaceholder(),
|
||||
Object? feedId = const $CopyWithPlaceholder(),
|
||||
Object? fetched = const $CopyWithPlaceholder(),
|
||||
Object? created = const $CopyWithPlaceholder(),
|
||||
Object? updated = const $CopyWithPlaceholder(),
|
||||
Object? lastRead = const $CopyWithPlaceholder(),
|
||||
Object? title = const $CopyWithPlaceholder(),
|
||||
Object? authors = const $CopyWithPlaceholder(),
|
||||
Object? tags = const $CopyWithPlaceholder(),
|
||||
Object? links = const $CopyWithPlaceholder(),
|
||||
Object? summaryHtml = const $CopyWithPlaceholder(),
|
||||
Object? summaryMarkdown = const $CopyWithPlaceholder(),
|
||||
Object? summaryPlain = const $CopyWithPlaceholder(),
|
||||
Object? contentHtml = const $CopyWithPlaceholder(),
|
||||
Object? contentMarkdown = const $CopyWithPlaceholder(),
|
||||
Object? contentPlain = const $CopyWithPlaceholder(),
|
||||
Object? icon = const $CopyWithPlaceholder(),
|
||||
Object? siteLink = const $CopyWithPlaceholder(),
|
||||
}) {
|
||||
return FeedArticle(
|
||||
id: id == const $CopyWithPlaceholder() || id == null
|
||||
? _value.id
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: id as String,
|
||||
feedId: feedId == const $CopyWithPlaceholder() || feedId == null
|
||||
? _value.feedId
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: feedId as Uri,
|
||||
fetched: fetched == const $CopyWithPlaceholder() || fetched == null
|
||||
? _value.fetched
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: fetched as DateTime,
|
||||
created: created == const $CopyWithPlaceholder()
|
||||
? _value.created
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: created as DateTime?,
|
||||
updated: updated == const $CopyWithPlaceholder()
|
||||
? _value.updated
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: updated as DateTime?,
|
||||
lastRead: lastRead == const $CopyWithPlaceholder()
|
||||
? _value.lastRead
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: lastRead as DateTime?,
|
||||
title: title == const $CopyWithPlaceholder()
|
||||
? _value.title
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: title as String?,
|
||||
authors: authors == const $CopyWithPlaceholder()
|
||||
? _value.authors
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: authors as List<FeedAuthor>?,
|
||||
tags: tags == const $CopyWithPlaceholder()
|
||||
? _value.tags
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: tags as List<FeedCategory>?,
|
||||
links: links == const $CopyWithPlaceholder()
|
||||
? _value.links
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: links as List<FeedLink>?,
|
||||
summaryHtml: summaryHtml == const $CopyWithPlaceholder()
|
||||
? _value.summaryHtml
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: summaryHtml as String?,
|
||||
summaryMarkdown: summaryMarkdown == const $CopyWithPlaceholder()
|
||||
? _value.summaryMarkdown
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: summaryMarkdown as String?,
|
||||
summaryPlain: summaryPlain == const $CopyWithPlaceholder()
|
||||
? _value.summaryPlain
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: summaryPlain as String?,
|
||||
contentHtml: contentHtml == const $CopyWithPlaceholder()
|
||||
? _value.contentHtml
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: contentHtml as String?,
|
||||
contentMarkdown: contentMarkdown == const $CopyWithPlaceholder()
|
||||
? _value.contentMarkdown
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: contentMarkdown as String?,
|
||||
contentPlain: contentPlain == const $CopyWithPlaceholder()
|
||||
? _value.contentPlain
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: contentPlain as String?,
|
||||
icon: icon == const $CopyWithPlaceholder()
|
||||
? _value.icon
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: icon as Uri?,
|
||||
siteLink: siteLink == const $CopyWithPlaceholder()
|
||||
? _value.siteLink
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: siteLink as Uri?,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
extension $FeedArticleCopyWith on FeedArticle {
|
||||
/// Returns a callable class used to build a new instance with modified fields.
|
||||
/// Example: `instanceOfFeedArticle.copyWith(...)` or `instanceOfFeedArticle.copyWith.fieldName(...)`.
|
||||
// ignore: library_private_types_in_public_api
|
||||
_$FeedArticleCWProxy get copyWith => _$FeedArticleCWProxyImpl(this);
|
||||
}
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
FeedArticle _$FeedArticleFromJson(Map<String, dynamic> json) => FeedArticle(
|
||||
id: json['id'] as String,
|
||||
feedId: Uri.parse(json['feedId'] as String),
|
||||
fetched: DateTime.parse(json['fetched'] as String),
|
||||
created: json['created'] == null
|
||||
? null
|
||||
: DateTime.parse(json['created'] as String),
|
||||
updated: json['updated'] == null
|
||||
? null
|
||||
: DateTime.parse(json['updated'] as String),
|
||||
lastRead: json['lastRead'] == null
|
||||
? null
|
||||
: DateTime.parse(json['lastRead'] as String),
|
||||
title: json['title'] as String?,
|
||||
authors: (json['authors'] as List<dynamic>?)
|
||||
?.map((e) => FeedAuthor.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
tags: (json['tags'] as List<dynamic>?)
|
||||
?.map((e) => FeedCategory.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
links: (json['links'] as List<dynamic>?)
|
||||
?.map((e) => FeedLink.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
summaryHtml: json['summaryHtml'] as String?,
|
||||
summaryMarkdown: json['summaryMarkdown'] as String?,
|
||||
summaryPlain: json['summaryPlain'] as String?,
|
||||
contentHtml: json['contentHtml'] as String?,
|
||||
contentMarkdown: json['contentMarkdown'] as String?,
|
||||
contentPlain: json['contentPlain'] as String?,
|
||||
icon: json['icon'] == null ? null : Uri.parse(json['icon'] as String),
|
||||
siteLink: json['siteLink'] == null
|
||||
? null
|
||||
: Uri.parse(json['siteLink'] as String),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$FeedArticleToJson(FeedArticle instance) =>
|
||||
<String, dynamic>{
|
||||
'id': instance.id,
|
||||
'feedId': instance.feedId.toString(),
|
||||
'fetched': instance.fetched.toIso8601String(),
|
||||
'created': instance.created?.toIso8601String(),
|
||||
'updated': instance.updated?.toIso8601String(),
|
||||
'lastRead': instance.lastRead?.toIso8601String(),
|
||||
'title': instance.title,
|
||||
'authors': instance.authors?.map((e) => e.toJson()).toList(),
|
||||
'tags': instance.tags?.map((e) => e.toJson()).toList(),
|
||||
'links': instance.links?.map((e) => e.toJson()).toList(),
|
||||
'summaryHtml': instance.summaryHtml,
|
||||
'summaryMarkdown': instance.summaryMarkdown,
|
||||
'summaryPlain': instance.summaryPlain,
|
||||
'contentHtml': instance.contentHtml,
|
||||
'contentMarkdown': instance.contentMarkdown,
|
||||
'contentPlain': instance.contentPlain,
|
||||
'icon': instance.icon?.toString(),
|
||||
'siteLink': instance.siteLink?.toString(),
|
||||
};
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright (c) 2024-2026 Fabian Freund.
|
||||
*
|
||||
* This file is part of WebLibre
|
||||
* (see https://weblibre.eu).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import 'package:weblibre/features/web_feed/data/models/feed_article.dart';
|
||||
|
||||
class FeedArticleQueryResult extends FeedArticle {
|
||||
final String? titleHighlight;
|
||||
final String? summarySnippet;
|
||||
final String? contentSnippet;
|
||||
|
||||
final double weightedRank;
|
||||
|
||||
FeedArticleQueryResult({
|
||||
required super.id,
|
||||
required super.feedId,
|
||||
required super.fetched,
|
||||
required this.weightedRank,
|
||||
required super.created,
|
||||
required super.updated,
|
||||
required super.lastRead,
|
||||
required super.title,
|
||||
required super.authors,
|
||||
required super.tags,
|
||||
required super.links,
|
||||
required super.summaryHtml,
|
||||
required super.summaryMarkdown,
|
||||
required super.summaryPlain,
|
||||
required super.contentHtml,
|
||||
required super.contentMarkdown,
|
||||
required super.contentPlain,
|
||||
required super.icon,
|
||||
this.titleHighlight,
|
||||
this.summarySnippet,
|
||||
this.contentSnippet,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object?> get hashParameters => [
|
||||
...super.hashParameters,
|
||||
weightedRank,
|
||||
summarySnippet,
|
||||
contentSnippet,
|
||||
titleHighlight,
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright (c) 2024-2026 Fabian Freund.
|
||||
*
|
||||
* This file is part of WebLibre
|
||||
* (see https://weblibre.eu).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import 'package:fast_equatable/fast_equatable.dart';
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
part 'feed_author.g.dart';
|
||||
|
||||
@JsonSerializable()
|
||||
class FeedAuthor with FastEquatable {
|
||||
final String? name;
|
||||
final String? email;
|
||||
|
||||
FeedAuthor({this.name, this.email});
|
||||
|
||||
factory FeedAuthor.fromJson(Map<String, dynamic> json) =>
|
||||
_$FeedAuthorFromJson(json);
|
||||
|
||||
Map<String, dynamic> toJson() => _$FeedAuthorToJson(this);
|
||||
|
||||
@override
|
||||
List<Object?> get hashParameters => [name, email];
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'feed_author.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
FeedAuthor _$FeedAuthorFromJson(Map<String, dynamic> json) =>
|
||||
FeedAuthor(name: json['name'] as String?, email: json['email'] as String?);
|
||||
|
||||
Map<String, dynamic> _$FeedAuthorToJson(FeedAuthor instance) =>
|
||||
<String, dynamic>{'name': instance.name, 'email': instance.email};
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright (c) 2024-2026 Fabian Freund.
|
||||
*
|
||||
* This file is part of WebLibre
|
||||
* (see https://weblibre.eu).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import 'package:fast_equatable/fast_equatable.dart';
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
part 'feed_category.g.dart';
|
||||
|
||||
@JsonSerializable()
|
||||
class FeedCategory with FastEquatable {
|
||||
final String id;
|
||||
final String? title;
|
||||
|
||||
FeedCategory({required this.id, this.title});
|
||||
|
||||
factory FeedCategory.fromJson(Map<String, dynamic> json) =>
|
||||
_$FeedCategoryFromJson(json);
|
||||
|
||||
Map<String, dynamic> toJson() => _$FeedCategoryToJson(this);
|
||||
|
||||
@override
|
||||
List<Object?> get hashParameters => [id, title];
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'feed_category.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
FeedCategory _$FeedCategoryFromJson(Map<String, dynamic> json) =>
|
||||
FeedCategory(id: json['id'] as String, title: json['title'] as String?);
|
||||
|
||||
Map<String, dynamic> _$FeedCategoryToJson(FeedCategory instance) =>
|
||||
<String, dynamic>{'id': instance.id, 'title': instance.title};
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright (c) 2024-2026 Fabian Freund.
|
||||
*
|
||||
* This file is part of WebLibre
|
||||
* (see https://weblibre.eu).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import 'package:fast_equatable/fast_equatable.dart';
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
part 'feed_link.g.dart';
|
||||
|
||||
enum FeedLinkRelation {
|
||||
///an alternate representation of the entry or feed, for example a permalink to the html version of the entry, or the front page of the weblog.
|
||||
alternate,
|
||||
|
||||
///a related resource which is potentially large in size and might require special handling, for example an audio or video recording.
|
||||
enclosure,
|
||||
|
||||
///an document related to the entry or feed.
|
||||
related,
|
||||
|
||||
///the feed itself.
|
||||
self,
|
||||
|
||||
///the source of the information provided in the entry.
|
||||
via,
|
||||
}
|
||||
|
||||
@JsonSerializable()
|
||||
class FeedLink with FastEquatable {
|
||||
final Uri uri;
|
||||
final FeedLinkRelation? relation;
|
||||
final String? title;
|
||||
|
||||
FeedLink({required this.uri, this.relation, this.title});
|
||||
|
||||
factory FeedLink.fromJson(Map<String, dynamic> json) =>
|
||||
_$FeedLinkFromJson(json);
|
||||
|
||||
Map<String, dynamic> toJson() => _$FeedLinkToJson(this);
|
||||
|
||||
@override
|
||||
List<Object?> get hashParameters => [uri, relation, title];
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'feed_link.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
FeedLink _$FeedLinkFromJson(Map<String, dynamic> json) => FeedLink(
|
||||
uri: Uri.parse(json['uri'] as String),
|
||||
relation: $enumDecodeNullable(_$FeedLinkRelationEnumMap, json['relation']),
|
||||
title: json['title'] as String?,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$FeedLinkToJson(FeedLink instance) => <String, dynamic>{
|
||||
'uri': instance.uri.toString(),
|
||||
'relation': _$FeedLinkRelationEnumMap[instance.relation],
|
||||
'title': instance.title,
|
||||
};
|
||||
|
||||
const _$FeedLinkRelationEnumMap = {
|
||||
FeedLinkRelation.alternate: 'alternate',
|
||||
FeedLinkRelation.enclosure: 'enclosure',
|
||||
FeedLinkRelation.related: 'related',
|
||||
FeedLinkRelation.self: 'self',
|
||||
FeedLinkRelation.via: 'via',
|
||||
};
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright (c) 2024-2026 Fabian Freund.
|
||||
*
|
||||
* This file is part of WebLibre
|
||||
* (see https://weblibre.eu).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
import 'package:weblibre/features/web_feed/data/database/converters/feed_data.dart';
|
||||
import 'package:weblibre/features/web_feed/data/database/definitions.drift.dart';
|
||||
import 'package:weblibre/features/web_feed/data/models/feed_article.dart';
|
||||
|
||||
part 'feed_parse_result.g.dart';
|
||||
|
||||
@JsonSerializable()
|
||||
class FeedParseResult {
|
||||
@FeedDataConverter()
|
||||
final FeedData feedData;
|
||||
final List<FeedArticle> articleData;
|
||||
|
||||
FeedParseResult({required this.feedData, required this.articleData});
|
||||
|
||||
factory FeedParseResult.fromJson(Map<String, dynamic> json) =>
|
||||
_$FeedParseResultFromJson(json);
|
||||
|
||||
Map<String, dynamic> toJson() => _$FeedParseResultToJson(this);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'feed_parse_result.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
FeedParseResult _$FeedParseResultFromJson(Map<String, dynamic> json) =>
|
||||
FeedParseResult(
|
||||
feedData: const FeedDataConverter().fromJson(
|
||||
json['feedData'] as Map<String, dynamic>,
|
||||
),
|
||||
articleData: (json['articleData'] as List<dynamic>)
|
||||
.map((e) => FeedArticle.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$FeedParseResultToJson(FeedParseResult instance) =>
|
||||
<String, dynamic>{
|
||||
'feedData': const FeedDataConverter().toJson(instance.feedData),
|
||||
'articleData': instance.articleData.map((e) => e.toJson()).toList(),
|
||||
};
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright (c) 2024-2026 Fabian Freund.
|
||||
*
|
||||
* This file is part of WebLibre
|
||||
* (see https://weblibre.eu).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:drift/native.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:sqlite3_flutter_libs/sqlite3_flutter_libs.dart';
|
||||
import 'package:weblibre/core/database_registry.dart';
|
||||
import 'package:weblibre/core/filesystem.dart';
|
||||
import 'package:weblibre/features/web_feed/data/database/database.dart';
|
||||
|
||||
part 'providers.g.dart';
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
FeedDatabase feedDatabase(Ref ref) {
|
||||
final db = FeedDatabase(
|
||||
LazyDatabase(() async {
|
||||
final file = File(p.join(filesystem.profileDatabasesDir.path, 'feed.db'));
|
||||
|
||||
// Also work around limitations on old Android versions
|
||||
if (Platform.isAndroid) {
|
||||
await applyWorkaroundToOpenSqlite3OnOldAndroidVersions();
|
||||
}
|
||||
|
||||
return NativeDatabase.createInBackground(file);
|
||||
}),
|
||||
);
|
||||
|
||||
DatabaseRegistry.instance.register('feed', db);
|
||||
|
||||
ref.onDispose(() async {
|
||||
await db.close();
|
||||
});
|
||||
|
||||
return db;
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'providers.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint, type=warning
|
||||
|
||||
@ProviderFor(feedDatabase)
|
||||
final feedDatabaseProvider = FeedDatabaseProvider._();
|
||||
|
||||
final class FeedDatabaseProvider
|
||||
extends $FunctionalProvider<FeedDatabase, FeedDatabase, FeedDatabase>
|
||||
with $Provider<FeedDatabase> {
|
||||
FeedDatabaseProvider._()
|
||||
: super(
|
||||
from: null,
|
||||
argument: null,
|
||||
retry: null,
|
||||
name: r'feedDatabaseProvider',
|
||||
isAutoDispose: false,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$feedDatabaseHash();
|
||||
|
||||
@$internal
|
||||
@override
|
||||
$ProviderElement<FeedDatabase> $createElement($ProviderPointer pointer) =>
|
||||
$ProviderElement(pointer);
|
||||
|
||||
@override
|
||||
FeedDatabase create(Ref ref) {
|
||||
return feedDatabase(ref);
|
||||
}
|
||||
|
||||
/// {@macro riverpod.override_with_value}
|
||||
Override overrideWithValue(FeedDatabase value) {
|
||||
return $ProviderOverride(
|
||||
origin: this,
|
||||
providerOverride: $SyncValueProvider<FeedDatabase>(value),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
String _$feedDatabaseHash() => r'1ecae87a3de5b2d43136fdb73411727b0b217621';
|
||||
Reference in New Issue
Block a user