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

Commit cdb03b5

Browse files
committed
feat: add pyo3 for excel transforming
1 parent d5328ef commit cdb03b5

File tree

15 files changed

+449
-3
lines changed

15 files changed

+449
-3
lines changed

.github/workflows/test.yml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,40 @@ jobs:
1313
build:
1414

1515
runs-on: ubuntu-latest
16+
strategy:
17+
matrix:
18+
python-version: [3.11]
1619

1720
steps:
1821
- uses: actions/checkout@v4
1922

23+
# Set up Python environment
24+
- name: Set up Python ${{ matrix.python-version }}
25+
uses: actions/setup-python@v4
26+
with:
27+
python-version: ${{ matrix.python-version }}
28+
29+
- name: Create and activate virtual environment
30+
run: |
31+
python -m venv venv
32+
source venv/bin/activate
33+
34+
# Install Python dependencies (if any)
35+
- name: Install Python dependencies
36+
run: |
37+
python -m pip install --upgrade pip
38+
# Add any dependencies here, for example:
39+
pip install maturin pandas
40+
41+
# Set up Rust environment
42+
- name: Set up Rust
43+
uses: actions-rs/toolchain@v1
44+
with:
45+
profile: minimal
46+
toolchain: stable
47+
components: rustfmt, clippy
48+
override: true
49+
2050
- name: Run tests
2151
run: cargo test
2252
env:

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,4 @@ private.pem
2020
public.pem
2121
config.json
2222
aes256.key
23+
init.sh

Cargo.lock

Lines changed: 78 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ mongodb = { version = "2.8.2", features = ["async-std"] }
2929
once_cell = "1.19.0"
3030
pem = { version = "3.0.4", features = ["serde"] }
3131
polars = "0.39.2"
32+
pyo3 = { version = "0.16", features = ["auto-initialize"] }
3233
rand = "0.8.5"
3334
reqwest = "0.12.3"
3435
rsa = "0.9.6"

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,11 @@ Technologies used:
1313
- `axum`: Web framework
1414
- `tokio`: Async runtime
1515
- `mongodb`: Database
16+
17+
> Remember to export the Python DLIB path before running the project.
18+
19+
For example:
20+
21+
```bash
22+
export DYLD_LIBRARY_PATH=/opt/anaconda3/envs/zvms/lib:$DYLD_LIBRARY_PATH
23+
```

src/main.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,14 @@ async fn main() {
9494
put(routers::activities::members::update::update_member_impression),
9595
)
9696
.route("/user/auth", post(routers::auth::login))
97+
.route(
98+
"/user/:user_id/activity",
99+
get(routers::users::activity::read_user_activities),
100+
)
101+
.route(
102+
"/user/:user_id/time",
103+
get(routers::users::time::calculate_user_activity_time),
104+
)
97105
.layer(Extension(shared_client.clone()));
98106

99107
// Run the server

src/models/response.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ pub enum ResponseStatus {
1212
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash)]
1313
pub struct SuccessResponse<T, M> {
1414
pub status: ResponseStatus,
15-
pub code: i32,
15+
pub code: u16,
1616
pub data: T,
1717
pub metadata: Option<M>,
1818
}

src/routers/mod.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1-
pub mod activities;
2-
pub mod auth;
1+
pub mod activities;
2+
pub mod auth;
3+
pub mod users;

src/routers/users/activity.rs

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
use crate::{
2+
models::{
3+
activities::Activity,
4+
groups::GroupPermission,
5+
response::{create_error, MetadataSize, ResponseStatus, SuccessResponse},
6+
},
7+
routers::activities::read::ReadActivityQuery,
8+
utils::{groups::same_class::validate_same_class, jwt::UserData},
9+
};
10+
use axum::{
11+
extract::{Extension, Path, Query},
12+
http::StatusCode,
13+
response::{IntoResponse, Json},
14+
};
15+
use bson::{doc, from_document, oid::ObjectId};
16+
use futures::stream::TryStreamExt;
17+
use mongodb::{Collection, Database};
18+
use std::{str::FromStr, sync::Arc};
19+
use tokio::sync::Mutex;
20+
21+
pub async fn read_user_activities(
22+
Extension(db): Extension<Arc<Mutex<Database>>>,
23+
user: UserData,
24+
Query(ReadActivityQuery {
25+
page,
26+
perpage,
27+
query,
28+
}): Query<ReadActivityQuery>,
29+
Path(user_id): Path<String>,
30+
) -> impl IntoResponse {
31+
let page = page.unwrap_or(1);
32+
let perpage = perpage.unwrap_or(10);
33+
let query = query.unwrap_or("".to_string());
34+
let user_id = ObjectId::from_str(&user_id);
35+
if let Err(_) = user_id {
36+
return create_error(StatusCode::BAD_REQUEST, "Invalid user ID".to_string());
37+
}
38+
let user_id = user_id.unwrap();
39+
let is_same_class = validate_same_class(
40+
db.clone(),
41+
ObjectId::from_str(user.id.as_str()).unwrap(),
42+
user_id.clone(),
43+
)
44+
.await;
45+
let db = db.lock().await;
46+
let collection: Collection<Activity> = db.collection("activities");
47+
if user.perms.contains(&GroupPermission::Admin)
48+
|| user.perms.contains(&GroupPermission::Auditor)
49+
|| user.perms.contains(&GroupPermission::Department)
50+
{
51+
} else if user.perms.contains(&GroupPermission::Secretary) {
52+
if let Ok(true) = is_same_class {
53+
} else {
54+
return create_error(StatusCode::FORBIDDEN, "Permission denied".to_string());
55+
}
56+
} else if user.id != user_id.clone().to_hex() {
57+
return create_error(StatusCode::FORBIDDEN, "Permission denied".to_string());
58+
}
59+
let counts = collection
60+
.count_documents(
61+
doc! {
62+
"_id": user_id
63+
},
64+
None,
65+
)
66+
.await;
67+
if let Err(_) = counts {
68+
return create_error(
69+
StatusCode::INTERNAL_SERVER_ERROR,
70+
"Failed to count documents".to_string(),
71+
);
72+
}
73+
let counts = counts.unwrap();
74+
let pipeline = [
75+
doc! {
76+
"$match": {
77+
"user": user_id
78+
}
79+
},
80+
doc! {
81+
"$match": {
82+
"$or": [
83+
{
84+
"name": {
85+
"$regex": &query,
86+
"$options": "i"
87+
}
88+
},
89+
]
90+
}
91+
},
92+
doc! {
93+
"$project": {
94+
// Only display `members._id == user_id` for `members` array
95+
"members": {
96+
"$filter": {
97+
"input": "$members",
98+
"as": "member",
99+
"cond": {
100+
"$eq": [
101+
"$$member._id",
102+
user_id
103+
]
104+
}
105+
}
106+
},
107+
}
108+
},
109+
doc! {
110+
"$project": {
111+
"members": 1,
112+
"name": 1,
113+
"description": 1,
114+
"start": 1,
115+
"end": 1,
116+
"created_at": 1,
117+
"updated_at": 1,
118+
"deleted_at": 1,
119+
}
120+
},
121+
doc! {
122+
"$sort": {
123+
"_id": -1
124+
}
125+
},
126+
doc! {
127+
"$skip": (page - 1) * perpage
128+
},
129+
doc! {
130+
"$limit": perpage
131+
},
132+
];
133+
let cursor = collection.aggregate(pipeline, None).await;
134+
if let Err(_) = cursor {
135+
return create_error(
136+
StatusCode::INTERNAL_SERVER_ERROR,
137+
"Failed to fetch documents".to_string(),
138+
);
139+
}
140+
let mut cursor = cursor.unwrap();
141+
let mut activities: Vec<Activity> = Vec::new();
142+
let metadata = MetadataSize { size: counts };
143+
while let Some(activity) = cursor.try_next().await.unwrap() {
144+
activities.push(from_document(activity).unwrap());
145+
}
146+
let response = SuccessResponse {
147+
status: ResponseStatus::Success,
148+
code: 200,
149+
data: activities,
150+
metadata: Some(metadata),
151+
};
152+
let response = serde_json::to_string(&response).unwrap();
153+
(StatusCode::OK, Json(response))
154+
}

src/routers/users/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
pub mod activity;
2+
pub mod time;

0 commit comments

Comments
 (0)