diff --git a/.dockerignore b/.dockerignore index c41cc9e..f072287 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1 +1,2 @@ -/target \ No newline at end of file +/target +fly.toml \ No newline at end of file diff --git a/.gitignore b/.gitignore index 953147f..f9113ae 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,6 @@ .DS_Store volts-front/dist + +.vscode +.lapce \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index daf8a09..9215c84 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,12 +2,6 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" - [[package]] name = "aes" version = "0.7.5" @@ -69,16 +63,16 @@ checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" [[package]] name = "async-io" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83e21f3a490c72b3b0cf44962180e60045de2925d8dff97918f7ee43c8f637c7" +checksum = "e8121296a9f05be7f34aa4196b1747243b3b62e048bb7906f644f3fbfc490cf7" dependencies = [ + "async-lock", "autocfg", "concurrent-queue", "futures-lite", "libc", "log", - "once_cell", "parking", "polling", "slab", @@ -89,11 +83,12 @@ dependencies = [ [[package]] name = "async-lock" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e97a171d191782fba31bb902b14ad94e24a68145032b7eedf871ab0bc0d077b6" +checksum = "c8101efe8695a6c17e02911402145357e718ac92d3ff88ae8419e84b1707b685" dependencies = [ "event-listener", + "futures-lite", ] [[package]] @@ -173,7 +168,7 @@ dependencies = [ "serde", "serde-xml-rs", "thiserror", - "time 0.3.16", + "time 0.3.17", "url", ] @@ -336,9 +331,12 @@ checksum = "c1db59621ec70f09c5e9b597b220c7a2b43611f4710dc03ceb8748637775692c" [[package]] name = "cc" -version = "1.0.73" +version = "1.0.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" +checksum = "581f5dba903aac52ea3feb5ec4810848460ee833876f1f9b0fdeab1f19091574" +dependencies = [ + "jobserver", +] [[package]] name = "cfg-if" @@ -484,15 +482,6 @@ dependencies = [ "libc", ] -[[package]] -name = "crc32fast" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" -dependencies = [ - "cfg-if 1.0.0", -] - [[package]] name = "crossbeam-channel" version = "0.5.6" @@ -777,16 +766,6 @@ dependencies = [ "windows-sys 0.42.0", ] -[[package]] -name = "flate2" -version = "1.0.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6" -dependencies = [ - "crc32fast", - "miniz_oxide", -] - [[package]] name = "fnv" version = "1.0.7" @@ -1000,9 +979,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.14" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca32592cf21ac7ccab1825cd87f6c9b3d9022c44d086172ed0966bec8af30be" +checksum = "5f9f29bc9dda355256b2916cf526ab02ce0aeaaaf2bad60d65ef3f12f11dd0f4" dependencies = [ "bytes", "fnv", @@ -1103,9 +1082,9 @@ dependencies = [ [[package]] name = "html-escape" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8e7479fa1ef38eb49fb6a42c426be515df2d063f06cb8efd3e50af073dbc26c" +checksum = "15315cfa9503e9aa85a477138eff76a1b203a430703548052c330b69d8d8c205" dependencies = [ "utf8-width", ] @@ -1152,9 +1131,9 @@ checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" [[package]] name = "hyper" -version = "0.14.20" +version = "0.14.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02c929dc5c39e335a03c405292728118860721b10190d98c2a0f0efd5baafbac" +checksum = "abfba89e19b959ca163c7752ba59d737c1ceea53a5d31a149c805446fc958064" dependencies = [ "bytes", "futures-channel", @@ -1202,9 +1181,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.51" +version = "0.1.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5a6ef98976b22b3b7f2f3a806f858cb862044cfa66805aa3ad84cb3d3b785ed" +checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -1255,9 +1234,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.5.0" +version = "2.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b" +checksum = "f88c5561171189e69df9d98bcf18fd5f9558300f7ea7b801eb8a0fd748bd8745" [[package]] name = "itertools" @@ -1274,6 +1253,15 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" +[[package]] +name = "jobserver" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "068b1ee6743e4d11fb9c6a1e6064b3693a1b600e7f5f5988047d98b3dc9fb90b" +dependencies = [ + "libc", +] + [[package]] name = "js-sys" version = "0.3.60" @@ -1342,9 +1330,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.135" +version = "0.2.137" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68783febc7782c6c5cb401fbda4de5a9898be1762314da0bb2c10ced61f18b0c" +checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" [[package]] name = "link-cplusplus" @@ -1455,32 +1443,23 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" -[[package]] -name = "miniz_oxide" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34" -dependencies = [ - "adler", -] - [[package]] name = "mio" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" +checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" dependencies = [ "libc", "log", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.36.1", + "windows-sys 0.42.0", ] [[package]] name = "native-tls" -version = "0.2.10" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd7e2f3618557f980e0b17e8856252eee3c97fa12c54dff0ca290fb6266ca4a9" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" dependencies = [ "lazy_static", "libc", @@ -1605,23 +1584,14 @@ dependencies = [ [[package]] name = "num_cpus" -version = "1.13.1" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5" dependencies = [ "hermit-abi", "libc", ] -[[package]] -name = "num_threads" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" -dependencies = [ - "libc", -] - [[package]] name = "oauth2" version = "4.2.3" @@ -1644,9 +1614,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1" +checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" [[package]] name = "opaque-debug" @@ -1833,9 +1803,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" +checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" [[package]] name = "polling" @@ -1882,9 +1852,9 @@ dependencies = [ [[package]] name = "ppv-lite86" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "pq-sys" @@ -2021,9 +1991,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" +checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a" dependencies = [ "aho-corasick", "memchr", @@ -2032,9 +2002,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.27" +version = "0.6.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" +checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" [[package]] name = "remove_dir_all" @@ -2144,7 +2114,7 @@ dependencies = [ "serde_derive", "sha2 0.10.6", "thiserror", - "time 0.3.16", + "time 0.3.17", "tokio", "tokio-stream", "url", @@ -2678,13 +2648,11 @@ dependencies = [ [[package]] name = "time" -version = "0.3.16" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fab5c8b9980850e06d92ddbe3ab839c062c801f3927c0fb8abd6fc8e918fbca" +checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376" dependencies = [ "itoa", - "libc", - "num_threads", "serde", "time-core", "time-macros", @@ -2698,9 +2666,9 @@ checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" [[package]] name = "time-macros" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bb801831d812c562ae7d2bfb531f26e66e4e1f6b17307ba4149c5064710e5b" +checksum = "d967f99f534ca7e495c575c62638eebc2898a8c84c119b89e250477bc4ba16b2" dependencies = [ "time-core", ] @@ -3011,7 +2979,6 @@ name = "volts" version = "0.1.1" dependencies = [ "clap", - "flate2", "keyring", "lapce-rpc", "reqwest", @@ -3019,6 +2986,7 @@ dependencies = [ "tar", "tempfile", "toml_edit", + "zstd", ] [[package]] @@ -3032,7 +3000,6 @@ dependencies = [ "diesel", "diesel-async", "dotenvy", - "flate2", "futures", "headers", "lapce-rpc", @@ -3050,6 +3017,7 @@ dependencies = [ "tokio-util", "toml_edit", "volts-core", + "zstd", ] [[package]] @@ -3410,6 +3378,35 @@ dependencies = [ "syn", ] +[[package]] +name = "zstd" +version = "0.11.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "5.0.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db" +dependencies = [ + "libc", + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.1+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fd07cbbc53846d9145dbffdf6dd09a7a0aa52be46741825f5c97bdd4f73f12b" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "zvariant" version = "2.10.0" diff --git a/Cargo.toml b/Cargo.toml index d8c9e7e..f305e6d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ name = "volts" path = "volts-cli/src/bin/volts.rs" [workspace] -memebers = [ +members = [ "volts-core", "volts-front", "volts-back", diff --git a/Dockerfile b/Dockerfile index f7e7266..791e43c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,38 +1,15 @@ FROM rust:latest as builder -RUN USER=root cargo new app -WORKDIR /usr/src/app RUN cargo install wasm-pack -RUN mkdir volts-back -RUN mkdir volts-core -RUN mkdir volts-cli -RUN mkdir volts-front -COPY Cargo.toml Cargo.lock ./ -COPY ./volts-back/Cargo.toml ./volts-back/ -COPY ./volts-core/Cargo.toml ./volts-core/ -COPY ./volts-front/Cargo.toml ./volts-front/ -COPY ./volts-cli/Cargo.toml ./volts-cli/ -# Needs at least a main.rs file with a main function -RUN mkdir src && echo "fn main(){}" > src/main.rs -RUN mkdir -p volts-back/src/bin && echo "fn main(){}" > volts-back/src/bin/server.rs && touch volts-back/src/lib.rs -RUN mkdir volts-core/src && touch volts-core/src/lib.rs -RUN mkdir -p volts-front/src/bin && echo "fn main(){}" > volts-front/src/bin/front.rs && touch volts-front/src/lib.rs -RUN mkdir -p volts-cli/src/bin && echo "fn main(){}" > volts-cli/src/bin/volts.rs && touch volts-cli/src/lib.rs -# Will build all dependent crates in release mode +COPY . /build +WORKDIR /build RUN --mount=type=cache,target=/usr/local/cargo/registry \ - --mount=type=cache,target=/usr/src/app/target \ - cargo build --bin volts-server --release && cd /usr/src/app/volts-front && wasm-pack build --target web -RUN cd /usr/src/app -RUN rm src/main.rs - -COPY ./volts-back ./volts-back -COPY ./volts-core ./volts-core -COPY ./volts-front ./volts-front -COPY ./volts-cli ./volts-cli -RUN cargo build --bin volts-server --release - -RUN cd /usr/src/app/volts-front && wasm-pack build --target web + --mount=type=cache,target=/build/target \ + cd ./volts-front && \ + wasm-pack build --target web && \ + cd .. && \ + cargo build --bin volts-server --release # Runtime image FROM debian:bullseye @@ -54,8 +31,8 @@ COPY ./nginx/volt.png /app/static/volt.png COPY ./volts-front/assets/tailwind.css /app/static/main.css # Get compiled binaries from builder's cargo install directory -COPY --from=builder /usr/src/app/volts-front/pkg/volts_front.js /app/static/main.js -COPY --from=builder /usr/src/app/volts-front/pkg/volts_front_bg.wasm /app/static/main.wasm -COPY --from=builder /usr/src/app/target/release/volts-server /app/volts-server +COPY --from=builder /build/volts-front/pkg/volts_front.js /app/static/main.js +COPY --from=builder /build/volts-front/pkg/volts_front_bg.wasm /app/static/main.wasm +COPY --from=builder /build/target/release/volts-server /app/volts-server CMD nginx && /app/volts-server \ No newline at end of file diff --git a/volts-back/Cargo.toml b/volts-back/Cargo.toml index decfb7f..502a99e 100644 --- a/volts-back/Cargo.toml +++ b/volts-back/Cargo.toml @@ -9,7 +9,6 @@ edition = "2021" semver = "1.0.14" rust-s3 = { version = "0.32.3", features = ["with-tokio"] } tempfile = "3.3.0" -flate2 = "1.0.24" tar = "0.4.38" sha2 = "0.10.6" rand = "0.8.5" @@ -31,3 +30,4 @@ dotenvy = "0.15.6" volts-core = { path = "../volts-core" } toml_edit = { version = "0.14.4", features = ["easy"] } lapce-rpc = "0.2.1" +zstd = { version = "0.11" } \ No newline at end of file diff --git a/volts-back/src/github.rs b/volts-back/src/github.rs index 5414470..9e4f694 100644 --- a/volts-back/src/github.rs +++ b/volts-back/src/github.rs @@ -3,6 +3,10 @@ use oauth2::AccessToken; use reqwest::{header, Client}; use serde::{de::DeserializeOwned, Deserialize}; +const GITHUB_API_ENDPOINT: &str = "https://api.github.com"; + +const VOLTS_USER_AGENT: &str = "volts (https://plugins.lapce.dev)"; + #[derive(Debug, Deserialize)] pub struct GithubUser { pub avatar_url: Option, @@ -28,7 +32,7 @@ impl GithubClient { pub fn new() -> Self { let client = reqwest::Client::new(); Self { - base_url: "https://api.github.com".to_string(), + base_url: GITHUB_API_ENDPOINT.to_string(), client, } } @@ -45,7 +49,7 @@ impl GithubClient { .get(&url) .header(header::ACCEPT, "application/vnd.github.v3+json") .header(header::AUTHORIZATION, format!("token {}", auth.secret())) - .header(header::USER_AGENT, "crates.io (https://crates.io)") + .header(header::USER_AGENT, VOLTS_USER_AGENT) .send() .await? .json() diff --git a/volts-back/src/plugin.rs b/volts-back/src/plugin.rs index d7dd045..ab13c47 100644 --- a/volts-back/src/plugin.rs +++ b/volts-back/src/plugin.rs @@ -1,4 +1,7 @@ -use std::collections::{HashMap, HashSet}; +use std::{ + collections::{HashMap, HashSet}, + fs::File, +}; use anyhow::Result; use axum::{ @@ -11,7 +14,6 @@ use axum::{ use diesel::{BelongingToDsl, BoolExpressionMethods, ExpressionMethods, GroupedBy}; use diesel::{PgTextExpressionMethods, QueryDsl}; use diesel_async::RunQueryDsl; -use flate2::{read::GzDecoder, write::GzEncoder, Compression}; use futures::{FutureExt, Stream, TryStreamExt}; use headers::authorization::Bearer; use lapce_rpc::plugin::VoltMetadata; @@ -27,12 +29,16 @@ use volts_core::{ }, EncodePlugin, PluginList, }; +use zstd::{Decoder, Encoder}; use crate::db::{ find_api_token, find_plugin, find_plugin_version, find_user, find_user_by_gh_login, modify_plugin_version_yank, DbPool, NewPlugin, NewVersion, }; +const VOLT_MANIFEST: &str = "volt.toml"; +const VOLT_ARCHIVE: &str = "plugin.volt"; + #[derive(Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] struct IconTheme { @@ -241,7 +247,7 @@ pub async fn download( .unwrap(); } - let s3_path = format!("{}/{}/{}/volt.tar.gz", user.gh_login, name, version.num); + let s3_path = format!("{}/{}/{}/{VOLT_ARCHIVE}", user.gh_login, name, version.num); bucket.presign_get(&s3_path, 60, None).unwrap() } @@ -361,15 +367,15 @@ pub async fn publish( let dir = tempfile::TempDir::new().unwrap(); let dest = tempfile::TempDir::new().unwrap(); - let tar_gz = dir.path().join("volt.tar.gz"); - stream_to_file(&tar_gz, body).await.unwrap(); + let archive = dir.path().join(VOLT_ARCHIVE); + stream_to_file(&archive, body).await.unwrap(); { - let tar_gz = tar_gz.clone(); + let archive = archive.clone(); let dir_path = dir.path().to_path_buf(); tokio::task::spawn_blocking(move || { - let tar_gz = std::fs::File::open(tar_gz).unwrap(); - let tar = GzDecoder::new(tar_gz); + let archive = File::open(archive).unwrap(); + let tar = Decoder::new(archive).unwrap(); let mut archive = Archive::new(tar); archive.unpack(dir_path).unwrap(); }) @@ -377,9 +383,13 @@ pub async fn publish( .unwrap(); } - let volt_path = dir.path().join("volt.toml"); + let volt_path = dir.path().join(VOLT_MANIFEST); if !volt_path.exists() { - return (StatusCode::BAD_REQUEST, "volt.toml doens't exist").into_response(); + return ( + StatusCode::BAD_REQUEST, + format!("{VOLT_MANIFEST} doesn't exist"), + ) + .into_response(); } let s = tokio::fs::read_to_string(&volt_path).await.unwrap(); @@ -389,7 +399,13 @@ pub async fn publish( volt.name = volt.name.to_lowercase(); volt } - Err(_) => return (StatusCode::BAD_REQUEST, "volt.tmol format invalid").into_response(), + Err(_) => { + return ( + StatusCode::BAD_REQUEST, + format!("{VOLT_MANIFEST} format invalid"), + ) + .into_response() + } }; if semver::Version::parse(&volt.version).is_err() { @@ -397,7 +413,7 @@ pub async fn publish( } { - let dest_volt_path = dest.path().join("volt.toml"); + let dest_volt_path = dest.path().join(VOLT_MANIFEST); tokio::fs::write( dest_volt_path, toml_edit::ser::to_string_pretty(&volt).unwrap(), @@ -551,13 +567,13 @@ pub async fn publish( } } - let dest_tar_gz_dir = tempfile::TempDir::new().unwrap(); - let dest_tar_gz = dest_tar_gz_dir.path().join("volt.tar.gz"); + let tmpdir = tempfile::TempDir::new().unwrap(); + let dest_volt_archive = tmpdir.path().join(VOLT_ARCHIVE); { - let tar_gz = dest_tar_gz.clone(); + let volt_archive = dest_volt_archive.clone(); tokio::task::spawn_blocking(move || { - let tar_gz = std::fs::File::create(tar_gz).unwrap(); - let encoder = GzEncoder::new(tar_gz, Compression::default()); + let volt_archive = std::fs::File::create(volt_archive).unwrap(); + let encoder = Encoder::new(volt_archive, 0).unwrap(); let mut tar = tar::Builder::new(encoder); tar.append_dir_all(".", dest.path()).unwrap(); }) @@ -565,9 +581,9 @@ pub async fn publish( .unwrap(); } - let volt_content = tokio::fs::read(&dest_tar_gz).await.unwrap(); + let volt_content = tokio::fs::read(&dest_volt_archive).await.unwrap(); bucket - .put_object(format!("{}/volt.tar.gz", s3_folder), &volt_content) + .put_object(format!("{s3_folder}/{VOLT_ARCHIVE}"), &volt_content) .await .unwrap(); @@ -608,8 +624,8 @@ where let body_reader = StreamReader::new(body_with_io_error); futures::pin_mut!(body_reader); - let mut tar_gz = tokio::fs::File::create(path).await?; - tokio::io::copy(&mut body_reader, &mut tar_gz).await?; + let mut archive = tokio::fs::File::create(path).await?; + tokio::io::copy(&mut body_reader, &mut archive).await?; Ok(()) } diff --git a/volts-back/src/router.rs b/volts-back/src/router.rs index 88aa5d5..d6f3d62 100644 --- a/volts-back/src/router.rs +++ b/volts-back/src/router.rs @@ -22,34 +22,37 @@ use crate::{ pub fn build_router() -> Router { let state = AppState::new(); - Router::with_state(state) - .route("/api/private/session", get(new_session)) - .route("/api/private/session/authorize", get(session_authorize)) - .route("/api/private/session", delete(logout)) - .route("/api/v1/me", get(me)) - .route("/api/v1/me/tokens", get(token::list)) - .route("/api/v1/me/tokens", post(token::new)) - .route("/api/v1/me/tokens/:id", delete(token::revoke)) - .route("/api/v1/me/plugins/new", put(plugin::publish)) - .route("/api/v1/me/plugins/:name/:version/yank", put(plugin::yank)) - .route( - "/api/v1/me/plugins/:name/:version/unyank", - put(plugin::unyank), - ) - .route("/api/v1/plugins", get(plugin::search)) - .route("/api/v1/plugins/:author/:name/:version", get(plugin::meta)) - .route( - "/api/v1/plugins/:author/:name/:version/download", - get(plugin::download), - ) - .route( - "/api/v1/plugins/:author/:name/:version/readme", - get(plugin::readme), - ) - .route( - "/api/v1/plugins/:author/:name/:version/icon", - get(plugin::icon), - ) + + let private_routes = Router::with_state(state.clone()) + .route("/session", get(new_session)) + .route("/session", delete(logout)) + .route("/session/authorize", get(session_authorize)); + + let user_routes = Router::with_state(state.clone()) + .route("/", get(me)) + .route("/tokens", get(token::list)) + .route("/tokens", post(token::new)) + .route("/tokens/:id", delete(token::revoke)); + + let plugins_routes = Router::with_state(state.clone()) + .route("/", get(plugin::search)) + .route("/new", put(plugin::publish)) + .route("/:name/:version/yank", post(plugin::yank)) + .route("/:name/:version/unyank", post(plugin::unyank)) + .route("/:author/:name/:version", get(plugin::meta)) + .route("/:author/:name/:version/download", get(plugin::download)) + .route("/:author/:name/:version/readme", get(plugin::readme)) + .route("/:author/:name/:version/icon", get(plugin::icon)); + + let v1 = Router::with_state(state.clone()) + .nest("/me", user_routes) + .nest("/plugins", plugins_routes); + + let api = Router::with_state(state.clone()) + .nest("/private", private_routes) + .nest("/v1", v1); + + Router::with_state(state).nest("/api", api) } async fn me( diff --git a/volts-back/src/state.rs b/volts-back/src/state.rs index 4a57289..dc88a51 100644 --- a/volts-back/src/state.rs +++ b/volts-back/src/state.rs @@ -7,8 +7,12 @@ use s3::{creds::Credentials, Bucket, Region}; use crate::{db::DbPool, github::GithubClient}; +const GITHUB_OAUTH_AUTHORIZE_ENDPOINT: &str = "https://github.com/login/oauth/authorize"; +const GITHUB_OAUTH_TOKEN_ENDPOINT: &str = "https://github.com/login/oauth/access_token"; + pub const SESSION_COOKIE_NAME: &str = "session"; +#[derive(Clone)] pub struct AppState { store: MemoryStore, /// The GitHub OAuth2 configuration @@ -64,9 +68,9 @@ impl AppState { env::var("GITHUB_CLIENT_SECRET") .expect("Missing the GITHUB_CLIENT_SECRET environment variable."), ); - let auth_url = AuthUrl::new("https://github.com/login/oauth/authorize".to_string()) + let auth_url = AuthUrl::new(GITHUB_OAUTH_AUTHORIZE_ENDPOINT.to_string()) .expect("Invalid authorization endpoint URL"); - let token_url = TokenUrl::new("https://github.com/login/oauth/access_token".to_string()) + let token_url = TokenUrl::new(GITHUB_OAUTH_TOKEN_ENDPOINT.to_string()) .expect("Invalid token endpoint URL"); // Set up the config for the Github OAuth2 process. diff --git a/volts-cli/Cargo.toml b/volts-cli/Cargo.toml index 7cd331e..306e8ad 100644 --- a/volts-cli/Cargo.toml +++ b/volts-cli/Cargo.toml @@ -11,9 +11,9 @@ edition = "2021" reqwest = { version = "0.11.12", features = ["blocking"] } lapce-rpc = "0.2.1" tempfile = "3.3.0" -flate2 = "1.0.24" tar = "0.4.38" toml_edit = { version = "0.14.4", features = ["easy"] } serde = { version = "1.0", features = ["derive"] } clap = { version = "4.0", features = ["derive"] } keyring = { version = "1.2.0" } +zstd = "0.11" \ No newline at end of file diff --git a/volts-cli/src/commands.rs b/volts-cli/src/commands.rs new file mode 100644 index 0000000..db9551e --- /dev/null +++ b/volts-cli/src/commands.rs @@ -0,0 +1,177 @@ +use std::{ + collections::HashSet, + fs::{self, File}, + path::PathBuf, +}; + +use lapce_rpc::plugin::VoltMetadata; +use reqwest::{Method, StatusCode}; +use tar::Builder; +use toml_edit::easy as toml; +use zstd::Encoder; + +use crate::{auth_token, Cli, IconTheme}; + +pub(crate) fn publish(cli: &Cli) { + let token = auth_token(cli); + + let temp_dir = tempfile::tempdir().unwrap(); + let archive_path = temp_dir.path().join("plugin.volt"); + + { + let archive = File::create(&archive_path).unwrap(); + let encoder = Encoder::new(archive, 0).unwrap(); + let mut tar = Builder::new(encoder); + + let volt_path = PathBuf::from("volt.toml"); + if !volt_path.exists() { + eprintln!("volt.toml doesn't exist"); + return; + } + + let s = fs::read_to_string(&volt_path).unwrap(); + let volt: VoltMetadata = match toml::from_str(&s) { + Ok(volt) => volt, + Err(e) => { + eprintln!("volt.toml format invalid: {e}"); + return; + } + }; + + tar.append_path(&volt_path).unwrap(); + + if let Some(wasm) = volt.wasm.as_ref() { + let wasm_path = PathBuf::from(wasm); + if !wasm_path.exists() { + eprintln!("wasm {wasm} not found"); + return; + } + + tar.append_path(&wasm_path).unwrap(); + } else if let Some(themes) = volt.color_themes.as_ref() { + if themes.is_empty() { + eprintln!("no color theme provided"); + return; + } + for theme in themes { + let theme_path = PathBuf::from(theme); + if !theme_path.exists() { + eprintln!("color theme {theme} not found"); + return; + } + + tar.append_path(&theme_path).unwrap(); + } + } else if let Some(themes) = volt.icon_themes.as_ref() { + if themes.is_empty() { + eprintln!("no icon theme provided"); + return; + } + for theme in themes { + let theme_path = PathBuf::from(theme); + if !theme_path.exists() { + eprintln!("icon theme {theme} not found"); + return; + } + + tar.append_path(&theme_path).unwrap(); + + let s = fs::read_to_string(&theme_path).unwrap(); + let theme_config: IconTheme = match toml::from_str(&s) { + Ok(config) => config, + Err(_) => { + eprintln!("icon theme {theme} format invalid"); + return; + } + }; + + let mut icons = HashSet::new(); + icons.extend(theme_config.icon_theme.ui.values()); + icons.extend(theme_config.icon_theme.filename.values()); + icons.extend(theme_config.icon_theme.foldername.values()); + icons.extend(theme_config.icon_theme.extension.values()); + + let cwd = PathBuf::from("."); + + for icon in icons { + let icon_path = theme_path.parent().unwrap_or(&cwd).join(icon); + if !icon_path.exists() { + eprintln!("icon {icon} not found"); + return; + } + tar.append_path(&icon_path).unwrap(); + } + } + } else { + eprintln!("not a valid plugin"); + return; + } + + let readme_path = PathBuf::from("README.md"); + if readme_path.exists() { + tar.append_path(&readme_path).unwrap(); + } + + if let Some(icon) = volt.icon.as_ref() { + let icon_path = PathBuf::from(icon); + if !icon_path.exists() { + eprintln!("icon not found at the specified path"); + return; + } + tar.append_path(&icon_path).unwrap(); + } + tar.finish().unwrap(); + } + + let resp = reqwest::blocking::Client::new() + .request( + Method::PUT, + "https://plugins.lapce.dev/api/v1/me/plugins/new", + ) + .bearer_auth(token.trim()) + .body(File::open(&archive_path).unwrap()) + .send() + .unwrap(); + if resp.status() == StatusCode::OK { + println!("plugin published successfully"); + return; + } + + eprintln!("{}", resp.text().unwrap()); +} + +pub(crate) fn yank(cli: &Cli, name: &String, version: &String) { + let token = auth_token(cli); + + let resp = reqwest::blocking::Client::new() + .request( + Method::PUT, + format!("https://plugins.lapce.dev/api/v1/me/plugins/{name}/{version}/yank"), + ) + .bearer_auth(token.trim()) + .send() + .unwrap(); + if resp.status() == StatusCode::OK { + println!("plugin version yanked successfully"); + } else { + eprintln!("failed to yank plugin version: {}", resp.text().unwrap()); + } +} + +pub(crate) fn unyank(cli: &Cli, name: &String, version: &String) { + let token = auth_token(cli); + + let resp = reqwest::blocking::Client::new() + .request( + Method::PUT, + format!("https://plugins.lapce.dev/api/v1/me/plugins/{name}/{version}/unyank"), + ) + .bearer_auth(token.trim()) + .send() + .unwrap(); + if resp.status() == StatusCode::OK { + println!("plugin version yanked successfully"); + } else { + eprintln!("failed to yank plugin version: {}", resp.text().unwrap()); + } +} diff --git a/volts-cli/src/lib.rs b/volts-cli/src/lib.rs index a64de83..9a895ec 100644 --- a/volts-cli/src/lib.rs +++ b/volts-cli/src/lib.rs @@ -1,15 +1,9 @@ -use std::{ - collections::{HashMap, HashSet}, - io::stdin, - path::PathBuf, -}; +mod commands; + +use std::{collections::HashMap, io::stdin}; use clap::{Parser, Subcommand}; -use flate2::{write::GzEncoder, Compression}; -use lapce_rpc::plugin::VoltMetadata; -use reqwest::{Method, StatusCode}; use serde::{Deserialize, Serialize}; -use toml_edit::easy as toml; #[derive(Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] @@ -40,20 +34,26 @@ struct Cli { enum Commands { /// Publish plugin to registry Publish {}, + /// Yank version from registry + Yank { name: String, version: String }, + /// Undo yanking version from registry + Unyank { name: String, version: String }, } pub fn cli() { let cli = Cli::parse(); match &cli.command { - Commands::Publish {} => publish(&cli), + Commands::Publish {} => commands::publish(&cli), + Commands::Yank { name, version } => commands::yank(&cli, name, version), + Commands::Unyank { name, version } => commands::unyank(&cli, name, version), } } -fn publish(cli: &Cli) { +fn auth_token(cli: &Cli) -> String { let api_credential = keyring::Entry::new("lapce-volts", "registry-api"); - let token = if cli.token.is_none() && api_credential.get_password().is_err() { + return if cli.token.is_none() && api_credential.get_password().is_err() { println!("Please paste the API Token you created on https://plugins.lapce.dev/"); let mut token = String::new(); stdin().read_line(&mut token).unwrap(); @@ -79,128 +79,4 @@ fn publish(cli: &Cli) { } else { api_credential.get_password().unwrap() }; - - let temp_dir = tempfile::tempdir().unwrap(); - let tar_gz_path = temp_dir.path().join("volt.tar.gz"); - - { - let tar_gz_file = std::fs::File::create(&tar_gz_path).unwrap(); - let encoder = GzEncoder::new(tar_gz_file, Compression::default()); - let mut tar = tar::Builder::new(encoder); - - let volt_path = PathBuf::from("volt.toml"); - if !volt_path.exists() { - eprintln!("volt.toml doesn't exist"); - return; - } - - let s = std::fs::read_to_string(&volt_path).unwrap(); - let volt: VoltMetadata = match toml::from_str(&s) { - Ok(volt) => volt, - Err(e) => { - eprintln!("volt.toml format invalid: {e}"); - return; - } - }; - - tar.append_path(&volt_path).unwrap(); - - if let Some(wasm) = volt.wasm.as_ref() { - let wasm_path = PathBuf::from(wasm); - if !wasm_path.exists() { - eprintln!("wasm {wasm} not found"); - return; - } - - tar.append_path(&wasm_path).unwrap(); - } else if let Some(themes) = volt.color_themes.as_ref() { - if themes.is_empty() { - eprintln!("no color theme provided"); - return; - } - for theme in themes { - let theme_path = PathBuf::from(theme); - if !theme_path.exists() { - eprintln!("color theme {theme} not found"); - return; - } - - tar.append_path(&theme_path).unwrap(); - } - } else if let Some(themes) = volt.icon_themes.as_ref() { - if themes.is_empty() { - eprintln!("no icon theme provided"); - return; - } - for theme in themes { - let theme_path = PathBuf::from(theme); - if !theme_path.exists() { - eprintln!("icon theme {theme} not found"); - return; - } - - tar.append_path(&theme_path).unwrap(); - - let s = std::fs::read_to_string(&theme_path).unwrap(); - let theme_config: IconTheme = match toml::from_str(&s) { - Ok(config) => config, - Err(_) => { - eprintln!("icon theme {theme} format invalid"); - return; - } - }; - - let mut icons = HashSet::new(); - icons.extend(theme_config.icon_theme.ui.values()); - icons.extend(theme_config.icon_theme.filename.values()); - icons.extend(theme_config.icon_theme.foldername.values()); - icons.extend(theme_config.icon_theme.extension.values()); - - let cwd = PathBuf::from("."); - - for icon in icons { - let icon_path = theme_path.parent().unwrap_or(&cwd).join(icon); - if !icon_path.exists() { - eprintln!("icon {icon} not found"); - return; - } - tar.append_path(&icon_path).unwrap(); - } - } - } else { - eprintln!("not a valid plugin"); - return; - } - - let readme_path = PathBuf::from("README.md"); - if readme_path.exists() { - tar.append_path(&readme_path).unwrap(); - } - - if let Some(icon) = volt.icon.as_ref() { - let icon_path = PathBuf::from(icon); - if !icon_path.exists() { - eprintln!("icon not found at the specified path"); - return; - } - tar.append_path(&icon_path).unwrap(); - } - tar.finish().unwrap(); - } - - let resp = reqwest::blocking::Client::new() - .request( - Method::PUT, - "https://plugins.lapce.dev/api/v1/me/plugins/new", - ) - .bearer_auth(token.trim()) - .body(std::fs::File::open(&tar_gz_path).unwrap()) - .send() - .unwrap(); - if resp.status() == StatusCode::OK { - println!("plugin published successfully"); - return; - } - - eprintln!("{}", resp.text().unwrap()); } diff --git a/volts-core/src/db/schema.rs b/volts-core/src/db/schema.rs index 3db2554..4b0cf77 100644 --- a/volts-core/src/db/schema.rs +++ b/volts-core/src/db/schema.rs @@ -52,9 +52,4 @@ diesel::joinable!(api_tokens -> users (user_id)); diesel::joinable!(plugins -> users (user_id)); diesel::joinable!(versions -> plugins (plugin_id)); -diesel::allow_tables_to_appear_in_same_query!( - api_tokens, - plugins, - users, - versions, -); +diesel::allow_tables_to_appear_in_same_query!(api_tokens, plugins, users, versions,);