Skip to content

Implement logo.png and color.txt on backend#8

Merged
SpaceFox1 merged 3 commits into
mainfrom
feat/backend/logo-and-color
Apr 20, 2026
Merged

Implement logo.png and color.txt on backend#8
SpaceFox1 merged 3 commits into
mainfrom
feat/backend/logo-and-color

Conversation

@kitsuvm
Copy link
Copy Markdown
Collaborator

@kitsuvm kitsuvm commented Apr 20, 2026

No description provided.

@kitsuvm kitsuvm requested a review from SpaceFox1 April 20, 2026 14:18
@kitsuvm kitsuvm self-assigned this Apr 20, 2026
Copilot AI review requested due to automatic review settings April 20, 2026 14:18
@kitsuvm kitsuvm added the enhancement New feature or request label Apr 20, 2026
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds dedicated backend routes for logo.png and color.txt so they can be served (preferably) from the filesystem next to the executable, with a fallback to the embedded frontend asset map.

Changes:

  • Introduced /logo.png and /color.txt Actix handlers with filesystem + embedded fallback logic.
  • Registered the new services in the Actix App startup.
  • Added actix-files dependency to support serving files from disk.

Reviewed changes

Copilot reviewed 5 out of 6 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
src/routes/mod.rs Exposes new color and logo route modules.
src/routes/logo.rs Implements /logo.png handler with filesystem/embedded fallback.
src/routes/color.rs Implements /color.txt handler with filesystem/embedded fallback.
src/main.rs Registers the new route services in the server.
Cargo.toml Adds actix-files dependency and reformats some dependency entries.
Cargo.lock Locks new transitive dependencies from actix-files.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/routes/color.rs
Comment on lines +14 to +24
async fn color_txt(req: HttpRequest) -> HttpResponse<EitherBody<BoxBody>> {
let file_path = current_exe()
.ok()
.and_then(|p| p.parent().map(|p| p.join("color.txt")));

if let Some(path) = file_path
&& path.exists()
{
tracing::debug!("Serving color.txt from file system: {path:?}");
NamedFile::open_async(path).await.respond_to(&req)
} else {
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The two branches of this handler build different HttpResponse body types: the embedded/not-found branch maps into EitherBody<BoxBody>, but the filesystem branch returns the NamedFile response directly. This will not type-check unless you map the filesystem response into the same body type, or (simpler) drop EitherBody entirely and return a plain HttpResponse from all branches.

Copilot uses AI. Check for mistakes.
Comment thread src/routes/color.rs
Comment on lines +19 to +23
if let Some(path) = file_path
&& path.exists()
{
tracing::debug!("Serving color.txt from file system: {path:?}");
NamedFile::open_async(path).await.respond_to(&req)
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

path.exists() performs a synchronous filesystem metadata check on the async request path and introduces a TOCTOU window (file may disappear/change between exists() and open_async). Prefer attempting NamedFile::open_async directly and falling back to the embedded resource only on Err, which also avoids blocking work per request.

Suggested change
if let Some(path) = file_path
&& path.exists()
{
tracing::debug!("Serving color.txt from file system: {path:?}");
NamedFile::open_async(path).await.respond_to(&req)
if let Some(path) = file_path {
match NamedFile::open_async(&path).await {
Ok(file) => {
tracing::debug!("Serving color.txt from file system: {path:?}");
file.respond_to(&req).map_into_left_body()
}
Err(_) => frontend::get("color.txt")
.map(|content| {
tracing::debug!("Serving color.txt from embedded resource");
HttpResponse::Ok()
.content_type(mime::TEXT_PLAIN)
.body(content)
.map_into_left_body()
})
.unwrap_or_else(|| {
tracing::debug!("File not found");
HttpResponse::NotFound()
.body("Not found")
.map_into_right_body()
}),
}

Copilot uses AI. Check for mistakes.
Comment thread src/routes/color.rs
Comment on lines +12 to +40
#[get("/color.txt")]
#[tracing::instrument(skip_all, name = "color.txt")]
async fn color_txt(req: HttpRequest) -> HttpResponse<EitherBody<BoxBody>> {
let file_path = current_exe()
.ok()
.and_then(|p| p.parent().map(|p| p.join("color.txt")));

if let Some(path) = file_path
&& path.exists()
{
tracing::debug!("Serving color.txt from file system: {path:?}");
NamedFile::open_async(path).await.respond_to(&req)
} else {
frontend::get("color.txt")
.map(|content| {
tracing::debug!("Serving color.txt from embedded resource");
HttpResponse::Ok()
.content_type(mime::TEXT_PLAIN)
.body(content)
.map_into_left_body()
})
.unwrap_or_else(|| {
tracing::debug!("File not found");
HttpResponse::NotFound()
.body("Not found")
.map_into_right_body()
})
}
}
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file duplicates the same “check next to executable → otherwise embedded resource → otherwise 404” logic as logo.rs. Consider extracting a small helper (e.g., serve_embedded_or_fs(req, filename, content_type)) to reduce duplication and keep behavior consistent if you later tweak caching/logging/error handling.

Suggested change
#[get("/color.txt")]
#[tracing::instrument(skip_all, name = "color.txt")]
async fn color_txt(req: HttpRequest) -> HttpResponse<EitherBody<BoxBody>> {
let file_path = current_exe()
.ok()
.and_then(|p| p.parent().map(|p| p.join("color.txt")));
if let Some(path) = file_path
&& path.exists()
{
tracing::debug!("Serving color.txt from file system: {path:?}");
NamedFile::open_async(path).await.respond_to(&req)
} else {
frontend::get("color.txt")
.map(|content| {
tracing::debug!("Serving color.txt from embedded resource");
HttpResponse::Ok()
.content_type(mime::TEXT_PLAIN)
.body(content)
.map_into_left_body()
})
.unwrap_or_else(|| {
tracing::debug!("File not found");
HttpResponse::NotFound()
.body("Not found")
.map_into_right_body()
})
}
}
async fn serve_embedded_or_fs(
req: &HttpRequest,
filename: &str,
content_type: mime::Mime,
) -> HttpResponse<EitherBody<BoxBody>> {
let file_path = current_exe()
.ok()
.and_then(|p| p.parent().map(|p| p.join(filename)));
if let Some(path) = file_path
&& path.exists()
{
tracing::debug!("Serving {filename} from file system: {path:?}");
NamedFile::open_async(path).await.respond_to(req)
} else {
frontend::get(filename)
.map(|content| {
tracing::debug!("Serving {filename} from embedded resource");
HttpResponse::Ok()
.content_type(content_type)
.body(content)
.map_into_left_body()
})
.unwrap_or_else(|| {
tracing::debug!("File not found: {filename}");
HttpResponse::NotFound()
.body("Not found")
.map_into_right_body()
})
}
}
#[get("/color.txt")]
#[tracing::instrument(skip_all, name = "color.txt")]
async fn color_txt(req: HttpRequest) -> HttpResponse<EitherBody<BoxBody>> {
serve_embedded_or_fs(&req, "color.txt", mime::TEXT_PLAIN).await
}

Copilot uses AI. Check for mistakes.
Comment thread src/routes/logo.rs
Comment on lines +14 to +24
async fn logo_png(req: HttpRequest) -> HttpResponse<EitherBody<BoxBody>> {
let file_path = current_exe()
.ok()
.and_then(|p| p.parent().map(|p| p.join("logo.png")));

if let Some(path) = file_path
&& path.exists()
{
tracing::debug!("Serving logo from file system: {path:?}");
NamedFile::open_async(path).await.respond_to(&req)
} else {
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The two branches of this handler build different HttpResponse body types: the embedded/not-found branch maps into EitherBody<BoxBody>, but the filesystem branch returns the NamedFile response directly. This will not type-check unless you map the filesystem response into the same body type, or (simpler) drop EitherBody entirely and return a plain HttpResponse from all branches.

Copilot uses AI. Check for mistakes.
Comment thread src/routes/logo.rs
Comment on lines +19 to +39
if let Some(path) = file_path
&& path.exists()
{
tracing::debug!("Serving logo from file system: {path:?}");
NamedFile::open_async(path).await.respond_to(&req)
} else {
frontend::get("logo.png")
.map(|content| {
tracing::debug!("Serving logo from embedded resource");
HttpResponse::Ok()
.content_type(mime::IMAGE_PNG)
.body(content)
.map_into_left_body()
})
.unwrap_or_else(|| {
tracing::debug!("File not found");
HttpResponse::NotFound()
.body("Not found")
.map_into_right_body()
})
}
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

path.exists() performs a synchronous filesystem metadata check on the async request path and introduces a TOCTOU window (file may disappear/change between exists() and open_async). Prefer attempting NamedFile::open_async directly and falling back to the embedded resource only on Err, which also avoids blocking work per request.

Suggested change
if let Some(path) = file_path
&& path.exists()
{
tracing::debug!("Serving logo from file system: {path:?}");
NamedFile::open_async(path).await.respond_to(&req)
} else {
frontend::get("logo.png")
.map(|content| {
tracing::debug!("Serving logo from embedded resource");
HttpResponse::Ok()
.content_type(mime::IMAGE_PNG)
.body(content)
.map_into_left_body()
})
.unwrap_or_else(|| {
tracing::debug!("File not found");
HttpResponse::NotFound()
.body("Not found")
.map_into_right_body()
})
}
if let Some(path) = file_path {
match NamedFile::open_async(path.clone()).await {
Ok(file) => {
tracing::debug!("Serving logo from file system: {path:?}");
return file.respond_to(&req);
}
Err(err) => {
tracing::debug!("Failed to serve logo from file system ({path:?}): {err}");
}
}
}
frontend::get("logo.png")
.map(|content| {
tracing::debug!("Serving logo from embedded resource");
HttpResponse::Ok()
.content_type(mime::IMAGE_PNG)
.body(content)
.map_into_left_body()
})
.unwrap_or_else(|| {
tracing::debug!("File not found");
HttpResponse::NotFound()
.body("Not found")
.map_into_right_body()
})

Copilot uses AI. Check for mistakes.
Comment thread src/routes/logo.rs
Comment on lines +12 to +40
#[get("/logo.png")]
#[tracing::instrument(skip_all, name = "logo.png")]
async fn logo_png(req: HttpRequest) -> HttpResponse<EitherBody<BoxBody>> {
let file_path = current_exe()
.ok()
.and_then(|p| p.parent().map(|p| p.join("logo.png")));

if let Some(path) = file_path
&& path.exists()
{
tracing::debug!("Serving logo from file system: {path:?}");
NamedFile::open_async(path).await.respond_to(&req)
} else {
frontend::get("logo.png")
.map(|content| {
tracing::debug!("Serving logo from embedded resource");
HttpResponse::Ok()
.content_type(mime::IMAGE_PNG)
.body(content)
.map_into_left_body()
})
.unwrap_or_else(|| {
tracing::debug!("File not found");
HttpResponse::NotFound()
.body("Not found")
.map_into_right_body()
})
}
}
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file duplicates the same “check next to executable → otherwise embedded resource → otherwise 404” logic as logo.rs. Consider extracting a small helper (e.g., serve_embedded_or_fs(req, filename, content_type)) to reduce duplication and keep behavior consistent if you later tweak caching/logging/error handling.

Suggested change
#[get("/logo.png")]
#[tracing::instrument(skip_all, name = "logo.png")]
async fn logo_png(req: HttpRequest) -> HttpResponse<EitherBody<BoxBody>> {
let file_path = current_exe()
.ok()
.and_then(|p| p.parent().map(|p| p.join("logo.png")));
if let Some(path) = file_path
&& path.exists()
{
tracing::debug!("Serving logo from file system: {path:?}");
NamedFile::open_async(path).await.respond_to(&req)
} else {
frontend::get("logo.png")
.map(|content| {
tracing::debug!("Serving logo from embedded resource");
HttpResponse::Ok()
.content_type(mime::IMAGE_PNG)
.body(content)
.map_into_left_body()
})
.unwrap_or_else(|| {
tracing::debug!("File not found");
HttpResponse::NotFound()
.body("Not found")
.map_into_right_body()
})
}
}
async fn serve_embedded_or_fs(
req: &HttpRequest,
filename: &str,
content_type: mime::Mime,
) -> HttpResponse<EitherBody<BoxBody>> {
let file_path = current_exe()
.ok()
.and_then(|p| p.parent().map(|p| p.join(filename)));
if let Some(path) = file_path
&& path.exists()
{
tracing::debug!("Serving {filename} from file system: {path:?}");
NamedFile::open_async(path).await.respond_to(req)
} else {
frontend::get(filename)
.map(|content| {
tracing::debug!("Serving {filename} from embedded resource");
HttpResponse::Ok()
.content_type(content_type)
.body(content)
.map_into_left_body()
})
.unwrap_or_else(|| {
tracing::debug!("File not found: {filename}");
HttpResponse::NotFound()
.body("Not found")
.map_into_right_body()
})
}
}
#[get("/logo.png")]
#[tracing::instrument(skip_all, name = "logo.png")]
async fn logo_png(req: HttpRequest) -> HttpResponse<EitherBody<BoxBody>> {
serve_embedded_or_fs(&req, "logo.png", mime::IMAGE_PNG).await
}

Copilot uses AI. Check for mistakes.
@SpaceFox1 SpaceFox1 merged commit 9caae0f into main Apr 20, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants