Compare commits

...

4 Commits

Author SHA1 Message Date
e7feb73f6f lint 2024-12-19 18:38:43 -08:00
5ddb4452ff email2db: stub CLI 2024-12-19 18:35:46 -08:00
760f90762d server: refer to async_graphql extensions through extensions module 2024-12-19 18:35:03 -08:00
51154044cc WIP 2024-12-19 12:56:53 -08:00
10 changed files with 505 additions and 80 deletions

222
Cargo.lock generated
View File

@ -94,6 +94,55 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "anstream"
version = "0.6.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"is_terminal_polyfill",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
[[package]]
name = "anstyle-parse"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c"
dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125"
dependencies = [
"anstyle",
"windows-sys 0.59.0",
]
[[package]] [[package]]
name = "anyhow" name = "anyhow"
version = "1.0.94" version = "1.0.94"
@ -138,43 +187,43 @@ checksum = "71938f30533e4d95a6d17aa530939da3842c2ab6f4f84b9dae68447e4129f74a"
[[package]] [[package]]
name = "async-graphql" name = "async-graphql"
version = "6.0.11" version = "7.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "298a5d587d6e6fdb271bf56af2dc325a80eb291fd0fc979146584b9a05494a8c" checksum = "59fd6bd734afb8b6e4d0f84a3e77305ce0a7ccc60d70f6001cb5e1c3f38d8ff1"
dependencies = [ dependencies = [
"async-graphql-derive", "async-graphql-derive",
"async-graphql-parser", "async-graphql-parser",
"async-graphql-value", "async-graphql-value",
"async-stream", "async-stream",
"async-trait", "async-trait",
"base64 0.13.1", "base64 0.22.1",
"bytes 1.9.0", "bytes 1.9.0",
"fast_chemail", "fast_chemail",
"fnv", "fnv",
"futures-timer",
"futures-util", "futures-util",
"handlebars", "handlebars",
"http 0.2.12", "http 1.2.0",
"indexmap 2.7.0", "indexmap 2.7.0",
"log", "log",
"mime", "mime",
"multer 2.1.0", "multer",
"num-traits", "num-traits",
"once_cell",
"pin-project-lite", "pin-project-lite",
"regex", "regex",
"serde", "serde",
"serde_json", "serde_json",
"serde_urlencoded", "serde_urlencoded",
"static_assertions", "static_assertions_next",
"tempfile", "tempfile",
"thiserror 1.0.69", "thiserror 1.0.69",
] ]
[[package]] [[package]]
name = "async-graphql-derive" name = "async-graphql-derive"
version = "6.0.11" version = "7.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7f329c7eb9b646a72f70c9c4b516c70867d356ec46cb00dcac8ad343fd006b0" checksum = "ac38b4dd452d529d6c0248b51df23603f0a875770352e26ae8c346ce6c149b3e"
dependencies = [ dependencies = [
"Inflector", "Inflector",
"async-graphql-parser", "async-graphql-parser",
@ -189,9 +238,9 @@ dependencies = [
[[package]] [[package]]
name = "async-graphql-parser" name = "async-graphql-parser"
version = "6.0.11" version = "7.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6139181845757fd6a73fbb8839f3d036d7150b798db0e9bb3c6e83cdd65bd53b" checksum = "42d271ddda2f55b13970928abbcbc3423cfc18187c60e8769b48f21a93b7adaa"
dependencies = [ dependencies = [
"async-graphql-value", "async-graphql-value",
"pest", "pest",
@ -201,9 +250,9 @@ dependencies = [
[[package]] [[package]]
name = "async-graphql-rocket" name = "async-graphql-rocket"
version = "6.0.11" version = "7.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10c5004043928e9ea8ca2faecc473e3c4fe4f5be259f63c9d735c9a0e4760c2b" checksum = "7488bd0becd6b97c0ef7ef489c9f30621f9ea2c13a4cfa4e39b92b8567239945"
dependencies = [ dependencies = [
"async-graphql", "async-graphql",
"rocket", "rocket",
@ -214,9 +263,9 @@ dependencies = [
[[package]] [[package]]
name = "async-graphql-value" name = "async-graphql-value"
version = "6.0.11" version = "7.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "323a5143f5bdd2030f45e3f2e0c821c9b1d36e79cf382129c64299c50a7f3750" checksum = "aefe909173a037eaf3281b046dc22580b59a38b765d7b8d5116f2ffef098048d"
dependencies = [ dependencies = [
"bytes 1.9.0", "bytes 1.9.0",
"indexmap 2.7.0", "indexmap 2.7.0",
@ -732,6 +781,46 @@ dependencies = [
"windows-targets 0.52.6", "windows-targets 0.52.6",
] ]
[[package]]
name = "clap"
version = "4.5.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84"
dependencies = [
"clap_builder",
"clap_derive",
]
[[package]]
name = "clap_builder"
version = "4.5.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838"
dependencies = [
"anstream",
"anstyle",
"clap_lex",
"strsim 0.11.1",
]
[[package]]
name = "clap_derive"
version = "4.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab"
dependencies = [
"heck 0.5.0",
"proc-macro2",
"quote",
"syn 2.0.90",
]
[[package]]
name = "clap_lex"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6"
[[package]] [[package]]
name = "cloudabi" name = "cloudabi"
version = "0.0.3" version = "0.0.3"
@ -741,6 +830,12 @@ dependencies = [
"bitflags 1.3.2", "bitflags 1.3.2",
] ]
[[package]]
name = "colorchoice"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
[[package]] [[package]]
name = "combine" name = "combine"
version = "4.6.7" version = "4.6.7"
@ -1606,6 +1701,12 @@ version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
[[package]]
name = "futures-timer"
version = "3.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24"
[[package]] [[package]]
name = "futures-util" name = "futures-util"
version = "0.3.31" version = "0.3.31"
@ -1927,9 +2028,9 @@ dependencies = [
[[package]] [[package]]
name = "handlebars" name = "handlebars"
version = "4.5.0" version = "5.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "faa67bab9ff362228eb3d00bd024a4965d8231bbb7921167f0cfa66c6626b225" checksum = "d08485b96a0e6393e9e4d1b8d48cf74ad6c063cd905eb33f42c1ce3f0377539b"
dependencies = [ dependencies = [
"log", "log",
"pest", "pest",
@ -2648,6 +2749,12 @@ dependencies = [
"windows-sys 0.52.0", "windows-sys 0.52.0",
] ]
[[package]]
name = "is_terminal_polyfill"
version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
[[package]] [[package]]
name = "itertools" name = "itertools"
version = "0.10.5" version = "0.10.5"
@ -3147,24 +3254,6 @@ dependencies = [
"winapi 0.3.9", "winapi 0.3.9",
] ]
[[package]]
name = "multer"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01acbdc23469fd8fe07ab135923371d5f5a422fbf9c522158677c8eb15bc51c2"
dependencies = [
"bytes 1.9.0",
"encoding_rs",
"futures-util",
"http 0.2.12",
"httparse",
"log",
"memchr",
"mime",
"spin",
"version_check",
]
[[package]] [[package]]
name = "multer" name = "multer"
version = "3.1.0" version = "3.1.0"
@ -3545,9 +3634,9 @@ dependencies = [
[[package]] [[package]]
name = "ordered-float" name = "ordered-float"
version = "4.5.0" version = "4.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c65ee1f9701bf938026630b455d5315f490640234259037edb259798b3bcf85e" checksum = "7bb71e1b3fa6ca1c61f383464aaf2bb0e2f8e772a1f01d486832464de363b951"
dependencies = [ dependencies = [
"num-traits", "num-traits",
] ]
@ -3987,12 +4076,11 @@ dependencies = [
[[package]] [[package]]
name = "proc-macro-crate" name = "proc-macro-crate"
version = "1.3.1" version = "3.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b"
dependencies = [ dependencies = [
"once_cell", "toml_edit",
"toml_edit 0.19.15",
] ]
[[package]] [[package]]
@ -4449,7 +4537,7 @@ dependencies = [
"indexmap 2.7.0", "indexmap 2.7.0",
"log", "log",
"memchr", "memchr",
"multer 3.1.0", "multer",
"num_cpus", "num_cpus",
"parking_lot 0.12.3", "parking_lot 0.12.3",
"pin-project-lite", "pin-project-lite",
@ -5061,6 +5149,8 @@ dependencies = [
"build-info", "build-info",
"build-info-build", "build-info-build",
"cacher", "cacher",
"chrono",
"clap",
"css-inline", "css-inline",
"html-escape", "html-escape",
"linkify", "linkify",
@ -5070,6 +5160,7 @@ dependencies = [
"maplit", "maplit",
"memmap", "memmap",
"notmuch", "notmuch",
"opentelemetry",
"reqwest 0.12.9", "reqwest 0.12.9",
"rocket", "rocket",
"rocket_cors", "rocket_cors",
@ -5508,10 +5599,10 @@ dependencies = [
] ]
[[package]] [[package]]
name = "static_assertions" name = "static_assertions_next"
version = "1.1.0" version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" checksum = "d7beae5182595e9a8b683fa98c4317f956c9a2dec3b9716990d20023cc60c766"
[[package]] [[package]]
name = "string" name = "string"
@ -5573,20 +5664,20 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]] [[package]]
name = "strum" name = "strum"
version = "0.25.0" version = "0.26.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06"
dependencies = [ dependencies = [
"strum_macros", "strum_macros",
] ]
[[package]] [[package]]
name = "strum_macros" name = "strum_macros"
version = "0.25.3" version = "0.26.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0" checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be"
dependencies = [ dependencies = [
"heck 0.4.1", "heck 0.5.0",
"proc-macro2", "proc-macro2",
"quote", "quote",
"rustversion", "rustversion",
@ -6304,7 +6395,7 @@ dependencies = [
"serde", "serde",
"serde_spanned", "serde_spanned",
"toml_datetime", "toml_datetime",
"toml_edit 0.22.22", "toml_edit",
] ]
[[package]] [[package]]
@ -6316,17 +6407,6 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "toml_edit"
version = "0.19.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421"
dependencies = [
"indexmap 2.7.0",
"toml_datetime",
"winnow 0.5.40",
]
[[package]] [[package]]
name = "toml_edit" name = "toml_edit"
version = "0.22.22" version = "0.22.22"
@ -6337,7 +6417,7 @@ dependencies = [
"serde", "serde",
"serde_spanned", "serde_spanned",
"toml_datetime", "toml_datetime",
"winnow 0.6.20", "winnow",
] ]
[[package]] [[package]]
@ -6685,6 +6765,12 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
[[package]]
name = "utf8parse"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]] [[package]]
name = "uuid" name = "uuid"
version = "1.11.0" version = "1.11.0"
@ -7115,15 +7201,6 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "winnow"
version = "0.5.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876"
dependencies = [
"memchr",
]
[[package]] [[package]]
name = "winnow" name = "winnow"
version = "0.6.20" version = "0.6.20"
@ -7174,7 +7251,6 @@ checksum = "ea8b391c9a790b496184c29f7f93b9ed5b16abb306c05415b68bcc16e4d06432"
[[package]] [[package]]
name = "xtracing" name = "xtracing"
version = "0.1.0" version = "0.1.0"
source = "git+http://git-private.h.xinu.tv/wathiede/xtracing.git#2802b8f6e2ef98895c703f1e6107a7396876f256"
dependencies = [ dependencies = [
"chrono", "chrono",
"if_empty", "if_empty",

View File

@ -9,11 +9,13 @@ default-run = "server"
[dependencies] [dependencies]
ammonia = "3.3.0" ammonia = "3.3.0"
anyhow = "1.0.79" anyhow = "1.0.79"
async-graphql = { version = "6.0.11", features = ["log"] } async-graphql = { version = "7", features = ["log"] }
async-graphql-rocket = "6.0.11" async-graphql-rocket = "7"
async-trait = "0.1.81" async-trait = "0.1.81"
build-info = "0.0.38" build-info = "0.0.38"
cacher = {git = "http://git-private.h.xinu.tv/wathiede/cacher.git"} cacher = {git = "http://git-private.h.xinu.tv/wathiede/cacher.git"}
chrono = "0.4.39"
clap = { version = "4.5.23", features = ["derive"] }
css-inline = "0.13.0" css-inline = "0.13.0"
html-escape = "0.2.13" html-escape = "0.2.13"
linkify = "0.10.0" linkify = "0.10.0"
@ -23,6 +25,7 @@ mailparse = "0.15.0"
maplit = "1.0.2" maplit = "1.0.2"
memmap = "0.7.0" memmap = "0.7.0"
notmuch = { path = "../notmuch" } notmuch = { path = "../notmuch" }
opentelemetry = "0.27.1"
reqwest = { version = "0.12.7", features = ["blocking"] } reqwest = { version = "0.12.7", features = ["blocking"] }
rocket = { version = "0.5.0-rc.2", features = [ "json" ] } rocket = { version = "0.5.0-rc.2", features = [ "json" ] }
rocket_cors = "0.6.0" rocket_cors = "0.6.0"
@ -37,8 +40,8 @@ tokio = "1.26.0"
tracing = "0.1.41" tracing = "0.1.41"
url = "2.5.2" url = "2.5.2"
urlencoding = "2.1.3" urlencoding = "2.1.3"
#xtracing = { path = "../../xtracing" } xtracing = { path = "../../xtracing" }
xtracing = { git = "http://git-private.h.xinu.tv/wathiede/xtracing.git" } #xtracing = { git = "http://git-private.h.xinu.tv/wathiede/xtracing.git" }
[build-dependencies] [build-dependencies]
build-info-build = "0.0.38" build-info-build = "0.0.38"

View File

@ -1 +1,3 @@
DROP INDEX IF EXISTS post_summary_idx; DROP INDEX IF EXISTS post_summary_idx;
DROP INDEX IF EXISTS post_site_idx;
DROP INDEX IF EXISTS post_title_idx;

View File

@ -0,0 +1,24 @@
BEGIN;
ALTER TABLE IF EXISTS public."Email" DROP CONSTRAINT IF EXISTS email_avatar_fkey;
ALTER TABLE IF EXISTS public."EmailDisplayName" DROP CONSTRAINT IF EXISTS email_id_fk;
ALTER TABLE IF EXISTS public."Message" DROP CONSTRAINT IF EXISTS message_to_fkey;
ALTER TABLE IF EXISTS public."Message" DROP CONSTRAINT IF EXISTS message_cc_fkey;
ALTER TABLE IF EXISTS public."Message" DROP CONSTRAINT IF EXISTS message_from_fkey;
ALTER TABLE IF EXISTS public."Message" DROP CONSTRAINT IF EXISTS message_header_fkey;
ALTER TABLE IF EXISTS public."Message" DROP CONSTRAINT IF EXISTS message_file_fkey;
ALTER TABLE IF EXISTS public."Message" DROP CONSTRAINT IF EXISTS message_body_id_fkey;
ALTER TABLE IF EXISTS public."Message" DROP CONSTRAINT IF EXISTS message_thread_fkey;
ALTER TABLE IF EXISTS public."Message" DROP CONSTRAINT IF EXISTS message_tag_fkey;
DROP TABLE IF EXISTS public."Email";
DROP TABLE IF EXISTS public."EmailDisplayName";
DROP TABLE IF EXISTS public."Message";
DROP TABLE IF EXISTS public."Header";
DROP TABLE IF EXISTS public."File";
DROP TABLE IF EXISTS public."Avatar";
DROP TABLE IF EXISTS public."Body";
DROP TABLE IF EXISTS public."Thread";
DROP TABLE IF EXISTS public."Tag";
END;

View File

@ -0,0 +1,174 @@
-- This script was generated by the ERD tool in pgAdmin 4.
-- Please log an issue at https://github.com/pgadmin-org/pgadmin4/issues/new/choose if you find any bugs, including reproduction steps.
BEGIN;
ALTER TABLE IF EXISTS public."Email" DROP CONSTRAINT IF EXISTS email_avatar_fkey;
ALTER TABLE IF EXISTS public."EmailDisplayName" DROP CONSTRAINT IF EXISTS email_id_fk;
ALTER TABLE IF EXISTS public."Message" DROP CONSTRAINT IF EXISTS message_to_fkey;
ALTER TABLE IF EXISTS public."Message" DROP CONSTRAINT IF EXISTS message_cc_fkey;
ALTER TABLE IF EXISTS public."Message" DROP CONSTRAINT IF EXISTS message_from_fkey;
ALTER TABLE IF EXISTS public."Message" DROP CONSTRAINT IF EXISTS message_header_fkey;
ALTER TABLE IF EXISTS public."Message" DROP CONSTRAINT IF EXISTS message_file_fkey;
ALTER TABLE IF EXISTS public."Message" DROP CONSTRAINT IF EXISTS message_body_id_fkey;
ALTER TABLE IF EXISTS public."Message" DROP CONSTRAINT IF EXISTS message_thread_fkey;
ALTER TABLE IF EXISTS public."Message" DROP CONSTRAINT IF EXISTS message_tag_fkey;
CREATE TABLE IF NOT EXISTS public."Email"
(
id integer NOT NULL GENERATED ALWAYS AS IDENTITY,
address text NOT NULL,
avatar_id integer,
PRIMARY KEY (id),
CONSTRAINT avatar_id UNIQUE (avatar_id)
);
CREATE TABLE IF NOT EXISTS public."EmailDisplayName"
(
id integer NOT NULL GENERATED ALWAYS AS IDENTITY,
email_id integer NOT NULL,
PRIMARY KEY (id)
);
CREATE TABLE IF NOT EXISTS public."Message"
(
id integer NOT NULL GENERATED ALWAYS AS IDENTITY,
subject text,
"from" integer,
"to" integer,
cc integer,
header_id integer,
hash text NOT NULL,
file_id integer NOT NULL,
date timestamp with time zone NOT NULL,
unread boolean NOT NULL,
body_id integer NOT NULL,
thread_id integer NOT NULL,
tag_id integer,
CONSTRAINT body_id UNIQUE (body_id)
);
CREATE TABLE IF NOT EXISTS public."Header"
(
id integer NOT NULL GENERATED ALWAYS AS IDENTITY,
key text NOT NULL,
value text NOT NULL,
PRIMARY KEY (id)
);
CREATE TABLE IF NOT EXISTS public."File"
(
id integer NOT NULL GENERATED ALWAYS AS IDENTITY,
path text NOT NULL,
PRIMARY KEY (id)
);
CREATE TABLE IF NOT EXISTS public."Avatar"
(
id integer NOT NULL GENERATED ALWAYS AS IDENTITY,
url text NOT NULL,
PRIMARY KEY (id)
);
CREATE TABLE IF NOT EXISTS public."Body"
(
id integer NOT NULL GENERATED ALWAYS AS IDENTITY,
text text NOT NULL,
PRIMARY KEY (id)
);
CREATE TABLE IF NOT EXISTS public."Thread"
(
id integer NOT NULL GENERATED ALWAYS AS IDENTITY,
PRIMARY KEY (id)
);
CREATE TABLE IF NOT EXISTS public."Tag"
(
id integer NOT NULL GENERATED ALWAYS AS IDENTITY,
name text NOT NULL,
display text,
fg_color integer,
bg_color integer,
PRIMARY KEY (id)
);
ALTER TABLE IF EXISTS public."Email"
ADD CONSTRAINT email_avatar_fkey FOREIGN KEY (avatar_id)
REFERENCES public."Avatar" (id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE NO ACTION
NOT VALID;
ALTER TABLE IF EXISTS public."EmailDisplayName"
ADD CONSTRAINT email_id_fk FOREIGN KEY (email_id)
REFERENCES public."Email" (id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE NO ACTION
NOT VALID;
ALTER TABLE IF EXISTS public."Message"
ADD CONSTRAINT message_to_fkey FOREIGN KEY ("to")
REFERENCES public."Email" (id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE NO ACTION
NOT VALID;
ALTER TABLE IF EXISTS public."Message"
ADD CONSTRAINT message_cc_fkey FOREIGN KEY (cc)
REFERENCES public."Email" (id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE NO ACTION
NOT VALID;
ALTER TABLE IF EXISTS public."Message"
ADD CONSTRAINT message_from_fkey FOREIGN KEY ("from")
REFERENCES public."Email" (id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE NO ACTION
NOT VALID;
ALTER TABLE IF EXISTS public."Message"
ADD CONSTRAINT message_header_fkey FOREIGN KEY (header_id)
REFERENCES public."Header" (id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE NO ACTION
NOT VALID;
ALTER TABLE IF EXISTS public."Message"
ADD CONSTRAINT message_file_fkey FOREIGN KEY (file_id)
REFERENCES public."File" (id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE NO ACTION
NOT VALID;
ALTER TABLE IF EXISTS public."Message"
ADD CONSTRAINT message_body_id_fkey FOREIGN KEY (body_id)
REFERENCES public."Body" (id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE NO ACTION
NOT VALID;
ALTER TABLE IF EXISTS public."Message"
ADD CONSTRAINT message_thread_fkey FOREIGN KEY (thread_id)
REFERENCES public."Thread" (id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE NO ACTION
NOT VALID;
ALTER TABLE IF EXISTS public."Message"
ADD CONSTRAINT message_tag_fkey FOREIGN KEY (tag_id)
REFERENCES public."Tag" (id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE NO ACTION
NOT VALID;
END;

View File

@ -0,0 +1,23 @@
use chrono::NaiveDateTime;
use clap::Parser;
use server::mail::read_mail_to_db;
use sqlx::postgres::PgPool;
/// Add certain emails as posts in newsfeed app.
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Args {
/// DB URL, something like postgres://newsreader@nixos-07.h.xinu.tv/newsreader
#[arg(short, long)]
db_url: String,
/// path to parse
path: String,
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let _guard = xtracing::init(env!("CARGO_BIN_NAME"))?;
let args = Args::parse();
let pool = PgPool::connect(&args.db_url).await?;
read_mail_to_db(&pool, &args.path).await?;
Ok(())
}

View File

@ -5,7 +5,7 @@
extern crate rocket; extern crate rocket;
use std::{error::Error, io::Cursor, str::FromStr}; use std::{error::Error, io::Cursor, str::FromStr};
use async_graphql::{http::GraphiQLSource, EmptySubscription, Schema}; use async_graphql::{extensions, http::GraphiQLSource, EmptySubscription, Schema};
use async_graphql_rocket::{GraphQLQuery, GraphQLRequest, GraphQLResponse}; use async_graphql_rocket::{GraphQLQuery, GraphQLRequest, GraphQLResponse};
use notmuch::{Notmuch, NotmuchError, ThreadSet}; use notmuch::{Notmuch, NotmuchError, ThreadSet};
use rocket::{ use rocket::{
@ -228,7 +228,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
#[cfg(feature = "tantivy")] #[cfg(feature = "tantivy")]
let schema = schema.data(tantivy_conn); let schema = schema.data(tantivy_conn);
let schema = schema.extension(async_graphql::extensions::Logger).finish(); let schema = schema.extension(extensions::Logger).finish();
let rkt = rkt.manage(schema).manage(pool).manage(Notmuch::default()); let rkt = rkt.manage(schema).manage(pool).manage(Notmuch::default());
//.manage(Notmuch::with_config("../notmuch/testdata/notmuch.config")) //.manage(Notmuch::with_config("../notmuch/testdata/notmuch.config"))

View File

@ -263,6 +263,7 @@ pub struct Tag {
struct SearchCursor { struct SearchCursor {
newsreader_offset: i32, newsreader_offset: i32,
notmuch_offset: i32, notmuch_offset: i32,
#[cfg(feature = "tantivy")]
tantivy_offset: i32, tantivy_offset: i32,
} }
@ -328,10 +329,12 @@ impl QueryRoot {
); );
let newsreader_after = after.as_ref().map(|sc| sc.newsreader_offset); let newsreader_after = after.as_ref().map(|sc| sc.newsreader_offset);
let notmuch_after = after.as_ref().map(|sc| sc.notmuch_offset); let notmuch_after = after.as_ref().map(|sc| sc.notmuch_offset);
#[cfg(feature = "tantivy")]
let tantivy_after = after.as_ref().map(|sc| sc.tantivy_offset); let tantivy_after = after.as_ref().map(|sc| sc.tantivy_offset);
let newsreader_before = before.as_ref().map(|sc| sc.newsreader_offset); let newsreader_before = before.as_ref().map(|sc| sc.newsreader_offset);
let notmuch_before = before.as_ref().map(|sc| sc.notmuch_offset); let notmuch_before = before.as_ref().map(|sc| sc.notmuch_offset);
#[cfg(feature = "tantivy")]
let tantivy_before = before.as_ref().map(|sc| sc.tantivy_offset); let tantivy_before = before.as_ref().map(|sc| sc.tantivy_offset);
let first = first.map(|v| v as i32); let first = first.map(|v| v as i32);
let last = last.map(|v| v as i32); let last = last.map(|v| v as i32);
@ -386,6 +389,7 @@ impl QueryRoot {
results.sort_by_key(|item| match item { results.sort_by_key(|item| match item {
ThreadSummaryCursor::Newsreader(_, ts) => -ts.timestamp, ThreadSummaryCursor::Newsreader(_, ts) => -ts.timestamp,
ThreadSummaryCursor::Notmuch(_, ts) => -ts.timestamp, ThreadSummaryCursor::Notmuch(_, ts) => -ts.timestamp,
#[cfg(feature = "tantivy")]
ThreadSummaryCursor::Tantivy(_, ts) => -ts.timestamp, ThreadSummaryCursor::Tantivy(_, ts) => -ts.timestamp,
}); });
@ -410,6 +414,7 @@ impl QueryRoot {
let mut connection = Connection::new(has_previous_page, has_next_page); let mut connection = Connection::new(has_previous_page, has_next_page);
let mut newsreader_offset = 0; let mut newsreader_offset = 0;
let mut notmuch_offset = 0; let mut notmuch_offset = 0;
#[cfg(feature = "tantivy")]
let mut tantivy_offset = 0; let mut tantivy_offset = 0;
connection.edges.extend(results.into_iter().map(|item| { connection.edges.extend(results.into_iter().map(|item| {
@ -423,6 +428,7 @@ impl QueryRoot {
thread_summary = ts; thread_summary = ts;
notmuch_offset = offset; notmuch_offset = offset;
} }
#[cfg(feature = "tantivy")]
ThreadSummaryCursor::Tantivy(offset, ts) => { ThreadSummaryCursor::Tantivy(offset, ts) => {
thread_summary = ts; thread_summary = ts;
tantivy_offset = offset; tantivy_offset = offset;
@ -431,6 +437,7 @@ impl QueryRoot {
let cur = OpaqueCursor(SearchCursor { let cur = OpaqueCursor(SearchCursor {
newsreader_offset, newsreader_offset,
notmuch_offset, notmuch_offset,
#[cfg(feature = "tantivy")]
tantivy_offset, tantivy_offset,
}); });
Edge::new(cur, thread_summary) Edge::new(cur, thread_summary)
@ -473,6 +480,7 @@ impl QueryRoot {
enum ThreadSummaryCursor { enum ThreadSummaryCursor {
Newsreader(i32, ThreadSummary), Newsreader(i32, ThreadSummary),
Notmuch(i32, ThreadSummary), Notmuch(i32, ThreadSummary),
#[cfg(feature = "tantivy")]
Tantivy(i32, ThreadSummary), Tantivy(i32, ThreadSummary),
} }
async fn newsreader_search( async fn newsreader_search(

View File

@ -1,6 +1,7 @@
pub mod config; pub mod config;
pub mod error; pub mod error;
pub mod graphql; pub mod graphql;
pub mod mail;
pub mod newsreader; pub mod newsreader;
pub mod nm; pub mod nm;
#[cfg(feature = "tantivy")] #[cfg(feature = "tantivy")]
@ -257,7 +258,7 @@ impl Transformer for SlurpContents {
let Some(selectors) = self.get_selectors(&link) else { let Some(selectors) = self.get_selectors(&link) else {
return Ok(html.to_string()); return Ok(html.to_string());
}; };
let mut cacher = self.cacher.lock().await; let cacher = self.cacher.lock().await;
let body = if let Some(body) = cacher.get(link.as_str()) { let body = if let Some(body) = cacher.get(link.as_str()) {
info!("cache hit for {link}"); info!("cache hit for {link}");
String::from_utf8_lossy(&body).to_string() String::from_utf8_lossy(&body).to_string()

114
server/src/mail.rs Normal file
View File

@ -0,0 +1,114 @@
use std::{fs::File, io, io::Read};
use mailparse::{
addrparse_header, dateparse, parse_mail, MailHeaderMap, MailParseError, ParsedMail,
};
use sqlx::postgres::PgPool;
use thiserror::Error;
use tracing::info;
#[derive(Error, Debug)]
pub enum MailError {
#[error("missing from header")]
MissingFrom,
#[error("missing from header display name")]
MissingFromDisplayName,
#[error("missing subject header")]
MissingSubject,
#[error("missing html part")]
MissingHtmlPart,
#[error("missing message ID")]
MissingMessageId,
#[error("missing date")]
MissingDate,
#[error("DB error {0}")]
SqlxError(#[from] sqlx::Error),
#[error("IO error {0}")]
IOError(#[from] std::io::Error),
#[error("mail parse error {0}")]
MailParseError(#[from] MailParseError),
}
pub async fn read_mail_to_db(pool: &PgPool, path: &str) -> Result<(), MailError> {
let mut file = File::open(path)?;
let mut buffer = Vec::new();
file.read_to_end(&mut buffer)?;
let m = parse_mail(&buffer)?;
let subject = m
.headers
.get_first_value("subject")
.ok_or(MailError::MissingSubject)?;
let from = addrparse_header(
m.headers
.get_first_header("from")
.ok_or(MailError::MissingFrom)?,
)?;
let from = from.extract_single_info().ok_or(MailError::MissingFrom)?;
let name = from.display_name.ok_or(MailError::MissingFromDisplayName)?;
let slug = name.to_lowercase().replace(' ', "-");
let url = from.addr;
let message_id = m
.headers
.get_first_value("Message-ID")
.ok_or(MailError::MissingMessageId)?;
let uid = &message_id;
let feed_id = find_feed(&pool, &name, &slug, &url).await?;
let date = dateparse(
&m.headers
.get_first_value("Date")
.ok_or(MailError::MissingDate)?,
)?;
println!("Feed: {feed_id} Subject: {}", subject);
if let Some(m) = first_html(&m) {
let body = m.get_body()?;
info!("add email {slug} {subject} {message_id} {date} {uid} {url}");
} else {
return Err(MailError::MissingHtmlPart.into());
}
Ok(())
}
fn first_html<'m>(m: &'m ParsedMail<'m>) -> Option<&'m ParsedMail<'m>> {
for ele in m.parts() {
if ele.ctype.mimetype == "text/html" {
return Some(ele);
}
}
None
}
async fn find_feed(pool: &PgPool, name: &str, slug: &str, url: &str) -> Result<i32, MailError> {
match sqlx::query!(
r#"
SELECT id
FROM feed
WHERE slug = $1
"#,
slug
)
.fetch_one(pool)
.await
{
Err(sqlx::Error::RowNotFound) => {
let rec = sqlx::query!(
r#"
INSERT INTO feed ( name, slug, url, homepage, selector )
VALUES ( $1, $2, $3, '', '' )
RETURNING id
"#,
name,
slug,
url
)
.fetch_one(pool)
.await?;
return Ok(rec.id);
}
Ok(rec) => return Ok(rec.id),
Err(e) => return Err(e.into()),
};
}