Skip to content

Commit 0705ea9

Browse files
committed
feat(course): add list courses endpoint
1 parent 54f9745 commit 0705ea9

File tree

18 files changed

+489
-13
lines changed

18 files changed

+489
-13
lines changed

Cargo.lock

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

Cargo.nix

Lines changed: 92 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: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ academy_core_config_contracts.path = "academy_core/config/contracts"
5858
academy_core_config_impl.path = "academy_core/config/impl"
5959
academy_core_contact_contracts.path = "academy_core/contact/contracts"
6060
academy_core_contact_impl.path = "academy_core/contact/impl"
61+
academy_core_course_contracts.path = "academy_core/course/contracts"
62+
academy_core_course_impl.path = "academy_core/course/impl"
6163
academy_core_finance_contracts.path = "academy_core/finance/contracts"
6264
academy_core_finance_impl.path = "academy_core/finance/impl"
6365
academy_core_health_contracts.path = "academy_core/health/contracts"

academy/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ academy_core_coin_contracts.workspace = true
2020
academy_core_coin_impl.workspace = true
2121
academy_core_config_impl.workspace = true
2222
academy_core_contact_impl.workspace = true
23+
academy_core_course_contracts.workspace = true
24+
academy_core_course_impl.workspace = true
2325
academy_core_finance_contracts.workspace = true
2426
academy_core_finance_impl.workspace = true
2527
academy_core_health_impl.workspace = true

academy/src/environment/types.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use academy_cache_valkey::ValkeyCache;
88
use academy_core_coin_impl::{CoinFeatureServiceImpl, coin::CoinServiceImpl};
99
use academy_core_config_impl::ConfigFeatureServiceImpl;
1010
use academy_core_contact_impl::ContactFeatureServiceImpl;
11+
use academy_core_course_impl::CourseFeatureServiceImpl;
1112
use academy_core_finance_impl::{
1213
FinanceFeatureServiceImpl, coin::FinanceCoinServiceImpl, invoice::FinanceInvoiceServiceImpl,
1314
};
@@ -68,6 +69,7 @@ pub type RestServer = academy_api_rest::RestServer<
6869
FinanceFeature,
6970
HeartFeature,
7071
PremiumFeature,
72+
CourseFeature,
7173
Internal,
7274
>;
7375

@@ -231,4 +233,6 @@ pub type PremiumPlan = PremiumPlanServiceImpl;
231233
pub type Premium = PremiumServiceImpl<Time, PremiumPurchase, PremiumRepo>;
232234
pub type PremiumPurchase = PremiumPurchaseServiceImpl<Id, Time, Coin, PremiumPlan, PremiumRepo>;
233235

236+
pub type CourseFeature = CourseFeatureServiceImpl;
237+
234238
pub type Internal = InternalServiceImpl<Database, AuthInternal, UserRepo, Coin, Heart, Premium>;

academy_api/rest/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ academy_auth_contracts.workspace = true
1515
academy_core_coin_contracts.workspace = true
1616
academy_core_config_contracts.workspace = true
1717
academy_core_contact_contracts.workspace = true
18+
academy_core_course_contracts.workspace = true
1819
academy_core_finance_contracts.workspace = true
1920
academy_core_health_contracts.workspace = true
2021
academy_core_heart_contracts.workspace = true

academy_api/rest/src/lib.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use std::{
66
use academy_core_coin_contracts::CoinFeatureService;
77
use academy_core_config_contracts::ConfigFeatureService;
88
use academy_core_contact_contracts::ContactFeatureService;
9+
use academy_core_course_contracts::CourseFeatureService;
910
use academy_core_finance_contracts::FinanceFeatureService;
1011
use academy_core_health_contracts::HealthFeatureService;
1112
use academy_core_heart_contracts::HeartFeatureService;
@@ -57,6 +58,7 @@ pub struct RestServer<
5758
Finance,
5859
Heart,
5960
Premium,
61+
Course,
6062
Internal,
6163
> {
6264
_config: RestServerConfig,
@@ -72,6 +74,7 @@ pub struct RestServer<
7274
finance: Finance,
7375
heart: Heart,
7476
premium: Premium,
77+
course: Course,
7578
internal: Internal,
7679
}
7780

@@ -101,6 +104,7 @@ impl<
101104
Finance,
102105
Heart,
103106
Premium,
107+
Course,
104108
Internal,
105109
>
106110
RestServer<
@@ -116,6 +120,7 @@ impl<
116120
Finance,
117121
Heart,
118122
Premium,
123+
Course,
119124
Internal,
120125
>
121126
where
@@ -131,6 +136,7 @@ where
131136
Finance: FinanceFeatureService,
132137
Heart: HeartFeatureService,
133138
Premium: PremiumFeatureService,
139+
Course: CourseFeatureService,
134140
Internal: InternalService,
135141
{
136142
pub async fn serve(self) -> anyhow::Result<()> {
@@ -162,6 +168,7 @@ where
162168
routes::finance::TAG,
163169
routes::heart::TAG,
164170
routes::premium::TAG,
171+
routes::course::TAG,
165172
routes::internal::TAG,
166173
]
167174
.into_iter()
@@ -239,6 +246,7 @@ where
239246
.merge(routes::finance::router(self.finance.into()))
240247
.merge(routes::heart::router(self.heart.into()))
241248
.merge(routes::premium::router(self.premium.into()))
249+
.merge(routes::course::router(self.course.into()))
242250
.merge(routes::internal::router(self.internal.into()))
243251
}
244252
}

academy_api/rest/src/models/course.rs

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
use academy_models::{
2+
SearchTerm,
3+
course::{
4+
CourseAuthor, CourseAuthorName, CourseDescription, CourseFilter, CourseId,
5+
CourseLectureTitle, CourseLectureUserSummary, CourseSectionTitle, CourseSectionUserSummary,
6+
CourseTitle, CourseUserSummary,
7+
},
8+
url::Url,
9+
};
10+
use schemars::JsonSchema;
11+
use serde::{Deserialize, Serialize};
12+
13+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, JsonSchema)]
14+
pub struct ApiCourseUserSummary {
15+
pub id: CourseId,
16+
pub title: CourseTitle,
17+
pub description: CourseDescription,
18+
pub category: Option<String>,
19+
pub language: Option<&'static str>,
20+
pub image: Option<Url>,
21+
pub authors: Vec<ApiCourseAuthor>,
22+
pub price: u64,
23+
pub learnings_goals: Vec<String>,
24+
pub requirements: Vec<String>,
25+
pub last_update: i64,
26+
pub sections: Vec<ApiCourseSectionUserSummary>,
27+
pub completed: Option<bool>,
28+
}
29+
30+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, JsonSchema)]
31+
pub struct ApiCourseAuthor {
32+
pub name: CourseAuthorName,
33+
pub url: Option<Url>,
34+
}
35+
36+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, JsonSchema)]
37+
pub struct ApiCourseSectionUserSummary {
38+
pub title: CourseSectionTitle,
39+
pub lectures: Vec<ApiCourseLectureUserSummary>,
40+
pub completed: Option<bool>,
41+
}
42+
43+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, JsonSchema)]
44+
pub struct ApiCourseLectureUserSummary {
45+
pub title: CourseLectureTitle,
46+
pub duration: u64,
47+
pub completed: Option<bool>,
48+
}
49+
50+
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, JsonSchema)]
51+
pub struct ApiCourseFilter {
52+
/// Search in `title`
53+
pub search_term: Option<SearchTerm>,
54+
/// Filter by author
55+
pub author: Option<SearchTerm>,
56+
/// Return only free (`true`) or unfree (`false`) courses
57+
pub free: Option<bool>,
58+
}
59+
60+
impl From<CourseUserSummary> for ApiCourseUserSummary {
61+
fn from(value: CourseUserSummary) -> Self {
62+
Self {
63+
id: value.base.id,
64+
title: value.base.title,
65+
description: value.base.description,
66+
category: None,
67+
language: Some("de"),
68+
image: value.base.image_url,
69+
authors: value.base.authors.into_iter().map(Into::into).collect(),
70+
price: value.base.price,
71+
learnings_goals: vec![],
72+
requirements: vec![],
73+
last_update: value.base.last_update.timestamp(),
74+
sections: value.sections.into_iter().map(Into::into).collect(),
75+
completed: value.completed,
76+
}
77+
}
78+
}
79+
80+
impl From<CourseAuthor> for ApiCourseAuthor {
81+
fn from(value: CourseAuthor) -> Self {
82+
Self {
83+
name: value.name,
84+
url: value.url,
85+
}
86+
}
87+
}
88+
89+
impl From<CourseSectionUserSummary> for ApiCourseSectionUserSummary {
90+
fn from(value: CourseSectionUserSummary) -> Self {
91+
Self {
92+
title: value.title,
93+
lectures: value.lectures.into_iter().map(Into::into).collect(),
94+
completed: value.completed,
95+
}
96+
}
97+
}
98+
99+
impl From<CourseLectureUserSummary> for ApiCourseLectureUserSummary {
100+
fn from(value: CourseLectureUserSummary) -> Self {
101+
Self {
102+
title: value.title,
103+
duration: value.duration.as_secs(),
104+
completed: value.completed,
105+
}
106+
}
107+
}
108+
109+
impl From<ApiCourseFilter> for CourseFilter {
110+
fn from(value: ApiCourseFilter) -> Self {
111+
Self {
112+
search_term: value.search_term,
113+
author: value.author,
114+
free: value.free,
115+
}
116+
}
117+
}

0 commit comments

Comments
 (0)