diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d8b03e1..26eb6e7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -17,14 +17,14 @@ on: jobs: create-release: name: Create release - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - name: Checkout repo - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Setup Nix - uses: cachix/install-nix-action@v15 + uses: cachix/install-nix-action@v19 with: extra_nix_config: | access-tokens = github.com=${{ secrets.GITHUB_TOKEN }} @@ -35,6 +35,9 @@ jobs: username: ${{ secrets.DOCKER_HUB_USERNAME }} password: ${{ secrets.DOCKER_HUB_PASSWORD }} + - name: Sanity check + run: nix flake check + # DOCKER IMAGE - name: Build image diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6ab0615..e90894d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,17 +12,31 @@ on: - 'src/**/*' - 'public/purify.min.js' + push: + branches: [ main ] + paths: + - '.github/workflows/test.yml' + - 'flake.*' + - 'nix/*' + - 'Cargo.*' + - 'assets/**/*' + - 'src/**/*' + - 'public/purify.min.js' + jobs: test: name: Build & test - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - name: Checkout repo - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Setup Nix - uses: cachix/install-nix-action@v15 + uses: cachix/install-nix-action@v19 + + - name: Sanity check + run: nix flake check - name: Build & test run: | diff --git a/Cargo.lock b/Cargo.lock index 3d2a3c3..eaf33cd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -197,7 +197,7 @@ dependencies = [ [[package]] name = "emojied" -version = "0.1.3" +version = "0.1.4" dependencies = [ "axum", "deadpool-postgres", diff --git a/flake.lock b/flake.lock index b678597..a4be078 100644 --- a/flake.lock +++ b/flake.lock @@ -144,9 +144,7 @@ }, "naersk": { "inputs": { - "nixpkgs": [ - "nixpkgs" - ] + "nixpkgs": "nixpkgs_2" }, "locked": { "lastModified": 1679567394, @@ -186,22 +184,6 @@ "type": "github" } }, - "nixospkgs": { - "locked": { - "lastModified": 1680213900, - "narHash": "sha256-cIDr5WZIj3EkKyCgj/6j3HBH4Jj1W296z7HTcWj1aMA=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "e3652e0735fbec227f342712f180f4f21f0594f2", - "type": "github" - }, - "original": { - "owner": "NixOS", - "ref": "nixos-unstable", - "repo": "nixpkgs", - "type": "github" - } - }, "nixpkgs": { "locked": { "lastModified": 1678875422, @@ -268,15 +250,46 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1680343703, - "narHash": "sha256-r9U3n9codXHR+I6zLYbvxaD/2MrJHpPtJWsnCW212js=", + "lastModified": 1680273054, + "narHash": "sha256-Bs6/5LpvYp379qVqGt9mXxxx9GSE789k3oFc+OAL07M=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "3364b5b117f65fe1ce65a3cdd5612a078a3b31e3", + "type": "github" + }, + "original": { + "id": "nixpkgs", + "type": "indirect" + } + }, + "nixpkgs_3": { + "locked": { + "lastModified": 1680213900, + "narHash": "sha256-cIDr5WZIj3EkKyCgj/6j3HBH4Jj1W296z7HTcWj1aMA=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "e3652e0735fbec227f342712f180f4f21f0594f2", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_4": { + "locked": { + "lastModified": 1680213900, + "narHash": "sha256-cIDr5WZIj3EkKyCgj/6j3HBH4Jj1W296z7HTcWj1aMA=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "9448487d9f7056253ecadfdab7d7bdc638191de2", + "rev": "e3652e0735fbec227f342712f180f4f21f0594f2", "type": "github" }, "original": { "owner": "NixOS", + "ref": "nixos-unstable", "repo": "nixpkgs", "type": "github" } @@ -314,9 +327,7 @@ "flake-compat": "flake-compat_2", "flake-utils": "flake-utils_2", "gitignore": "gitignore_2", - "nixpkgs": [ - "nixpkgs" - ], + "nixpkgs": "nixpkgs_4", "nixpkgs-stable": "nixpkgs-stable_2" }, "locked": { @@ -337,8 +348,7 @@ "inputs": { "devenv": "devenv", "naersk": "naersk", - "nixospkgs": "nixospkgs", - "nixpkgs": "nixpkgs_2", + "nixpkgs": "nixpkgs_3", "pre-commit-hooks": "pre-commit-hooks_2" } } diff --git a/flake.nix b/flake.nix index 575e278..8f39e7b 100644 --- a/flake.nix +++ b/flake.nix @@ -2,24 +2,15 @@ description = "A URL shortener, except emojis"; inputs = { - nixpkgs.url = "github:NixOS/nixpkgs"; - nixospkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; devenv.url = "github:cachix/devenv"; - - naersk = { - url = "github:nix-community/naersk"; - inputs.nixpkgs.follows = "nixpkgs"; - }; - - pre-commit-hooks = { - url = "github:cachix/pre-commit-hooks.nix"; - inputs.nixpkgs.follows = "nixpkgs"; - }; + naersk.url = "github:nix-community/naersk"; + pre-commit-hooks.url = "github:cachix/pre-commit-hooks.nix"; }; - outputs = { self, nixpkgs, nixospkgs, devenv, naersk, pre-commit-hooks } @ inputs: ( + outputs = { self, nixpkgs, devenv, naersk, pre-commit-hooks } @ inputs: ( let system = "x86_64-linux"; - pkgs = nixospkgs.legacyPackages.${system}; + pkgs = nixpkgs.legacyPackages.${system}; naersk-lib = naersk.lib.${system}.override { cargo = pkgs.cargo; @@ -89,7 +80,7 @@ program = "${self.packages.${system}.emojied}/bin/emojied"; }; - nixosModule = import ./nix/modules/services/emojied.nix; + nixosModules.default = import ./nix/modules/services/emojied.nix; devShells.${system}.default = devenv.lib.mkShell { inherit inputs pkgs; diff --git a/src/controllers.rs b/src/controllers.rs index 109ba7b..9652540 100644 --- a/src/controllers.rs +++ b/src/controllers.rs @@ -1,4 +1,4 @@ -use axum::extract::{Extension, Form, Path, Query}; +use axum::extract::{Form, Path, Query, State}; use axum::http::StatusCode; use axum::response::Json; use hyper::{ @@ -11,10 +11,10 @@ use std::collections::HashMap; use std::fs; use std::sync::Arc; -use crate::config::AppConfig; +use crate::state::AppState; use crate::url::CreateUrl; use crate::views::url::{AutogeneratedUrl, CustomUrl, RootData}; -use crate::{db, emoji, leaderboard, url, views}; +use crate::{emoji, leaderboard, url, views}; // TODO: Move stuff to their own modules // TODO: Implement SSE for URL stats page @@ -29,13 +29,13 @@ pub async fn root(Query(params): Query>) -> Markup { } pub async fn insert_url( - Extension(handle): Extension>, + State(app_state): State>, Query(params): Query>, Form(form_data): Form, ) -> (StatusCode, Markup) { let custom_url = params.get(&String::from("custom_url")).is_some(); - match url::insert_url(&*handle, form_data).await { + match url::insert_url(&(*app_state).db_handle, form_data).await { Ok(i) => { let root_data = if custom_url { RootData::Custom(CustomUrl::HasIdentifier(i)) @@ -62,10 +62,10 @@ pub async fn insert_url( } pub async fn rpc_insert_url( - Extension(handle): Extension>, + State(app_state): State>, Json(data): Json, ) -> (StatusCode, Json) { - match url::insert_url(&*handle, data).await { + match url::insert_url(&(*app_state).db_handle, data).await { Ok(identifier) => ( StatusCode::CREATED, Json(json!({ "identifier": identifier })), @@ -75,10 +75,10 @@ pub async fn rpc_insert_url( } pub async fn url_stats( - Extension(handle): Extension>, + State(app_state): State>, Path(identifier): Path, ) -> (StatusCode, Markup) { - match url::url_stats(&*handle, identifier).await { + match url::url_stats(&(*app_state).db_handle, identifier).await { Ok(url_stat) => (StatusCode::OK, views::url::view_stats(&url_stat)), Err(_e) => { // TODO: Give back 50x when it's a problem with the DB @@ -88,13 +88,13 @@ pub async fn url_stats( } pub async fn fetch_url( - Extension(handle): Extension>, + State(app_state): State>, Path(identifier): Path, ) -> (StatusCode, HeaderMap, Markup) { let mut headers = HeaderMap::new(); if emoji::is_valid(&identifier) { - match url::fetch_url(&*handle, identifier).await { + match url::fetch_url(&(*app_state).db_handle, identifier).await { Ok(u) => { headers.insert(LOCATION, u.parse().unwrap()); @@ -109,19 +109,17 @@ pub async fn fetch_url( } } -pub async fn leaderboard(Extension(handle): Extension>) -> (StatusCode, Markup) { - match leaderboard::fetch(&*handle).await { +pub async fn leaderboard(State(app_state): State>) -> (StatusCode, Markup) { + match leaderboard::fetch(&(*app_state).db_handle).await { Ok(entries) => (StatusCode::OK, views::leaderboard::render(entries)), Err(_e) => (StatusCode::INTERNAL_SERVER_ERROR, maud::html! {}), } } // TODO: Static assets controller -pub async fn js( - Extension(config): Extension> -) -> (StatusCode, HeaderMap, String) { +pub async fn js(State(app_state): State>) -> (StatusCode, HeaderMap, String) { let mut headers = HeaderMap::new(); - let mut static_assets_path = config.static_assets_path.clone(); + let mut static_assets_path = (*app_state).config.static_assets_path.clone(); static_assets_path.push("app.js"); @@ -139,11 +137,9 @@ pub async fn js( } } -pub async fn purifyjs( - Extension(config): Extension> -) -> (StatusCode, HeaderMap, String) { +pub async fn purifyjs(State(app_state): State>) -> (StatusCode, HeaderMap, String) { let mut headers = HeaderMap::new(); - let mut static_assets_path = config.static_assets_path.clone(); + let mut static_assets_path = (*app_state).config.static_assets_path.clone(); static_assets_path.push("purify.min.js"); @@ -161,11 +157,9 @@ pub async fn purifyjs( } } -pub async fn stylesheet( - Extension(config): Extension> -) -> (StatusCode, HeaderMap, String) { +pub async fn stylesheet(State(app_state): State>) -> (StatusCode, HeaderMap, String) { let mut headers = HeaderMap::new(); - let mut static_assets_path = config.static_assets_path.clone(); + let mut static_assets_path = (*app_state).config.static_assets_path.clone(); static_assets_path.push("app.css"); @@ -226,33 +220,25 @@ fn parse_url_error(e: url::Error) -> (StatusCode, Json) { ), url::Error::MissingScheme => ( StatusCode::BAD_REQUEST, - Json( - json!({ - "message": "URL must have a scheme. Valid schemes are: https, or http.\ne.g https://example.com", - "type": "E008" - }), - ), + Json(json!({ + "message": "URL must have a scheme. Valid schemes are: https, or http.\ne.g https://example.com", + "type": "E008" + })), ), url::Error::UnsupportedScheme => ( StatusCode::BAD_REQUEST, - Json( - json!({ - "message": "Unsupported scheme. Acceptable schemes are: https, or http.\ne.g https://example.com", - "type": "E009" - }), - ), + Json(json!({ + "message": "Unsupported scheme. Acceptable schemes are: https, or http.\ne.g https://example.com", + "type": "E009" + })), ), url::Error::MissingHost => ( StatusCode::BAD_REQUEST, - Json( - json!({"message": "URL host does not exist, or is invalid.", "type": "E010"}), - ), + Json(json!({"message": "URL host does not exist, or is invalid.", "type": "E010"})), ), url::Error::MissingPath => ( StatusCode::BAD_REQUEST, - Json( - json!({"message": "URL path does not exist, or is invalid.", "type": "E011"}), - ), + Json(json!({"message": "URL path does not exist, or is invalid.", "type": "E011"})), ), } } diff --git a/src/db.rs b/src/db.rs index 5a6c791..913473a 100644 --- a/src/db.rs +++ b/src/db.rs @@ -5,6 +5,7 @@ use postgres_native_tls::MakeTlsConnector; use std::{fmt, io}; use tokio_postgres::NoTls; +#[derive(Clone)] pub struct Handle { pub pool: Pool, } diff --git a/src/lib.rs b/src/lib.rs index 4ab5bee..5444dcf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,21 +3,20 @@ pub mod config; mod controllers; pub mod db; mod emoji; -pub mod url; pub mod leaderboard; +pub mod state; +pub mod url; mod views; -use axum::extract::Extension; use axum::routing; use axum::Router; -use config::AppConfig; -use std::sync::Arc; +use state::AppState; use std::net::SocketAddr; +use std::sync::Arc; -pub async fn run(config: AppConfig, handle: db::Handle) -> Result<(), hyper::Error> { - // TODO: Read about `Arc` because I have no idea what this does. - let handle = Arc::new(handle); - let arc_config = Arc::new(config.clone()); +pub async fn run(app_state: AppState) -> Result<(), hyper::Error> { + let config = app_state.config.clone(); + let handle_state = Arc::new(app_state); // https://docs.rs/axum/0.4.8/axum/extract/struct.Extension.html let app = Router::new() @@ -34,8 +33,7 @@ pub async fn run(config: AppConfig, handle: db::Handle) -> Result<(), hyper::Err .route("/purify.min.js", routing::get(controllers::purifyjs)) .route("/stats/:id", routing::get(controllers::url_stats)) .route("/:id", routing::get(controllers::fetch_url)) - .layer(Extension(handle)) - .layer(Extension(arc_config)); + .with_state(handle_state); let addr = SocketAddr::from(([0, 0, 0, 0], config.port)); diff --git a/src/main.rs b/src/main.rs index 0170a3b..2397469 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,9 @@ #![forbid(unsafe_code)] -use emojied::db; use std::process; +use emojied::db; +use emojied::state::AppState; use emojied::config::AppConfig; #[tokio::main] @@ -17,8 +18,14 @@ async fn main() { match db::Handle::new(config.clone()).await { Ok(db_handle) => { eprintln!("Database connection established"); + + let state = AppState { + config, + db_handle, + }; + // https://docs.rs/axum/0.4.8/axum/extract/struct.Extension.html - if let Err(e) = emojied::run(config, db_handle).await { + if let Err(e) = emojied::run(state).await { eprintln!("Application error: {}", e); process::exit(1); } diff --git a/src/state.rs b/src/state.rs new file mode 100644 index 0000000..76e3667 --- /dev/null +++ b/src/state.rs @@ -0,0 +1,8 @@ +use crate::db; +use crate::config; + +#[derive(Clone)] +pub struct AppState { + pub db_handle: db::Handle, + pub config: config::AppConfig, +}