Skip to content

Commit db2cfaa

Browse files
authored
Monitor people (#654)
* chore(frontend): display stringified stat in html * chore: remove animation * Revert "chore: remove animation" This reverts commit 3ffa4be. * feat(backend): preference for disabling navigation animations * feat(frontend): allow updating preference to disable animation * feat(frontend): respect new preference * feat(database): migration to rename column * feat(backend): adapt to new database changes * feat(database): migration to change metadata reminder to media reminder * feat(backend): adapt to new database changes * feat(backend): add more reasons to user_to_person * refactor(backend): make fn to update user_to_entity more generic * refactor(backend): make fn to get entities in collection more generic * fix(backend): use correct filter get user to entity association * refactor(backend): make fn to add entities to collection more generic * chore(frontend): adapt to new gql schema * refactor(frontend): fn to extract the required params * feat(frontend): move "update person" btn to menu * feat(backend): allow setting media reminder for people * feat(database): add check constraint to user_to_entity * Revert "feat(database): add check constraint to user_to_entity" This reverts commit 9d9f399. * refactor(frontend): ext cmpt to add media reminder * perf(frontend): do not re-run root loader on every run * fix(frontend): use correct map key * fix(frontend): make hidden input readonly * refactor(frontend): move delete reminder to common action * feat(frontend): component to add hidden location input * feat(frontend): new common action route * feat(frontend): use correct key for toast messages * feat(frontend): remove usage of useless refs * feat(database): add migration to add new field to user_to_entity table * fix(database): add correct check for adding column * feat(backend): respect new `needs_to_be_updated` field * fix(database): migration should make sure all ute are updated * fix(backend): update user to entity correctly * chore(backend): remove useless checks * chore(frontend): remove old upgrade code * refactor(graphql): extract fragment for reminders * feat(backend): get additional user person details * feat(frontend): allow adding reminders for people * fix(frontend): change default text for people * feat(frontend): display reminder for people * feat(backend): allow toggling monitor on people * refactor(frontend): action to monitor media * feat(frontend): display if media is monitored * chore(backend): add additional for progress update * fix(frontend): update progress for shows and podcasts correctly Fixes #659. * refactor(backend): change name of function to get users to notify * feat(backend): fn to get users to notify for person * feat(config,backend): remove useless config param Signed-off-by Diptesh Choudhuri <ignisda2001@gmail.com> * refactor(backend): change name of enum members * feat(backend): add new pref for person notification * feat(frontend): adapt to new gql schema * feat(backend): add new media state change enum variant * refactor(backend): extract fn to update metadata and notify user * perf(backend): do not send a lot of data to update person * perf(backend): follow clippy lints * feat(backend): notify users when person updated * feat(backend): add a development only mutation * feat(backend): scaffold basic code for cron job * feat(database): add constraint to `user_to_entity` * refactor(backend): change description of preference * feat(backend): generate notifications * fix(backend): allow tmdb to get all related media for people * fix(backend): add debug statement for notif * fix(backend): change notification text for person assoc * build(backend): bump version * feat(config): param to disable apalis * feat(backend): respect new config param * refactor(backend): code more readable
1 parent 4c1006a commit db2cfaa

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+1202
-844
lines changed

Cargo.lock

+1-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

apps/backend/Cargo.toml

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "ryot"
3-
version = "4.2.10"
3+
version = "4.2.11"
44
edition = "2021"
55
repository = "https://github.com/IgnisDa/ryot"
66
license = "GPL-3.0"
@@ -60,7 +60,6 @@ scraper = "0.18.1"
6060
sea-orm = { workspace = true }
6161
sea-orm-migration = { workspace = true }
6262
sea-query = "0.30.7"
63-
semver = "1.0.22"
6463
serde = { workspace = true }
6564
serde_json = { workspace = true }
6665
serde_with = { version = "3.6.1", features = ["chrono_0_4"] }

apps/backend/src/background.rs

+20-31
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ use serde::{Deserialize, Serialize};
88
use strum::Display;
99

1010
use crate::{
11-
entities::{metadata, person},
1211
exporter::ExporterService,
1312
fitness::resolver::ExerciseService,
1413
importer::{DeployImportJobInput, ImporterService},
@@ -47,7 +46,14 @@ pub async fn media_jobs(_information: ScheduledJob, ctx: JobContext) -> Result<(
4746
if env::var("DISABLE_UPDATE_WATCHLIST_MEDIA").is_err() {
4847
tracing::trace!("Checking for updates for media in Watchlist");
4948
service
50-
.update_watchlist_media_and_send_notifications()
49+
.update_watchlist_metadata_and_send_notifications()
50+
.await
51+
.unwrap();
52+
}
53+
if env::var("DISABLE_UPDATE_MONITORED_PEOPLE").is_err() {
54+
tracing::trace!("Checking for updates for monitored people");
55+
service
56+
.update_monitored_people_and_send_notifications()
5157
.await
5258
.unwrap();
5359
}
@@ -138,14 +144,14 @@ pub async fn perform_core_application_job(
138144
Ok(())
139145
}
140146

141-
// The background jobs which can be throttled.
147+
// The background jobs which can be deployed by the application.
142148
#[derive(Debug, Deserialize, Serialize, Display)]
143149
pub enum ApplicationJob {
144-
ImportFromExternalSource(i32, DeployImportJobInput),
150+
ImportFromExternalSource(i32, Box<DeployImportJobInput>),
145151
ReEvaluateUserWorkouts(i32),
146-
UpdateMetadata(metadata::Model),
152+
UpdateMetadata(i32),
147153
UpdateExerciseJob(Exercise),
148-
UpdatePerson(person::Model),
154+
UpdatePerson(i32),
149155
RecalculateCalendarEvents,
150156
AssociateGroupWithMetadata(MetadataLot, MetadataSource, String),
151157
ReviewPosted(ReviewPostedEvent),
@@ -181,31 +187,14 @@ pub async fn perform_application_job(
181187
.re_evaluate_user_workouts(user_id)
182188
.await
183189
.is_ok(),
184-
ApplicationJob::UpdateMetadata(metadata) => {
185-
let notifications = misc_service.update_metadata(metadata.id).await.unwrap();
186-
if !notifications.is_empty() {
187-
let users_to_notify = misc_service
188-
.users_to_be_notified_for_state_changes()
189-
.await
190-
.unwrap()
191-
.get(&metadata.id)
192-
.cloned()
193-
.unwrap_or_default();
194-
for notification in notifications {
195-
for user_id in users_to_notify.iter() {
196-
misc_service
197-
.send_media_state_changed_notification_for_user(
198-
user_id.to_owned(),
199-
&notification,
200-
)
201-
.await
202-
.ok();
203-
}
204-
}
205-
}
206-
true
207-
}
208-
ApplicationJob::UpdatePerson(person) => misc_service.update_person(person.id).await.is_ok(),
190+
ApplicationJob::UpdateMetadata(metadata_id) => misc_service
191+
.update_metadata_and_notify_users(metadata_id)
192+
.await
193+
.is_ok(),
194+
ApplicationJob::UpdatePerson(person_id) => misc_service
195+
.update_person_and_notify_users(person_id)
196+
.await
197+
.is_ok(),
209198
ApplicationJob::UpdateExerciseJob(exercise) => {
210199
exercise_service.update_exercise(exercise).await.is_ok()
211200
}

apps/backend/src/entities/collection_to_entity.rs

+4-10
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use async_trait::async_trait;
44
use sea_orm::entity::prelude::*;
55
use serde::{Deserialize, Serialize};
66

7-
use crate::utils::{associate_user_with_metadata, associate_user_with_person};
7+
use crate::utils::associate_user_with_entity;
88

99
use super::prelude::Collection;
1010

@@ -106,15 +106,9 @@ impl ActiveModelBehavior for ActiveModel {
106106
.one(db)
107107
.await?
108108
.unwrap();
109-
if let Some(metadata_id) = model.metadata_id {
110-
associate_user_with_metadata(&collection.user_id, &metadata_id, db)
111-
.await
112-
.ok();
113-
} else if let Some(person_id) = model.person_id {
114-
associate_user_with_person(&collection.user_id, &person_id, db)
115-
.await
116-
.ok();
117-
}
109+
associate_user_with_entity(&collection.user_id, model.metadata_id, model.person_id, db)
110+
.await
111+
.ok();
118112
}
119113
Ok(model)
120114
}

apps/backend/src/entities/review.rs

+4-10
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use crate::{
1111
ImportOrExportItemReviewComment, SeenAnimeExtraInformation, SeenMangaExtraInformation,
1212
SeenPodcastExtraInformation, SeenShowExtraInformation,
1313
},
14-
utils::{associate_user_with_metadata, associate_user_with_person},
14+
utils::associate_user_with_entity,
1515
};
1616

1717
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
@@ -118,15 +118,9 @@ impl ActiveModelBehavior for ActiveModel {
118118
C: ConnectionTrait,
119119
{
120120
if insert {
121-
if let Some(metadata_id) = model.metadata_id {
122-
associate_user_with_metadata(&model.user_id, &metadata_id, db)
123-
.await
124-
.ok();
125-
} else if let Some(person_id) = model.person_id {
126-
associate_user_with_person(&model.user_id, &person_id, db)
127-
.await
128-
.ok();
129-
}
121+
associate_user_with_entity(&model.user_id, model.metadata_id, model.person_id, db)
122+
.await
123+
.ok();
130124
}
131125
Ok(model)
132126
}

apps/backend/src/entities/seen.rs

+3-14
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,17 @@
22
33
use async_graphql::SimpleObject;
44
use async_trait::async_trait;
5-
use chrono::{NaiveDate, Utc};
5+
use chrono::NaiveDate;
66
use database::SeenState;
77
use sea_orm::{entity::prelude::*, ActiveValue};
8-
use sea_query::Expr;
98
use serde::{Deserialize, Serialize};
109

1110
use crate::{
12-
entities::{prelude::UserToEntity, user_to_entity},
1311
models::media::{
1412
SeenAnimeExtraInformation, SeenMangaExtraInformation, SeenPodcastExtraInformation,
1513
SeenShowExtraInformation,
1614
},
17-
utils::associate_user_with_metadata,
15+
utils::associate_user_with_entity,
1816
};
1917

2018
// When updating a media item's progress, here are the things that should happen:
@@ -95,19 +93,10 @@ impl ActiveModelBehavior for ActiveModel {
9593
C: ConnectionTrait,
9694
{
9795
if insert {
98-
associate_user_with_metadata(&model.user_id, &model.metadata_id, db)
96+
associate_user_with_entity(&model.user_id, Some(model.metadata_id), None, db)
9997
.await
10098
.ok();
10199
}
102-
UserToEntity::update_many()
103-
.filter(user_to_entity::Column::UserId.eq(model.user_id))
104-
.filter(user_to_entity::Column::MetadataId.eq(model.metadata_id))
105-
.col_expr(
106-
user_to_entity::Column::LastUpdatedOn,
107-
Expr::value(Utc::now()),
108-
)
109-
.exec(db)
110-
.await?;
111100
Ok(model)
112101
}
113102
}

apps/backend/src/entities/user_to_entity.rs

+7-5
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,16 @@ pub struct Model {
2121
pub person_id: Option<i32>,
2222
pub metadata_id: Option<i32>,
2323
pub exercise_id: Option<String>,
24-
pub metadata_monitored: Option<bool>,
25-
pub metadata_units_consumed: Option<i32>,
26-
pub metadata_reminder: Option<UserMediaReminder>,
2724
pub metadata_ownership: Option<UserMediaOwnership>,
28-
#[graphql(skip)]
29-
pub media_reason: Option<Vec<UserToMediaReason>>,
25+
pub metadata_units_consumed: Option<i32>,
3026
pub exercise_extra_information: Option<UserToExerciseExtraInformation>,
3127
pub exercise_num_times_interacted: Option<i32>,
28+
#[graphql(skip)]
29+
pub media_reason: Option<Vec<UserToMediaReason>>,
30+
pub media_reminder: Option<UserMediaReminder>,
31+
pub media_monitored: Option<bool>,
32+
#[graphql(skip)]
33+
pub needs_to_be_updated: Option<bool>,
3234
}
3335

3436
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]

apps/backend/src/fitness/resolver.rs

+7-6
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,7 @@ use crate::{
3838
GithubExerciseAttributes, UserExerciseInput, UserWorkoutInput, UserWorkoutSetRecord,
3939
WorkoutListItem, WorkoutSetRecord,
4040
},
41-
ChangeCollectionToEntityInput, EntityLot, SearchDetails, SearchInput, SearchResults,
42-
StoredUrl,
41+
ChangeCollectionToEntityInput, SearchDetails, SearchInput, SearchResults, StoredUrl,
4342
},
4443
traits::{AuthProvider, GraphqlRepresentation},
4544
utils::{add_entity_to_collection, entity_in_collections, get_ilike_query, partial_user_by_id},
@@ -371,8 +370,10 @@ impl ExerciseService {
371370
let collections = entity_in_collections(
372371
&self.db,
373372
user_id,
374-
input.exercise_id.clone(),
375-
EntityLot::Exercise,
373+
None,
374+
None,
375+
None,
376+
Some(input.exercise_id.clone()),
376377
)
377378
.await?;
378379
let mut resp = UserExerciseDetails {
@@ -764,8 +765,8 @@ impl ExerciseService {
764765
user_id,
765766
ChangeCollectionToEntityInput {
766767
collection_name: DefaultCollection::Custom.to_string(),
767-
entity_id: exercise.id.clone(),
768-
entity_lot: EntityLot::Exercise,
768+
exercise_id: Some(exercise.id.clone()),
769+
..Default::default()
769770
},
770771
)
771772
.await?;

apps/backend/src/importer/mod.rs

+14-7
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ use crate::{
2525
CreateOrUpdateCollectionInput, ImportOrExportItemIdentifier, ImportOrExportMediaItem,
2626
PostReviewInput, ProgressUpdateInput,
2727
},
28-
BackgroundJob, ChangeCollectionToEntityInput, EntityLot,
28+
BackgroundJob, ChangeCollectionToEntityInput,
2929
},
3030
traits::AuthProvider,
3131
users::UserReviewScale,
@@ -235,7 +235,10 @@ impl ImporterService {
235235
.media_service
236236
.perform_application_job
237237
.clone()
238-
.push(ApplicationJob::ImportFromExternalSource(user_id, input))
238+
.push(ApplicationJob::ImportFromExternalSource(
239+
user_id,
240+
Box::new(input),
241+
))
239242
.await
240243
.unwrap();
241244
Ok(job.to_string())
@@ -267,15 +270,19 @@ impl ImporterService {
267270
Ok(reports)
268271
}
269272

270-
pub async fn start_importing(&self, user_id: i32, input: DeployImportJobInput) -> Result<()> {
273+
pub async fn start_importing(
274+
&self,
275+
user_id: i32,
276+
input: Box<DeployImportJobInput>,
277+
) -> Result<()> {
271278
match input.source {
272279
ImportSource::StrongApp => self.import_exercises(user_id, input).await,
273280
_ => self.import_media(user_id, input).await,
274281
}
275282
}
276283

277284
#[instrument(skip(self, input))]
278-
async fn import_exercises(&self, user_id: i32, input: DeployImportJobInput) -> Result<()> {
285+
async fn import_exercises(&self, user_id: i32, input: Box<DeployImportJobInput>) -> Result<()> {
279286
let db_import_job = self.start_import_job(user_id, input.source).await?;
280287
let import = match input.source {
281288
ImportSource::StrongApp => {
@@ -302,7 +309,7 @@ impl ImporterService {
302309
}
303310

304311
#[instrument(skip(self, input))]
305-
async fn import_media(&self, user_id: i32, input: DeployImportJobInput) -> Result<()> {
312+
async fn import_media(&self, user_id: i32, input: Box<DeployImportJobInput>) -> Result<()> {
306313
let db_import_job = self.start_import_job(user_id, input.source).await?;
307314
let mut import = match input.source {
308315
ImportSource::MediaTracker => media_tracker::import(input.media_tracker.unwrap())
@@ -469,8 +476,8 @@ impl ImporterService {
469476
user_id,
470477
ChangeCollectionToEntityInput {
471478
collection_name: col.to_string(),
472-
entity_id: metadata.id.to_string(),
473-
entity_lot: EntityLot::Media,
479+
metadata_id: Some(metadata.id),
480+
..Default::default()
474481
},
475482
)
476483
.await

apps/backend/src/main.rs

+11-7
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
use std::{
22
env,
33
fs::{self, create_dir_all},
4-
io::{Error as IoError, ErrorKind as IoErrorKind},
54
path::PathBuf,
65
str::FromStr,
76
sync::{Arc, Mutex},
@@ -32,7 +31,7 @@ use rs_utils::PROJECT_NAME;
3231
use sea_orm::{ConnectOptions, Database, EntityTrait, PaginatorTrait};
3332
use sea_orm_migration::MigratorTrait;
3433
use sqlx::{pool::PoolOptions, SqlitePool};
35-
use tokio::{net::TcpListener, try_join};
34+
use tokio::{join, net::TcpListener};
3635
use tower_http::{
3736
catch_panic::CatchPanicLayer as TowerCatchPanicLayer, cors::CorsLayer as TowerCorsLayer,
3837
trace::TraceLayer as TowerTraceLayer,
@@ -95,6 +94,7 @@ async fn main() -> Result<()> {
9594
let user_cleanup_every = config.scheduler.user_cleanup_every;
9695
let pull_every = config.integration.pull_every;
9796
let max_file_size = config.server.max_file_size;
97+
let disable_background_jobs = config.server.disable_background_jobs;
9898
fs::write(
9999
&config.server.config_dump_path,
100100
serde_json::to_string_pretty(&config)?,
@@ -248,7 +248,7 @@ async fn main() -> Result<()> {
248248
let exercise_service_1 = app_services.exercise_service.clone();
249249

250250
let monitor = async {
251-
let mn = Monitor::new()
251+
Monitor::new()
252252
// cron jobs
253253
.register_with_count(1, move |c| {
254254
WorkerBuilder::new(format!("general_user_cleanup-{c}"))
@@ -313,17 +313,21 @@ async fn main() -> Result<()> {
313313
.build_fn(perform_application_job)
314314
})
315315
.run()
316-
.await;
317-
Ok(mn)
316+
.await
317+
.unwrap();
318318
};
319319

320320
let http = async {
321321
axum::serve(listener, app_routes.into_make_service())
322322
.await
323-
.map_err(|e| IoError::new(IoErrorKind::Interrupted, e))
323+
.unwrap();
324324
};
325325

326-
let _res = try_join!(monitor, http).expect("Could not start services");
326+
if disable_background_jobs {
327+
join!(http);
328+
} else {
329+
join!(monitor, http);
330+
}
327331

328332
Ok(())
329333
}

0 commit comments

Comments
 (0)