Add emotes HTML page at /, health endpoint, Dockerfile, and Docker workflow

This commit is contained in:
Ganonmaster
2026-04-07 00:21:42 +00:00
committed by GitHub
parent 55f42ac1f3
commit 5d9fc26011
7 changed files with 183 additions and 3 deletions
+47
View File
@@ -0,0 +1,47 @@
name: Docker
on:
push:
tags:
- "v*"
jobs:
docker:
name: Build and push Docker image
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Docker registry
uses: docker/login-action@v3
with:
registry: ${{ vars.DOCKER_REGISTRY }}
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ vars.DOCKER_REGISTRY }}/${{ vars.DOCKER_IMAGE }}
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
+33
View File
@@ -0,0 +1,33 @@
FROM rust:1.85-bookworm AS builder
WORKDIR /app
# Copy manifests first for better layer caching
COPY Cargo.toml Cargo.lock ./
# Create a dummy main to cache dependency compilation
RUN mkdir src && echo "fn main() {}" > src/main.rs && \
mkdir migrations && touch migrations/.keep && \
echo "fn main() {}" > build.rs && \
cargo build --release && \
rm -rf src build.rs migrations
# Copy real source code
COPY . .
# Touch main.rs so cargo knows it changed
RUN touch src/main.rs && cargo build --release
# Runtime stage
FROM debian:bookworm-slim
RUN apt-get update && \
apt-get install -y --no-install-recommends ca-certificates && \
rm -rf /var/lib/apt/lists/*
COPY --from=builder /app/target/release/mikebase /usr/local/bin/mikebase
COPY --from=builder /app/config.example.toml /etc/mikebase/config.example.toml
EXPOSE 3000
CMD ["mikebase"]
+1
View File
@@ -54,6 +54,7 @@ async fn main() {
let app = Router::new() let app = Router::new()
.route("/", get(routes::emotes::root)) .route("/", get(routes::emotes::root))
.route("/health", get(routes::health::health))
.route("/version", get(routes::version::version)) .route("/version", get(routes::version::version))
.route("/json", get(routes::emotes::list_emotes)) .route("/json", get(routes::emotes::list_emotes))
.route("/emotes", post(routes::emotes::create_emote)) .route("/emotes", post(routes::emotes::create_emote))
+3 -3
View File
@@ -1,7 +1,7 @@
use axum::{ use axum::{
extract::{Multipart, Path, State}, extract::{Multipart, Path, State},
http::StatusCode, http::StatusCode,
response::{IntoResponse, Json}, response::{Html, IntoResponse, Json},
}; };
use serde_json::json; use serde_json::json;
@@ -11,9 +11,9 @@ use crate::{
}; };
/// GET / /// GET /
/// Returns a simple health-check message. /// Serves an HTML page that dynamically loads and displays emotes from /json.
pub async fn root() -> impl IntoResponse { pub async fn root() -> impl IntoResponse {
Json(json!({"status": "ok", "message": "mikebase server is running"})) Html(include_str!("../templates/index.html"))
} }
/// GET /json /// GET /json
+8
View File
@@ -0,0 +1,8 @@
use axum::response::{IntoResponse, Json};
use serde_json::json;
/// GET /health
/// Returns a simple health-check response.
pub async fn health() -> impl IntoResponse {
Json(json!({"status": "ok"}))
}
+1
View File
@@ -1,2 +1,3 @@
pub mod emotes; pub mod emotes;
pub mod health;
pub mod version; pub mod version;
+90
View File
@@ -0,0 +1,90 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>MIKEBASE</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: system-ui, -apple-system, sans-serif; background: #1a1a2e; color: #eee; }
nav { height: 60px; display: flex; align-items: center; padding: 0 20px; background: #16213e; }
nav .logo { font-size: 1.4rem; letter-spacing: 0.05em; color: #fff; }
nav .logo strong { font-weight: 700; }
nav .logo span { font-weight: 300; }
.grid { display: flex; flex-wrap: wrap; gap: 12px; padding: 20px; justify-content: center; }
.emote { display: flex; flex-direction: column; align-items: center; cursor: pointer; padding: 8px; border-radius: 8px; transition: background 0.15s; }
.emote:hover { background: rgba(255,255,255,0.08); }
.emote img { width: 64px; height: 64px; object-fit: contain; }
.emote .code { font-size: 0.7rem; margin-top: 4px; color: #aaa; max-width: 80px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; text-align: center; }
.toast { position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%); background: #0f3460; color: #fff; padding: 8px 16px; border-radius: 6px; font-size: 0.85rem; opacity: 0; transition: opacity 0.3s; pointer-events: none; }
.toast.show { opacity: 1; }
</style>
</head>
<body>
<nav>
<div class="logo"><strong>MIKE</strong><span>BASE</span></div>
</nav>
<div class="grid" id="grid"></div>
<div class="toast" id="toast">Copied!</div>
<script>
(function(){
var toast = document.getElementById('toast');
var timer;
function showToast(text) {
toast.textContent = text;
toast.classList.add('show');
clearTimeout(timer);
timer = setTimeout(function(){ toast.classList.remove('show'); }, 1500);
}
function copyCode(code) {
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(code).then(function(){
showToast('Copied ' + code);
});
} else {
var ta = document.createElement('textarea');
ta.value = code;
ta.style.position = 'fixed';
ta.style.opacity = '0';
document.body.appendChild(ta);
ta.select();
document.execCommand('copy');
document.body.removeChild(ta);
showToast('Copied ' + code);
}
}
fetch('/json')
.then(function(r){ return r.json(); })
.then(function(data){
var grid = document.getElementById('grid');
var emotes = data.emotes || [];
emotes.forEach(function(emote){
var code = ':' + emote.name + ':';
var card = document.createElement('div');
card.className = 'emote';
card.title = code;
card.addEventListener('click', function(){ copyCode(code); });
var img = document.createElement('img');
img.alt = emote.name;
img.loading = 'lazy';
img.width = 64;
img.height = 64;
img.src = emote.url;
var span = document.createElement('span');
span.className = 'code';
span.textContent = code;
card.appendChild(img);
card.appendChild(span);
grid.appendChild(card);
});
});
})();
</script>
</body>
</html>