This commit is contained in:
Dominic Grimm 2023-03-03 17:13:19 +01:00
parent 73fdcb4bd2
commit e00f809d00
No known key found for this signature in database
GPG key ID: 6F294212DEAAC530
13 changed files with 416 additions and 237 deletions

49
bvplan/Cargo.lock generated
View file

@ -21,15 +21,15 @@ dependencies = [
[[package]]
name = "actix-http"
version = "3.3.0"
version = "3.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0070905b2c4a98d184c4e81025253cb192aa8a73827553f38e9410801ceb35bb"
checksum = "c2079246596c18b4a33e274ae10c0e50613f4d32a4198e09c7b93771013fed74"
dependencies = [
"actix-codec",
"actix-rt",
"actix-service",
"actix-utils",
"ahash",
"ahash 0.8.3",
"base64",
"bitflags",
"brotli",
@ -145,7 +145,7 @@ dependencies = [
"actix-service",
"actix-utils",
"actix-web-codegen",
"ahash",
"ahash 0.7.6",
"bytes",
"bytestring",
"cfg-if",
@ -221,6 +221,18 @@ dependencies = [
"version_check",
]
[[package]]
name = "ahash"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f"
dependencies = [
"cfg-if",
"getrandom 0.2.8",
"once_cell",
"version_check",
]
[[package]]
name = "aho-corasick"
version = "0.7.20"
@ -450,12 +462,11 @@ dependencies = [
[[package]]
name = "async-lock"
version = "2.6.0"
version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8101efe8695a6c17e02911402145357e718ac92d3ff88ae8419e84b1707b685"
checksum = "fa24f727524730b077666307f2734b4a1a1c57acb79193127dcc8914d5242dd7"
dependencies = [
"event-listener",
"futures-lite",
]
[[package]]
@ -636,7 +647,6 @@ dependencies = [
"env_logger",
"envconfig",
"fancy-regex",
"flate2",
"lazy_static",
"log",
"minify-html",
@ -644,7 +654,6 @@ dependencies = [
"reqwest",
"scraper",
"serde",
"serde_json",
"static-files",
"stdext",
"tikv-jemallocator",
@ -694,8 +703,8 @@ dependencies = [
[[package]]
name = "celery"
version = "0.5.2"
source = "git+https://github.com/rusty-celery/rusty-celery.git?branch=main#6a71f338dc6decdb647ebfa48628444e1cfc6582"
version = "0.5.3"
source = "git+https://github.com/rusty-celery/rusty-celery.git?branch=main#32cb1c97d3bf8b57a1f4a501ec50024fbb0cea7f"
dependencies = [
"async-trait",
"base64",
@ -723,8 +732,8 @@ dependencies = [
[[package]]
name = "celery-codegen"
version = "0.5.2"
source = "git+https://github.com/rusty-celery/rusty-celery.git?branch=main#6a71f338dc6decdb647ebfa48628444e1cfc6582"
version = "0.5.3"
source = "git+https://github.com/rusty-celery/rusty-celery.git?branch=main#32cb1c97d3bf8b57a1f4a501ec50024fbb0cea7f"
dependencies = [
"proc-macro2",
"quote",
@ -876,9 +885,9 @@ dependencies = [
[[package]]
name = "crossbeam-utils"
version = "0.8.14"
version = "0.8.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f"
checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b"
dependencies = [
"cfg-if",
]
@ -1674,9 +1683,9 @@ checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440"
[[package]]
name = "jobserver"
version = "0.1.25"
version = "0.1.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "068b1ee6743e4d11fb9c6a1e6064b3693a1b600e7f5f5988047d98b3dc9fb90b"
checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2"
dependencies = [
"libc",
]
@ -3099,9 +3108,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
version = "1.25.0"
version = "1.26.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8e00990ebabbe4c14c08aca901caed183ecd5c09562a12c824bb53d3c3fd3af"
checksum = "03201d01c3c27a29c8a5cee5b55a93ddae1ccf6f08f65365c2c918f8c1b76f64"
dependencies = [
"autocfg",
"bytes",
@ -3114,7 +3123,7 @@ dependencies = [
"signal-hook-registry",
"socket2",
"tokio-macros",
"windows-sys 0.42.0",
"windows-sys 0.45.0",
]
[[package]]

View file

@ -28,7 +28,6 @@ diesel = { version = "2.0.2", features = ["i-implement-a-third-party-backend-and
env_logger = "0.9.3"
envconfig = "0.10.0"
fancy-regex = "0.11.0"
flate2 = "1.0.25"
lazy_static = "1.4.0"
log = "0.4.17"
minify-html = "0.10.8"
@ -36,7 +35,6 @@ r2d2_redis = "0.14.0"
reqwest = "0.11.13"
scraper = "0.14.0"
serde = "1.0.148"
serde_json = "1.0.93"
static-files = "0.2.3"
stdext = "0.3.1"
tokio = { version = "1.22.0", features = ["full"] }

View file

@ -38,7 +38,3 @@ function scrollDown() {
}
window.scrollTo(0, 0);
setTimeout(
$(document).height() > $(window).height() ? scrollDown : reload,
waitDelay
);

View file

@ -7,10 +7,13 @@ body {
body {
min-height: 100%;
margin: 0 5vw;
overflow: hidden;
display: flex;
flex-direction: column;
font-family: Arial, Helvetica, sans-serif;
&.display {
overflow: hidden;
}
}
#info {
@ -84,7 +87,8 @@ body {
}
#footer {
margin: auto 0 2vh;
margin-top: auto;
padding-bottom: 2vh;
text-align: center;
p {

View file

@ -38,7 +38,45 @@ async fn index(redis_pool: web::Data<cache::RedisPool>) -> HttpResponse {
};
if let Some(html) = match redis::cmd("GET")
.arg(cache::keys::SUBSTITUTIONS_HTML)
.arg(cache::keys::HTML)
.query::<Option<String>>(redis_conn.deref_mut())
{
Ok(x) => x,
Err(_) => {
return templates::StatusCode {
status_code: http::StatusCode::INTERNAL_SERVER_ERROR,
message: None,
}
.to_response()
}
} {
HttpResponse::Accepted()
.content_type("text/html; charset=utf-8")
.body(html)
} else {
templates::StatusCode {
status_code: http::StatusCode::INTERNAL_SERVER_ERROR,
message: None,
}
.to_response()
}
}
#[get("/display")]
async fn display(redis_pool: web::Data<cache::RedisPool>) -> HttpResponse {
let redis_conn = &mut match redis_pool.get() {
Ok(x) => x,
Err(_) => {
return templates::StatusCode {
status_code: http::StatusCode::INTERNAL_SERVER_ERROR,
message: None,
}
.to_response()
}
};
if let Some(html) = match redis::cmd("GET")
.arg(cache::keys::DISPLAY_HTML)
.query::<Option<String>>(redis_conn.deref_mut())
{
Ok(x) => x,
@ -74,6 +112,7 @@ async fn main() -> std::io::Result<()> {
.wrap(middleware::Compress::default())
.wrap(middleware::Logger::default())
.service(index)
.service(display)
.service(ResourceFiles::new("/static", generated))
.default_service(web::route().to(not_found))
})

View file

@ -4,7 +4,6 @@ use lazy_static::lazy_static;
use r2d2_redis::{r2d2, redis, RedisConnectionManager};
use crate::config;
use crate::db;
pub type RedisPool = r2d2::Pool<RedisConnectionManager>;
pub type ConnectionPool = r2d2::PooledConnection<RedisConnectionManager>;
@ -25,52 +24,7 @@ lazy_static! {
}
pub mod keys {
use super::*;
// pub const SUBSTITUTIONS: &str = "substs";
pub const SUBSTITUTIONS_HTML: &str = "substs_html";
pub mod substitutions {
use serde::{Deserialize, Serialize};
use super::*;
#[derive(Deserialize, Serialize, Debug)]
pub struct Substitution {
#[serde(rename = "p")]
pub period: usize,
#[serde(rename = "cl")]
pub classes: Vec<String>,
#[serde(rename = "ps")]
pub prev_subject: Option<String>,
#[serde(rename = "s")]
pub subject: Option<String>,
#[serde(rename = "t")]
pub teachers: Option<Vec<String>>,
#[serde(rename = "pr")]
pub prev_rooms: Vec<String>,
#[serde(rename = "r")]
pub rooms: Vec<String>,
#[serde(rename = "tx")]
pub text: Option<String>,
}
#[derive(Deserialize, Serialize, Debug)]
pub struct SubstitutionQuery {
#[serde(rename = "d")]
pub date: String,
#[serde(rename = "w")]
pub week_type: db::models::WeekType,
#[serde(rename = "q")]
pub queried_at: String,
#[serde(rename = "i")]
pub last_import_time: String,
#[serde(rename = "y")]
pub schoolyear: String,
#[serde(rename = "t")]
pub tenant: String,
#[serde(rename = "s")]
pub substitutions: Vec<Substitution>,
}
}
pub const HTML: &str = "html";
pub const DISPLAY_HTML: &str = "disp_html";
}

View file

@ -2,8 +2,6 @@ use actix_web::{body::BoxBody, http, HttpResponse, HttpResponseBuilder, Response
use askama::Template;
use std::fmt;
use crate::cache;
struct ActixError(askama::Error);
impl fmt::Debug for ActixError {
@ -37,10 +35,43 @@ impl<T: askama_actix::Template> TemplateToResponseWithStatusCode for T {
}
}
pub mod substitutions {
use crate::db;
#[derive(Clone, Debug)]
pub struct Substitution {
pub period: usize,
pub classes: Vec<String>,
pub prev_subject: Option<String>,
pub subject: Option<String>,
pub teachers: Option<Vec<String>>,
pub prev_rooms: Vec<String>,
pub rooms: Vec<String>,
pub text: Option<String>,
}
#[derive(Clone, Debug)]
pub struct SubstitutionQuery {
pub date: String,
pub week_type: db::models::WeekType,
pub queried_at: String,
pub last_import_time: String,
pub schoolyear: String,
pub tenant: String,
pub substitutions: Vec<Substitution>,
}
}
#[derive(Template)]
#[template(path = "bvplan.html")]
pub struct BVplan {
pub data: cache::keys::substitutions::SubstitutionQuery,
pub data: substitutions::SubstitutionQuery,
}
#[derive(Template)]
#[template(path = "bvplan_display.html")]
pub struct BVplanDisplay {
pub data: substitutions::SubstitutionQuery,
}
#[derive(Template)]

View file

@ -7,9 +7,6 @@ use diesel::prelude::*;
use fancy_regex::Regex;
use lazy_static::lazy_static;
use r2d2_redis::redis;
use std::fs;
use std::io::Write;
use std::path::Path;
use std::thread;
use std::time::Duration;
use std::vec;
@ -371,7 +368,7 @@ fn cache_substitutions(
}
};
Ok(Some(cache::keys::substitutions::Substitution {
Ok(Some(templates::substitutions::Substitution {
period: get_period(&times, true, s.start_time)
.context("Could not find period from start time")?,
classes,
@ -427,7 +424,7 @@ fn cache_substitutions(
)
});
let query = cache::keys::substitutions::SubstitutionQuery {
let query = templates::substitutions::SubstitutionQuery {
date: format!(
"{} {}",
date.format("%d.%m.%Y"),
@ -455,22 +452,30 @@ fn cache_substitutions(
substitutions,
};
fs::write(
Path::new("/backups").join(format!(
"{}_{}_{}.json.gz",
date, queried_at, substitution_query_id
)),
{
let mut e = flate2::write::ZlibEncoder::new(vec![], flate2::Compression::best());
e.write_all(serde_json::to_string(&query)?.as_bytes())?;
e.finish()?
},
)?;
redis::cmd("SET")
.arg(cache::keys::SUBSTITUTIONS_HTML)
.arg(cache::keys::HTML)
.arg(minify_html::minify(
templates::BVplan { data: query }.render()?.as_bytes(),
templates::BVplan {
data: query.to_owned(),
}
.render()?
.as_bytes(),
&minify_html::Cfg {
do_not_minify_doctype: false,
ensure_spec_compliant_unquoted_attribute_values: false,
keep_spaces_between_attributes: false,
minify_css: true,
minify_js: true,
..Default::default()
},
))
.query::<()>(redis_conn)?;
redis::cmd("SET")
.arg(cache::keys::DISPLAY_HTML)
.arg(minify_html::minify(
templates::BVplanDisplay { data: query }
.render()?
.as_bytes(),
&minify_html::Cfg {
do_not_minify_doctype: false,
ensure_spec_compliant_unquoted_attribute_values: false,

View file

@ -25,7 +25,7 @@ pub async fn init() {
tasks = [
get_substitutions::get_substitutions,
update_meta::update_meta,
cleanup::cleanup,
// cleanup::cleanup,
],
task_routes = [
"*" => QUEUE_NAME,
@ -57,11 +57,11 @@ pub fn beat() -> impl std::future::Future<
schedule = DeltaSchedule::new(Duration::from_hours(6)),
args = (),
},
"cleanup" => {
cleanup::cleanup,
schedule = DeltaSchedule::new(Duration::from_hours(1)),
args = (),
}
// "cleanup" => {
// cleanup::cleanup,
// schedule = DeltaSchedule::new(Duration::from_hours(1)),
// args = (),
// }
],
task_routes = [
"*" => QUEUE_NAME,

View file

@ -15,6 +15,13 @@
{% block head %}{% endblock %}
<script
src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.3/jquery.min.js"
integrity="sha512-STof4xm1wgkfm7heWqFJVn58Hm3EtS31XFaagaa8VMReCXAkQnJZ+jEy8PCC/iT18dFy95WcExNHFTqLyp72eQ=="
crossorigin="anonymous"
referrerpolicy="no-referrer"
></script>
<!-- Matomo -->
<script>
var _paq = window._paq = window._paq || [];
@ -31,7 +38,5 @@
</script>
<!-- End Matomo Code -->
</head>
<body>
{% block body %}{% endblock %}
</body>
{% block body %}{% endblock %}
</html>

View file

@ -6,138 +6,136 @@
<link rel="stylesheet" type="text/css" href="/static/css/bvplan.css" />
<script id="data-element-count" type="application/json">{{ data.substitutions.len()|json|safe }}</script>
<script
src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.3/jquery.min.js"
integrity="sha512-STof4xm1wgkfm7heWqFJVn58Hm3EtS31XFaagaa8VMReCXAkQnJZ+jEy8PCC/iT18dFy95WcExNHFTqLyp72eQ=="
crossorigin="anonymous"
referrerpolicy="no-referrer"
></script>
{% endblock %}
{% block body %}
<header id="info">
<div id="title-wrapper" class="column">
<h1 id="title">BVplan</h1>
<span id="subtitle">dergrimm.net</span>
</div>
<div id="tenant-info" class="column">
<span>{{ data.tenant }}, {{ data.schoolyear }}</span>
<br />
<span>Stand: {{ data.queried_at }}</span>
<br />
<span>Vertretungsplan (offline): {{ data.last_import_time }}</span>
</div>
</header>
<body>
<header id="info">
<div id="title-wrapper" class="column">
<h1 id="title">BVplan</h1>
<span id="subtitle">dergrimm.net</span>
</div>
<div id="tenant-info" class="column">
<span>{{ data.tenant }}, {{ data.schoolyear }}</span>
<br />
<span>Stand: {{ data.queried_at }}</span>
<br />
<span>Vertretungsplan (offline): {{ data.last_import_time }}</span>
</div>
</header>
<main>
<table id="plan">
<caption>
{{ data.date }}, Woche {{ data.week_type }}
</caption>
<main>
<table id="plan">
<caption>
{{ data.date }}, Woche {{ data.week_type }}
</caption>
<colgroup>
<col />
<col />
<col />
<col style="width: 30%" />
<col />
<col />
<col />
<col style="width: 20%" />
</colgroup>
<colgroup>
<col />
<col />
<col />
<col style="width: 30%" />
<col />
<col />
<col />
<col style="width: 20%" />
</colgroup>
<thead>
<tr>
<th>Stunde</th>
<th>Klasse(n)</th>
<th>(Fach)</th>
<th>Fach</th>
<th>Vertreter</th>
<th>(Raum)</th>
<th>Raum</th>
<th>Text</th>
</tr>
</thead>
<thead>
<tr>
<th>Stunde</th>
<th>Klasse(n)</th>
<th>(Fach)</th>
<th>Fach</th>
<th>Vertreter</th>
<th>(Raum)</th>
<th>Raum</th>
<th>Text</th>
</tr>
</thead>
<tbody>
{% for subst in data.substitutions %}
<tr>
<td>{{ subst.period }}</td>
<td>{{ subst.classes|join(", ") }}</td>
<td>
{% match subst.prev_subject %}
{% when Some with (x) %}
{{ x }}
{% when None %}
<tbody>
{% for subst in data.substitutions %}
<tr>
<td>{{ subst.period }}</td>
<td>{{ subst.classes|join(", ") }}</td>
<td>
{% match subst.prev_subject %}
{% when Some with (x) %}
{{ x }}
{% when None %}
---
{% endmatch %}
</td>
<td>
{% match subst.subject %}
{% when Some with (x) %}
{{ x }}
{% when None %}
---
{% endmatch %}
</td>
<td>
{% match subst.teachers %}
{% when Some with (x) %}
{{ x|join(", ") }}
{% when None %}
---
{% endmatch %}
</td>
<td>
{% if subst.prev_rooms.is_empty() %}
---
{% endmatch %}
</td>
<td>
{% match subst.subject %}
{% when Some with (x) %}
{{ x }}
{% when None %}
{% else %}
{{ subst.prev_rooms|join(", ") }}
{% endif %}
</td>
<td>
{% if subst.rooms.is_empty() %}
---
{% endmatch %}
</td>
<td>
{% match subst.teachers %}
{% when Some with (x) %}
{{ x|join(", ") }}
{% when None %}
---
{% endmatch %}
</td>
<td>
{% if subst.prev_rooms.is_empty() %}
---
{% else %}
{{ subst.prev_rooms|join(", ") }}
{% endif %}
</td>
<td>
{% if subst.rooms.is_empty() %}
---
{% else %}
{{ subst.rooms|join(", ") }}
{% endif %}
</td>
<td>
{% match subst.text %}
{% when Some with (x) %}
{{ x }}
{% when None %}
{% endmatch %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</main>
{% else %}
{{ subst.rooms|join(", ") }}
{% endif %}
</td>
<td>
{% match subst.text %}
{% when Some with (x) %}
{{ x }}
{% when None %}
{% endmatch %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</main>
<footer id="footer">
<div class="element">
<p>
BVplan - der bessere Vertretungsplan sogar mit UTF-8 Support!
Wow
<footer id="footer">
<div class="element">
<p>
BVplan - der bessere Vertretungsplan sogar mit UTF-8 Support!
Wow
</p>
<p>
<a href="https://git.dergrimm.net/dergrimm/bvplan">
https://git.dergrimm.net/dergrimm/bvplan
</a>
</p>
</div>
<hr />
<p class="element">
<code>
Powered by Dominic Grimm &lt;<a
href="mailto:dominic@dergrimm.net"
>dominic@dergrimm.net</a
>&gt;, Untis sucks
</code>
</p>
<p>
<a href="https://git.dergrimm.net/dergrimm/bvplan">
https://git.dergrimm.net/dergrimm/bvplan
</a>
</p>
</div>
<hr />
<p class="element">
<code>
Powered by Dominic Grimm &lt;<a
href="mailto:dominic@dergrimm.net"
>dominic@dergrimm.net</a
>&gt;, Untis sucks
</code>
</p>
</footer>
</footer>
<script src="/static/js/bvplan.js"></script>
<script src="/static/js/bvplan.js"></script>
<script>
setTimeout(reload, waitDelay);
</script>
</body>
{% endblock %}

View file

@ -0,0 +1,144 @@
{% extends "base.html" %}
{% block title %}BVplan{% endblock %}
{% block head %}
<link rel="stylesheet" type="text/css" href="/static/css/bvplan.css" />
<script id="data-element-count" type="application/json">{{ data.substitutions.len()|json|safe }}</script>
{% endblock %}
{% block body %}
<body class="display">
<header id="info">
<div id="title-wrapper" class="column">
<h1 id="title">BVplan</h1>
<span id="subtitle">dergrimm.net</span>
</div>
<div id="tenant-info" class="column">
<span>{{ data.tenant }}, {{ data.schoolyear }}</span>
<br />
<span>Stand: {{ data.queried_at }}</span>
<br />
<span>Vertretungsplan (offline): {{ data.last_import_time }}</span>
</div>
</header>
<main>
<table id="plan">
<caption>
{{ data.date }}, Woche {{ data.week_type }}
</caption>
<colgroup>
<col />
<col />
<col />
<col style="width: 30%" />
<col />
<col />
<col />
<col style="width: 20%" />
</colgroup>
<thead>
<tr>
<th>Stunde</th>
<th>Klasse(n)</th>
<th>(Fach)</th>
<th>Fach</th>
<th>Vertreter</th>
<th>(Raum)</th>
<th>Raum</th>
<th>Text</th>
</tr>
</thead>
<tbody>
{% for subst in data.substitutions %}
<tr>
<td>{{ subst.period }}</td>
<td>{{ subst.classes|join(", ") }}</td>
<td>
{% match subst.prev_subject %}
{% when Some with (x) %}
{{ x }}
{% when None %}
---
{% endmatch %}
</td>
<td>
{% match subst.subject %}
{% when Some with (x) %}
{{ x }}
{% when None %}
---
{% endmatch %}
</td>
<td>
{% match subst.teachers %}
{% when Some with (x) %}
{{ x|join(", ") }}
{% when None %}
---
{% endmatch %}
</td>
<td>
{% if subst.prev_rooms.is_empty() %}
---
{% else %}
{{ subst.prev_rooms|join(", ") }}
{% endif %}
</td>
<td>
{% if subst.rooms.is_empty() %}
---
{% else %}
{{ subst.rooms|join(", ") }}
{% endif %}
</td>
<td>
{% match subst.text %}
{% when Some with (x) %}
{{ x }}
{% when None %}
{% endmatch %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</main>
<footer id="footer">
<div class="element">
<p>
BVplan - der bessere Vertretungsplan sogar mit UTF-8 Support!
Wow
</p>
<p>
<a href="https://git.dergrimm.net/dergrimm/bvplan">
https://git.dergrimm.net/dergrimm/bvplan
</a>
</p>
</div>
<hr />
<p class="element">
<code>
Powered by Dominic Grimm &lt;<a
href="mailto:dominic@dergrimm.net"
>dominic@dergrimm.net</a
>&gt;, Untis sucks
</code>
</p>
</footer>
<script src="/static/js/bvplan.js"></script>
<script>
setTimeout(
$(document).height() > $(window).height() ? scrollDown : reload,
waitDelay
);
</script>
</body>
{% endblock %}

View file

@ -24,6 +24,9 @@ x-bvplan:
BVPLAN_UNTIS_VPLAN_URL: ${BVPLAN_UNTIS_VPLAN_URL}
BVPLAN_UNTIS_VPLAN_USERNAME: ${BVPLAN_UNTIS_VPLAN_USERNAME}
BVPLAN_UNTIS_VPLAN_PASSWORD: ${BVPLAN_UNTIS_VPLAN_PASSWORD}
volumes:
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
services:
nginx:
@ -82,10 +85,6 @@ services:
worker:
<<: *bvplan
command: worker
volumes:
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
- ./data/backups:/backups
web:
<<: *bvplan
@ -95,9 +94,6 @@ services:
- rabbitmq
- worker
- redis
volumes:
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
volumes:
db: