diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 5c2e748..c81965f 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -71,7 +71,7 @@ dependencies = [ "miniz_oxide", "object", "rustc-demangle", - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -82,9 +82,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bitflags" -version = "2.9.1" +version = "2.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +checksum = "6a65b545ab31d687cff52899d4890855fec459eb6afe0da6417b8a18da87aa29" [[package]] name = "bumpalo" @@ -100,9 +100,9 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "cc" -version = "1.2.31" +version = "1.2.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3a42d84bb6b69d3a8b3eaacf0d88f179e1929695e1ad012b6cf64d9caaa5fd2" +checksum = "3ee0f8803222ba5a7e2777dd72ca451868909b1ac410621b676adf07280e9b5f" dependencies = [ "shlex", ] @@ -176,7 +176,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -683,9 +683,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.174" +version = "0.2.175" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" +checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" [[package]] name = "linux-raw-sys" @@ -852,7 +852,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -899,9 +899,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.95" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" dependencies = [ "unicode-ident", ] @@ -1030,7 +1030,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -1068,9 +1068,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.21" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" @@ -1177,9 +1177,9 @@ dependencies = [ [[package]] name = "slab" -version = "0.4.10" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" [[package]] name = "smallvec" @@ -1221,9 +1221,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.104" +version = "2.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" dependencies = [ "proc-macro2", "quote", @@ -1644,7 +1644,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -1653,7 +1653,16 @@ version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.3", ] [[package]] @@ -1662,14 +1671,31 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", ] [[package]] @@ -1678,48 +1704,96 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + [[package]] name = "wit-bindgen-rt" version = "0.39.0" diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 7f13085..4636921 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "backend" version = "0.1.0" -edition = "2024" +edition = "2021" [dependencies] tokio = { version = "1", features = ["full"] } @@ -17,3 +17,7 @@ serde_json = "1.0.142" [dev-dependencies] tokio = { version = "1", features = ["full"] } rand = "0.8" + +[[bin]] +name = "backend" +path = "src/main.rs" \ No newline at end of file diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 0000000..09e8bf8 --- /dev/null +++ b/backend/Dockerfile @@ -0,0 +1,28 @@ +# 1. ビルドステージ +FROM rust:1.84 as builder +WORKDIR /usr/src/app + +COPY ./Cargo.toml ./Cargo.lock ./ +RUN mkdir src && echo "fn main() {}" > src/main.rs +RUN cargo build --release +RUN rm -rf src + +COPY . ./backend +WORKDIR /usr/src/app/backend +RUN cargo build --release + +# 2. 実行ステージ +FROM debian:bookworm-slim +RUN apt-get update && apt-get install -y ca-certificates && rm -rf /var/lib/apt/lists/* + +WORKDIR /app + +# バイナリコピー +COPY --from=builder /usr/src/app/backend/target/release/backend /app/backend + +# データコピー +COPY ./data /app/data + +ENV PORT=3000 +EXPOSE 3000 +CMD ["/app/backend"] \ No newline at end of file diff --git a/data/json_fetch.mjs b/backend/data/json_fetch.mjs similarity index 100% rename from data/json_fetch.mjs rename to backend/data/json_fetch.mjs diff --git a/data/problem-models.json b/backend/data/problem-models.json similarity index 100% rename from data/problem-models.json rename to backend/data/problem-models.json diff --git a/data/problems.json b/backend/data/problems.json similarity index 100% rename from data/problems.json rename to backend/data/problems.json diff --git a/backend/fly.toml b/backend/fly.toml new file mode 100644 index 0000000..af29db4 --- /dev/null +++ b/backend/fly.toml @@ -0,0 +1,26 @@ +# fly.toml app configuration file generated for atcoder-random-picker-be on 2025-08-18T01:08:21+09:00 +# +# See https://fly.io/docs/reference/configuration/ for information about how to use this file. +# + +app = 'atcoder-random-picker-be' +primary_region = 'nrt' + +[build] + +[env] + PORT = '3000' + +[http_service] + internal_port = 3000 + force_https = true + protocol = "tcp" + auto_stop_machines = 'stop' + auto_start_machines = true + min_machines_running = 0 + processes = ['app'] + +[[vm]] + memory = '1gb' + cpu_kind = 'shared' + cpus = 1 diff --git a/backend/src/api/mod.rs b/backend/src/api/mod.rs index f6a9db0..ab696d6 100644 --- a/backend/src/api/mod.rs +++ b/backend/src/api/mod.rs @@ -1,6 +1,9 @@ +use std::option::Option::None; +use std::path::PathBuf; +use std::fs; use serde::Deserialize; -use core::prelude::v1::derive; -use std::{collections::HashMap, error::Error, fs, iter::{IntoIterator, Iterator}, option::Option::{self, None, Some}, path::PathBuf, string::String, vec::Vec}; +use std::collections::HashMap; +use std::error::Error; #[derive(Debug, Clone, Deserialize)] pub struct Problem { @@ -28,12 +31,19 @@ fn adjust_difficulty(difficulty: Option) -> Option { } pub async fn fetch_problem() -> Result<(Vec, HashMap), Box> { - // カレントディレクトリ基準で src/data を指す - let mut problems_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - problems_path.push("../data/problems.json"); + // コンテナ内のパスを直接指定 + let problems_path = PathBuf::from("/app/data/problems.json"); + let problem_models_path = PathBuf::from("/app/data/problem-models.json"); - let mut problem_models_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - problem_models_path.push("../data/problem-models.json"); + // ファイルが存在しない場合に落ちないようにする + if !problems_path.exists() { + eprintln!("Warning: {} not found", problems_path.display()); + return Ok((Vec::new(), HashMap::new())); + } + if !problem_models_path.exists() { + eprintln!("Warning: {} not found", problem_models_path.display()); + return Ok((Vec::new(), HashMap::new())); + } // ファイル読み込み let problems_text = fs::read_to_string(problems_path)?; @@ -42,7 +52,6 @@ pub async fn fetch_problem() -> Result<(Vec, HashMap = serde_json::from_str(&problem_models_text)?; - // 補正式を適用させる let problem_models: HashMap = raw_models .into_iter() .map(|(id, raw)| { @@ -56,4 +65,4 @@ pub async fn fetch_problem() -> Result<(Vec, HashMap { - println!("Succeeded to fetch problems"); - - let state = Arc::new(AppState { problems, problem_models }); - let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); - let make_svc = make_service_fn(move |_conn| { - let state = state.clone(); - async move { - Ok::<_, Infallible>(service_fn(move |req| { - let state = state.clone(); - router(req, state) - })) - } - }); + match api::fetch_problem().await { + Ok((problems, problem_models)) => { + println!("Succeeded to fetch problems"); + + let state = Arc::new(AppState { problems, problem_models }); + + // Fly.io 環境変数 PORT を使用 + let port: u16 = std::env::var("PORT") + .unwrap_or_else(|_| "3000".to_string()) + .parse() + .expect("PORT must be a number"); - let server = Server::bind(&addr).serve(make_svc); + // 0.0.0.0 でバインド + let addr = SocketAddr::from(([0, 0, 0, 0], port)); - println!("Running server on http://{}.", addr); + let make_svc = make_service_fn(move |_conn| { + let state = state.clone(); + async move { + Ok::<_, Infallible>(service_fn(move |req| { + let state = state.clone(); + router(req, state) + })) + } + }); - if let Err(e) = server.await { - eprintln!("error on {}.", e); - } else { - println!("server shut down."); - } + let server = Server::bind(&addr).serve(make_svc); + + println!("Running server on http://{}.", addr); + + if let Err(e) = server.await { + eprintln!("error on {}.", e); + } else { + println!("server shut down."); + } + } + Err(e) => eprintln!("Failed to fetch problems: {}", e), } - Err(e) => eprintln!("Failed to fetch problems: {}", e), - } } \ No newline at end of file diff --git a/backend/tests/api_test.rs b/backend/tests/api_test.rs index a2bd3ee..f9942b8 100644 --- a/backend/tests/api_test.rs +++ b/backend/tests/api_test.rs @@ -1,22 +1,48 @@ -#![allow(unused)] - -use backend::api::{fetch_problem, Problem, ProblemModel}; +use backend::api::{Problem, ProblemModel}; use std::collections::HashMap; -#[tokio::test] -async fn test_fetch_problem_returns_data() { - let result = fetch_problem().await; +/// fetch_problem のモック版 +pub async fn fetch_problem_mock() -> (Vec, HashMap) { + // ダミーの Problem + let problems = vec![ + Problem { + id: "abc_001".to_string(), + contest_id: "contest1".to_string(), + name: "Test Problem 1".to_string(), + }, + Problem { + id: "def_002".to_string(), + contest_id: "contest2".to_string(), + name: "Test Problem 2".to_string(), + }, + ]; + + // ダミーの ProblemModel + let mut problem_models = HashMap::new(); + problem_models.insert( + "abc_001".to_string(), + ProblemModel { difficulty: Some(400.0) }, + ); + problem_models.insert( + "def_002".to_string(), + ProblemModel { difficulty: Some(800.0) }, + ); + + (problems, problem_models) +} - assert!(result.is_ok()); - let (problems, problem_models): (Vec, HashMap) = result.unwrap(); +#[tokio::test] +async fn test_fetch_problem_mock() { + let (problems, problem_models) = fetch_problem_mock().await; - // 空でないことを確認 assert!(!problems.is_empty()); assert!(!problem_models.is_empty()); - // Problem の中身確認 - let first_problem = &problems[0]; - assert!(!first_problem.id.is_empty()); - assert!(!first_problem.contest_id.is_empty()); - assert!(!first_problem.name.is_empty()); -} + let first = &problems[0]; + assert_eq!(first.id, "abc_001"); + assert_eq!(first.contest_id, "contest1"); + assert_eq!(first.name, "Test Problem 1"); + + let first_model = problem_models.get(&first.id).unwrap(); + assert_eq!(first_model.difficulty, Some(400.0)); +} \ No newline at end of file