This commit is contained in:
Ondrej Vlach 2023-12-03 15:46:05 +01:00
parent a5e0857285
commit 2b133a9f3f
Signed by: ovlach
GPG Key ID: 4FF1A23B4914DE70
13 changed files with 779 additions and 152 deletions

View File

@ -0,0 +1,31 @@
name: release
run-name: release
on:
push:
branches:
- master
jobs:
tests:
uses: ./.gitea/workflows/test.yaml # use the callable tests job to run tests
secrets: inherit
deploy:
name: deploy
runs-on: ubuntu-latest
needs: [tests] # require tests to pass before deploy runs
container:
image: ovlach/docker-gitea:v0.0.2-alpha
steps:
- uses: https://github.com/actions/checkout.git@v4
- name: Login to git.nanobyte.cz
uses: https://github.com/docker/login-action@v3
with:
username: ${{ secrets.DEPLOYMENT_PACKAGE }}
password: ${{ secrets.DEPLOYMENT_PACKAGE }}
registry: git.nanobyte.cz
- name: Get short hash from git repository
id: git
run: echo "::set-output name=short_hash::$(git rev-parse --short HEAD)"
- name: Build and push docker
run: |
docker build . -t git.nanobyte.cz/$GITHUB_REPOSITORY_OWNER/ovlach_backend:${{steps.git.outputs.short_hash}} --build-arg GITEA_TOKEN="Bearer ${{secrets.DEPLOYMENT_PACKAGE}}" && \
docker push git.nanobyte.cz/$GITHUB_REPOSITORY_OWNER/ovlach_backend:${{steps.git.outputs.short_hash}}

View File

@ -2,52 +2,66 @@ name: test
run-name: test
on:
workflow_call:
secrets:
DEPLOYMENT_PACKAGE:
description: 'needed for checkout depenediencies'
required: true
push:
branches:
- '*'
jobs:
clippy:
runs-on: ubuntu-latest
container:
image: git.nanobyte.cz/nanobyte/nano-rust-builder:1.74.5f84654
steps:
- uses: https://github.com/actions/checkout.git@v4
- name: Hash of Cargo.lock
id: get-hash
run: echo "::set-output name=hash::$(md5sum Cargo.lock | awk '{ print $1; }')"
- uses: https://gitea.com/wolfogre/cache/restore@v3
id: cache
with:
key: "${{github.repository}}-cache-cargo-clippy-v4-${{ steps.get-hash.outputs.hash }}"
path: |
.cache
target
key: ${{github.repository}}-cache-cargo-clippy-v4-${{ hashFiles('Cargo.lock') }}
restore-keys: ${{github.repository}}-cache-cargo-clippy-v4
- uses: nanobyte-public/rust-action@master
with:
args: CARGO_HOME=./.cache cargo clippy -- -Dwarnings
args: CARGO_HOME=./.cache cargo-nan \"Bearer ${{secrets.DEPLOYMENT_PACKAGE}}\" cargo clippy -- -Dwarnings
- uses: https://gitea.com/wolfogre/cache/save@v3
if: steps.cache.outputs.cache-hit != 'true'
with:
path: |
.cache
target
key: ${{github.repository}}-cache-cargo-clippy-v4-${{ hashFiles('Cargo.lock') }}
key: "${{github.repository}}-cache-cargo-clippy-v4-${{ steps.get-hash.outputs.hash }}"
test:
container:
image: git.nanobyte.cz/nanobyte/nano-rust-builder:1.74.5f84654
runs-on: ubuntu-latest
steps:
- uses: https://github.com/actions/checkout.git@v4
- name: Hash of Cargo.lock
id: get-hash
run: echo "::set-output name=hash::$(md5sum Cargo.lock | awk '{ print $1; '})"
- uses: https://gitea.com/wolfogre/cache/restore@v3
id: cache
with:
path: |
.cache
target
key: ${{github.repository}}-cache-cargo-test-v4-${{ hashFiles('Cargo.lock') }}
restore-keys: ${{github.repository}}-cache-cargo-test-v4
key: "${{github.repository}}-cache-test-clippy-v4-${{ steps.get-hash.outputs.hash }}"
restore-keys: ${{github.repository}}-cache-test-test-v4
- uses: nanobyte-public/rust-action@master
with:
args: |
CARGO_HOME=./.cache cargo test
CARGO_HOME=./.cache cargo-nan \"Bearer ${{secrets.DEPLOYMENT_PACKAGE}}\" cargo test
- uses: https://gitea.com/wolfogre/cache/save@v3
if: steps.cache.outputs.cache-hit != 'true'
with:
path: |
.cache
target
key: ${{github.repository}}-cache-cargo-test-v4-${{ hashFiles('Cargo.lock') }}
key: "${{github.repository}}-cache-test-clippy-v4-${{ steps.get-hash.outputs.hash }}"

524
Cargo.lock generated
View File

@ -41,6 +41,12 @@ dependencies = [
"libc",
]
[[package]]
name = "anyhow"
version = "1.0.75"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
[[package]]
name = "async-stream"
version = "0.3.5"
@ -60,7 +66,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.39",
]
[[package]]
@ -71,7 +77,7 @@ checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.39",
]
[[package]]
@ -95,6 +101,51 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "axum"
version = "0.6.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf"
dependencies = [
"async-trait",
"axum-core",
"bitflags 1.3.2",
"bytes",
"futures-util",
"http",
"http-body",
"hyper",
"itoa",
"matchit",
"memchr",
"mime",
"percent-encoding",
"pin-project-lite",
"rustversion",
"serde",
"sync_wrapper",
"tower",
"tower-layer",
"tower-service",
]
[[package]]
name = "axum-core"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c"
dependencies = [
"async-trait",
"bytes",
"futures-util",
"http",
"http-body",
"mime",
"rustversion",
"tower-layer",
"tower-service",
]
[[package]]
name = "backtrace"
version = "0.3.69"
@ -362,7 +413,7 @@ dependencies = [
"proc-macro2",
"proc-macro2-diagnostics",
"quote",
"syn",
"syn 2.0.39",
]
[[package]]
@ -383,7 +434,7 @@ checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.39",
]
[[package]]
@ -625,7 +676,7 @@ checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.39",
]
[[package]]
@ -681,6 +732,16 @@ dependencies = [
"version_check",
]
[[package]]
name = "gethostname"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0176e0459c2e4a1fe232f984bca6890e681076abb9934f6cea7c326f3fc47818"
dependencies = [
"libc",
"windows-targets",
]
[[package]]
name = "getrandom"
version = "0.2.11"
@ -740,13 +801,19 @@ dependencies = [
"futures-sink",
"futures-util",
"http",
"indexmap",
"indexmap 2.1.0",
"slab",
"tokio",
"tokio-util",
"tracing",
]
[[package]]
name = "hashbrown"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
[[package]]
name = "hashbrown"
version = "0.14.3"
@ -832,6 +899,18 @@ dependencies = [
"want",
]
[[package]]
name = "hyper-timeout"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1"
dependencies = [
"hyper",
"pin-project-lite",
"tokio",
"tokio-io-timeout",
]
[[package]]
name = "hyper-tls"
version = "0.5.0"
@ -894,6 +973,16 @@ dependencies = [
"winapi-util",
]
[[package]]
name = "indexmap"
version = "1.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
dependencies = [
"autocfg",
"hashbrown 0.12.3",
]
[[package]]
name = "indexmap"
version = "2.1.0"
@ -901,7 +990,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f"
dependencies = [
"equivalent",
"hashbrown",
"hashbrown 0.14.3",
"serde",
]
@ -967,6 +1056,15 @@ dependencies = [
"windows-sys",
]
[[package]]
name = "itertools"
version = "0.10.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
dependencies = [
"either",
]
[[package]]
name = "itoa"
version = "1.0.9"
@ -1066,6 +1164,12 @@ dependencies = [
"regex-automata 0.1.10",
]
[[package]]
name = "matchit"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94"
[[package]]
name = "memchr"
version = "2.6.4"
@ -1128,18 +1232,46 @@ dependencies = [
"version_check",
]
[[package]]
name = "nanobyte_opentelemetry"
version = "0.2.3"
source = "sparse+https://git.nanobyte.cz/api/packages/nanobyte/cargo/"
checksum = "053fac4ff7f3cab0a088383bc68774e402c07c92d19478bcbb51d1d49a5d42bf"
dependencies = [
"gethostname",
"opentelemetry",
"opentelemetry-appender-tracing",
"opentelemetry-otlp",
"opentelemetry-semantic-conventions",
"opentelemetry-stdout",
"opentelemetry_sdk",
"reqwest",
"rocket",
"tracing",
"tracing-appender",
"tracing-core",
"tracing-log",
"tracing-opentelemetry",
"tracing-subscriber",
"uuid",
"yansi 0.5.1",
]
[[package]]
name = "nanobyte_tera"
version = "0.1.0"
source = "git+https://glpat-Us_EdFTzQLv4shViQXi_:glpat-Us_EdFTzQLv4shViQXi_@gitlab.nanobyte.cz/tools/nanobyte_tera.git?branch=master#75c20a9806663ca04c6f8a7afff64d7b5906d113"
version = "0.2.0"
source = "sparse+https://git.nanobyte.cz/api/packages/nanobyte/cargo/"
checksum = "6fa686074d8273526885446e3c7f4a6f35affab0dfcbba55c9b1aca6efae7c67"
dependencies = [
"chrono",
"fluent-bundle",
"fluent-resmgr",
"log",
"ovlach_data",
"rocket_dyn_templates",
"serde",
"serde_json",
"sha256",
"tracing",
"unic-langid",
]
@ -1256,7 +1388,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.39",
]
[[package]]
@ -1277,6 +1409,125 @@ dependencies = [
"vcpkg",
]
[[package]]
name = "opentelemetry"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e32339a5dc40459130b3bd269e9892439f55b33e772d2a9d402a789baaf4e8a"
dependencies = [
"futures-core",
"futures-sink",
"indexmap 2.1.0",
"js-sys",
"once_cell",
"pin-project-lite",
"thiserror",
"urlencoding",
]
[[package]]
name = "opentelemetry-appender-tracing"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12c4bd073648dae8ac45cfc81588d74b3dc5f334119ac08567ddcbfe16f2d809"
dependencies = [
"once_cell",
"opentelemetry",
"opentelemetry_sdk",
"tracing",
"tracing-core",
"tracing-subscriber",
]
[[package]]
name = "opentelemetry-otlp"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f24cda83b20ed2433c68241f918d0f6fdec8b1d43b7a9590ab4420c5095ca930"
dependencies = [
"async-trait",
"futures-core",
"http",
"opentelemetry",
"opentelemetry-proto",
"opentelemetry-semantic-conventions",
"opentelemetry_sdk",
"prost",
"thiserror",
"tokio",
"tonic",
]
[[package]]
name = "opentelemetry-proto"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2e155ce5cc812ea3d1dffbd1539aed653de4bf4882d60e6e04dcf0901d674e1"
dependencies = [
"opentelemetry",
"opentelemetry_sdk",
"prost",
"tonic",
]
[[package]]
name = "opentelemetry-semantic-conventions"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f5774f1ef1f982ef2a447f6ee04ec383981a3ab99c8e77a1a7b30182e65bbc84"
dependencies = [
"opentelemetry",
]
[[package]]
name = "opentelemetry-stdout"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c13b2df4cd59c176099ac82806725ba340c8fa7b1a7004c0912daad30470f63e"
dependencies = [
"async-trait",
"chrono",
"futures-util",
"opentelemetry",
"opentelemetry_sdk",
"ordered-float",
"serde",
"serde_json",
"thiserror",
]
[[package]]
name = "opentelemetry_sdk"
version = "0.21.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "968ba3f2ca03e90e5187f5e4f46c791ef7f2c163ae87789c8ce5f5ca3b7b7de5"
dependencies = [
"async-trait",
"crossbeam-channel",
"futures-channel",
"futures-executor",
"futures-util",
"glob",
"once_cell",
"opentelemetry",
"ordered-float",
"percent-encoding",
"rand",
"serde_json",
"thiserror",
"tokio",
"tokio-stream",
]
[[package]]
name = "ordered-float"
version = "4.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "536900a8093134cf9ccf00a27deb3532421099e958d9dd431135d0c7543ca1e8"
dependencies = [
"num-traits",
]
[[package]]
name = "overload"
version = "0.1.1"
@ -1285,8 +1536,9 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
[[package]]
name = "ovlach_data"
version = "0.1.0"
source = "git+https://glpat-Ju_qUN9Yh8qa5rEnd6T7:glpat-Ju_qUN9Yh8qa5rEnd6T7@gitlab.nanobyte.cz/ondrej/ov-site-api-data.git?branch=add_missing_fields#c13748b039d812d3bf1aaa93312699da7b921868"
version = "0.1.2"
source = "sparse+https://git.nanobyte.cz/api/packages/ovlach/cargo/"
checksum = "98843b3cefbdbf054f13312e82493248501cdd8abb39379ca2546559358c1437"
dependencies = [
"chrono",
"rocket",
@ -1302,6 +1554,7 @@ dependencies = [
"fluent-bundle",
"fluent-resmgr",
"log",
"nanobyte_opentelemetry",
"nanobyte_tera",
"ovlach_data",
"ovlach_tera",
@ -1310,16 +1563,19 @@ dependencies = [
"rocket",
"rocket_dyn_templates",
"serde",
"serde_json",
"serde_yaml",
"sha256",
"tokio",
"tracing",
"unic-langid",
]
[[package]]
name = "ovlach_tera"
version = "0.1.0"
source = "git+https://glpat-_yPuXbEzECyk3FaHudCN:glpat-_yPuXbEzECyk3FaHudCN@gitlab.nanobyte.cz/ondrej/ovlach_tera.git?branch=master#0ab1bbadd76c1336e00b2a38e572048003879ab6"
version = "0.2.0"
source = "sparse+https://git.nanobyte.cz/api/packages/ovlach/cargo/"
checksum = "fd6d3a0c415f223c68db7bd0290405b247f488180e6b96810b3be4acd0b37764"
dependencies = [
"rocket_dyn_templates",
"serde",
@ -1365,7 +1621,7 @@ checksum = "61a386cd715229d399604b50d1361683fe687066f42d56f54be995bc6868f71c"
dependencies = [
"inlinable_string",
"pear_codegen",
"yansi",
"yansi 1.0.0-rc.1",
]
[[package]]
@ -1377,7 +1633,7 @@ dependencies = [
"proc-macro2",
"proc-macro2-diagnostics",
"quote",
"syn",
"syn 2.0.39",
]
[[package]]
@ -1417,7 +1673,7 @@ dependencies = [
"pest_meta",
"proc-macro2",
"quote",
"syn",
"syn 2.0.39",
]
[[package]]
@ -1471,7 +1727,7 @@ dependencies = [
"phf_shared",
"proc-macro2",
"quote",
"syn",
"syn 2.0.39",
]
[[package]]
@ -1483,6 +1739,26 @@ dependencies = [
"siphasher",
]
[[package]]
name = "pin-project"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422"
dependencies = [
"pin-project-internal",
]
[[package]]
name = "pin-project-internal"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.39",
]
[[package]]
name = "pin-project-lite"
version = "0.2.13"
@ -1530,9 +1806,32 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.39",
"version_check",
"yansi",
"yansi 1.0.0-rc.1",
]
[[package]]
name = "prost"
version = "0.11.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b82eaa1d779e9a4bc1c3217db8ffbeabaae1dca241bf70183242128d48681cd"
dependencies = [
"bytes",
"prost-derive",
]
[[package]]
name = "prost-derive"
version = "0.11.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5d2d8d10f3c6ded6da8b05b5fb3b8a5082514344d56c9f871412d29b4e075b4"
dependencies = [
"anyhow",
"itertools",
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
@ -1609,7 +1908,7 @@ checksum = "7f7473c2cfcf90008193dd0e3e16599455cb601a9fce322b5bb55de799664925"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.39",
]
[[package]]
@ -1708,7 +2007,7 @@ dependencies = [
"either",
"figment",
"futures",
"indexmap",
"indexmap 2.1.0",
"log",
"memchr",
"multer",
@ -1729,7 +2028,7 @@ dependencies = [
"tokio-util",
"ubyte",
"version_check",
"yansi",
"yansi 1.0.0-rc.1",
]
[[package]]
@ -1740,11 +2039,11 @@ checksum = "a2238066abf75f21be6cd7dc1a09d5414a671f4246e384e49fe3f8a4936bd04c"
dependencies = [
"devise",
"glob",
"indexmap",
"indexmap 2.1.0",
"proc-macro2",
"quote",
"rocket_http",
"syn",
"syn 2.0.39",
"unicode-xid",
"version_check",
]
@ -1773,7 +2072,7 @@ dependencies = [
"futures",
"http",
"hyper",
"indexmap",
"indexmap 2.1.0",
"log",
"memchr",
"pear",
@ -1911,7 +2210,7 @@ checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.39",
]
[[package]]
@ -1952,7 +2251,7 @@ version = "0.9.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3cc7a1570e38322cfe4154732e5110f887ea57e22b76f4bfd32b5bdd3368666c"
dependencies = [
"indexmap",
"indexmap 2.1.0",
"itoa",
"ryu",
"serde",
@ -2082,6 +2381,17 @@ dependencies = [
"loom",
]
[[package]]
name = "syn"
version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "syn"
version = "2.0.39"
@ -2093,6 +2403,12 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "sync_wrapper"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160"
[[package]]
name = "system-configuration"
version = "0.5.1"
@ -2166,7 +2482,7 @@ checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.39",
]
[[package]]
@ -2251,6 +2567,16 @@ dependencies = [
"windows-sys",
]
[[package]]
name = "tokio-io-timeout"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf"
dependencies = [
"pin-project-lite",
"tokio",
]
[[package]]
name = "tokio-macros"
version = "2.2.0"
@ -2259,7 +2585,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.39",
]
[[package]]
@ -2324,13 +2650,67 @@ version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d34d383cd00a163b4a5b85053df514d45bc330f6de7737edfe0a93311d1eaa03"
dependencies = [
"indexmap",
"indexmap 2.1.0",
"serde",
"serde_spanned",
"toml_datetime",
"winnow",
]
[[package]]
name = "tonic"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3082666a3a6433f7f511c7192923fa1fe07c69332d3c6a2e6bb040b569199d5a"
dependencies = [
"async-trait",
"axum",
"base64",
"bytes",
"futures-core",
"futures-util",
"h2",
"http",
"http-body",
"hyper",
"hyper-timeout",
"percent-encoding",
"pin-project",
"prost",
"tokio",
"tokio-stream",
"tower",
"tower-layer",
"tower-service",
"tracing",
]
[[package]]
name = "tower"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c"
dependencies = [
"futures-core",
"futures-util",
"indexmap 1.9.3",
"pin-project",
"pin-project-lite",
"rand",
"slab",
"tokio",
"tokio-util",
"tower-layer",
"tower-service",
"tracing",
]
[[package]]
name = "tower-layer"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0"
[[package]]
name = "tower-service"
version = "0.3.2"
@ -2348,6 +2728,18 @@ dependencies = [
"tracing-core",
]
[[package]]
name = "tracing-appender"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3566e8ce28cc0a3fe42519fc80e6b4c943cc4c8cef275620eb8dac2d3d4e06cf"
dependencies = [
"crossbeam-channel",
"thiserror",
"time",
"tracing-subscriber",
]
[[package]]
name = "tracing-attributes"
version = "0.1.27"
@ -2356,7 +2748,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.39",
]
[[package]]
@ -2380,6 +2772,34 @@ dependencies = [
"tracing-core",
]
[[package]]
name = "tracing-opentelemetry"
version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c67ac25c5407e7b961fafc6f7e9aa5958fd297aada2d20fa2ae1737357e55596"
dependencies = [
"js-sys",
"once_cell",
"opentelemetry",
"opentelemetry_sdk",
"smallvec",
"tracing",
"tracing-core",
"tracing-log",
"tracing-subscriber",
"web-time",
]
[[package]]
name = "tracing-serde"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc6b213177105856957181934e4920de57730fc69bf42c37ee5bb664d406d9e1"
dependencies = [
"serde",
"tracing-core",
]
[[package]]
name = "tracing-subscriber"
version = "0.3.18"
@ -2390,12 +2810,15 @@ dependencies = [
"nu-ansi-term",
"once_cell",
"regex",
"serde",
"serde_json",
"sharded-slab",
"smallvec",
"thread_local",
"tracing",
"tracing-core",
"tracing-log",
"tracing-serde",
]
[[package]]
@ -2556,6 +2979,21 @@ dependencies = [
"percent-encoding",
]
[[package]]
name = "urlencoding"
version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
[[package]]
name = "uuid"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e395fcf16a7a3d8127ec99782007af141946b4795001f876d54fb0d55978560"
dependencies = [
"getrandom",
]
[[package]]
name = "valuable"
version = "0.1.0"
@ -2620,7 +3058,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
"syn",
"syn 2.0.39",
"wasm-bindgen-shared",
]
@ -2654,7 +3092,7 @@ checksum = "c5353b8dab669f5e10f5bd76df26a9360c748f054f862ff5f3f8aae0c7fb3907"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.39",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
@ -2675,6 +3113,16 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "web-time"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57099a701fb3a8043f993e8228dc24229c7b942e2b009a1b962e54489ba1d3bf"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "winapi"
version = "0.3.9"
@ -2809,6 +3257,12 @@ dependencies = [
"windows-sys",
]
[[package]]
name = "yansi"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec"
[[package]]
name = "yansi"
version = "1.0.0-rc.1"

View File

@ -14,12 +14,17 @@ rocket = { version = "0.5.0", features = ["json"] }
rocket_dyn_templates = { version = "0.1.0", features = ["tera"]}
reqwest = { version = "0.11", features = ["json"] }
tokio = { version = "1", features = ["full"] }
ovlach_data = { git = "https://glpat-Ju_qUN9Yh8qa5rEnd6T7:glpat-Ju_qUN9Yh8qa5rEnd6T7@gitlab.nanobyte.cz/ondrej/ov-site-api-data.git", branch = "add_missing_fields"}
ovlach_data = { version = "0.1.2", registry = "gitea_ovlach"}
nanobyte_opentelemetry = { version = "0.2.3", registry = "gitea_nanobyte", features = ["rocket-reqwest"] }
chrono = "0.4.31"
sha256 = "1.4.0"
fluent-bundle = "0.15.2"
fluent-resmgr = "0.0.6"
unic-langid = "0.9.1"
phf = { version = "0.11.2", features = ["macros"] }
nanobyte_tera = { git = "https://glpat-Us_EdFTzQLv4shViQXi_:glpat-Us_EdFTzQLv4shViQXi_@gitlab.nanobyte.cz/tools/nanobyte_tera.git", branch = "master" }
ovlach_tera = { git = "https://glpat-_yPuXbEzECyk3FaHudCN:glpat-_yPuXbEzECyk3FaHudCN@gitlab.nanobyte.cz/ondrej/ovlach_tera.git", branch = "master" }
nanobyte_tera = { version = "0.2.0", registry = "gitea_nanobyte" }
ovlach_tera = { version="0.2.0", registry="gitea_ovlach" }
tracing = "0.1.40"
[dev-dependencies]
serde_json = "1.0.108"

View File

@ -5,3 +5,4 @@ cv_backend_path = "http://localhost:8002"
[default]
static_route = "http://localhost:8001"
cv_backend_path = "http://localhost:8002"
default_person_name = "ovlach"

View File

@ -4,6 +4,7 @@ use ovlach_tera::entity::lang_entity;
use rocket::{*, fairing::AdHoc};
use rocket_dyn_templates::Template;
use ::serde::Deserialize;
use tools::tera::advanced_filter;
pub mod routes;
pub mod services;
@ -21,6 +22,12 @@ pub struct CVBackendConfig {
cv_backend_path: String,
}
#[derive(Deserialize)]
#[serde(crate = "rocket::serde")]
pub struct DefaultPerson {
default_person_name: String,
}
pub fn rocket_builder() -> Rocket<Build> {
let rocket = rocket::build();
@ -39,6 +46,7 @@ pub fn rocket_builder() -> Rocket<Build> {
engines.tera.register_filter("lang_entity", lang_entity);
engines.tera.register_filter("format_date", get_year); // deprecated
engines.tera.register_filter("get_year", get_year);
engines.tera.register_filter("advanced_filter", advanced_filter);
Ok(())
})
).attach(
@ -46,7 +54,9 @@ pub fn rocket_builder() -> Rocket<Build> {
).attach(
AdHoc::config::<CVBackendConfig>()
).attach(
tools::rocket::RequestTimer
AdHoc::config::<DefaultPerson>()
).attach(
nanobyte_opentelemetry::rocket::TracingFairing::ignite()
).mount("/", routes![
routes::root::index,
routes::root::index_without_lang

View File

@ -1,8 +1,14 @@
use nanobyte_opentelemetry::{install_panic_handler, default_filter_layer, LogLevel};
use ovlach_frontend::rocket_builder;
use rocket::launch;
#[launch]
fn rocket() -> _ {
rocket_builder()
#[rocket::main]
async fn main() {
install_panic_handler();
let _opentelemetry = nanobyte_opentelemetry::init_telemetry(
env!("CARGO_PKG_NAME"),
env!("CARGO_PKG_VERSION"),
&std::env::var("OTLP_ENDPOINT").unwrap_or("".to_string()),
Some(default_filter_layer(LogLevel::DebugWithoutRs))
);
let _ = rocket_builder().launch().await;
}

View File

@ -1,35 +1,45 @@
use std::{thread::sleep, time::Duration};
use log::error;
use ovlach_data::cv::cv::CV;
use rocket::{get, State, response::Redirect, http::Status};
use nanobyte_opentelemetry::rocket::{OtelReqwestClient, TracingSpan};
use nanobyte_tera::l18n::LanguageDescription;
use ovlach_data::cv::data::CV;
use rocket::{get, State, response::Redirect, http::Status, futures::executor::enter};
use rocket_dyn_templates::Template;
use serde::Serialize;
use crate::{PresentationConfig, services::cv::fetch_cv_data_from_backend, CVBackendConfig, tools::rocket::RequestLanguage};
use crate::{PresentationConfig, services::cv::fetch_cv_data_from_backend, CVBackendConfig, tools::rocket::RequestLanguage, DefaultPerson};
#[derive(Serialize, Debug)]
struct RootPage {
static_host: String,
cv: CV,
download_cv_url: String,
lang: String,
lang: LanguageDescription,
}
#[get("/<language>")]
pub async fn index(presentation_config: &State<PresentationConfig>, cv_config: &State<CVBackendConfig>, language: RequestLanguage) -> Result<Template, Status> {
let context = match fetch_cv_data_from_backend(cv_config.cv_backend_path.clone()).await {
pub async fn index(presentation_config: &State<PresentationConfig>, cv_config: &State<CVBackendConfig>, language: RequestLanguage, client: OtelReqwestClient,
default_person: &State<DefaultPerson>, span: TracingSpan) -> Result<Template, Status> {
let span = span.0.enter();
let context = match fetch_cv_data_from_backend(&cv_config.cv_backend_path, &default_person.default_person_name, &client.0).await {
Ok(cv) => RootPage {
static_host: presentation_config.static_route.clone(),
cv,
download_cv_url: "FIXME!".to_string(),
lang: language.language,
lang: LanguageDescription::new(&language.language.as_str(), "ovlach_frontend"),
},
Err(e) => {
error!("Can't fetch CV data from backend {:?}", e);
drop(span);
return Err(Status::InternalServerError)
}
};
Ok(Template::render("default", &context))
let result = Ok(Template::render("default", &context));
drop(span);
result
}

View File

@ -1,4 +1,6 @@
use ovlach_data::cv::cv::CV;
use ovlach_data::cv::data::CV;
use reqwest::Client;
use tracing::{debug, instrument};
#[derive(Debug)]
@ -12,8 +14,12 @@ impl From<reqwest::Error> for FetchError {
}
}
pub async fn fetch_cv_data_from_backend(backend_host: String) -> Result<CV, FetchError> {
let resp = reqwest::get(format!("{}/{}", backend_host, "/api/cv/ovlach"))
#[instrument]
pub async fn fetch_cv_data_from_backend(backend_host: &String, person_name: &String, client: &Client) -> Result<CV, FetchError> {
let url = format!("{}/{}/{}", backend_host, "/api/v1/cv", person_name);
debug!("Fetching CV data from backend: {}", url);
let resp = client
.get(url).send()
.await?
.json::<CV>()
.await?;

View File

@ -1,2 +1,3 @@
pub mod rocket;
pub mod lang;
pub(crate) mod rocket;
pub(crate) mod lang;
pub(crate) mod tera;

View File

@ -27,57 +27,3 @@ impl<'r> FromParam<'r> for RequestLanguage {
}
}
}
/// Fairing for timing requests.
pub struct RequestTimer;
/// Value stored in request-local state.
#[derive(Copy, Clone)]
struct TimerStart(Option<SystemTime>);
#[rocket::async_trait]
impl Fairing for RequestTimer {
fn info(&self) -> Info {
Info {
name: "Request Timer",
kind: Kind::Request | Kind::Response
}
}
/// Stores the start time of the request in request-local state.
async fn on_request(&self, request: &mut Request<'_>, _: &mut Data<'_>) {
// Store a `TimerStart` instead of directly storing a `SystemTime`
// to ensure that this usage doesn't conflict with anything else
// that might store a `SystemTime` in request-local cache.
request.local_cache(|| TimerStart(Some(SystemTime::now())));
}
/// Adds a header to the response indicating how long the server took to
/// process the request.
async fn on_response<'r>(&self, req: &'r Request<'_>, res: &mut Response<'r>) {
let start_time = req.local_cache(|| TimerStart(None));
if let Some(Ok(duration)) = start_time.0.map(|st| st.elapsed()) {
let ms = duration.as_secs() * 1000 + duration.subsec_millis() as u64;
res.set_raw_header("X-Response-Time", format!("{} ms", ms));
info!("Response time: {} ms", ms);
}
}
}
/// Request guard used to retrieve the start time of a request.
#[derive(Copy, Clone)]
pub struct StartTime(pub SystemTime);
// Allows a route to access the time a request was initiated.
#[rocket::async_trait]
impl<'r> FromRequest<'r> for StartTime {
type Error = ();
async fn from_request(request: &'r Request<'_>) -> request::Outcome<Self, ()> {
match *request.local_cache(|| TimerStart(None)) {
TimerStart(Some(time)) => request::Outcome::Success(StartTime(time)),
TimerStart(None) => request::Outcome::Error((Status::InternalServerError, ())),
}
}
}

116
src/tools/tera.rs Normal file
View File

@ -0,0 +1,116 @@
use std::collections::HashMap;
use rocket_dyn_templates::tera::{try_get_value, Error, Value, to_value};
/// If the `value` is not passed, optionally discard all elements where the attribute is null. (include_null -> all, none, only)
pub fn advanced_filter(value: &Value, args: &HashMap<String, Value>) -> Result<Value, Error> {
let mut arr = try_get_value!("filter", "value", Vec<Value>, value);
if arr.is_empty() {
return Ok(arr.into());
}
let key = match args.get("attribute") {
Some(val) => try_get_value!("filter", "attribute", String, val),
None => return Err(Error::msg("The `filter` filter has to have an `attribute` argument")),
};
let null = match args.get("include_null") {
Some(val) => try_get_value!("filter", "attribute", String, val),
None => "none".to_string(),
};
let against_value = match args.get("value") {
Some(val) => Some(try_get_value!("filter", "value", String, val)),
None => None
};
if null == "only" && against_value.is_some() {
return Err(Error::msg("The `filter` filter cannot have both `include_null=only` and `value`"))
}
arr = arr
.into_iter()
.filter(|v| {
let tested_value = v.get(key.clone()).unwrap_or(&Value::Null);
if tested_value.is_null() {
return match null.as_str() {
"all" => true,
"none" => false,
"only" => true,
_ => false,
}
} else {
if null != "only" {
let val = tested_value.as_str();
match val {
Some(match_v) => {
match against_value.clone() {
Some(against_v) => match_v == against_v,
None => true,
}
}
None => true,
}
} else {
false
}
}
})
.collect::<Vec<_>>();
Ok(to_value(arr).unwrap())
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
use std::collections::HashMap;
fn call_advanced_filter(data: &Vec<TestClass>, include_null: &str, value: Option<&str>) -> Result<Value, Error> {
let mut args = HashMap::new();
args.insert("attribute".to_string(), json!("against_value"));
args.insert("include_null".to_string(), json!(include_null));
if let Some(val) = value {
args.insert("value".to_string(), json!(val));
}
advanced_filter(&json!(data), &args)
}
#[derive(serde::Serialize, serde::Deserialize, Debug)]
struct TestClass {
against_value: Option<String>,
}
impl TestClass {
pub fn new(against_value: Option<String>) -> Self {
TestClass { against_value }
}
}
fn generate_test_class(first_data: Option<String>, second_data: Option<String>) -> Vec<TestClass> {
vec![TestClass::new(first_data), TestClass::new(second_data)]
}
#[test]
fn test_advanced_filter() {
let data = vec![TestClass::new(Some("foo".to_string())), TestClass::new(None)];
let result = call_advanced_filter(&data, "all", None).unwrap();
assert_eq!(result, json!(generate_test_class(Some("foo".to_string()), None)));
let result = call_advanced_filter(&data, "none", None).unwrap();
assert_eq!(result, json!(vec![TestClass::new(Some("foo".to_string()))]));
let mut vec: Vec<TestClass> = Vec::new();
vec.push(TestClass::new(None));
let result = call_advanced_filter(&data, "only", None).unwrap();
assert_eq!(result, json!(vec![TestClass::new(None)]));
// Test filtering strings
let result = call_advanced_filter(&data, "all", Some("foo")).unwrap();
assert_eq!(result, json!(vec![TestClass::new(Some("foo".to_string())), TestClass::new(None)]));
let result = call_advanced_filter(&data, "none", Some("bar")).unwrap();
let vec: Vec<TestClass> = Vec::new();
assert_eq!(result, json!(vec));
let result = call_advanced_filter(&data, "only", Some("zz")).is_err();
assert_eq!(result, true);
}
}

View File

@ -20,29 +20,29 @@
<nav role="navigation">
<ul class="nav justify-content-center">
{% block navlinks %}
{% if cv.social.facebook %}
{% if cv.person.social.facebook %}
<li class="nav-item">
<a class="nav-link" href="{{ cv.social.facebook }}" title="Facebook"><i class="fab fa-facebook"></i><span class="menu-title sr-only">Facebook</span></a>
<a class="nav-link" href="{{ cv.person.social.facebook }}" title="Facebook"><i class="fab fa-facebook"></i><span class="menu-title sr-only">Facebook</span></a>
</li>
{% endif %}
{% if cv.social.github %}
{% if cv.person.social.github %}
<li class="nav-item">
<a class="nav-link" href="{{ cv.social.github }}" title="github"><i class="fab fa-github"></i><span class="menu-title sr-only">Github</span></a>
<a class="nav-link" href="{{ cv.person.social.github }}" title="github"><i class="fab fa-github"></i><span class="menu-title sr-only">Github</span></a>
</li>
{% endif %}
{% if cv.social.linkedin %}
{% if cv.person.social.linkedin %}
<li class="nav-item">
<a class="nav-link" href="{{ cv.social.linkedin }}" title="github"><i class="fab fa-linkedin"></i><span class="menu-title sr-only">Linkedin</span></a>
<a class="nav-link" href="{{ cv.person.social.linkedin }}" title="github"><i class="fab fa-linkedin"></i><span class="menu-title sr-only">Linkedin</span></a>
</li>
{% endif %}
{% if cv.social.instagram %}
{% if cv.person.social.instagram %}
<li class="nav-item">
<a class="nav-link" href="{{ cv.social.instagram }}" title="github"><i class="fab fa-instagram"></i><span class="menu-title sr-only">Instagram</span></a>
<a class="nav-link" href="{{ cv.person.social.instagram }}" title="github"><i class="fab fa-instagram"></i><span class="menu-title sr-only">Instagram</span></a>
</li>
{% endif %}
{% if cv.social.mastodon %}
{% if cv.person.social.mastodon %}
<li class="nav-item">
<a class="nav-link" href="{{ cv.social.mastodon }}" title="github"><i class="fab fa-mastodon"></i><span class="menu-title sr-only">Mastodon</span></a>
<a class="nav-link" href="{{ cv.person.social.mastodon }}" title="github"><i class="fab fa-mastodon"></i><span class="menu-title sr-only">Mastodon</span></a>
</li>
{% endif %}
{% endblock navlinks %}
@ -94,6 +94,23 @@
<div class="pb-2">{{ cv.person.address }} </div>
</div>
{% endif %}
<div class="col-sm-10">
{% if cv.person.social.facebook %}
<a class="nav-link" href="{{ cv.person.social.facebook }}" title="facebook"><i class="pe-2 fab fa-facebook"></i>&nbsp;{{ cv.person.social.github }}</a>
{% endif %}
{% if cv.person.social.github %}
<a class="nav-link" href="{{ cv.person.social.github }}" title="github"><i class="pe-2 fab fa-github"></i>&nbsp;{{ cv.person.social.github }}</a>
{% endif %}
{% if cv.person.social.linkedin %}
<a class="nav-link" href="{{ cv.person.social.linkedin }}" title="linkedin"><i class="pe-2 fab fa-linkedin"></i>&nbsp;{{ cv.person.social.linkedin }}</a>
{% endif %}
{% if cv.person.social.instagram %}
<a class="nav-link" href="{{ cv.person.social.instagram }}" title="instagram"><i class="pe-2 fab fa-instagram"></i>&nbsp;{{ cv.person.social.instagram }}</a>
{% endif %}
{% if cv.person.social.mastodon %}
<a class="nav-link" href="{{ cv.person.social.mastodon }}" title="mastodon"><i class="pe-2 fab fa-mastodon"></i>&nbsp;{{ cv.person.social.mastodon }}</a>
{% endif %}
</div>
</div>
</div>
</div>
@ -104,14 +121,11 @@
<h2 class="h2 fw-light mb-4">{{ "professional-skills" | translate(lang=lang) }}</h2>
<div class="row">
<div class="col-md-6">
{% set arr_cnt = cv.skills | length %}
{% set split_index = arr_cnt / 2 | round(method="ceil") %}
{% for skill in cv.skills | slice(start = 0, end = split_index) %}
{{ split_index}}
{% for skill in cv.skills | filter(attribute="techtype",value="Language") %}
<div class="mb-3"><span class="fw-bolder">{{ skill.name }}</span>
<div class="progress my-2 rounded" style="height: 20px">
<div class="progress-bar bg-info" role="progressbar" data-aos="zoom-in-right" data-aos-delay="100"
data-aos-anchor=".skills-section" style="width: {% if skill.skill == "MASTER" %}100%{% endif %}{% if skill.skill == "EXPERT" %}75%{% endif %}{% if skill.skill == "INTERMEDIATE" %}50%{% endif %}{% if skill.skill == "BEGINER" %}25%{% endif %};"
data-aos-anchor=".skills-section" style="width: {% if skill.skill == "Master" %}100%{% endif %}{% if skill.skill == "Expert" %}75%{% endif %}{% if skill.skill == "Intermediate" %}50%{% endif %}{% if skill.skill == "Beginer" %}25%{% endif %};"
aria-valuenow="95" aria-valuemin="0" aria-valuemax="100">
{{ skill.skill}}
</div>
@ -120,18 +134,31 @@
{% endfor %}
</div>
<div class="col-md-6">
{% for skill in cv.skills | slice(start = split_index) %}
{{ split_index}}
{% for skill in cv.skills | filter(attribute="techtype",value="Technology") | advanced_filter(attribute="skill", include_null="none") %}
<div class="mb-3"><span class="fw-bolder">{{ skill.name }}</span>
<div class="progress my-2 rounded" style="height: 20px">
<div class="progress-bar bg-info" role="progressbar" data-aos="zoom-in-right" data-aos-delay="100"
data-aos-anchor=".skills-section" style="width: {% if skill.skill == "MASTER" %}100%{% endif %}{% if skill.skill == "EXPERT" %}75%{% endif %}{% if skill.skill == "INTERMEDIATE" %}50%{% endif %}{% if skill.skill == "BEGINER" %}25%{% endif %};"
data-aos-anchor=".skills-section" style="width: {% if skill.skill == "Master" %}100%{% endif %}{% if skill.skill == "Expert" %}75%{% endif %}{% if skill.skill == "Intermediate" %}50%{% endif %}{% if skill.skill == "Beginer" %}25%{% endif %};"
aria-valuenow="95" aria-valuemin="0" aria-valuemax="100">
{{ skill.skill}}
</div>
</div>
</div>
{% endfor %}
{% for skill in cv.skills | filter(attribute="techtype",value="Framework") | advanced_filter(attribute="skill", include_null="none") %}
<div class="mb-3"><span class="fw-bolder">{{ skill.name }}</span>
<div class="progress my-2 rounded" style="height: 20px">
<div class="progress-bar bg-info" role="progressbar" data-aos="zoom-in-right" data-aos-delay="100"
data-aos-anchor=".skills-section" style="width: {% if skill.skill == "Master" %}100%{% endif %}{% if skill.skill == "Expert" %}75%{% endif %}{% if skill.skill == "Intermediate" %}50%{% endif %}{% if skill.skill == "Beginer" %}25%{% endif %};"
aria-valuenow="95" aria-valuemin="0" aria-valuemax="100">
{{ skill.skill}}
</div>
</div>
</div>
{% endfor %}
{% for skill in cv.skills | filter(attribute="techtype",value="Technology") | advanced_filter(attribute="skill", include_null="only") %}
<span class="fw-bolder">{{ skill.name }}</span>,<!-- TODO: fix last, -->
{% endfor %}
</div>
</div>
</div>