Author: Manos Pitsidianakis [manos@pitsidianak.is]
Hash: 19860d2d87f0bacde969d55a58d0042d9ef4810b
Timestamp: Fri, 12 May 2023 13:07:55 +0000 (1 year ago)

+666 -23 +/-13 browse
rest-http: add axum-based server
1diff --git a/Cargo.lock b/Cargo.lock
2index 422f381..8ceffe9 100644
3--- a/Cargo.lock
4+++ b/Cargo.lock
5 @@ -23,6 +23,21 @@ dependencies = [
6 ]
7
8 [[package]]
9+ name = "alloc-no-stdlib"
10+ version = "2.0.4"
11+ source = "registry+https://github.com/rust-lang/crates.io-index"
12+ checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3"
13+
14+ [[package]]
15+ name = "alloc-stdlib"
16+ version = "0.2.2"
17+ source = "registry+https://github.com/rust-lang/crates.io-index"
18+ checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece"
19+ dependencies = [
20+ "alloc-no-stdlib",
21+ ]
22+
23+ [[package]]
24 name = "android_system_properties"
25 version = "0.1.5"
26 source = "registry+https://github.com/rust-lang/crates.io-index"
27 @@ -56,6 +71,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
28 checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
29
30 [[package]]
31+ name = "assert-json-diff"
32+ version = "2.0.2"
33+ source = "registry+https://github.com/rust-lang/crates.io-index"
34+ checksum = "47e4f2b81832e72834d7518d8487a0396a28cc408186a2e8854c0f98011faf12"
35+ dependencies = [
36+ "serde",
37+ "serde_json",
38+ ]
39+
40+ [[package]]
41 name = "assert_cmd"
42 version = "2.0.11"
43 source = "registry+https://github.com/rust-lang/crates.io-index"
44 @@ -82,6 +107,19 @@ dependencies = [
45 ]
46
47 [[package]]
48+ name = "async-compression"
49+ version = "0.3.15"
50+ source = "registry+https://github.com/rust-lang/crates.io-index"
51+ checksum = "942c7cd7ae39e91bde4820d74132e9862e62c2f386c3aa90ccf55949f5bad63a"
52+ dependencies = [
53+ "brotli",
54+ "futures-core",
55+ "memchr",
56+ "pin-project-lite",
57+ "tokio",
58+ ]
59+
60+ [[package]]
61 name = "async-executor"
62 version = "1.5.1"
63 source = "registry+https://github.com/rust-lang/crates.io-index"
64 @@ -399,6 +437,19 @@ dependencies = [
65 ]
66
67 [[package]]
68+ name = "bcrypt"
69+ version = "0.14.0"
70+ source = "registry+https://github.com/rust-lang/crates.io-index"
71+ checksum = "9df288bec72232f78c1ec5fe4e8f1d108aa0265476e93097593c803c8c02062a"
72+ dependencies = [
73+ "base64 0.21.0",
74+ "blowfish",
75+ "getrandom",
76+ "subtle",
77+ "zeroize",
78+ ]
79+
80+ [[package]]
81 name = "bincode"
82 version = "1.3.3"
83 source = "registry+https://github.com/rust-lang/crates.io-index"
84 @@ -462,6 +513,37 @@ dependencies = [
85 ]
86
87 [[package]]
88+ name = "blowfish"
89+ version = "0.9.1"
90+ source = "registry+https://github.com/rust-lang/crates.io-index"
91+ checksum = "e412e2cd0f2b2d93e02543ceae7917b3c70331573df19ee046bcbc35e45e87d7"
92+ dependencies = [
93+ "byteorder",
94+ "cipher",
95+ ]
96+
97+ [[package]]
98+ name = "brotli"
99+ version = "3.3.4"
100+ source = "registry+https://github.com/rust-lang/crates.io-index"
101+ checksum = "a1a0b1dbcc8ae29329621f8d4f0d835787c1c38bb1401979b49d13b0b305ff68"
102+ dependencies = [
103+ "alloc-no-stdlib",
104+ "alloc-stdlib",
105+ "brotli-decompressor",
106+ ]
107+
108+ [[package]]
109+ name = "brotli-decompressor"
110+ version = "2.3.4"
111+ source = "registry+https://github.com/rust-lang/crates.io-index"
112+ checksum = "4b6561fd3f895a11e8f72af2cb7d22e08366bebc2b6b57f7744c4bda27034744"
113+ dependencies = [
114+ "alloc-no-stdlib",
115+ "alloc-stdlib",
116+ ]
117+
118+ [[package]]
119 name = "bstr"
120 version = "1.4.0"
121 source = "registry+https://github.com/rust-lang/crates.io-index"
122 @@ -632,6 +714,16 @@ dependencies = [
123 ]
124
125 [[package]]
126+ name = "cipher"
127+ version = "0.4.4"
128+ source = "registry+https://github.com/rust-lang/crates.io-index"
129+ checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
130+ dependencies = [
131+ "crypto-common",
132+ "inout",
133+ ]
134+
135+ [[package]]
136 name = "clap"
137 version = "4.2.7"
138 source = "registry+https://github.com/rust-lang/crates.io-index"
139 @@ -706,6 +798,25 @@ dependencies = [
140 ]
141
142 [[package]]
143+ name = "config"
144+ version = "0.13.3"
145+ source = "registry+https://github.com/rust-lang/crates.io-index"
146+ checksum = "d379af7f68bfc21714c6c7dea883544201741d2ce8274bb12fa54f89507f52a7"
147+ dependencies = [
148+ "async-trait",
149+ "json5",
150+ "lazy_static",
151+ "nom",
152+ "pathdiff",
153+ "ron",
154+ "rust-ini",
155+ "serde",
156+ "serde_json",
157+ "toml",
158+ "yaml-rust",
159+ ]
160+
161+ [[package]]
162 name = "constant_time_eq"
163 version = "0.1.5"
164 source = "registry+https://github.com/rust-lang/crates.io-index"
165 @@ -903,6 +1014,12 @@ dependencies = [
166 ]
167
168 [[package]]
169+ name = "dlv-list"
170+ version = "0.3.0"
171+ source = "registry+https://github.com/rust-lang/crates.io-index"
172+ checksum = "0688c2a7f92e427f44895cd63841bff7b29f8d7a1648b9e7e07a4a365b2e1257"
173+
174+ [[package]]
175 name = "doc-comment"
176 version = "0.3.3"
177 source = "registry+https://github.com/rust-lang/crates.io-index"
178 @@ -1475,6 +1592,19 @@ dependencies = [
179 ]
180
181 [[package]]
182+ name = "hyper-tls"
183+ version = "0.5.0"
184+ source = "registry+https://github.com/rust-lang/crates.io-index"
185+ checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905"
186+ dependencies = [
187+ "bytes",
188+ "hyper",
189+ "native-tls",
190+ "tokio",
191+ "tokio-native-tls",
192+ ]
193+
194+ [[package]]
195 name = "iana-time-zone"
196 version = "0.1.56"
197 source = "registry+https://github.com/rust-lang/crates.io-index"
198 @@ -1546,6 +1676,15 @@ dependencies = [
199 ]
200
201 [[package]]
202+ name = "inout"
203+ version = "0.1.3"
204+ source = "registry+https://github.com/rust-lang/crates.io-index"
205+ checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5"
206+ dependencies = [
207+ "generic-array",
208+ ]
209+
210+ [[package]]
211 name = "instant"
212 version = "0.1.12"
213 source = "registry+https://github.com/rust-lang/crates.io-index"
214 @@ -1614,6 +1753,17 @@ dependencies = [
215 ]
216
217 [[package]]
218+ name = "json5"
219+ version = "0.4.1"
220+ source = "registry+https://github.com/rust-lang/crates.io-index"
221+ checksum = "96b0db21af676c1ce64250b5f40f3ce2cf27e4e47cb91ed91eb6fe9350b430c1"
222+ dependencies = [
223+ "pest",
224+ "pest_derive",
225+ "serde",
226+ ]
227+
228+ [[package]]
229 name = "kernel32-sys"
230 version = "0.2.2"
231 source = "registry+https://github.com/rust-lang/crates.io-index"
232 @@ -1696,6 +1846,12 @@ dependencies = [
233 ]
234
235 [[package]]
236+ name = "linked-hash-map"
237+ version = "0.5.6"
238+ source = "registry+https://github.com/rust-lang/crates.io-index"
239+ checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
240+
241+ [[package]]
242 name = "linux-raw-sys"
243 version = "0.3.7"
244 source = "registry+https://github.com/rust-lang/crates.io-index"
245 @@ -1816,8 +1972,23 @@ dependencies = [
246 name = "mailpot-http"
247 version = "0.1.1"
248 dependencies = [
249+ "assert-json-diff",
250+ "async-trait",
251+ "axum",
252+ "bcrypt",
253+ "config",
254+ "http",
255+ "lazy_static",
256+ "log",
257 "mailpot",
258+ "mailpot-web",
259+ "reqwest",
260+ "serde",
261+ "serde_json",
262+ "stderrlog",
263+ "thiserror",
264 "tokio",
265+ "tower-http 0.4.0",
266 ]
267
268 [[package]]
269 @@ -2169,6 +2340,16 @@ dependencies = [
270 ]
271
272 [[package]]
273+ name = "ordered-multimap"
274+ version = "0.4.3"
275+ source = "registry+https://github.com/rust-lang/crates.io-index"
276+ checksum = "ccd746e37177e1711c20dd619a1620f34f5c8b569c53590a72dedd5344d8924a"
277+ dependencies = [
278+ "dlv-list",
279+ "hashbrown",
280+ ]
281+
282+ [[package]]
283 name = "output_vt100"
284 version = "0.1.3"
285 source = "registry+https://github.com/rust-lang/crates.io-index"
286 @@ -2207,12 +2388,62 @@ dependencies = [
287 ]
288
289 [[package]]
290+ name = "pathdiff"
291+ version = "0.2.1"
292+ source = "registry+https://github.com/rust-lang/crates.io-index"
293+ checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd"
294+
295+ [[package]]
296 name = "percent-encoding"
297 version = "2.2.0"
298 source = "registry+https://github.com/rust-lang/crates.io-index"
299 checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e"
300
301 [[package]]
302+ name = "pest"
303+ version = "2.6.0"
304+ source = "registry+https://github.com/rust-lang/crates.io-index"
305+ checksum = "e68e84bfb01f0507134eac1e9b410a12ba379d064eab48c50ba4ce329a527b70"
306+ dependencies = [
307+ "thiserror",
308+ "ucd-trie",
309+ ]
310+
311+ [[package]]
312+ name = "pest_derive"
313+ version = "2.6.0"
314+ source = "registry+https://github.com/rust-lang/crates.io-index"
315+ checksum = "6b79d4c71c865a25a4322296122e3924d30bc8ee0834c8bfc8b95f7f054afbfb"
316+ dependencies = [
317+ "pest",
318+ "pest_generator",
319+ ]
320+
321+ [[package]]
322+ name = "pest_generator"
323+ version = "2.6.0"
324+ source = "registry+https://github.com/rust-lang/crates.io-index"
325+ checksum = "6c435bf1076437b851ebc8edc3a18442796b30f1728ffea6262d59bbe28b077e"
326+ dependencies = [
327+ "pest",
328+ "pest_meta",
329+ "proc-macro2",
330+ "quote",
331+ "syn 2.0.15",
332+ ]
333+
334+ [[package]]
335+ name = "pest_meta"
336+ version = "2.6.0"
337+ source = "registry+https://github.com/rust-lang/crates.io-index"
338+ checksum = "745a452f8eb71e39ffd8ee32b3c5f51d03845f99786fa9b68db6ff509c505411"
339+ dependencies = [
340+ "once_cell",
341+ "pest",
342+ "sha2 0.10.6",
343+ ]
344+
345+ [[package]]
346 name = "pin-project"
347 version = "1.0.12"
348 source = "registry+https://github.com/rust-lang/crates.io-index"
349 @@ -2443,10 +2674,12 @@ dependencies = [
350 "http",
351 "http-body",
352 "hyper",
353+ "hyper-tls",
354 "ipnet",
355 "js-sys",
356 "log",
357 "mime",
358+ "native-tls",
359 "once_cell",
360 "percent-encoding",
361 "pin-project-lite",
362 @@ -2454,6 +2687,7 @@ dependencies = [
363 "serde_json",
364 "serde_urlencoded",
365 "tokio",
366+ "tokio-native-tls",
367 "tower-service",
368 "url",
369 "wasm-bindgen",
370 @@ -2484,6 +2718,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
371 checksum = "b833d8d034ea094b1ea68aa6d5c740e0d04bad9d16568d08ba6f76823a114316"
372
373 [[package]]
374+ name = "ron"
375+ version = "0.7.1"
376+ source = "registry+https://github.com/rust-lang/crates.io-index"
377+ checksum = "88073939a61e5b7680558e6be56b419e208420c2adb92be54921fa6b72283f1a"
378+ dependencies = [
379+ "base64 0.13.1",
380+ "bitflags",
381+ "serde",
382+ ]
383+
384+ [[package]]
385 name = "rusqlite"
386 version = "0.28.0"
387 source = "registry+https://github.com/rust-lang/crates.io-index"
388 @@ -2500,6 +2745,16 @@ dependencies = [
389 ]
390
391 [[package]]
392+ name = "rust-ini"
393+ version = "0.18.0"
394+ source = "registry+https://github.com/rust-lang/crates.io-index"
395+ checksum = "f6d5f2436026b4f6e79dc829837d467cc7e9a55ee40e750d716713540715a2df"
396+ dependencies = [
397+ "cfg-if 1.0.0",
398+ "ordered-multimap",
399+ ]
400+
401+ [[package]]
402 name = "rustc_version"
403 version = "0.4.0"
404 source = "registry+https://github.com/rust-lang/crates.io-index"
405 @@ -3006,9 +3261,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
406
407 [[package]]
408 name = "tokio"
409- version = "1.28.0"
410+ version = "1.28.1"
411 source = "registry+https://github.com/rust-lang/crates.io-index"
412- checksum = "c3c786bf8134e5a3a166db9b29ab8f48134739014a3eca7bc6bfa95d673b136f"
413+ checksum = "0aa32867d44e6f2ce3385e89dceb990188b8bb0fb25b0cf576647a6f98ac5105"
414 dependencies = [
415 "autocfg",
416 "bytes",
417 @@ -3035,6 +3290,16 @@ dependencies = [
418 ]
419
420 [[package]]
421+ name = "tokio-native-tls"
422+ version = "0.3.1"
423+ source = "registry+https://github.com/rust-lang/crates.io-index"
424+ checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
425+ dependencies = [
426+ "native-tls",
427+ "tokio",
428+ ]
429+
430+ [[package]]
431 name = "tokio-util"
432 version = "0.7.8"
433 source = "registry+https://github.com/rust-lang/crates.io-index"
434 @@ -3098,6 +3363,7 @@ version = "0.4.0"
435 source = "registry+https://github.com/rust-lang/crates.io-index"
436 checksum = "5d1d42a9b3f3ec46ba828e8d376aec14592ea199f70a06a548587ecd1c4ab658"
437 dependencies = [
438+ "async-compression",
439 "bitflags",
440 "bytes",
441 "futures-core",
442 @@ -3106,8 +3372,11 @@ dependencies = [
443 "http-body",
444 "http-range-header",
445 "pin-project-lite",
446+ "tokio",
447+ "tokio-util",
448 "tower-layer",
449 "tower-service",
450+ "tracing",
451 ]
452
453 [[package]]
454 @@ -3168,6 +3437,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
455 checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
456
457 [[package]]
458+ name = "ucd-trie"
459+ version = "0.1.5"
460+ source = "registry+https://github.com/rust-lang/crates.io-index"
461+ checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81"
462+
463+ [[package]]
464 name = "unicase"
465 version = "2.6.0"
466 source = "registry+https://github.com/rust-lang/crates.io-index"
467 @@ -3637,6 +3912,15 @@ dependencies = [
468 ]
469
470 [[package]]
471+ name = "yaml-rust"
472+ version = "0.4.5"
473+ source = "registry+https://github.com/rust-lang/crates.io-index"
474+ checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85"
475+ dependencies = [
476+ "linked-hash-map",
477+ ]
478+
479+ [[package]]
480 name = "yansi"
481 version = "0.5.1"
482 source = "registry+https://github.com/rust-lang/crates.io-index"
483 diff --git a/rest-http/.gitignore b/rest-http/.gitignore
484new file mode 100644
485index 0000000..856c436
486--- /dev/null
487+++ b/rest-http/.gitignore
488 @@ -0,0 +1,2 @@
489+ .env
490+ config/local.json
491 diff --git a/rest-http/Cargo.toml b/rest-http/Cargo.toml
492index 9cd7197..b237918 100644
493--- a/rest-http/Cargo.toml
494+++ b/rest-http/Cargo.toml
495 @@ -16,5 +16,29 @@ name = "mpot-http"
496 path = "src/main.rs"
497
498 [dependencies]
499+ async-trait = "0.1"
500+ axum = { version = "0.6", features = ["headers"] }
501+ #jsonwebtoken = "8.3"
502+ bcrypt = "0.14"
503+ config = "0.13"
504+ http = "0.2"
505+ lazy_static = "1.4"
506+ log = "0.4"
507 mailpot = { version = "^0.1", path = "../core" }
508- tokio = { version = "^1", features = ["full"] }
509+ mailpot-web = { version = "^0.1", path = "../web" }
510+ serde = { version = "1", features = ["derive"] }
511+ serde_json = "1"
512+ stderrlog = "^0.5"
513+ thiserror = "1"
514+ tokio = { version = "1", features = ["full"] }
515+ tower-http = { version = "0.4", features = [
516+ "trace",
517+ "compression-br",
518+ "propagate-header",
519+ "sensitive-headers",
520+ "cors",
521+ ] }
522+
523+ [dev-dependencies]
524+ assert-json-diff = "2"
525+ reqwest = { version = "0.11", features = ["json"] }
526 diff --git a/rest-http/README.md b/rest-http/README.md
527index 8fac8eb..a89e59d 100644
528--- a/rest-http/README.md
529+++ b/rest-http/README.md
530 @@ -1,3 +1,2 @@
531 # mailpot REST http server
532
533- Not implemented.
534 diff --git a/rest-http/config/default.json b/rest-http/config/default.json
535new file mode 100644
536index 0000000..fba51c5
537--- /dev/null
538+++ b/rest-http/config/default.json
539 @@ -0,0 +1,12 @@
540+ {
541+ "environment": "development",
542+ "server": {
543+ "port": 8080
544+ },
545+ "auth": {
546+ "secret": "secret"
547+ },
548+ "logger": {
549+ "level": "debug"
550+ }
551+ }
552 diff --git a/rest-http/config/production.json b/rest-http/config/production.json
553new file mode 100644
554index 0000000..0b731fa
555--- /dev/null
556+++ b/rest-http/config/production.json
557 @@ -0,0 +1,6 @@
558+ {
559+ "environment": "production",
560+ "logger": {
561+ "level": "info"
562+ }
563+ }
564 diff --git a/rest-http/config/test.json b/rest-http/config/test.json
565new file mode 100644
566index 0000000..a162f57
567--- /dev/null
568+++ b/rest-http/config/test.json
569 @@ -0,0 +1,9 @@
570+ {
571+ "environment": "test",
572+ "server": {
573+ "port": 8088
574+ },
575+ "logger": {
576+ "level": "error"
577+ }
578+ }
579 diff --git a/rest-http/src/errors.rs b/rest-http/src/errors.rs
580new file mode 100644
581index 0000000..7d78020
582--- /dev/null
583+++ b/rest-http/src/errors.rs
584 @@ -0,0 +1,98 @@
585+ use axum::{
586+ http::StatusCode,
587+ response::{IntoResponse, Response},
588+ Json,
589+ };
590+ use bcrypt::BcryptError;
591+ use serde_json::json;
592+ use tokio::task::JoinError;
593+
594+ #[derive(thiserror::Error, Debug)]
595+ #[error("...")]
596+ pub enum Error {
597+ #[error("Error parsing ObjectID {0}")]
598+ ParseObjectID(String),
599+
600+ #[error("{0}")]
601+ Authenticate(#[from] AuthenticateError),
602+
603+ #[error("{0}")]
604+ BadRequest(#[from] BadRequest),
605+
606+ #[error("{0}")]
607+ NotFound(#[from] NotFound),
608+
609+ #[error("{0}")]
610+ RunSyncTask(#[from] JoinError),
611+
612+ #[error("{0}")]
613+ HashPassword(#[from] BcryptError),
614+
615+ #[error("{0}")]
616+ System(#[from] mailpot::Error),
617+ }
618+
619+ impl Error {
620+ fn get_codes(&self) -> (StatusCode, u16) {
621+ match *self {
622+ // 4XX Errors
623+ Error::ParseObjectID(_) => (StatusCode::BAD_REQUEST, 40001),
624+ Error::BadRequest(_) => (StatusCode::BAD_REQUEST, 40002),
625+ Error::NotFound(_) => (StatusCode::NOT_FOUND, 40003),
626+ Error::Authenticate(AuthenticateError::WrongCredentials) => {
627+ (StatusCode::UNAUTHORIZED, 40004)
628+ }
629+ Error::Authenticate(AuthenticateError::InvalidToken) => {
630+ (StatusCode::UNAUTHORIZED, 40005)
631+ }
632+ Error::Authenticate(AuthenticateError::Locked) => (StatusCode::LOCKED, 40006),
633+
634+ // 5XX Errors
635+ Error::Authenticate(AuthenticateError::TokenCreation) => {
636+ (StatusCode::INTERNAL_SERVER_ERROR, 5001)
637+ }
638+ Error::RunSyncTask(_) => (StatusCode::INTERNAL_SERVER_ERROR, 5005),
639+ Error::HashPassword(_) => (StatusCode::INTERNAL_SERVER_ERROR, 5006),
640+ Error::System(_) => (StatusCode::INTERNAL_SERVER_ERROR, 5007),
641+ }
642+ }
643+
644+ pub fn bad_request() -> Self {
645+ Error::BadRequest(BadRequest {})
646+ }
647+
648+ pub fn not_found() -> Self {
649+ Error::NotFound(NotFound {})
650+ }
651+ }
652+
653+ impl IntoResponse for Error {
654+ fn into_response(self) -> Response {
655+ let (status_code, code) = self.get_codes();
656+ let message = self.to_string();
657+ let body = Json(json!({ "code": code, "message": message }));
658+
659+ (status_code, body).into_response()
660+ }
661+ }
662+
663+ #[derive(thiserror::Error, Debug)]
664+ #[error("...")]
665+ pub enum AuthenticateError {
666+ #[error("Wrong authentication credentials")]
667+ WrongCredentials,
668+ #[error("Failed to create authentication token")]
669+ TokenCreation,
670+ #[error("Invalid authentication credentials")]
671+ InvalidToken,
672+ #[error("User is locked")]
673+ Locked,
674+ }
675+
676+ #[derive(thiserror::Error, Debug)]
677+ #[error("Bad Request")]
678+ pub struct BadRequest {}
679+
680+ #[derive(thiserror::Error, Debug)]
681+ #[error("Not found")]
682+ pub struct NotFound {}
683 diff --git a/rest-http/src/lib.rs b/rest-http/src/lib.rs
684index 48813d6..6bc9fc6 100644
685--- a/rest-http/src/lib.rs
686+++ b/rest-http/src/lib.rs
687 @@ -16,3 +16,13 @@
688 * You should have received a copy of the GNU Affero General Public License
689 * along with this program. If not, see <https://www.gnu.org/licenses/>.
690 */
691+
692+ pub use std::{net::SocketAddr, sync::Arc};
693+
694+ pub use axum::Router;
695+ pub use http::header;
696+ pub use log::{debug, info, trace};
697+ pub use mailpot::{models::*, Configuration, Connection};
698+ pub mod errors;
699+ pub mod routes;
700+ pub mod settings;
701 diff --git a/rest-http/src/main.rs b/rest-http/src/main.rs
702index 17d836f..c86a5ef 100644
703--- a/rest-http/src/main.rs
704+++ b/rest-http/src/main.rs
705 @@ -1,20 +1,50 @@
706- /*
707- * This file is part of mailpot
708- *
709- * Copyright 2020 - Manos Pitsidianakis
710- *
711- * This program is free software: you can redistribute it and/or modify
712- * it under the terms of the GNU Affero General Public License as
713- * published by the Free Software Foundation, either version 3 of the
714- * License, or (at your option) any later version.
715- *
716- * This program is distributed in the hope that it will be useful,
717- * but WITHOUT ANY WARRANTY; without even the implied warranty of
718- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
719- * GNU Affero General Public License for more details.
720- *
721- * You should have received a copy of the GNU Affero General Public License
722- * along with this program. If not, see <https://www.gnu.org/licenses/>.
723- */
724+ use mailpot_http::{settings::SETTINGS, *};
725+ use tower_http::{
726+ compression::CompressionLayer, cors::CorsLayer, propagate_header::PropagateHeaderLayer,
727+ sensitive_headers::SetSensitiveHeadersLayer,
728+ };
729
730- fn main() {}
731+ use crate::routes;
732+
733+ #[tokio::main]
734+ async fn main() {
735+ let app = create_app().await;
736+
737+ let port = SETTINGS.server.port;
738+ let address = SocketAddr::from(([127, 0, 0, 1], port));
739+
740+ info!("Server listening on {}", &address);
741+ axum::Server::bind(&address)
742+ .serve(app.into_make_service())
743+ .await
744+ .expect("Failed to start server");
745+ }
746+ pub async fn create_app() -> Router {
747+ let config_path = std::env::args()
748+ .nth(1)
749+ .expect("Expected configuration file path as first argument.");
750+ stderrlog::new()
751+ .quiet(false)
752+ .verbosity(15)
753+ .show_module_names(true)
754+ .timestamp(stderrlog::Timestamp::Millisecond)
755+ .init()
756+ .unwrap();
757+ let conf = Arc::new(Configuration::from_file(config_path).unwrap());
758+
759+ Router::new()
760+ .with_state(conf.clone())
761+ .merge(Router::new().nest("/v1", Router::new().merge(routes::list::create_route(conf))))
762+ .layer(SetSensitiveHeadersLayer::new(std::iter::once(
763+ header::AUTHORIZATION,
764+ )))
765+ // Compress responses
766+ .layer(CompressionLayer::new())
767+ // Propagate `X-Request-Id`s from requests to responses
768+ .layer(PropagateHeaderLayer::new(header::HeaderName::from_static(
769+ "x-request-id",
770+ )))
771+ // CORS configuration. This should probably be more restrictive in
772+ // production.
773+ .layer(CorsLayer::permissive())
774+ }
775 diff --git a/rest-http/src/routes/list.rs b/rest-http/src/routes/list.rs
776new file mode 100644
777index 0000000..6aa7f7d
778--- /dev/null
779+++ b/rest-http/src/routes/list.rs
780 @@ -0,0 +1,107 @@
781+ use std::sync::Arc;
782+
783+ pub use axum::extract::{Path, Query, State};
784+ use axum::{
785+ http::StatusCode,
786+ routing::{get, post},
787+ Json, Router,
788+ };
789+ use mailpot_web::{typed_paths::*, ResponseError, RouterExt};
790+ use serde::{Deserialize, Serialize};
791+
792+ use crate::*;
793+
794+ pub fn create_route(conf: Arc<Configuration>) -> Router {
795+ Router::new()
796+ .route("/list/", get(all_lists))
797+ .route("/list/", post(post_list))
798+ .typed_get(get_list)
799+ .with_state(conf)
800+ }
801+
802+ async fn get_list(
803+ ListPath(id): ListPath,
804+ State(state): State<Arc<Configuration>>,
805+ ) -> Result<Json<DbVal<MailingList>>, ResponseError> {
806+ let db = Connection::open_db(Configuration::clone(&state))?;
807+ let Some(list) = (match id {
808+ ListPathIdentifier::Pk(id) => db.list(id)?,
809+ ListPathIdentifier::Id(id) => db.list_by_id(id)?,
810+ }) else {
811+ return Err(mailpot_web::ResponseError::new(
812+ "Not found".to_string(),
813+ StatusCode::NOT_FOUND,
814+ ));
815+ };
816+ Ok(Json(list))
817+ }
818+
819+ async fn all_lists(
820+ Query(GetRequest {
821+ filter: _,
822+ count,
823+ page,
824+ }): Query<GetRequest>,
825+ State(state): State<Arc<Configuration>>,
826+ ) -> Result<Json<GetResponse>, ResponseError> {
827+ let db = Connection::open_db(Configuration::clone(&state))?;
828+ let lists_values = db.lists()?;
829+ let page = page.unwrap_or(0);
830+ let Some(count) = count else {
831+ let mut stmt = db
832+ .connection
833+ .prepare("SELECT count(*) FROM list;")?;
834+ return Ok(Json(GetResponse {
835+ entries: vec![],
836+ total: stmt.query_row([], |row| {
837+ let count: usize = row.get(0)?;
838+ Ok(count)
839+ })?,
840+ start: 0,
841+ }));
842+ };
843+ let offset = page * count;
844+ let res: Vec<_> = lists_values.into_iter().skip(offset).take(count).collect();
845+
846+ Ok(Json(GetResponse {
847+ total: res.len(),
848+ start: offset,
849+ entries: res,
850+ }))
851+ }
852+
853+ async fn post_list(
854+ State(state): State<Arc<Configuration>>,
855+ Json(_body): Json<GetRequest>,
856+ ) -> Result<Json<()>, ResponseError> {
857+ let _db = Connection::open_db(Configuration::clone(&state))?;
858+ // let password_hash = list::hash_password(body.password).await?;
859+ // let list = list::new(body.name, body.email, password_hash);
860+ // let list = list::create(list).await?;
861+ // let res = Publiclist::from(list);
862+ //
863+
864+ Ok(Json(()))
865+ }
866+
867+ #[derive(Debug, Serialize, Deserialize)]
868+ enum GetFilter {
869+ Pk(i64),
870+ Address(String),
871+ Id(String),
872+ Name(String),
873+ }
874+
875+ #[derive(Debug, Serialize, Deserialize)]
876+ struct GetRequest {
877+ filter: Option<GetFilter>,
878+ count: Option<usize>,
879+ page: Option<usize>,
880+ }
881+
882+ #[derive(Debug, Serialize, Deserialize)]
883+ struct GetResponse {
884+ entries: Vec<DbVal<MailingList>>,
885+ total: usize,
886+ start: usize,
887+ }
888 diff --git a/rest-http/src/routes/mod.rs b/rest-http/src/routes/mod.rs
889new file mode 100644
890index 0000000..d17e233
891--- /dev/null
892+++ b/rest-http/src/routes/mod.rs
893 @@ -0,0 +1 @@
894+ pub mod list;
895 diff --git a/rest-http/src/settings.rs b/rest-http/src/settings.rs
896new file mode 100644
897index 0000000..b1ef467
898--- /dev/null
899+++ b/rest-http/src/settings.rs
900 @@ -0,0 +1,61 @@
901+ use std::{env, fmt};
902+
903+ use config::{Config, ConfigError, Environment, File};
904+ use lazy_static::lazy_static;
905+ use serde::Deserialize;
906+
907+ lazy_static! {
908+ pub static ref SETTINGS: Settings = Settings::new().expect("Failed to setup settings");
909+ }
910+
911+ #[derive(Debug, Clone, Deserialize)]
912+ pub struct Server {
913+ pub port: u16,
914+ }
915+
916+ #[derive(Debug, Clone, Deserialize)]
917+ pub struct Logger {
918+ pub level: String,
919+ }
920+
921+ #[derive(Debug, Clone, Deserialize)]
922+ pub struct Auth {
923+ pub secret: String,
924+ }
925+
926+ #[derive(Debug, Clone, Deserialize)]
927+ pub struct Settings {
928+ pub environment: String,
929+ pub server: Server,
930+ pub logger: Logger,
931+ pub auth: Auth,
932+ }
933+
934+ impl Settings {
935+ pub fn new() -> Result<Self, ConfigError> {
936+ let run_mode = env::var("RUN_MODE").unwrap_or_else(|_| "development".into());
937+
938+ let mut builder = Config::builder()
939+ .add_source(File::with_name("config/default"))
940+ .add_source(File::with_name(&format!("config/{run_mode}")).required(false))
941+ .add_source(File::with_name("config/local").required(false))
942+ .add_source(Environment::default().separator("__"));
943+
944+ // Some cloud services like Heroku exposes a randomly assigned port in
945+ // the PORT env var and there is no way to change the env var name.
946+ if let Ok(port) = env::var("PORT") {
947+ builder = builder.set_override("server.port", port)?;
948+ }
949+
950+ builder
951+ .build()?
952+ // Deserialize (and thus freeze) the entire configuration.
953+ .try_deserialize()
954+ }
955+ }
956+
957+ impl fmt::Display for Server {
958+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
959+ write!(f, "http://localhost:{}", &self.port)
960+ }
961+ }