6 Commits

Author SHA1 Message Date
Priec
a169999ff1 fixed title
Some checks failed
CI / Check Style (push) Has been cancelled
CI / Run Clippy (push) Has been cancelled
CI / Run Tests (push) Has been cancelled
2026-05-20 20:42:51 +02:00
Priec
8b175557dc fixing ceo n performance 2026-05-20 20:42:26 +02:00
5e31f00b77 fixing name
Some checks failed
CI / Check Style (push) Has been cancelled
CI / Run Clippy (push) Has been cancelled
CI / Run Tests (push) Has been cancelled
2026-05-20 12:15:37 +00:00
8f89423994 prod setup
Some checks failed
CI / Check Style (push) Has been cancelled
CI / Run Clippy (push) Has been cancelled
CI / Run Tests (push) Has been cancelled
2026-05-20 11:01:23 +00:00
Priec
f92cb1f134 caddyfile
Some checks failed
CI / Check Style (push) Has been cancelled
CI / Run Clippy (push) Has been cancelled
CI / Run Tests (push) Has been cancelled
2026-05-20 11:24:06 +02:00
Priec
a385b0540d prod setup is now fully ready 2026-05-20 11:14:42 +02:00
38 changed files with 251 additions and 48 deletions

19
.dockerignore Normal file
View File

@@ -0,0 +1,19 @@
target
node_modules
Dockerfile
.dockerignore
docker-compose.prod.yml
Makefile
Caddyfile
.git
.gitignore
.env
.env.*
*.sqlite
*.sqlite-*
uploads
*report.html

26
.env.production.example Normal file
View File

@@ -0,0 +1,26 @@
CONTAINER_NAME=universal-web
REVERSE_PROXY_NETWORK=
UPLOADS_VOLUME_NAME=universal_web_uploads
APP_HOST=https://gitara.farmeris.sk
PORT=5150
SERVER_BINDING=0.0.0.0
DATABASE_URL=
JWT_SECRET=
ADMIN_EMAIL=
ADMIN_PASSWORD=
ADMIN_NAME=Admin
UPLOADS_ROOT=data/uploads
LOG_LEVEL=info
LOG_FORMAT=compact
MAILER_STUB=true
SMTP_ENABLE=false
SMTP_HOST=localhost
SMTP_PORT=1025
SMTP_SECURE=false
SMTP_USER=
SMTP_PASSWORD=

2
.gitignore vendored
View File

@@ -1,6 +1,5 @@
**/config/local.yaml **/config/local.yaml
**/config/*.local.yaml **/config/*.local.yaml
**/config/production.yaml
# Generated by Cargo # Generated by Cargo
# will have compiled files and executables # will have compiled files and executables
@@ -22,3 +21,4 @@ target/
.env.production .env.production
uploads/ uploads/
*.report.html *.report.html
favicon_io.zip

14
Caddyfile Normal file
View File

@@ -0,0 +1,14 @@
gitara.farmeris.sk {
encode gzip
@static path /static/*
header @static Cache-Control "public, max-age=2592000"
rewrite /favicon.ico /static/favicon/favicon.ico
reverse_proxy gitara-web:5150
}
www.gitara.farmeris.sk {
redir https://gitara.farmeris.sk{uri} permanent
}

60
Cargo.lock generated
View File

@@ -1523,6 +1523,36 @@ dependencies = [
"wasip3", "wasip3",
] ]
[[package]]
name = "gitara_web"
version = "0.1.0"
dependencies = [
"async-trait",
"axum",
"axum-extra",
"bytes",
"chrono",
"dotenvy",
"fluent-templates",
"include_dir",
"insta",
"loco-rs",
"migration",
"regex",
"rstest",
"sea-orm",
"serde",
"serde_json",
"serial_test",
"time",
"tokio",
"tracing",
"tracing-subscriber",
"unic-langid",
"uuid",
"validator",
]
[[package]] [[package]]
name = "glob" name = "glob"
version = "0.3.3" version = "0.3.3"
@@ -5052,36 +5082,6 @@ version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
[[package]]
name = "universal_web"
version = "0.1.0"
dependencies = [
"async-trait",
"axum",
"axum-extra",
"bytes",
"chrono",
"dotenvy",
"fluent-templates",
"include_dir",
"insta",
"loco-rs",
"migration",
"regex",
"rstest",
"sea-orm",
"serde",
"serde_json",
"serial_test",
"time",
"tokio",
"tracing",
"tracing-subscriber",
"unic-langid",
"uuid",
"validator",
]
[[package]] [[package]]
name = "unsafe-libyaml" name = "unsafe-libyaml"
version = "0.2.11" version = "0.2.11"

View File

@@ -1,11 +1,11 @@
[workspace] [workspace]
[package] [package]
name = "universal_web" name = "gitara_web"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2021"
publish = false publish = false
default-run = "universal_web-cli" default-run = "gitara_web-cli"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -45,7 +45,7 @@ axum-extra = { version = "0.10", features = ["form"] }
bytes = { version = "1" } bytes = { version = "1" }
[[bin]] [[bin]]
name = "universal_web-cli" name = "gitara_web-cli"
path = "src/bin/main.rs" path = "src/bin/main.rs"
required-features = [] required-features = []

25
Dockerfile Normal file
View File

@@ -0,0 +1,25 @@
FROM rust:1-slim-bookworm AS builder
WORKDIR /usr/src
COPY . .
RUN cargo build --release --bin gitara_web-cli
FROM debian:bookworm-slim
RUN apt-get update \
&& apt-get install -y --no-install-recommends ca-certificates curl \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /usr/app
COPY --from=builder /usr/src/assets assets
COPY --from=builder /usr/src/config config
COPY --from=builder /usr/src/target/release/gitara_web-cli gitara_web-cli
ENV LOCO_ENV=production
EXPOSE 5150
ENTRYPOINT ["/usr/app/gitara_web-cli"]
CMD ["start"]

20
Makefile Normal file
View File

@@ -0,0 +1,20 @@
COMPOSE = docker compose -f docker-compose.prod.yml --env-file .env.production
.PHONY: up down restart logs build ps
up:
$(COMPOSE) up -d --build
down:
$(COMPOSE) down
restart: down up
logs:
$(COMPOSE) logs -f --tail=100
build:
$(COMPOSE) build --no-cache
ps:
$(COMPOSE) ps

View File

@@ -1,4 +1,4 @@
brand = Universal Web brand = My guitar
hello-world = Hello world! hello-world = Hello world!
meta-description = A guitar player's personal site. News, blog posts, albums, and songs in one place. meta-description = A guitar player's personal site. News, blog posts, albums, and songs in one place.
nav-home = Home nav-home = Home

View File

@@ -1,4 +1,4 @@
brand = Universal Web brand = My guitar
hello-world = Hello world! hello-world = Hello world!
meta-description = A guitar player's personal site. News, blog posts, albums, and songs in one place. meta-description = A guitar player's personal site. News, blog posts, albums, and songs in one place.
nav-home = Home nav-home = Home

View File

@@ -1,4 +1,4 @@
brand = Universal Web brand = Moja gitara
hello-world = Ahoj svet! hello-world = Ahoj svet!
meta-description = Osobná stránka gitaristu. Novinky, blog, albumy a skladby na jednom mieste. meta-description = Osobná stránka gitaristu. Novinky, blog, albumy a skladby na jednom mieste.
nav-home = Domov nav-home = Domov

View File

@@ -142,7 +142,7 @@ body {
.t-green { color: oklch(var(--su)); } .t-green { color: oklch(var(--su)); }
.t-yellow { color: oklch(var(--wa)); } .t-yellow { color: oklch(var(--wa)); }
.t-red { color: oklch(var(--er)); } .t-red { color: oklch(var(--er)); }
.t-dim { color: oklch(var(--bc) / 0.5); } .t-dim { color: oklch(var(--bc) / 0.75); }
/* --- window titlebar (the header) -------------------------- */ /* --- window titlebar (the header) -------------------------- */
.term-titlebar { .term-titlebar {
@@ -230,7 +230,7 @@ body {
line-height: 1.15; line-height: 1.15;
color: oklch(var(--p)); color: oklch(var(--p));
} }
.term-sub { margin-top: 0.2rem; font-size: 0.85rem; color: oklch(var(--bc) / 0.55); } .term-sub { margin-top: 0.2rem; font-size: 0.85rem; color: oklch(var(--bc) / 0.8); }
.term-cmd-actions { display: flex; gap: 0.5rem; flex-wrap: wrap; } .term-cmd-actions { display: flex; gap: 0.5rem; flex-wrap: wrap; }
/* --- responsive card grid ---------------------------------- */ /* --- responsive card grid ---------------------------------- */

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 601 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -0,0 +1 @@
{"name":"","short_name":"","icons":[{"src":"/static/favicon/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/static/favicon/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}

File diff suppressed because one or more lines are too long

View File

@@ -4,6 +4,12 @@
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>{% block title %}{{ t(key="admin-title", lang=lang | default(value='sk')) }}{% endblock title %}</title> <title>{% block title %}{{ t(key="admin-title", lang=lang | default(value='sk')) }}{% endblock title %}</title>
<meta name="description" content="{% block meta_description %}{{ t(key="meta-description", lang=lang | default(value='sk')) }}{% endblock meta_description %}">
<link rel="icon" type="image/x-icon" href="/static/favicon/favicon.ico">
<link rel="icon" type="image/png" sizes="32x32" href="/static/favicon/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/static/favicon/favicon-16x16.png">
<link rel="apple-touch-icon" sizes="180x180" href="/static/favicon/apple-touch-icon.png">
<link rel="manifest" href="/static/favicon/site.webmanifest">
<script> <script>
function applyTheme(t) { function applyTheme(t) {
var dark = t === 'dark' var dark = t === 'dark'
@@ -39,7 +45,7 @@
<link href="/static/css/app.css" rel="stylesheet" type="text/css"> <link href="/static/css/app.css" rel="stylesheet" type="text/css">
{% block head %}{% endblock head %} {% block head %}{% endblock head %}
<link href="/static/css/theme.css" rel="stylesheet" type="text/css"> <link href="/static/css/theme.css" rel="stylesheet" type="text/css">
<script src="https://unpkg.com/htmx.org@1.9.12"></script> <script src="/static/vendor/htmx/htmx-1.9.12.min.js"></script>
<style> <style>
@media (min-width: 768px) { @media (min-width: 768px) {
.nav-menu { flex-direction: row; } .nav-menu { flex-direction: row; }

View File

@@ -4,6 +4,12 @@
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>{% block title %}{{ t(key="brand", lang=lang | default(value='sk')) }}{% endblock title %}</title> <title>{% block title %}{{ t(key="brand", lang=lang | default(value='sk')) }}{% endblock title %}</title>
<meta name="description" content="{% block meta_description %}{{ t(key="meta-description", lang=lang | default(value='sk')) }}{% endblock meta_description %}">
<link rel="icon" type="image/x-icon" href="/static/favicon/favicon.ico">
<link rel="icon" type="image/png" sizes="32x32" href="/static/favicon/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/static/favicon/favicon-16x16.png">
<link rel="apple-touch-icon" sizes="180x180" href="/static/favicon/apple-touch-icon.png">
<link rel="manifest" href="/static/favicon/site.webmanifest">
<script> <script>
function applyTheme(t) { function applyTheme(t) {
var dark = t === 'dark' var dark = t === 'dark'
@@ -222,7 +228,7 @@
</script> </script>
<link href="/static/css/app.css" rel="stylesheet" type="text/css"> <link href="/static/css/app.css" rel="stylesheet" type="text/css">
<link href="/static/css/theme.css" rel="stylesheet" type="text/css"> <link href="/static/css/theme.css" rel="stylesheet" type="text/css">
<script src="https://unpkg.com/htmx.org@1.9.12"></script> <script src="/static/vendor/htmx/htmx-1.9.12.min.js"></script>
<style> <style>
@media (min-width: 768px) { @media (min-width: 768px) {
.nav-menu { flex-direction: row; } .nav-menu { flex-direction: row; }

View File

@@ -71,7 +71,7 @@ mailer:
# Database Configuration # Database Configuration
database: database:
# Database connection URI # Database connection URI
uri: {{ get_env(name="DATABASE_URL", default="postgres://uni_loco_web_user:3@localhost:5432/universal_web_development") }} uri: {{ get_env(name="DATABASE_URL", default="postgres://uni_loco_web_user:3@localhost:5432/gitara_web_development") }}
# When enabled, the sql query will be logged. # When enabled, the sql query will be logged.
enable_logging: false enable_logging: false
# Set the timeout duration when acquiring a connection. # Set the timeout duration when acquiring a connection.

57
config/production.yaml Normal file
View File

@@ -0,0 +1,57 @@
logger:
enable: true
pretty_backtrace: false
level: "{{ get_env(name="LOG_LEVEL", default="info") }}"
format: "{{ get_env(name="LOG_FORMAT", default="compact") }}"
server:
port: {{ get_env(name="PORT", default="5150") }}
binding: "{{ get_env(name="SERVER_BINDING", default="0.0.0.0") }}"
host: "{{ get_env(name="APP_HOST") }}"
middlewares:
static:
enable: true
must_exist: true
precompressed: false
folder:
uri: "/static"
path: "assets/static"
fallback: "assets/static/404.html"
workers:
mode: "{{ get_env(name="WORKER_MODE", default="BackgroundAsync") }}"
mailer:
stub: {{ get_env(name="MAILER_STUB", default="true") }}
smtp:
enable: {{ get_env(name="SMTP_ENABLE", default="false") }}
host: "{{ get_env(name="SMTP_HOST", default="localhost") }}"
port: {{ get_env(name="SMTP_PORT", default="1025") }}
secure: {{ get_env(name="SMTP_SECURE", default="false") }}
auth:
user: "{{ get_env(name="SMTP_USER", default="") }}"
password: "{{ get_env(name="SMTP_PASSWORD", default="") }}"
database:
uri: "{{ get_env(name="DATABASE_URL") }}"
enable_logging: {{ get_env(name="DB_ENABLE_LOGGING", default="false") }}
connect_timeout: {{ get_env(name="DB_CONNECT_TIMEOUT", default="500") }}
idle_timeout: {{ get_env(name="DB_IDLE_TIMEOUT", default="500") }}
min_connections: {{ get_env(name="DB_MIN_CONNECTIONS", default="1") }}
max_connections: {{ get_env(name="DB_MAX_CONNECTIONS", default="5") }}
auto_migrate: {{ get_env(name="DB_AUTO_MIGRATE", default="true") }}
dangerously_truncate: false
dangerously_recreate: false
auth:
jwt:
location:
- from: Cookie
name: auth_token
- from: Bearer
secret: "{{ get_env(name="JWT_SECRET") }}"
expiration: {{ get_env(name="JWT_EXPIRATION", default="604800") }}
settings:
admin_email: "{{ get_env(name="ADMIN_EMAIL", default="") }}"
uploads_root: "{{ get_env(name="UPLOADS_ROOT", default="data/uploads") }}"

View File

@@ -68,7 +68,7 @@ mailer:
# Database Configuration # Database Configuration
database: database:
# Database connection URI # Database connection URI
uri: {{ get_env(name="DATABASE_URL", default="postgres://uni_loco_web_user:3@localhost:5432/universal_web_test") }} uri: {{ get_env(name="DATABASE_URL", default="postgres://uni_loco_web_user:3@localhost:5432/gitara_web_test") }}
# When enabled, the sql query will be logged. # When enabled, the sql query will be logged.
enable_logging: false enable_logging: false
# Set the timeout duration when acquiring a connection. # Set the timeout duration when acquiring a connection.

27
docker-compose.prod.yml Normal file
View File

@@ -0,0 +1,27 @@
services:
universal-web:
container_name: gitara-web
build:
context: .
dockerfile: Dockerfile
env_file:
- .env.production
volumes:
- gitara_web_data:/usr/app/data
networks:
- gitara-net
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "curl -fsS http://localhost:5150/_ping"]
interval: 30s
timeout: 5s
retries: 3
start_period: 20s
networks:
gitara-net:
external: true
volumes:
gitara_web_data:
name: gitara_web_data

View File

@@ -1,6 +1,6 @@
#[allow(unused_imports)] #[allow(unused_imports)]
use loco_rs::{cli::playground, prelude::*}; use loco_rs::{cli::playground, prelude::*};
use universal_web::app::App; use gitara_web::app::App;
#[tokio::main] #[tokio::main]
async fn main() -> loco_rs::Result<()> { async fn main() -> loco_rs::Result<()> {

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

BIN
favicon/favicon-16x16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 601 B

BIN
favicon/favicon-32x32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
favicon/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

1
favicon/site.webmanifest Normal file
View File

@@ -0,0 +1 @@
{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}

View File

@@ -1,6 +1,6 @@
use loco_rs::cli; use loco_rs::cli;
use migration::Migrator; use migration::Migrator;
use universal_web::app::App; use gitara_web::app::App;
#[tokio::main] #[tokio::main]
async fn main() -> loco_rs::Result<()> { async fn main() -> loco_rs::Result<()> {

View File

@@ -3,7 +3,7 @@ use insta::assert_debug_snapshot;
use loco_rs::testing::prelude::*; use loco_rs::testing::prelude::*;
use sea_orm::{ActiveModelTrait, ActiveValue, IntoActiveModel}; use sea_orm::{ActiveModelTrait, ActiveValue, IntoActiveModel};
use serial_test::serial; use serial_test::serial;
use universal_web::{ use gitara_web::{
app::App, app::App,
models::users::{self, Model, RegisterParams}, models::users::{self, Model, RegisterParams},
}; };

View File

@@ -2,7 +2,7 @@ use insta::{assert_debug_snapshot, with_settings};
use loco_rs::testing::prelude::*; use loco_rs::testing::prelude::*;
use rstest::rstest; use rstest::rstest;
use serial_test::serial; use serial_test::serial;
use universal_web::{app::App, models::users}; use gitara_web::{app::App, models::users};
use super::prepare_data; use super::prepare_data;

View File

@@ -1,6 +1,6 @@
use axum::http::{HeaderName, HeaderValue}; use axum::http::{HeaderName, HeaderValue};
use loco_rs::{app::AppContext, TestServer}; use loco_rs::{app::AppContext, TestServer};
use universal_web::{models::users, views::auth::LoginResponse}; use gitara_web::{models::users, views::auth::LoginResponse};
const USER_EMAIL: &str = "test@loco.com"; const USER_EMAIL: &str = "test@loco.com";
const USER_PASSWORD: &str = "1234"; const USER_PASSWORD: &str = "1234";