From 40ca80b6544562fa784eff799f55b74f70df4d28 Mon Sep 17 00:00:00 2001 From: Jane Petrovna Date: Wed, 22 Sep 2021 19:08:13 -0400 Subject: [PATCH] split todo backend from frontend --- backend/Cargo.lock => Cargo.lock | 116 ++++++++++++++++++ backend/Cargo.toml => Cargo.toml | 5 + backend/.env.example | 3 - backend/src/endpoints.rs | 30 ----- backend/src/logging.rs | 26 ---- backend/src/main.rs | 66 ---------- backend/default.profraw => default.profraw | 0 backend/diesel.toml => diesel.toml | 0 backend/env.sh => env.sh | 0 frontend/.gitignore | 5 - frontend/.prettierrc.yaml | 16 --- frontend/.storybook/main.js | 11 -- frontend/.storybook/preview.js | 9 -- frontend/README.md | 1 - frontend/src_old/ambient.d.ts | 39 ------ frontend/src_old/client.js | 5 - frontend/src_old/components/Button.svelte | 42 ------- frontend/src_old/components/Header.svelte | 49 -------- frontend/src_old/components/Image.svelte | 33 ----- frontend/src_old/components/Nav.svelte | 60 --------- frontend/src_old/components/button.css | 30 ----- frontend/src_old/components/header.css | 26 ---- frontend/src_old/components/image.css | 23 ---- frontend/src_old/deprecated/[slug].json.js | 28 ----- frontend/src_old/deprecated/[slug].svelte | 64 ---------- frontend/src_old/deprecated/_posts.js | 92 -------------- frontend/src_old/deprecated/index.json.js | 16 --- frontend/src_old/deprecated/index.svelte | 34 ----- frontend/src_old/routes/_error.svelte | 40 ------ frontend/src_old/routes/_layout.svelte | 24 ---- frontend/src_old/routes/about.svelte | 7 -- frontend/src_old/routes/index.svelte | 9 -- frontend/src_old/routes/settings.svelte | 0 frontend/src_old/server.js | 17 --- frontend/src_old/service-worker.js | 86 ------------- frontend/src_old/template.html | 34 ----- frontend/src_old/utils/events.js | 0 frontend/src_old/utils/requests.js | 0 frontend/src_old/utils/settings.js | 1 - frontend/src_old/utils/stores.js | 31 ----- frontend/src_old/utils/user.js | 0 frontend/static_old/clipboard.svg | 1 - frontend/static_old/code-brackets.svg | 1 - frontend/static_old/colors.svg | 1 - frontend/static_old/comments.svg | 1 - frontend/static_old/direction.svg | 1 - frontend/static_old/favicon.png | Bin 3127 -> 0 bytes frontend/static_old/flow.svg | 1 - frontend/static_old/global.css | 36 ------ frontend/static_old/logo-192.png | Bin 4760 -> 0 bytes frontend/static_old/logo-512.png | Bin 13928 -> 0 bytes frontend/static_old/manifest.json | 22 ---- frontend/static_old/plugin.svg | 1 - frontend/static_old/repo.svg | 1 - frontend/static_old/stackalt.svg | 1 - {backend/migrations => migrations}/.gitkeep | 0 .../down.sql | 0 .../up.sql | 0 .../2021-08-05-011028_create_user/down.sql | 0 .../2021-08-05-011028_create_user/up.sql | 0 .../2021-08-06-173217_create_block/down.sql | 0 .../2021-08-06-173217_create_block/up.sql | 0 src/endpoints.rs | 15 +++ {backend/src => src}/endpoints/block.rs | 0 {backend/src => src}/endpoints/discord.rs | 26 ++-- {backend/src => src}/endpoints/user.rs | 0 {backend/src => src}/helpers.rs | 0 {backend/src => src}/helpers/block.rs | 11 +- {backend/src => src}/helpers/user.rs | 11 +- src/main.rs | 88 +++++++++++++ {backend/src => src}/migration.rs | 0 {backend/src => src}/models.rs | 0 {backend/src => src}/schema.rs | 0 {backend/src => src}/tests.rs | 0 {backend/src => src}/tests/db.rs | 15 ++- 75 files changed, 260 insertions(+), 1051 deletions(-) rename backend/Cargo.lock => Cargo.lock (95%) rename backend/Cargo.toml => Cargo.toml (80%) delete mode 100644 backend/.env.example delete mode 100644 backend/src/endpoints.rs delete mode 100644 backend/src/logging.rs delete mode 100644 backend/src/main.rs rename backend/default.profraw => default.profraw (100%) rename backend/diesel.toml => diesel.toml (100%) rename backend/env.sh => env.sh (100%) delete mode 100644 frontend/.gitignore delete mode 100644 frontend/.prettierrc.yaml delete mode 100644 frontend/.storybook/main.js delete mode 100644 frontend/.storybook/preview.js delete mode 100644 frontend/README.md delete mode 100644 frontend/src_old/ambient.d.ts delete mode 100644 frontend/src_old/client.js delete mode 100644 frontend/src_old/components/Button.svelte delete mode 100644 frontend/src_old/components/Header.svelte delete mode 100644 frontend/src_old/components/Image.svelte delete mode 100644 frontend/src_old/components/Nav.svelte delete mode 100644 frontend/src_old/components/button.css delete mode 100644 frontend/src_old/components/header.css delete mode 100644 frontend/src_old/components/image.css delete mode 100644 frontend/src_old/deprecated/[slug].json.js delete mode 100644 frontend/src_old/deprecated/[slug].svelte delete mode 100644 frontend/src_old/deprecated/_posts.js delete mode 100644 frontend/src_old/deprecated/index.json.js delete mode 100644 frontend/src_old/deprecated/index.svelte delete mode 100644 frontend/src_old/routes/_error.svelte delete mode 100644 frontend/src_old/routes/_layout.svelte delete mode 100644 frontend/src_old/routes/about.svelte delete mode 100644 frontend/src_old/routes/index.svelte delete mode 100644 frontend/src_old/routes/settings.svelte delete mode 100644 frontend/src_old/server.js delete mode 100644 frontend/src_old/service-worker.js delete mode 100644 frontend/src_old/template.html delete mode 100644 frontend/src_old/utils/events.js delete mode 100644 frontend/src_old/utils/requests.js delete mode 100644 frontend/src_old/utils/settings.js delete mode 100644 frontend/src_old/utils/stores.js delete mode 100644 frontend/src_old/utils/user.js delete mode 100644 frontend/static_old/clipboard.svg delete mode 100644 frontend/static_old/code-brackets.svg delete mode 100644 frontend/static_old/colors.svg delete mode 100644 frontend/static_old/comments.svg delete mode 100644 frontend/static_old/direction.svg delete mode 100644 frontend/static_old/favicon.png delete mode 100644 frontend/static_old/flow.svg delete mode 100644 frontend/static_old/global.css delete mode 100644 frontend/static_old/logo-192.png delete mode 100644 frontend/static_old/logo-512.png delete mode 100644 frontend/static_old/manifest.json delete mode 100644 frontend/static_old/plugin.svg delete mode 100644 frontend/static_old/repo.svg delete mode 100644 frontend/static_old/stackalt.svg rename {backend/migrations => migrations}/.gitkeep (100%) rename {backend/migrations => migrations}/00000000000000_diesel_initial_setup/down.sql (100%) rename {backend/migrations => migrations}/00000000000000_diesel_initial_setup/up.sql (100%) rename {backend/migrations => migrations}/2021-08-05-011028_create_user/down.sql (100%) rename {backend/migrations => migrations}/2021-08-05-011028_create_user/up.sql (100%) rename {backend/migrations => migrations}/2021-08-06-173217_create_block/down.sql (100%) rename {backend/migrations => migrations}/2021-08-06-173217_create_block/up.sql (100%) create mode 100644 src/endpoints.rs rename {backend/src => src}/endpoints/block.rs (100%) rename {backend/src => src}/endpoints/discord.rs (89%) rename {backend/src => src}/endpoints/user.rs (100%) rename {backend/src => src}/helpers.rs (100%) rename {backend/src => src}/helpers/block.rs (79%) rename {backend/src => src}/helpers/user.rs (77%) create mode 100644 src/main.rs rename {backend/src => src}/migration.rs (100%) rename {backend/src => src}/models.rs (100%) rename {backend/src => src}/schema.rs (100%) rename {backend/src => src}/tests.rs (100%) rename {backend/src => src}/tests/db.rs (89%) diff --git a/backend/Cargo.lock b/Cargo.lock similarity index 95% rename from backend/Cargo.lock rename to Cargo.lock index 5a8455a..9838535 100644 --- a/backend/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi 0.3.9", +] + [[package]] name = "anyhow" version = "1.0.42" @@ -245,6 +254,7 @@ dependencies = [ "axum", "chrono", "diesel", + "diesel-tracing", "diesel_migrations", "dotenv", "fern", @@ -261,6 +271,10 @@ dependencies = [ "tokio 1.9.0", "tokio-diesel", "tower", + "tower-http", + "tracing", + "tracing-log", + "tracing-subscriber", "url", "uuid", ] @@ -528,12 +542,25 @@ dependencies = [ "byteorder", "chrono", "diesel_derives", + "ipnetwork", + "libc", "pq-sys", "r2d2", "serde_json", "uuid", ] +[[package]] +name = "diesel-tracing" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c039d591e23293c9d9682139758ea499e2a56b27c2ef38704c8f1dc5e2044ad" +dependencies = [ + "diesel", + "ipnetwork", + "tracing", +] + [[package]] name = "diesel_derives" version = "1.4.1" @@ -1014,6 +1041,15 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68f2d64f2edebec4ce84ad108148e67e1064789bee435edc5b60ad398714a3a9" +[[package]] +name = "ipnetwork" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4088d739b183546b239688ddbc79891831df421773df95e236daf7867866d355" +dependencies = [ + "serde", +] + [[package]] name = "itoa" version = "0.4.7" @@ -1079,6 +1115,15 @@ dependencies = [ "value-bag", ] +[[package]] +name = "matchers" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f099785f7595cc4b4553a174ce30dd7589ef93391ff414dbb67f62392b9e0ce1" +dependencies = [ + "regex-automata", +] + [[package]] name = "matches" version = "0.1.8" @@ -1586,6 +1631,15 @@ dependencies = [ "regex-syntax", ] +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax", +] + [[package]] name = "regex-syntax" version = "0.6.25" @@ -1826,6 +1880,15 @@ dependencies = [ "opaque-debug", ] +[[package]] +name = "sharded-slab" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "740223c51853f3145fe7c90360d2d4232f2b62e3449489c207eccde818979982" +dependencies = [ + "lazy_static", +] + [[package]] name = "signal-hook-registry" version = "1.4.0" @@ -1914,6 +1977,15 @@ dependencies = [ "syn", ] +[[package]] +name = "thread_local" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8018d24e04c95ac8790716a5987d0fec4f8b27249ffa0f7d33f1369bdfb88cbd" +dependencies = [ + "once_cell", +] + [[package]] name = "time" version = "0.1.44" @@ -2096,6 +2168,7 @@ dependencies = [ "pin-project", "tower-layer", "tower-service", + "tracing", ] [[package]] @@ -2143,6 +2216,49 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "tracing-log" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6923477a48e41c1951f1999ef8bb5a3023eb723ceadafe78ffb65dc366761e3" +dependencies = [ + "lazy_static", + "log", + "tracing-core", +] + +[[package]] +name = "tracing-serde" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb65ea441fbb84f9f6748fd496cf7f63ec9af5bca94dd86456978d055e8eb28b" +dependencies = [ + "serde", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9cbe87a2fa7e35900ce5de20220a582a9483a7063811defce79d7cbd59d4cfe" +dependencies = [ + "ansi_term", + "chrono", + "lazy_static", + "matchers", + "regex", + "serde", + "serde_json", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", + "tracing-serde", +] + [[package]] name = "try-lock" version = "0.2.3" diff --git a/backend/Cargo.toml b/Cargo.toml similarity index 80% rename from backend/Cargo.toml rename to Cargo.toml index 712764a..03bb407 100644 --- a/backend/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ async-std = {version = "1.9.0", features = ["attributes"]} axum = {version = "0.1.1", features = ["headers"]} chrono = {version = "0.4.0", features = ["serde"]} diesel = {version = "1.4.7", features = ["postgres", "chrono", "serde_json", "r2d2", "uuidv07"]} +diesel-tracing = {version = "0.1.5", features = ["postgres"]} diesel_migrations = {version = "1.4.0"} dotenv = "0.15.0" fern = "0.6.0" @@ -29,5 +30,9 @@ serde_json = "1.0.66" tokio = {version = "1.9.0", features = ["full"]} tokio-diesel = {git = "https://github.com/mehcode/tokio-diesel", version = "0.3.0"} tower = {version = "0.4.6", features = ["full"]} +tower-http = {version = "0.1.1", features = ["trace"]} +tracing = {version = "0.1.26", features = ["log-always"]} +tracing-log = {version = "0.1.2", features = ["log-tracer"]} +tracing-subscriber = {version = "0.2.20", features = ["fmt"]} url = "2.2.2" uuid = {version = "0.8.2", features = ["serde", "v4"]} diff --git a/backend/.env.example b/backend/.env.example deleted file mode 100644 index 8c48d49..0000000 --- a/backend/.env.example +++ /dev/null @@ -1,3 +0,0 @@ -DATABASE_URL=postgres://postgres@localhost/todo_dev -LOG_LEVEL=DEBUG -PORT=8080 \ No newline at end of file diff --git a/backend/src/endpoints.rs b/backend/src/endpoints.rs deleted file mode 100644 index 284e2cf..0000000 --- a/backend/src/endpoints.rs +++ /dev/null @@ -1,30 +0,0 @@ -use crate::diesel::PgConnection; -use crate::logging::LogService; -use async_redis_session::RedisSessionStore; -use axum::{prelude::*, routing::BoxRoute, AddExtensionLayer}; -use diesel::r2d2::{ConnectionManager, Pool}; -use std::env; - -pub mod block; -pub mod discord; -pub mod user; - -// this should never get called, because the reverse -// proxy on caddy should only direct calls from /api -async fn root() -> &'static str { - "Hi" -} - -pub fn get_routes(pool: Pool>) -> BoxRoute { - let redis_url = env::var("REDIS_URL").unwrap_or(String::from("redis://localhost")); - - let client = redis::Client::open(redis_url.as_str()).expect("Could not create redis client."); - route("/", get(root)) - .nest("/discord", discord::get_routes()) - .layer(tower::layer::layer_fn(|service| LogService { service })) - .layer(AddExtensionLayer::new(RedisSessionStore::from_client( - client, - ))) - .layer(AddExtensionLayer::new(pool)) - .boxed() -} diff --git a/backend/src/logging.rs b/backend/src/logging.rs deleted file mode 100644 index 6e0f587..0000000 --- a/backend/src/logging.rs +++ /dev/null @@ -1,26 +0,0 @@ -use std::fmt; -use std::task::{Context, Poll}; -use tower::Service; - -pub struct LogService { - pub service: S, -} - -impl Service for LogService -where - S: Service, - Request: fmt::Debug, -{ - type Response = S::Response; - type Error = S::Error; - type Future = S::Future; - - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { - self.service.poll_ready(cx) - } - - fn call(&mut self, request: Request) -> Self::Future { - log::debug!("request = {:?}", request); - self.service.call(request) - } -} diff --git a/backend/src/main.rs b/backend/src/main.rs deleted file mode 100644 index b85d1fa..0000000 --- a/backend/src/main.rs +++ /dev/null @@ -1,66 +0,0 @@ -use axum::prelude::*; -use std::net::SocketAddr; - -use diesel::{ - prelude::*, - r2d2::{ConnectionManager, Pool}, -}; -use dotenv::dotenv; -use std::env; -use std::str::FromStr; - -#[macro_use] -extern crate diesel; -extern crate redis; - -mod endpoints; -pub mod helpers; -pub mod logging; -pub mod migration; -pub mod models; -pub mod schema; -pub mod tests; - -#[tokio::main] -async fn main() { - dotenv().ok(); - let _ = setup_logger(); - - let db_url = env::var("DATABASE_URL").expect("DATABASE_URL is not set"); - - migration::run_migrations(&db_url); - let manager = ConnectionManager::::new(&db_url); - let pool = Pool::builder() - .build(manager) - .expect("Could not build connection pool"); - - let root = route("/api", endpoints::get_routes(pool)); - - let port = env::var("PORT").unwrap_or(String::from("8000")); - let addr = SocketAddr::from(([127, 0, 0, 1], port.parse().unwrap_or(8000))); - - log::info!("started listening on {:?}", addr); - hyper::Server::bind(&addr) - .serve(root.into_make_service()) - .await - .unwrap(); -} - -fn setup_logger() -> Result<(), fern::InitError> { - let log_level = env::var("LOG_LEVEL").unwrap_or(String::from("INFO")); - fern::Dispatch::new() - .format(|out, message, record| { - out.finish(format_args!( - "{} <{}> [{}] {}", - chrono::Local::now().format("%Y-%m-%d %H:%M:%S"), - record.file().unwrap_or(record.target()), - record.level(), - message - )) - }) - .level(log::LevelFilter::from_str(log_level.as_str()).unwrap_or(log::LevelFilter::Info)) - .chain(std::io::stdout()) - .chain(fern::log_file("latest.log")?) - .apply()?; - Ok(()) -} diff --git a/backend/default.profraw b/default.profraw similarity index 100% rename from backend/default.profraw rename to default.profraw diff --git a/backend/diesel.toml b/diesel.toml similarity index 100% rename from backend/diesel.toml rename to diesel.toml diff --git a/backend/env.sh b/env.sh similarity index 100% rename from backend/env.sh rename to env.sh diff --git a/frontend/.gitignore b/frontend/.gitignore deleted file mode 100644 index f220e37..0000000 --- a/frontend/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -.DS_Store -/node_modules/ -/src/node_modules/@sapper/ -yarn-error.log -/__sapper__/ diff --git a/frontend/.prettierrc.yaml b/frontend/.prettierrc.yaml deleted file mode 100644 index 3a9fff3..0000000 --- a/frontend/.prettierrc.yaml +++ /dev/null @@ -1,16 +0,0 @@ -arrowParens: 'always' -bracketSpacing: true -endOfLine: 'lf' -htmlWhitespaceSensitivity: 'css' -insertPragma: false -jsxBracketSameLine: true -jsxSingleQuote: true -printWidth: 120 -proseWrap: 'preserve' -quoteProps: 'consistent' -requirePragma: false -semi: true -singleQuote: true -tabWidth: 2 -trailingComma: 'none' -useTabs: false diff --git a/frontend/.storybook/main.js b/frontend/.storybook/main.js deleted file mode 100644 index d3aaa5e..0000000 --- a/frontend/.storybook/main.js +++ /dev/null @@ -1,11 +0,0 @@ -module.exports = { - "stories": [ - "../src/**/*.stories.mdx", - "../src/**/*.stories.@(js|jsx|ts|tsx|svelte)" - ], - "addons": [ - "@storybook/addon-links", - "@storybook/addon-essentials", - "@storybook/addon-svelte-csf" - ] -} \ No newline at end of file diff --git a/frontend/.storybook/preview.js b/frontend/.storybook/preview.js deleted file mode 100644 index 48afd56..0000000 --- a/frontend/.storybook/preview.js +++ /dev/null @@ -1,9 +0,0 @@ -export const parameters = { - actions: { argTypesRegex: "^on[A-Z].*" }, - controls: { - matchers: { - color: /(background|color)$/i, - date: /Date$/, - }, - }, -} \ No newline at end of file diff --git a/frontend/README.md b/frontend/README.md deleted file mode 100644 index 9550922..0000000 --- a/frontend/README.md +++ /dev/null @@ -1 +0,0 @@ -# Todo Frontend \ No newline at end of file diff --git a/frontend/src_old/ambient.d.ts b/frontend/src_old/ambient.d.ts deleted file mode 100644 index a26164d..0000000 --- a/frontend/src_old/ambient.d.ts +++ /dev/null @@ -1,39 +0,0 @@ -/** - * These declarations tell TypeScript that we allow import of images, e.g. - * ``` - - - - ``` - */ -declare module "*.gif" { - const value: string; - export default value; -} - -declare module "*.jpg" { - const value: string; - export default value; -} - -declare module "*.jpeg" { - const value: string; - export default value; -} - -declare module "*.png" { - const value: string; - export default value; -} - -declare module "*.svg" { - const value: string; - export default value; -} - -declare module "*.webp" { - const value: string; - export default value; -} diff --git a/frontend/src_old/client.js b/frontend/src_old/client.js deleted file mode 100644 index cec9172..0000000 --- a/frontend/src_old/client.js +++ /dev/null @@ -1,5 +0,0 @@ -import * as sapper from '@sapper/app'; - -sapper.start({ - target: document.querySelector('#sapper') -}); \ No newline at end of file diff --git a/frontend/src_old/components/Button.svelte b/frontend/src_old/components/Button.svelte deleted file mode 100644 index ee2d74d..0000000 --- a/frontend/src_old/components/Button.svelte +++ /dev/null @@ -1,42 +0,0 @@ - - - diff --git a/frontend/src_old/components/Header.svelte b/frontend/src_old/components/Header.svelte deleted file mode 100644 index eeab2c2..0000000 --- a/frontend/src_old/components/Header.svelte +++ /dev/null @@ -1,49 +0,0 @@ - - -
-
-
- -

Todo

-
-
- {#if user} - user profile picture -
-
-
diff --git a/frontend/src_old/components/Image.svelte b/frontend/src_old/components/Image.svelte deleted file mode 100644 index 2aaf8c3..0000000 --- a/frontend/src_old/components/Image.svelte +++ /dev/null @@ -1,33 +0,0 @@ - - - diff --git a/frontend/src_old/components/Nav.svelte b/frontend/src_old/components/Nav.svelte deleted file mode 100644 index 49a94ed..0000000 --- a/frontend/src_old/components/Nav.svelte +++ /dev/null @@ -1,60 +0,0 @@ - - - - - diff --git a/frontend/src_old/components/button.css b/frontend/src_old/components/button.css deleted file mode 100644 index dc91dc7..0000000 --- a/frontend/src_old/components/button.css +++ /dev/null @@ -1,30 +0,0 @@ -.storybook-button { - font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; - font-weight: 700; - border: 0; - border-radius: 3em; - cursor: pointer; - display: inline-block; - line-height: 1; -} -.storybook-button--primary { - color: white; - background-color: #1ea7fd; -} -.storybook-button--secondary { - color: #333; - background-color: transparent; - box-shadow: rgba(0, 0, 0, 0.15) 0px 0px 0px 1px inset; -} -.storybook-button--small { - font-size: 12px; - padding: 10px 16px; -} -.storybook-button--medium { - font-size: 14px; - padding: 11px 20px; -} -.storybook-button--large { - font-size: 16px; - padding: 12px 24px; -} diff --git a/frontend/src_old/components/header.css b/frontend/src_old/components/header.css deleted file mode 100644 index acadc9e..0000000 --- a/frontend/src_old/components/header.css +++ /dev/null @@ -1,26 +0,0 @@ -.wrapper { - font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; - border-bottom: 1px solid rgba(0, 0, 0, 0.1); - padding: 15px 20px; - display: flex; - align-items: center; - justify-content: space-between; -} - -svg { - display: inline-block; - vertical-align: top; -} - -h1 { - font-weight: 900; - font-size: 20px; - line-height: 1; - margin: 6px 0 6px 10px; - display: inline-block; - vertical-align: top; -} - -button + button { - margin-left: 10px; -} diff --git a/frontend/src_old/components/image.css b/frontend/src_old/components/image.css deleted file mode 100644 index 731f906..0000000 --- a/frontend/src_old/components/image.css +++ /dev/null @@ -1,23 +0,0 @@ -.storybook-icon { - font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; - font-weight: 700; - border: 0; - border-radius: 3em; - cursor: pointer; - display: inline-block; - line-height: 1; - } - - .storybook-icon--small { - height: 32px; - width: auto; - } - .storybook-icon--medium { - height: 64px; - width: auto; - } - .storybook-icon--large { - height: 128px; - width: auto; - } - \ No newline at end of file diff --git a/frontend/src_old/deprecated/[slug].json.js b/frontend/src_old/deprecated/[slug].json.js deleted file mode 100644 index 176890d..0000000 --- a/frontend/src_old/deprecated/[slug].json.js +++ /dev/null @@ -1,28 +0,0 @@ -import posts from './_posts.js'; - -const lookup = new Map(); -posts.forEach(post => { - lookup.set(post.slug, JSON.stringify(post)); -}); - -export function get(req, res, next) { - // the `slug` parameter is available because - // this file is called [slug].json.js - const { slug } = req.params; - - if (lookup.has(slug)) { - res.writeHead(200, { - 'Content-Type': 'application/json' - }); - - res.end(lookup.get(slug)); - } else { - res.writeHead(404, { - 'Content-Type': 'application/json' - }); - - res.end(JSON.stringify({ - message: `Not found` - })); - } -} diff --git a/frontend/src_old/deprecated/[slug].svelte b/frontend/src_old/deprecated/[slug].svelte deleted file mode 100644 index 84e8084..0000000 --- a/frontend/src_old/deprecated/[slug].svelte +++ /dev/null @@ -1,64 +0,0 @@ - - - - - - - - {post.title} - - -

{post.title}

- -
- {@html post.html} -
diff --git a/frontend/src_old/deprecated/_posts.js b/frontend/src_old/deprecated/_posts.js deleted file mode 100644 index 7791a21..0000000 --- a/frontend/src_old/deprecated/_posts.js +++ /dev/null @@ -1,92 +0,0 @@ -// Ordinarily, you'd generate this data from markdown files in your -// repo, or fetch them from a database of some kind. But in order to -// avoid unnecessary dependencies in the starter template, and in the -// service of obviousness, we're just going to leave it here. - -// This file is called `_posts.js` rather than `posts.js`, because -// we don't want to create an `/blog/posts` route — the leading -// underscore tells Sapper not to do that. - -const posts = [ - { - title: 'What is Sapper?', - slug: 'what-is-sapper', - html: ` -

First, you have to know what Svelte is. Svelte is a UI framework with a bold new idea: rather than providing a library that you write code with (like React or Vue, for example), it's a compiler that turns your components into highly optimized vanilla JavaScript. If you haven't already read the introductory blog post, you should!

- -

Sapper is a Next.js-style framework (more on that here) built around Svelte. It makes it embarrassingly easy to create extremely high performance web apps. Out of the box, you get:

- -
    -
  • Code-splitting, dynamic imports and hot module replacement, powered by webpack
  • -
  • Server-side rendering (SSR) with client-side hydration
  • -
  • Service worker for offline support, and all the PWA bells and whistles
  • -
  • The nicest development experience you've ever had, or your money back
  • -
- -

It's implemented as Express middleware. Everything is set up and waiting for you to get started, but you keep complete control over the server, service worker, webpack config and everything else, so it's as flexible as you need it to be.

- ` - }, - - { - title: 'How to use Sapper', - slug: 'how-to-use-sapper', - html: ` -

Step one

-

Create a new project, using degit:

- -
npx degit "sveltejs/sapper-template#rollup" my-app
-			cd my-app
-			npm install # or yarn!
-			npm run dev
-			
- -

Step two

-

Go to localhost:3000. Open my-app in your editor. Edit the files in the src/routes directory or add new ones.

- -

Step three

-

...

- -

Step four

-

Resist overdone joke formats.

- ` - }, - - { - title: 'Why the name?', - slug: 'why-the-name', - html: ` -

In war, the soldiers who build bridges, repair roads, clear minefields and conduct demolitions — all under combat conditions — are known as sappers.

- -

For web developers, the stakes are generally lower than those for combat engineers. But we face our own hostile environment: underpowered devices, poor network connections, and the complexity inherent in front-end engineering. Sapper, which is short for Svelte app maker, is your courageous and dutiful ally.

- ` - }, - - { - title: 'How is Sapper different from Next.js?', - slug: 'how-is-sapper-different-from-next', - html: ` -

Next.js is a React framework from Vercel, and is the inspiration for Sapper. There are a few notable differences, however:

- -
    -
  • It's powered by Svelte instead of React, so it's faster and your apps are smaller
  • -
  • Instead of route masking, we encode route parameters in filenames. For example, the page you're looking at right now is src/routes/blog/[slug].svelte
  • -
  • As well as pages (Svelte components, which render on server or client), you can create server routes in your routes directory. These are just .js files that export functions corresponding to HTTP methods, and receive Express request and response objects as arguments. This makes it very easy to, for example, add a JSON API such as the one powering this very page
  • -
  • Links are just <a> elements, rather than framework-specific <Link> components. That means, for example, that this link right here, despite being inside a blob of HTML, works with the router as you'd expect.
  • -
- ` - }, - - { - title: 'How can I get involved?', - slug: 'how-can-i-get-involved', - html: ` -

We're so glad you asked! Come on over to the Svelte and Sapper repos, and join us in the Discord chatroom. Everyone is welcome, especially you!

- ` - } -]; - -posts.forEach(post => { - post.html = post.html.replace(/^\t{3}/gm, ''); -}); - -export default posts; diff --git a/frontend/src_old/deprecated/index.json.js b/frontend/src_old/deprecated/index.json.js deleted file mode 100644 index bfd9389..0000000 --- a/frontend/src_old/deprecated/index.json.js +++ /dev/null @@ -1,16 +0,0 @@ -import posts from './_posts.js'; - -const contents = JSON.stringify(posts.map(post => { - return { - title: post.title, - slug: post.slug - }; -})); - -export function get(req, res) { - res.writeHead(200, { - 'Content-Type': 'application/json' - }); - - res.end(contents); -} \ No newline at end of file diff --git a/frontend/src_old/deprecated/index.svelte b/frontend/src_old/deprecated/index.svelte deleted file mode 100644 index 2b7d64c..0000000 --- a/frontend/src_old/deprecated/index.svelte +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - Blog - - -

Recent posts

- - diff --git a/frontend/src_old/routes/_error.svelte b/frontend/src_old/routes/_error.svelte deleted file mode 100644 index 320e587..0000000 --- a/frontend/src_old/routes/_error.svelte +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - {status} - - -

{status}

- -

{error.message}

- -{#if dev && error.stack} -
{error.stack}
-{/if} diff --git a/frontend/src_old/routes/_layout.svelte b/frontend/src_old/routes/_layout.svelte deleted file mode 100644 index cdddc89..0000000 --- a/frontend/src_old/routes/_layout.svelte +++ /dev/null @@ -1,24 +0,0 @@ - - - - - -
- -
- -
\ No newline at end of file diff --git a/frontend/src_old/routes/about.svelte b/frontend/src_old/routes/about.svelte deleted file mode 100644 index e1734b3..0000000 --- a/frontend/src_old/routes/about.svelte +++ /dev/null @@ -1,7 +0,0 @@ - - About - - -

About this site

- -

This is the 'about' page. There's not much here.

\ No newline at end of file diff --git a/frontend/src_old/routes/index.svelte b/frontend/src_old/routes/index.svelte deleted file mode 100644 index 3ec61d3..0000000 --- a/frontend/src_old/routes/index.svelte +++ /dev/null @@ -1,9 +0,0 @@ - - - - Todo - - - diff --git a/frontend/src_old/routes/settings.svelte b/frontend/src_old/routes/settings.svelte deleted file mode 100644 index e69de29..0000000 diff --git a/frontend/src_old/server.js b/frontend/src_old/server.js deleted file mode 100644 index c77f593..0000000 --- a/frontend/src_old/server.js +++ /dev/null @@ -1,17 +0,0 @@ -import sirv from 'sirv'; -import polka from 'polka'; -import compression from 'compression'; -import * as sapper from '@sapper/server'; - -const { PORT, NODE_ENV } = process.env; -const dev = NODE_ENV === 'development'; - -polka() // You can also use Express - .use( - compression({ threshold: 0 }), - sirv('static', { dev }), - sapper.middleware() - ) - .listen(PORT, err => { - if (err) console.log('error', err); - }); diff --git a/frontend/src_old/service-worker.js b/frontend/src_old/service-worker.js deleted file mode 100644 index 02ab1d2..0000000 --- a/frontend/src_old/service-worker.js +++ /dev/null @@ -1,86 +0,0 @@ -import { timestamp, files, shell } from '@sapper/service-worker'; - -const ASSETS = `cache${timestamp}`; - -// `shell` is an array of all the files generated by the bundler, -// `files` is an array of everything in the `static` directory -const to_cache = shell.concat(files); -const staticAssets = new Set(to_cache); - -self.addEventListener('install', event => { - event.waitUntil( - caches - .open(ASSETS) - .then(cache => cache.addAll(to_cache)) - .then(() => { - self.skipWaiting(); - }) - ); -}); - -self.addEventListener('activate', event => { - event.waitUntil( - caches.keys().then(async keys => { - // delete old caches - for (const key of keys) { - if (key !== ASSETS) await caches.delete(key); - } - - self.clients.claim(); - }) - ); -}); - - -/** - * Fetch the asset from the network and store it in the cache. - * Fall back to the cache if the user is offline. - */ -async function fetchAndCache(request) { - const cache = await caches.open(`offline${timestamp}`) - - try { - const response = await fetch(request); - cache.put(request, response.clone()); - return response; - } catch (err) { - const response = await cache.match(request); - if (response) return response; - - throw err; - } -} - -self.addEventListener('fetch', event => { - if (event.request.method !== 'GET' || event.request.headers.has('range')) return; - - const url = new URL(event.request.url); - - // don't try to handle e.g. data: URIs - const isHttp = url.protocol.startsWith('http'); - const isDevServerRequest = url.hostname === self.location.hostname && url.port !== self.location.port; - const isStaticAsset = url.host === self.location.host && staticAssets.has(url.pathname); - const skipBecauseUncached = event.request.cache === 'only-if-cached' && !isStaticAsset; - - if (isHttp && !isDevServerRequest && !skipBecauseUncached) { - event.respondWith( - (async () => { - // always serve static files and bundler-generated assets from cache. - // if your application has other URLs with data that will never change, - // set this variable to true for them and they will only be fetched once. - const cachedAsset = isStaticAsset && await caches.match(event.request); - - // for pages, you might want to serve a shell `service-worker-index.html` file, - // which Sapper has generated for you. It's not right for every - // app, but if it's right for yours then uncomment this section - /* - if (!cachedAsset && url.origin === self.origin && routes.find(route => route.pattern.test(url.pathname))) { - return caches.match('/service-worker-index.html'); - } - */ - - return cachedAsset || fetchAndCache(event.request); - })() - ); - } -}); diff --git a/frontend/src_old/template.html b/frontend/src_old/template.html deleted file mode 100644 index 92eb4af..0000000 --- a/frontend/src_old/template.html +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - %sapper.base% - - - - - - - - %sapper.scripts% - - - %sapper.styles% - - - %sapper.head% - - - -
%sapper.html%
- - diff --git a/frontend/src_old/utils/events.js b/frontend/src_old/utils/events.js deleted file mode 100644 index e69de29..0000000 diff --git a/frontend/src_old/utils/requests.js b/frontend/src_old/utils/requests.js deleted file mode 100644 index e69de29..0000000 diff --git a/frontend/src_old/utils/settings.js b/frontend/src_old/utils/settings.js deleted file mode 100644 index 83d3dbf..0000000 --- a/frontend/src_old/utils/settings.js +++ /dev/null @@ -1 +0,0 @@ -export const API_URL = 'https://dev.j4.pm/api'; diff --git a/frontend/src_old/utils/stores.js b/frontend/src_old/utils/stores.js deleted file mode 100644 index 872d1c4..0000000 --- a/frontend/src_old/utils/stores.js +++ /dev/null @@ -1,31 +0,0 @@ -import { writable } from 'svelte/store'; -import axios from 'axios'; -import { API_URL } from './settings'; - -export const discordUser = writable({}); - -export const hasSession = () => { - return getCookie('SESSION') != null; -}; - -function getCookie(name) { - if (!window.document) { - return null; - } - var dc = window.document.cookie; - var prefix = name + '='; - var begin = dc.indexOf('; ' + prefix); - if (begin == -1) { - begin = dc.indexOf(prefix); - if (begin != 0) return null; - } else { - begin += 2; - var end = window.document.cookie.indexOf(';', begin); - if (end == -1) { - end = dc.length; - } - } - // because unescape has been deprecated, replaced with decodeURI - //return unescape(dc.substring(begin + prefix.length, end)); - return decodeURI(dc.substring(begin + prefix.length, end)); -} diff --git a/frontend/src_old/utils/user.js b/frontend/src_old/utils/user.js deleted file mode 100644 index e69de29..0000000 diff --git a/frontend/static_old/clipboard.svg b/frontend/static_old/clipboard.svg deleted file mode 100644 index 4b09b6c..0000000 --- a/frontend/static_old/clipboard.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/frontend/static_old/code-brackets.svg b/frontend/static_old/code-brackets.svg deleted file mode 100644 index 73de947..0000000 --- a/frontend/static_old/code-brackets.svg +++ /dev/null @@ -1 +0,0 @@ -illustration/code-brackets \ No newline at end of file diff --git a/frontend/static_old/colors.svg b/frontend/static_old/colors.svg deleted file mode 100644 index 17d58d5..0000000 --- a/frontend/static_old/colors.svg +++ /dev/null @@ -1 +0,0 @@ -illustration/colors \ No newline at end of file diff --git a/frontend/static_old/comments.svg b/frontend/static_old/comments.svg deleted file mode 100644 index 6493a13..0000000 --- a/frontend/static_old/comments.svg +++ /dev/null @@ -1 +0,0 @@ -illustration/comments \ No newline at end of file diff --git a/frontend/static_old/direction.svg b/frontend/static_old/direction.svg deleted file mode 100644 index 65676ac..0000000 --- a/frontend/static_old/direction.svg +++ /dev/null @@ -1 +0,0 @@ -illustration/direction \ No newline at end of file diff --git a/frontend/static_old/favicon.png b/frontend/static_old/favicon.png deleted file mode 100644 index 7e6f5eb5a2f1f1c882d265cf479de25caa925645..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3127 zcmV-749N3|P)i z7)}s4L53SJCkR}iVi00SFk;`MXX*#X*kkwKs@nFGS}c;=?XFjU|G$3t^5sjIVS2G+ zw)WGF83CpoGXhLGW(1gW%uV|X7>1P6VhCX=Ux)Lb!*DZ%@I3!{Gsf7d?gtIQ%nQiK z3%(LUSkBji;C5Rfgd6$VsF@H`Pk@xtY6t<>FNR-pD}=C~$?)9pdm3XZ36N5PNWYjb z$xd$yNQR9N!dfj-Vd@BwQo^FIIWPPmT&sZyQ$v81(sCBV=PGy{0wltEjB%~h157*t zvbe_!{=I_783x!0t1-r#-d{Y?ae$Q4N_Nd^Ui^@y(%)Gjou6y<3^XJdu{rmUf-Me?)zZ>9OR&6U5H*cK; z$gUlB{g0O4gN0sLSO|Of?hU(l?;h(jA3uH!Z{EBKuV23ouU@^Y6#%v+QG;>e*E}%?wlu-NT4DG zs)z)7WbLr)vGAu(ohrKc^em@OpO&f~6_>E61n_e0_V3@{U3^O;j{`^mNCJUj_>;7v zsMs6Hu3g7+@v+lSo;=yTYFqq}jZmQ-BK8K{C4kqi_i*jBaQE(Au0607V-zKeT;EPg zX(`vrn=L+e74+-Tqeok@_`tDa$G9I|$nTU5H*2V8@y()n*zqM?J1G!-1aX;CfDC9B zTnJ#j_%*n8Qb1)re*Bno7g0RG{Eb;IK14irJYJp$5Z6ac9~b_P?+5t~95~SRG$g?1 znFJ7p$xV&GZ18m~79TGRdfsc-BcX$9yXTR*n)mPD@1~O(_?cT$ZvFPucRmGlq&se0 zKrcUf^k}4hM*biEJOWKzz!qQe;CB_ZtSOO9Owg#lZAc=s65^rb{fZe(TYu_rk!wKkEf}RIt=#Om( zR8mN`DM<^xj~59euMMspBolVN zAPTr8sSDI104orIAdmL$uOXn*6hga1G+0WD0E?UtabxC#VC~vf3|10|phW;yQ3CY8 z2CM=)ErF;xq-YJ5G|um}>*1#E+O_Mu|Nr#qQ&G1P-NMq@f?@*XUcSbV?tX=)ilM-Q zBZP|!Bpv0V;#ojKcpc7$=eqO;#Uy~#?^kNI{vSZfLx&DEt~LTmaKWXcx=joubklI<*Aw z>LtMaQ7DR<1I2LkWvwyu#Rwn~;ezT}_g(@5l3h?W%-a86Y-t#O1PubP+z<%?V5D(U zy57A6{h+{?kOZp7&WKZR+=sznMJ}+Dnpo=C_0%R_x_t~J5T?E_{+))l5v1%52>)d-`iiZyx|5!%M2Fb2dU zW3~MwwpEH9Rhue+k$UIOoo($Ds!NbOyMR36fRHu;*15(YcA7siIZk#%JWz>P!qX1?IUojG&nKR>^gArBt2 zit(ETyZ=@V&7mv_Fi4bABcnwP+jzQuHcfU&BrAV91u-rFvEi7y-KnWsvHH=d2 zgAk(GKm_S8RcTJ>2N3~&Hbwp{Z3NF_Xeh}g4Eke)V&dY{W(3&b1j9t4yK_aYJisZZ{1rcU5- z;eD>K;ndPq&B-8yA_S0F!4ThA&{1{x)H<#?k9a#6Pc6L?V^s0``ynL&D;p(!Nmx`Y zFkHex{4p!Ggm^@DlehW}iHHVi}~u=$&N? z(NEBLQ#UxxAkdW>X9LnqUr#t4Lu0=9L8&o>JsqTtT5|%gb3QA~hr0pED71+iFFr)dZ=Q=E6ng{NE{Z~0)C?deO#?Aj zSDQ$z#TeC2T^|=}6GBo-&$;E{HL3!q3Z-szuf)O=G#zDjin4SSP%o%6+2IT#sLjQa ziyxFFz~LMjWY+_a5H!U6%a<=b7QVP^ z*90a62;bVq{?@)P6^DWd^Yilq4|YTV2Nw!Yu;a1lPI-sxR)rf@Fe5DhDP7FH zZZ%4S*1C30P;|O+jB!1;m|rXT90Sm5*RBbQN`PKu+hDD*S^yE(CdtSfg=z>u$cIj> zillustration/flow \ No newline at end of file diff --git a/frontend/static_old/global.css b/frontend/static_old/global.css deleted file mode 100644 index 3566e73..0000000 --- a/frontend/static_old/global.css +++ /dev/null @@ -1,36 +0,0 @@ -body { - margin: 0; - font-family: Roboto, -apple-system, BlinkMacSystemFont, Segoe UI, Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; - font-size: 14px; - line-height: 1.5; - color: #333; -} - -h1, h2, h3, h4, h5, h6 { - margin: 0 0 0.5em 0; - font-weight: 400; - line-height: 1.2; -} - -h1 { - font-size: 2em; -} - -a { - color: inherit; -} - -code { - font-family: menlo, inconsolata, monospace; - font-size: calc(1em - 2px); - color: #555; - background-color: #f0f0f0; - padding: 0.2em 0.4em; - border-radius: 2px; -} - -@media (min-width: 400px) { - body { - font-size: 16px; - } -} \ No newline at end of file diff --git a/frontend/static_old/logo-192.png b/frontend/static_old/logo-192.png deleted file mode 100644 index 96fac03286e04e6f36fc91be8f90b102903bbedc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4760 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 diff --git a/frontend/static_old/manifest.json b/frontend/static_old/manifest.json deleted file mode 100644 index 8171da6..0000000 --- a/frontend/static_old/manifest.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "background_color": "#ffffff", - "theme_color": "#333333", - "name": "TODO", - "short_name": "TODO", - "display": "minimal-ui", - "start_url": "/", - "icons": [ - { - "src": "logo-192.png", - "sizes": "192x192", - "type": "image/png", - "purpose": "any maskable" - }, - { - "src": "logo-512.png", - "sizes": "512x512", - "type": "image/png", - "purpose": "any maskable" - } - ] -} diff --git a/frontend/static_old/plugin.svg b/frontend/static_old/plugin.svg deleted file mode 100644 index 29e5c69..0000000 --- a/frontend/static_old/plugin.svg +++ /dev/null @@ -1 +0,0 @@ -illustration/plugin \ No newline at end of file diff --git a/frontend/static_old/repo.svg b/frontend/static_old/repo.svg deleted file mode 100644 index f386ee9..0000000 --- a/frontend/static_old/repo.svg +++ /dev/null @@ -1 +0,0 @@ -illustration/repo \ No newline at end of file diff --git a/frontend/static_old/stackalt.svg b/frontend/static_old/stackalt.svg deleted file mode 100644 index 9b7ad27..0000000 --- a/frontend/static_old/stackalt.svg +++ /dev/null @@ -1 +0,0 @@ -illustration/stackalt \ No newline at end of file diff --git a/backend/migrations/.gitkeep b/migrations/.gitkeep similarity index 100% rename from backend/migrations/.gitkeep rename to migrations/.gitkeep diff --git a/backend/migrations/00000000000000_diesel_initial_setup/down.sql b/migrations/00000000000000_diesel_initial_setup/down.sql similarity index 100% rename from backend/migrations/00000000000000_diesel_initial_setup/down.sql rename to migrations/00000000000000_diesel_initial_setup/down.sql diff --git a/backend/migrations/00000000000000_diesel_initial_setup/up.sql b/migrations/00000000000000_diesel_initial_setup/up.sql similarity index 100% rename from backend/migrations/00000000000000_diesel_initial_setup/up.sql rename to migrations/00000000000000_diesel_initial_setup/up.sql diff --git a/backend/migrations/2021-08-05-011028_create_user/down.sql b/migrations/2021-08-05-011028_create_user/down.sql similarity index 100% rename from backend/migrations/2021-08-05-011028_create_user/down.sql rename to migrations/2021-08-05-011028_create_user/down.sql diff --git a/backend/migrations/2021-08-05-011028_create_user/up.sql b/migrations/2021-08-05-011028_create_user/up.sql similarity index 100% rename from backend/migrations/2021-08-05-011028_create_user/up.sql rename to migrations/2021-08-05-011028_create_user/up.sql diff --git a/backend/migrations/2021-08-06-173217_create_block/down.sql b/migrations/2021-08-06-173217_create_block/down.sql similarity index 100% rename from backend/migrations/2021-08-06-173217_create_block/down.sql rename to migrations/2021-08-06-173217_create_block/down.sql diff --git a/backend/migrations/2021-08-06-173217_create_block/up.sql b/migrations/2021-08-06-173217_create_block/up.sql similarity index 100% rename from backend/migrations/2021-08-06-173217_create_block/up.sql rename to migrations/2021-08-06-173217_create_block/up.sql diff --git a/src/endpoints.rs b/src/endpoints.rs new file mode 100644 index 0000000..858ac99 --- /dev/null +++ b/src/endpoints.rs @@ -0,0 +1,15 @@ +use axum::{prelude::*, routing::BoxRoute}; + +pub mod block; +pub mod discord; +pub mod user; + +async fn hello() -> &'static str { + "Hi" +} + +pub fn get_routes() -> BoxRoute { + route("/api/hello", any(hello)) + .nest("/api/auth", discord::get_routes()) + .boxed() +} diff --git a/backend/src/endpoints/block.rs b/src/endpoints/block.rs similarity index 100% rename from backend/src/endpoints/block.rs rename to src/endpoints/block.rs diff --git a/backend/src/endpoints/discord.rs b/src/endpoints/discord.rs similarity index 89% rename from backend/src/endpoints/discord.rs rename to src/endpoints/discord.rs index 6d308d2..14b1792 100644 --- a/backend/src/endpoints/discord.rs +++ b/src/endpoints/discord.rs @@ -19,7 +19,7 @@ use std::env; static COOKIE_NAME: &str = "SESSION"; -fn oauth_client() -> BasicClient { +pub fn oauth_client() -> BasicClient { // Environment variables (* = required): // *"CLIENT_ID" "123456789123456789"; // *"CLIENT_SECRET" "rAn60Mch4ra-CTErsSf-r04utHcLienT"; @@ -39,13 +39,15 @@ fn oauth_client() -> BasicClient { let token_url = env::var("TOKEN_URL") .unwrap_or_else(|_| "https://discord.com/api/oauth2/token".to_string()); - BasicClient::new( + let client = BasicClient::new( ClientId::new(client_id), Some(ClientSecret::new(client_secret)), AuthUrl::new(auth_url).unwrap(), Some(TokenUrl::new(token_url).unwrap()), ) - .set_redirect_uri(RedirectUrl::new(redirect_url).unwrap()) + .set_redirect_uri(RedirectUrl::new(redirect_url).unwrap()); + tracing::debug!("client: {:?}", client); + client } // The user data we'll get back from Discord. @@ -81,10 +83,15 @@ async fn discord_auth(Extension(client): Extension) -> impl IntoRes // Valid user session required. If there is none, redirect to the auth page async fn protected(user: DiscordUser) -> impl IntoResponse { - format!( - "Welcome to the protected area :)\nHere's your info:\n{:?}", - user - ) + serde_json::to_string(&user).expect("could not serialize user") +} + +async fn avatar_url(user: DiscordUser) -> impl IntoResponse { + let cdn_url = env::var("CDN_URL").unwrap_or_else(|_| "https://cdn.discordapp.com".to_string()); + match user.avatar { + Some(id) => format!("{}/avatars/{}/{}.webp?size=256", cdn_url, user.id, id), + None => format!("{}/embed/avatars/0.png?size=256", cdn_url), + } } async fn logout( @@ -205,10 +212,11 @@ where pub fn get_routes() -> BoxRoute { route("/", get(index)) - .route("/login/discord", get(discord_auth)) + .route("/discord", get(discord_auth)) .route("/authorized", get(login_authorized)) .route("/protected", get(protected)) + .route("/avatar", get(avatar_url)) .route("/logout", get(logout)) - .layer(AddExtensionLayer::new(oauth_client)) + .layer(AddExtensionLayer::new(oauth_client())) .boxed() } diff --git a/backend/src/endpoints/user.rs b/src/endpoints/user.rs similarity index 100% rename from backend/src/endpoints/user.rs rename to src/endpoints/user.rs diff --git a/backend/src/helpers.rs b/src/helpers.rs similarity index 100% rename from backend/src/helpers.rs rename to src/helpers.rs diff --git a/backend/src/helpers/block.rs b/src/helpers/block.rs similarity index 79% rename from backend/src/helpers/block.rs rename to src/helpers/block.rs index 00dd3b5..55b8d72 100644 --- a/backend/src/helpers/block.rs +++ b/src/helpers/block.rs @@ -1,13 +1,14 @@ -use crate::diesel::{prelude::*, PgConnection, QueryDsl}; +use crate::diesel::{prelude::*, QueryDsl}; use crate::models::*; use crate::schema::*; use diesel::r2d2::{ConnectionManager, Pool}; +use diesel_tracing::pg::InstrumentedPgConnection; use std::error::Error; use tokio_diesel::*; use uuid::Uuid; pub async fn create_block( - pool: &Pool>, + pool: &Pool>, block: InsertableBlock, ) -> Result> { let inserted: Block = diesel::insert_into(blocks::table) @@ -18,7 +19,7 @@ pub async fn create_block( } pub async fn update_block( - pool: &Pool>, + pool: &Pool>, block: Block, ) -> Result> { use crate::schema::blocks::dsl::*; @@ -35,7 +36,7 @@ pub async fn update_block( } pub async fn find_block_by_id( - pool: &Pool>, + pool: &Pool>, block_id: Uuid, ) -> Result> { use crate::schema::blocks::dsl::*; @@ -45,7 +46,7 @@ pub async fn find_block_by_id( } pub async fn delete_block_by_id( - pool: &Pool>, + pool: &Pool>, block_id: Uuid, ) -> Result> { use crate::schema::blocks::dsl::*; diff --git a/backend/src/helpers/user.rs b/src/helpers/user.rs similarity index 77% rename from backend/src/helpers/user.rs rename to src/helpers/user.rs index abea039..3550085 100644 --- a/backend/src/helpers/user.rs +++ b/src/helpers/user.rs @@ -1,13 +1,14 @@ -use crate::diesel::{prelude::*, PgConnection, QueryDsl}; +use crate::diesel::{prelude::*, QueryDsl}; use crate::models::*; use crate::schema::*; use diesel::r2d2::{ConnectionManager, Pool}; +use diesel_tracing::pg::InstrumentedPgConnection; use std::error::Error; use tokio_diesel::*; use uuid::Uuid; pub async fn create_user( - pool: &Pool>, + pool: &Pool>, user: InsertableUser, ) -> Result> { let inserted: User = diesel::insert_into(users::table) @@ -18,7 +19,7 @@ pub async fn create_user( } pub async fn update_user( - pool: &Pool>, + pool: &Pool>, user: User, ) -> Result> { use crate::schema::users::dsl::*; @@ -30,7 +31,7 @@ pub async fn update_user( } pub async fn find_user_by_id( - pool: &Pool>, + pool: &Pool>, user_id: Uuid, ) -> Result> { use crate::schema::users::dsl::*; @@ -40,7 +41,7 @@ pub async fn find_user_by_id( } pub async fn delete_user_by_id( - pool: &Pool>, + pool: &Pool>, user_id: Uuid, ) -> Result> { use crate::schema::users::dsl::*; diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..de7f0fc --- /dev/null +++ b/src/main.rs @@ -0,0 +1,88 @@ +use axum::prelude::*; +use std::net::{IpAddr, Ipv4Addr, SocketAddr}; + +use async_redis_session::RedisSessionStore; +use axum::AddExtensionLayer; +use diesel::r2d2::{ConnectionManager, Pool}; +use diesel_tracing::pg::InstrumentedPgConnection; +use dotenv::dotenv; +use std::env; +use std::str::FromStr; +use tower_http::trace::TraceLayer; + +#[macro_use] +extern crate diesel; +extern crate redis; + +mod endpoints; +pub mod helpers; +pub mod migration; +pub mod models; +pub mod schema; +pub mod tests; + +#[tokio::main] +async fn main() { + dotenv().ok(); + let _ = setup_logger(); + + let db_url = env::var("DATABASE_URL").expect("DATABASE_URL is not set"); + + migration::run_migrations(&db_url); + let manager = ConnectionManager::::new(&db_url); + let pool = Pool::builder() + .build(manager) + .expect("Could not build connection pool"); + + let redis_url = env::var("REDIS_URL").unwrap_or(String::from("redis://localhost")); + let redis_client = + redis::Client::open(redis_url.as_str()).expect("Could not create redis client."); + + let root = endpoints::get_routes() + .layer(TraceLayer::new_for_http()) + .layer(AddExtensionLayer::new(RedisSessionStore::from_client( + redis_client, + ))) + .layer(AddExtensionLayer::new(pool)); + + let ip = env::var("IP").unwrap_or(String::from("127.0.0.1")); + let port = env::var("PORT").unwrap_or(String::from("8000")); + let addr = SocketAddr::from(( + IpAddr::from_str(ip.as_str()).unwrap_or(IpAddr::from(Ipv4Addr::new(127, 0, 0, 1))), + port.parse().unwrap_or(8000), + )); + + log::info!("started listening on {:?}", addr); + hyper::Server::bind(&addr) + .serve(root.into_make_service()) + .await + .unwrap(); +} + +fn setup_logger() -> () { + let log_level = env::var("LOG_LEVEL").unwrap_or(String::from("INFO")); + let subscriber = tracing_subscriber::FmtSubscriber::builder() + .with_max_level( + tracing::Level::from_str(log_level.as_str()).unwrap_or(tracing::Level::INFO), + ) + .finish(); + + tracing_log::LogTracer::init().expect("could not init log tracer"); + + tracing::subscriber::set_global_default(subscriber).expect("could not set default subscriber"); + // fern::Dispatch::new() + // .format(|out, message, record| { + // out.finish(format_args!( + // "{} <{}> [{}] {}", + // chrono::Local::now().format("%Y-%m-%d %H:%M:%S"), + // record.file().unwrap_or(record.target()), + // record.level(), + // message + // )) + // }) + // .level(log::LevelFilter::from_str(log_level.as_str()).unwrap_or(log::LevelFilter::Info)) + // .chain(std::io::stdout()) + // .chain(fern::log_file("latest.log")?) + // .apply()?; + // Ok(()) +} diff --git a/backend/src/migration.rs b/src/migration.rs similarity index 100% rename from backend/src/migration.rs rename to src/migration.rs diff --git a/backend/src/models.rs b/src/models.rs similarity index 100% rename from backend/src/models.rs rename to src/models.rs diff --git a/backend/src/schema.rs b/src/schema.rs similarity index 100% rename from backend/src/schema.rs rename to src/schema.rs diff --git a/backend/src/tests.rs b/src/tests.rs similarity index 100% rename from backend/src/tests.rs rename to src/tests.rs diff --git a/backend/src/tests/db.rs b/src/tests/db.rs similarity index 89% rename from backend/src/tests/db.rs rename to src/tests/db.rs index ff3f569..5ee6068 100644 --- a/backend/src/tests/db.rs +++ b/src/tests/db.rs @@ -1,20 +1,19 @@ use crate::helpers::*; use crate::models::*; -use diesel::{ - r2d2::{ConnectionManager, Pool}, - PgConnection, -}; +use diesel::r2d2::{ConnectionManager, Pool}; + +use diesel_tracing::pg::InstrumentedPgConnection; use std::{error::Error, vec::Vec}; use uuid::Uuid; -async fn get_pool(url: &String) -> Pool> { - let manager = ConnectionManager::::new(url); +async fn get_pool(url: &String) -> Pool> { + let manager = ConnectionManager::::new(url); Pool::builder() .build(manager) .expect("Could not build connection pool") } -async fn user_tests(pool: &Pool>) { +async fn user_tests(pool: &Pool>) { let user = InsertableUser { discord_id: String::from("test"), }; @@ -66,7 +65,7 @@ async fn user_tests(pool: &Pool>) { } } -async fn block_tests(pool: &Pool>) { +async fn block_tests(pool: &Pool>) { let json = serde_json::from_str("[]"); let block = InsertableBlock { user_id: Uuid::new_v4(),