Furumi init
This commit is contained in:
+525
-8
@@ -4,6 +4,7 @@ use std::sync::Arc;
|
||||
|
||||
use cot::db::Database;
|
||||
use cot::db::migrations::SyncDynMigration;
|
||||
use cot::json::Json;
|
||||
use cot::request::extractors::{Path, RequestForm, UrlQuery};
|
||||
use cot::response::IntoResponse;
|
||||
use cot::router::method::get;
|
||||
@@ -15,8 +16,14 @@ use serde::Deserialize;
|
||||
use crate::auth;
|
||||
use crate::config::AppConfig;
|
||||
use crate::i18n::I18n;
|
||||
use crate::scheduler::{JobRegistry, SchedulerHandle};
|
||||
use crate::user::User;
|
||||
use views::{OidcSettingsForm, SetupForm, UserForm};
|
||||
use views::{ArtistForm, CronForm, OidcSettingsForm, ReleaseForm, SetImageBody, SetupForm, UploadImageBody, UserForm};
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct ReviewsQuery {
|
||||
status: Option<String>,
|
||||
}
|
||||
|
||||
/// Build-time metadata baked in by `build.rs` and Cargo env vars.
|
||||
#[derive(Debug)]
|
||||
@@ -42,11 +49,17 @@ pub static BUILD_INFO: BuildInfo = BuildInfo {
|
||||
|
||||
pub struct AdminApp {
|
||||
config: Arc<AppConfig>,
|
||||
registry: Arc<JobRegistry>,
|
||||
scheduler_handle: Arc<tokio::sync::OnceCell<Arc<SchedulerHandle>>>,
|
||||
}
|
||||
|
||||
impl AdminApp {
|
||||
pub fn new(config: Arc<AppConfig>) -> Self {
|
||||
Self { config }
|
||||
pub fn new(
|
||||
config: Arc<AppConfig>,
|
||||
registry: Arc<JobRegistry>,
|
||||
scheduler_handle: Arc<tokio::sync::OnceCell<Arc<SchedulerHandle>>>,
|
||||
) -> Self {
|
||||
Self { config, registry, scheduler_handle }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,12 +73,32 @@ struct PathId {
|
||||
id: i64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct PathName {
|
||||
name: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct PathNameRunId {
|
||||
name: String,
|
||||
run_id: i64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct ReleasesQuery {
|
||||
artist_id: Option<i64>,
|
||||
}
|
||||
|
||||
impl App for AdminApp {
|
||||
fn name(&self) -> &'static str {
|
||||
"admin"
|
||||
}
|
||||
|
||||
fn router(&self) -> Router {
|
||||
// Create a shared sqlx pool for admin routes that need it
|
||||
let pool_config = Arc::clone(&self.config);
|
||||
let pool: Arc<tokio::sync::OnceCell<sqlx::PgPool>> = Arc::new(tokio::sync::OnceCell::new());
|
||||
|
||||
Router::with_urls([
|
||||
// -- Setup (first-run, no auth required) --------------------------
|
||||
Route::with_handler_and_name(
|
||||
@@ -95,15 +128,15 @@ impl App for AdminApp {
|
||||
Route::with_handler_and_name(
|
||||
"/",
|
||||
|session: Session, db: Database, i18n: I18n| async move {
|
||||
// First-run redirect
|
||||
let count = User::count_all(&db).await.unwrap_or(0);
|
||||
if count == 0 {
|
||||
return Ok(auth::redirect("/admin/setup"));
|
||||
}
|
||||
let admin = match auth::require_admin_or_redirect(&session, &db).await {
|
||||
Ok(u) => u,
|
||||
Err(resp) => return Ok(resp),
|
||||
};
|
||||
let admin =
|
||||
match auth::require_admin_or_redirect(&session, &db).await {
|
||||
Ok(u) => u,
|
||||
Err(resp) => return Ok(resp),
|
||||
};
|
||||
views::admin_index(admin, i18n).await?.into_response()
|
||||
},
|
||||
"admin_index",
|
||||
@@ -167,6 +200,27 @@ impl App for AdminApp {
|
||||
}),
|
||||
"admin_settings",
|
||||
),
|
||||
// -- Settings probe (HTMX fragment) -----------------------------------
|
||||
Route::with_handler_and_name(
|
||||
"/settings/probe",
|
||||
{
|
||||
let config = Arc::clone(&self.config);
|
||||
move |session: Session, db: Database, i18n: I18n| {
|
||||
let config = Arc::clone(&config);
|
||||
async move {
|
||||
let admin =
|
||||
match auth::require_admin_or_redirect(&session, &db).await {
|
||||
Ok(u) => u,
|
||||
Err(resp) => return Ok(resp),
|
||||
};
|
||||
views::settings_probe_handler(admin, i18n, &config, &db)
|
||||
.await?
|
||||
.into_response()
|
||||
}
|
||||
}
|
||||
},
|
||||
"admin_settings_probe",
|
||||
),
|
||||
// -- Users --------------------------------------------------------
|
||||
Route::with_handler_and_name(
|
||||
"/users",
|
||||
@@ -238,6 +292,463 @@ impl App for AdminApp {
|
||||
),
|
||||
"admin_users_delete",
|
||||
),
|
||||
// -- Artists ------------------------------------------------------
|
||||
Route::with_handler_and_name(
|
||||
"/artists",
|
||||
|session: Session, db: Database, i18n: I18n| async move {
|
||||
let admin = match auth::require_admin_or_redirect(&session, &db).await {
|
||||
Ok(u) => u,
|
||||
Err(resp) => return Ok(resp),
|
||||
};
|
||||
views::artists_list(admin, i18n, &db).await?.into_response()
|
||||
},
|
||||
"admin_artists",
|
||||
),
|
||||
Route::with_handler_and_name(
|
||||
"/artists/new",
|
||||
get(|session: Session, db: Database, i18n: I18n| async move {
|
||||
let admin = match auth::require_admin_or_redirect(&session, &db).await {
|
||||
Ok(u) => u,
|
||||
Err(resp) => return Ok(resp),
|
||||
};
|
||||
views::artists_new(admin, i18n).await?.into_response()
|
||||
})
|
||||
.post(
|
||||
|session: Session, db: Database, form: RequestForm<ArtistForm>| async move {
|
||||
let admin = match auth::require_admin_or_redirect(&session, &db).await {
|
||||
Ok(u) => u,
|
||||
Err(resp) => return Ok(resp),
|
||||
};
|
||||
views::artists_create(admin, &db, form).await
|
||||
},
|
||||
),
|
||||
"admin_artists_new",
|
||||
),
|
||||
Route::with_handler_and_name(
|
||||
"/artists/{id}/edit",
|
||||
get(
|
||||
|session: Session, db: Database, i18n: I18n,
|
||||
path: Path<PathId>| async move {
|
||||
let admin = match auth::require_admin_or_redirect(&session, &db).await {
|
||||
Ok(u) => u,
|
||||
Err(resp) => return Ok(resp),
|
||||
};
|
||||
views::artists_edit(admin, i18n, &db, path.0.id)
|
||||
.await?
|
||||
.into_response()
|
||||
},
|
||||
)
|
||||
.post(
|
||||
|session: Session, db: Database, path: Path<PathId>,
|
||||
form: RequestForm<ArtistForm>| async move {
|
||||
let admin = match auth::require_admin_or_redirect(&session, &db).await {
|
||||
Ok(u) => u,
|
||||
Err(resp) => return Ok(resp),
|
||||
};
|
||||
views::artists_update(admin, &db, path.0.id, form).await
|
||||
},
|
||||
),
|
||||
"admin_artists_edit",
|
||||
),
|
||||
Route::with_handler_and_name(
|
||||
"/artists/{id}/delete",
|
||||
cot::router::method::post(
|
||||
|session: Session, db: Database, path: Path<PathId>| async move {
|
||||
let admin = match auth::require_admin_or_redirect(&session, &db).await {
|
||||
Ok(u) => u,
|
||||
Err(resp) => return Ok(resp),
|
||||
};
|
||||
views::artists_delete(admin, &db, path.0.id).await
|
||||
},
|
||||
),
|
||||
"admin_artists_delete",
|
||||
),
|
||||
Route::with_handler_and_name(
|
||||
"/artists/{id}/available-covers",
|
||||
get(
|
||||
|session: Session, db: Database, path: Path<PathId>| async move {
|
||||
let admin = match auth::require_admin_or_redirect(&session, &db).await {
|
||||
Ok(u) => u,
|
||||
Err(resp) => return Ok(resp),
|
||||
};
|
||||
views::artists_available_covers(admin, &db, path.0.id).await
|
||||
},
|
||||
),
|
||||
"admin_artists_available_covers",
|
||||
),
|
||||
Route::with_handler_and_name(
|
||||
"/artists/{id}/set-image",
|
||||
cot::router::method::post(
|
||||
|session: Session, db: Database, path: Path<PathId>,
|
||||
json: Json<SetImageBody>| async move {
|
||||
let admin = match auth::require_admin_or_redirect(&session, &db).await {
|
||||
Ok(u) => u,
|
||||
Err(resp) => return Ok(resp),
|
||||
};
|
||||
views::artists_set_image(admin, &db, path.0.id, json.0).await
|
||||
},
|
||||
),
|
||||
"admin_artists_set_image",
|
||||
),
|
||||
Route::with_handler_and_name(
|
||||
"/artists/{id}/upload-image",
|
||||
cot::router::method::post({
|
||||
let pool = Arc::clone(&pool);
|
||||
let pool_config = Arc::clone(&pool_config);
|
||||
move |session: Session, db: Database, path: Path<PathId>,
|
||||
json: Json<UploadImageBody>| {
|
||||
let pool = Arc::clone(&pool);
|
||||
let pool_config = Arc::clone(&pool_config);
|
||||
async move {
|
||||
let admin = match auth::require_admin_or_redirect(&session, &db).await {
|
||||
Ok(u) => u,
|
||||
Err(resp) => return Ok(resp),
|
||||
};
|
||||
let pg_pool = pool.get_or_init(|| async {
|
||||
sqlx::postgres::PgPoolOptions::new()
|
||||
.max_connections(3)
|
||||
.connect(&pool_config.database_url)
|
||||
.await
|
||||
.expect("admin pool")
|
||||
}).await;
|
||||
let (live_config, _) = AppConfig::load_with_db(&db).await;
|
||||
views::artists_upload_image(admin, &db, pg_pool, &live_config, path.0.id, json.0).await
|
||||
}
|
||||
}
|
||||
}),
|
||||
"admin_artists_upload_image",
|
||||
),
|
||||
// -- Releases -----------------------------------------------------
|
||||
Route::with_handler_and_name(
|
||||
"/releases",
|
||||
|session: Session, db: Database, i18n: I18n,
|
||||
query: UrlQuery<ReleasesQuery>| async move {
|
||||
let admin = match auth::require_admin_or_redirect(&session, &db).await {
|
||||
Ok(u) => u,
|
||||
Err(resp) => return Ok(resp),
|
||||
};
|
||||
views::releases_list(admin, i18n, &db, query.0.artist_id)
|
||||
.await?
|
||||
.into_response()
|
||||
},
|
||||
"admin_releases",
|
||||
),
|
||||
Route::with_handler_and_name(
|
||||
"/releases/new",
|
||||
get(|session: Session, db: Database, i18n: I18n| async move {
|
||||
let admin = match auth::require_admin_or_redirect(&session, &db).await {
|
||||
Ok(u) => u,
|
||||
Err(resp) => return Ok(resp),
|
||||
};
|
||||
views::releases_new(admin, i18n, &db).await?.into_response()
|
||||
})
|
||||
.post(
|
||||
|session: Session, db: Database,
|
||||
form: RequestForm<ReleaseForm>| async move {
|
||||
let admin = match auth::require_admin_or_redirect(&session, &db).await {
|
||||
Ok(u) => u,
|
||||
Err(resp) => return Ok(resp),
|
||||
};
|
||||
views::releases_create(admin, &db, form).await
|
||||
},
|
||||
),
|
||||
"admin_releases_new",
|
||||
),
|
||||
Route::with_handler_and_name(
|
||||
"/releases/{id}/edit",
|
||||
get(
|
||||
|session: Session, db: Database, i18n: I18n,
|
||||
path: Path<PathId>| async move {
|
||||
let admin = match auth::require_admin_or_redirect(&session, &db).await {
|
||||
Ok(u) => u,
|
||||
Err(resp) => return Ok(resp),
|
||||
};
|
||||
views::releases_edit(admin, i18n, &db, path.0.id)
|
||||
.await?
|
||||
.into_response()
|
||||
},
|
||||
)
|
||||
.post(
|
||||
|session: Session, db: Database, path: Path<PathId>,
|
||||
form: RequestForm<ReleaseForm>| async move {
|
||||
let admin = match auth::require_admin_or_redirect(&session, &db).await {
|
||||
Ok(u) => u,
|
||||
Err(resp) => return Ok(resp),
|
||||
};
|
||||
views::releases_update(admin, &db, path.0.id, form).await
|
||||
},
|
||||
),
|
||||
"admin_releases_edit",
|
||||
),
|
||||
Route::with_handler_and_name(
|
||||
"/releases/{id}/delete",
|
||||
cot::router::method::post(
|
||||
|session: Session, db: Database, path: Path<PathId>| async move {
|
||||
let admin = match auth::require_admin_or_redirect(&session, &db).await {
|
||||
Ok(u) => u,
|
||||
Err(resp) => return Ok(resp),
|
||||
};
|
||||
views::releases_delete(admin, &db, path.0.id).await
|
||||
},
|
||||
),
|
||||
"admin_releases_delete",
|
||||
),
|
||||
// -- Media Files --------------------------------------------------
|
||||
Route::with_handler_and_name(
|
||||
"/media-files",
|
||||
|session: Session, db: Database, i18n: I18n| async move {
|
||||
let admin = match auth::require_admin_or_redirect(&session, &db).await {
|
||||
Ok(u) => u,
|
||||
Err(resp) => return Ok(resp),
|
||||
};
|
||||
views::media_files_list(admin, i18n, &db).await?.into_response()
|
||||
},
|
||||
"admin_media_files",
|
||||
),
|
||||
Route::with_handler_and_name(
|
||||
"/media-files/{id}/delete",
|
||||
cot::router::method::post(
|
||||
|session: Session, db: Database, path: Path<PathId>| async move {
|
||||
let admin = match auth::require_admin_or_redirect(&session, &db).await {
|
||||
Ok(u) => u,
|
||||
Err(resp) => return Ok(resp),
|
||||
};
|
||||
views::media_files_delete(admin, &db, path.0.id).await
|
||||
},
|
||||
),
|
||||
"admin_media_files_delete",
|
||||
),
|
||||
// -- Jobs ---------------------------------------------------------
|
||||
Route::with_handler_and_name(
|
||||
"/jobs",
|
||||
{
|
||||
let registry = Arc::clone(&self.registry);
|
||||
move |session: Session, db: Database, i18n: I18n| {
|
||||
let registry = Arc::clone(®istry);
|
||||
async move {
|
||||
let admin = match auth::require_admin_or_redirect(&session, &db).await {
|
||||
Ok(u) => u,
|
||||
Err(resp) => return Ok(resp),
|
||||
};
|
||||
views::jobs_list(admin, i18n, &db, ®istry).await?.into_response()
|
||||
}
|
||||
}
|
||||
},
|
||||
"admin_jobs",
|
||||
),
|
||||
Route::with_handler_and_name(
|
||||
"/jobs/{name}/run",
|
||||
cot::router::method::post({
|
||||
let handle = Arc::clone(&self.scheduler_handle);
|
||||
move |session: Session, db: Database, path: Path<PathName>| {
|
||||
let handle = Arc::clone(&handle);
|
||||
async move {
|
||||
let admin = match auth::require_admin_or_redirect(&session, &db).await {
|
||||
Ok(u) => u,
|
||||
Err(resp) => return Ok(resp),
|
||||
};
|
||||
views::job_run_now(admin, &handle, &path.0.name).await
|
||||
}
|
||||
}
|
||||
}),
|
||||
"admin_job_run",
|
||||
),
|
||||
Route::with_handler_and_name(
|
||||
"/jobs/{name}/toggle",
|
||||
cot::router::method::post({
|
||||
let handle = Arc::clone(&self.scheduler_handle);
|
||||
move |session: Session, db: Database, path: Path<PathName>| {
|
||||
let handle = Arc::clone(&handle);
|
||||
async move {
|
||||
let admin = match auth::require_admin_or_redirect(&session, &db).await {
|
||||
Ok(u) => u,
|
||||
Err(resp) => return Ok(resp),
|
||||
};
|
||||
views::job_toggle_enabled(admin, &db, &handle, &path.0.name).await
|
||||
}
|
||||
}
|
||||
}),
|
||||
"admin_job_toggle",
|
||||
),
|
||||
Route::with_handler_and_name(
|
||||
"/jobs/{name}/cron",
|
||||
cot::router::method::post({
|
||||
let handle = Arc::clone(&self.scheduler_handle);
|
||||
move |session: Session, db: Database, path: Path<PathName>,
|
||||
form: RequestForm<CronForm>| {
|
||||
let handle = Arc::clone(&handle);
|
||||
async move {
|
||||
let admin = match auth::require_admin_or_redirect(&session, &db).await {
|
||||
Ok(u) => u,
|
||||
Err(resp) => return Ok(resp),
|
||||
};
|
||||
views::job_update_cron(admin, &db, &handle, &path.0.name, form).await
|
||||
}
|
||||
}
|
||||
}),
|
||||
"admin_job_cron",
|
||||
),
|
||||
Route::with_handler_and_name(
|
||||
"/jobs/{name}/runs/{run_id}",
|
||||
{
|
||||
move |session: Session, db: Database, i18n: I18n,
|
||||
path: Path<PathNameRunId>| {
|
||||
async move {
|
||||
let admin = match auth::require_admin_or_redirect(&session, &db).await {
|
||||
Ok(u) => u,
|
||||
Err(resp) => return Ok(resp),
|
||||
};
|
||||
views::job_run_detail(admin, i18n, &db, &path.0.name, path.0.run_id)
|
||||
.await?
|
||||
.into_response()
|
||||
}
|
||||
}
|
||||
},
|
||||
"admin_job_run_detail",
|
||||
),
|
||||
Route::with_handler_and_name(
|
||||
"/jobs/{name}",
|
||||
{
|
||||
let pool = Arc::clone(&pool);
|
||||
let pool_config = Arc::clone(&pool_config);
|
||||
move |session: Session, db: Database, i18n: I18n,
|
||||
path: Path<PathName>| {
|
||||
let pool = Arc::clone(&pool);
|
||||
let pool_config = Arc::clone(&pool_config);
|
||||
async move {
|
||||
let admin = match auth::require_admin_or_redirect(&session, &db).await {
|
||||
Ok(u) => u,
|
||||
Err(resp) => return Ok(resp),
|
||||
};
|
||||
let pg_pool = pool.get_or_init(|| async {
|
||||
sqlx::postgres::PgPoolOptions::new()
|
||||
.max_connections(3)
|
||||
.connect(&pool_config.database_url)
|
||||
.await
|
||||
.expect("admin pool")
|
||||
}).await;
|
||||
views::job_detail(admin, i18n, &db, pg_pool, &path.0.name)
|
||||
.await?
|
||||
.into_response()
|
||||
}
|
||||
}
|
||||
},
|
||||
"admin_job_detail",
|
||||
),
|
||||
// -- Reviews: clear -----------------------------------------------
|
||||
Route::with_handler_and_name(
|
||||
"/reviews/clear",
|
||||
cot::router::method::post(
|
||||
|session: Session, db: Database,
|
||||
query: UrlQuery<ReviewsQuery>| async move {
|
||||
let admin =
|
||||
match auth::require_admin_or_redirect(&session, &db).await {
|
||||
Ok(u) => u,
|
||||
Err(resp) => return Ok(resp),
|
||||
};
|
||||
views::reviews_clear(admin, &db, query.0.status.as_deref()).await
|
||||
},
|
||||
),
|
||||
"admin_reviews_clear",
|
||||
),
|
||||
// -- Reviews ------------------------------------------------------
|
||||
Route::with_handler_and_name(
|
||||
"/reviews",
|
||||
{
|
||||
let pool = Arc::clone(&pool);
|
||||
let pool_config = Arc::clone(&pool_config);
|
||||
move |session: Session, db: Database, i18n: I18n,
|
||||
query: UrlQuery<ReviewsQuery>| {
|
||||
let pool = Arc::clone(&pool);
|
||||
let pool_config = Arc::clone(&pool_config);
|
||||
async move {
|
||||
let admin = match auth::require_admin_or_redirect(&session, &db).await {
|
||||
Ok(u) => u,
|
||||
Err(resp) => return Ok(resp),
|
||||
};
|
||||
let pg_pool = pool.get_or_init(|| async {
|
||||
sqlx::postgres::PgPoolOptions::new()
|
||||
.max_connections(3)
|
||||
.connect(&pool_config.database_url)
|
||||
.await
|
||||
.expect("admin pool")
|
||||
}).await;
|
||||
views::reviews_list(admin, i18n, &db, pg_pool, query.0.status.as_deref())
|
||||
.await?
|
||||
.into_response()
|
||||
}
|
||||
}
|
||||
},
|
||||
"admin_reviews",
|
||||
),
|
||||
Route::with_handler_and_name(
|
||||
"/reviews/{id}",
|
||||
|session: Session, db: Database, i18n: I18n,
|
||||
path: Path<PathId>| async move {
|
||||
let admin = match auth::require_admin_or_redirect(&session, &db).await {
|
||||
Ok(u) => u,
|
||||
Err(resp) => return Ok(resp),
|
||||
};
|
||||
views::review_detail(admin, i18n, &db, path.0.id)
|
||||
.await?
|
||||
.into_response()
|
||||
},
|
||||
"admin_review_detail",
|
||||
),
|
||||
Route::with_handler_and_name(
|
||||
"/reviews/{id}/approve",
|
||||
cot::router::method::post({
|
||||
let config = Arc::clone(&self.config);
|
||||
let pool = Arc::clone(&pool);
|
||||
let pool_config = Arc::clone(&pool_config);
|
||||
move |session: Session, db: Database, path: Path<PathId>| {
|
||||
let config = Arc::clone(&config);
|
||||
let pool = Arc::clone(&pool);
|
||||
let pool_config = Arc::clone(&pool_config);
|
||||
async move {
|
||||
let admin = match auth::require_admin_or_redirect(&session, &db).await {
|
||||
Ok(u) => u,
|
||||
Err(resp) => return Ok(resp),
|
||||
};
|
||||
let pg_pool = pool.get_or_init(|| async {
|
||||
sqlx::postgres::PgPoolOptions::new()
|
||||
.max_connections(3)
|
||||
.connect(&pool_config.database_url)
|
||||
.await
|
||||
.expect("admin pool")
|
||||
}).await;
|
||||
views::review_approve(admin, &config, &db, pg_pool, path.0.id).await
|
||||
}
|
||||
}
|
||||
}),
|
||||
"admin_review_approve",
|
||||
),
|
||||
Route::with_handler_and_name(
|
||||
"/reviews/{id}/reject",
|
||||
cot::router::method::post(
|
||||
|session: Session, db: Database, path: Path<PathId>| async move {
|
||||
let admin = match auth::require_admin_or_redirect(&session, &db).await {
|
||||
Ok(u) => u,
|
||||
Err(resp) => return Ok(resp),
|
||||
};
|
||||
views::review_reject(admin, &db, path.0.id).await
|
||||
},
|
||||
),
|
||||
"admin_review_reject",
|
||||
),
|
||||
Route::with_handler_and_name(
|
||||
"/reviews/{id}/requeue",
|
||||
cot::router::method::post(
|
||||
|session: Session, db: Database, path: Path<PathId>| async move {
|
||||
let admin = match auth::require_admin_or_redirect(&session, &db).await {
|
||||
Ok(u) => u,
|
||||
Err(resp) => return Ok(resp),
|
||||
};
|
||||
views::review_requeue(admin, &db, path.0.id).await
|
||||
},
|
||||
),
|
||||
"admin_review_requeue",
|
||||
),
|
||||
])
|
||||
}
|
||||
|
||||
@@ -247,6 +758,12 @@ impl App for AdminApp {
|
||||
all.extend(cot::db::migrations::wrap_migrations(
|
||||
crate::user::db_migrations::MIGRATIONS,
|
||||
));
|
||||
all.extend(cot::db::migrations::wrap_migrations(
|
||||
crate::music::db_migrations::MIGRATIONS,
|
||||
));
|
||||
all.extend(cot::db::migrations::wrap_migrations(
|
||||
crate::scheduler::db_migrations::MIGRATIONS,
|
||||
));
|
||||
all
|
||||
}
|
||||
}
|
||||
|
||||
+1141
-11
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user