Skip to content
This repository was archived by the owner on Feb 24, 2025. It is now read-only.

Commit d840274

Browse files
committed
feat: complete activity reading projections
1 parent 1af765c commit d840274

File tree

8 files changed

+159
-31
lines changed

8 files changed

+159
-31
lines changed

aes256.key

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
d765fcb8ae87f29c4de1645728ff57e7668e4c6be695fdf38d5cdd90a23f6456
1+
a3f3447d8dd79340400452d75fcf6c7d89134b65bb1c217f334773c79712b8e3

src/models/activities.rs

Lines changed: 76 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1-
use std::fmt;
2-
1+
use crate::utils::config::load_config_sync;
32
use bson::oid::ObjectId;
4-
use chrono::DateTime;
5-
use serde::{de::{self, Visitor}, Deserialize, Deserializer, Serialize};
3+
use chrono::{DateTime, NaiveDateTime, TimeZone};
4+
use serde::{
5+
de::{self, Visitor},
6+
Deserialize, Deserializer, Serialize, Serializer,
7+
};
8+
use std::{fmt, str::FromStr};
69

710
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash)]
811
#[serde(rename_all = "kebab-case")]
@@ -49,10 +52,10 @@ pub enum SpecialActivityCategory {
4952
Other,
5053
}
5154

52-
5355
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
5456
pub struct ActivityMember {
55-
pub _id: String, // ObjectId
57+
#[serde(serialize_with = "objectid_to_string", deserialize_with = "string_to_objectid")]
58+
pub _id: ObjectId, // ObjectId
5659
pub status: ActivityMemberStatus,
5760
pub impression: Option<String>,
5861
pub duration: f64,
@@ -66,10 +69,53 @@ pub struct ActivityMemberHistory {
6669
pub impression: String,
6770
pub duration: f64,
6871
pub time: String,
69-
pub actioner: String, // ObjectId
72+
#[serde(serialize_with = "objectid_to_string", deserialize_with = "string_to_objectid")]
73+
pub actioner: ObjectId, // ObjectId
7074
pub action: ActivityMemberStatus,
7175
}
7276

77+
pub fn objectid_to_string<S>(value: &ObjectId, serializer: S) -> Result<S::Ok, S::Error>
78+
where
79+
S: Serializer,
80+
{
81+
serializer.serialize_str(&value.to_hex())
82+
}
83+
84+
pub fn string_to_objectid<'de, D>(deserializer: D) -> Result<ObjectId, D::Error>
85+
where
86+
D: Deserializer<'de>,
87+
{
88+
struct ObjectIdOrStringVisitor;
89+
90+
impl<'de> Visitor<'de> for ObjectIdOrStringVisitor {
91+
type Value = ObjectId;
92+
93+
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
94+
formatter.write_str("string or ObjectId")
95+
}
96+
97+
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
98+
where
99+
E: de::Error,
100+
{
101+
ObjectId::from_str(v).map_err(de::Error::custom)
102+
}
103+
104+
fn visit_map<M>(self, mut map: M) -> Result<Self::Value, M::Error>
105+
where
106+
M: de::MapAccess<'de>,
107+
{
108+
if let Some((key, value)) = map.next_entry::<String, String>()? {
109+
if key == "$oid" {
110+
return self.visit_str(&value);
111+
}
112+
}
113+
Err(de::Error::custom("expected a map with a key of '$oid'"))
114+
}
115+
}
116+
117+
deserializer.deserialize_any(ObjectIdOrStringVisitor)
118+
}
73119

74120
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
75121
#[serde(rename_all = "camelCase")]
@@ -80,13 +126,13 @@ pub struct Activity {
80126
pub activity_type: ActivityType,
81127
pub name: String,
82128
pub description: Option<String>,
83-
pub duration: Option<f64>,
84129
#[serde(deserialize_with = "datetime_or_u64")]
85130
pub date: u64,
86131
#[serde(deserialize_with = "datetime_or_u64")]
87132
pub created_at: u64,
88133
#[serde(deserialize_with = "datetime_or_u64")]
89134
pub updated_at: u64,
135+
#[serde(serialize_with = "objectid_to_string", deserialize_with = "string_to_objectid")]
90136
pub creator: ObjectId, // ObjectId
91137
pub status: ActivityStatus,
92138
pub members: Option<Vec<ActivityMember>>,
@@ -98,7 +144,9 @@ pub fn datetime_or_u64<'de, D>(deserializer: D) -> Result<u64, D::Error>
98144
where
99145
D: Deserializer<'de>,
100146
{
101-
struct DateTimeOrU64;
147+
struct DateTimeOrU64 {
148+
timezone: chrono::FixedOffset,
149+
}
102150

103151
impl<'de> Visitor<'de> for DateTimeOrU64 {
104152
type Value = u64;
@@ -125,11 +173,26 @@ where
125173
where
126174
E: de::Error,
127175
{
128-
DateTime::parse_from_rfc3339(value)
129-
.map_err(de::Error::custom)
130-
.and_then(|dt| Ok(dt.timestamp() as u64))
176+
if let Ok(dt) = DateTime::parse_from_rfc3339(value) {
177+
Ok(dt.timestamp() as u64)
178+
} else if let Ok(naive_dt) = NaiveDateTime::parse_from_str(value, "%Y-%m-%d %H:%M:%S") {
179+
let dt = self
180+
.timezone
181+
.from_local_datetime(&naive_dt)
182+
.single()
183+
.ok_or_else(|| {
184+
de::Error::custom("Invalid datetime for the specified timezone")
185+
})?;
186+
Ok(dt.timestamp() as u64)
187+
} else {
188+
Err(de::Error::custom("Invalid datetime format"))
189+
}
131190
}
132191
}
133192

134-
deserializer.deserialize_any(DateTimeOrU64)
193+
let config = load_config_sync().unwrap();
194+
let offset: i32 = config.timezone.parse().unwrap();
195+
let timezone = chrono::FixedOffset::east_opt(offset * 3600).unwrap();
196+
let visitor = DateTimeOrU64 { timezone };
197+
deserializer.deserialize_any(visitor)
135198
}

src/models/users.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use bson::oid::ObjectId;
66
use mongodb::Collection;
77
use serde::{Deserialize, Serialize};
88
use std::str::FromStr;
9+
// use crate::models::activities::{objectid_to_string, string_to_objectid};
910

1011
#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq, Hash)]
1112
#[serde(rename_all = "kebab-case")]

src/routers/activities/insert.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use crate::{models::{
22
activities::{Activity, ActivityStatus, ActivityType}, groups::GroupPermission, response::{ErrorResponse, ResponseStatus, SuccessResponse}
33
}, utils::jwt::UserData};
44
use axum::{
5-
extract::{Extension, Path, Query},
5+
extract::{Extension, Query},
66
http::StatusCode,
77
response::{IntoResponse, Json},
88
};

src/routers/activities/read.rs

Lines changed: 65 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use crate::{
22
models::{
33
activities::Activity,
44
groups::GroupPermission,
5-
response::{ErrorResponse, Response as ZVMSResponse, ResponseStatus, SuccessResponse},
5+
response::{ErrorResponse, ResponseStatus, SuccessResponse},
66
},
77
utils::jwt::UserData,
88
};
@@ -13,7 +13,7 @@ use axum::{
1313
};
1414
use bson::{doc, from_document, oid::ObjectId};
1515
use futures::stream::TryStreamExt;
16-
use mongodb::{Collection, Database};
16+
use mongodb::{options::FindOneOptions, Collection, Database};
1717
use serde::{Deserialize, Serialize};
1818
use std::sync::Arc;
1919
use tokio::sync::Mutex;
@@ -53,15 +53,64 @@ pub async fn read_all(
5353
let query = query.unwrap_or("".to_string());
5454
let db = db.lock().await;
5555
let collection: Collection<Activity> = db.collection("activities");
56+
let target = if user.perms.contains(&GroupPermission::Auditor) || user.perms.contains(&GroupPermission::Admin) {
57+
"pending"
58+
} else {
59+
""
60+
};
5661
let pipeline = vec![
5762
doc! {"$match": {"name": {"$regex": query, "$options": "i"}}},
63+
doc! {"$sort": {"_id": -1}},
64+
doc! {"$project": {
65+
"name": 1,
66+
"type": 1,
67+
"status": 1,
68+
"date": 1,
69+
"createdAt": 1,
70+
"updatedAt": 1,
71+
"creator": 1,
72+
"members": {
73+
"$filter": {
74+
"input": "$members",
75+
"as": "member",
76+
"cond": {"$eq": ["$$member.status", target]}
77+
}
78+
}
79+
}
80+
},
81+
doc! {"$project": {
82+
"members.history": 0,
83+
"members.impression": 0,
84+
"members.images": 0,
85+
}},
5886
doc! {"$skip": (page - 1) * perpage},
5987
doc! {"$limit": perpage},
6088
];
61-
let mut cursor = collection.aggregate(pipeline, None).await.unwrap();
89+
let cursor = collection.aggregate(pipeline, None).await;
90+
if let Err(e) = cursor {
91+
let response: ErrorResponse = ErrorResponse {
92+
status: ResponseStatus::Error,
93+
code: 500,
94+
message: "Failed to read activity: ".to_string() + &e.to_string(),
95+
}
96+
.into();
97+
let response = serde_json::to_string(&response).unwrap();
98+
return (StatusCode::INTERNAL_SERVER_ERROR, Json(response));
99+
}
100+
let mut cursor = cursor.unwrap();
62101
let mut activities = Vec::new();
63102
loop {
64103
let doc_result = cursor.try_next().await;
104+
if let Err(e) = doc_result {
105+
let response: ErrorResponse = ErrorResponse {
106+
status: ResponseStatus::Error,
107+
code: 500,
108+
message: "Failed to read activity: ".to_string() + &e.to_string(),
109+
}
110+
.into();
111+
let response = serde_json::to_string(&response).unwrap();
112+
return (StatusCode::INTERNAL_SERVER_ERROR, Json(response));
113+
}
65114
if let Ok(Some(document)) = doc_result {
66115
match from_document::<Activity>(document) {
67116
Ok(activity) => activities.push(activity),
@@ -72,7 +121,6 @@ pub async fn read_all(
72121
message: "Failed to read activity: ".to_string() + &e.to_string(),
73122
}
74123
.into();
75-
println!("{:?}", response);
76124
let response = serde_json::to_string(&response).unwrap();
77125
return (StatusCode::INTERNAL_SERVER_ERROR, Json(response));
78126
}
@@ -86,9 +134,7 @@ pub async fn read_all(
86134
status: ResponseStatus::Error,
87135
code: 404,
88136
message: "No activities found".to_string(),
89-
}
90-
.into();
91-
let response = ZVMSResponse::<(), ()>::Error(response);
137+
};
92138
let response = serde_json::to_string(&response).unwrap();
93139
(StatusCode::NOT_FOUND, Json(response))
94140
} else {
@@ -97,9 +143,7 @@ pub async fn read_all(
97143
code: 200,
98144
data: activities,
99145
metadata: None,
100-
}
101-
.into();
102-
let response = ZVMSResponse::Success(response);
146+
};
103147
let response = serde_json::to_string(&response).unwrap();
104148
(StatusCode::OK, Json(response))
105149
}
@@ -110,7 +154,6 @@ pub async fn read_one(
110154
_: UserData,
111155
Path(id): Path<String>,
112156
) -> impl IntoResponse {
113-
println!("ID: {:?}", id);
114157
let db = client.lock().await;
115158
let collection = db.collection("activities");
116159
let id = ObjectId::parse_str(&id);
@@ -125,9 +168,18 @@ pub async fn read_one(
125168
}
126169
let id = id.unwrap();
127170
let filter = doc! {"_id": id};
128-
let result = collection.find_one(filter, None).await;
171+
let projection = doc! {
172+
"members.history": 0,
173+
"members.impression": 0,
174+
"members.images": 0,
175+
};
176+
let result = collection
177+
.find_one(
178+
filter,
179+
Some(FindOneOptions::builder().projection(projection).build()),
180+
)
181+
.await;
129182
if let Err(e) = result {
130-
println!("Failed to read activity: {:?}", e);
131183
let response = ErrorResponse {
132184
status: ResponseStatus::Error,
133185
code: 500,
@@ -139,7 +191,6 @@ pub async fn read_one(
139191
let result: Option<Activity> = result.unwrap();
140192
match result {
141193
Some(document) => {
142-
println!("{:?}", document);
143194
let response: SuccessResponse<Activity, ()> = SuccessResponse {
144195
status: ResponseStatus::Success,
145196
code: 200,
@@ -150,7 +201,6 @@ pub async fn read_one(
150201
(StatusCode::OK, Json(response))
151202
}
152203
None => {
153-
println!("Activity not found");
154204
let response = ErrorResponse {
155205
status: ResponseStatus::Error,
156206
code: 404,

src/tests/apis/activity.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,6 @@ mod tests {
5353
),
5454
creator: ObjectId::from_str("65e6fa210edc81d012ec45e3").unwrap(),
5555
status: ActivityStatus::Pending,
56-
duration: Some(1.0),
5756
date: SystemTime::now()
5857
.duration_since(SystemTime::UNIX_EPOCH)
5958
.unwrap()

src/tests/auth.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ mod tests {
5050
];
5151
let token = generate_token(sub.as_str(), TokenType::LongTerm, perms.clone());
5252
let token = token.as_str();
53+
println!("Token: {:?}", token);
5354
let result = verify_token(token.to_string());
5455
if let Err(_) = result {
5556
assert!(false);

src/utils/config.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ use tokio::fs::{read, write};
77
pub struct Config {
88
pub server: String,
99
pub database: String,
10+
pub timezone: String,
11+
pub port: u16,
1012
}
1113

1214
pub async fn load_config() -> Result<Config, Box<dyn std::error::Error>> {
@@ -26,6 +28,8 @@ pub async fn init_config() -> Result<(), Box<dyn std::error::Error>> {
2628
let config = Config {
2729
server: url,
2830
database: "zvms".to_string(),
31+
timezone: "0".to_string(),
32+
port: 8080,
2933
};
3034
save_config(config).await?;
3135
Ok(())
@@ -41,3 +45,13 @@ pub async fn load_or_init_config() -> Result<Config, Box<dyn std::error::Error>>
4145
}
4246
}
4347
}
48+
49+
pub fn load_config_sync() -> Result<Config, Box<dyn std::error::Error>> {
50+
let config = std::fs::read("config.json");
51+
if let Err(_) = config {
52+
return Err("Failed to read config file".into());
53+
}
54+
let config = config.unwrap();
55+
let config: Config = serde_json::from_slice(&config)?;
56+
Ok(config)
57+
}

0 commit comments

Comments
 (0)