11use std:: sync:: Arc ;
22
3- use academy_core_course_contracts:: CourseFeatureService ;
3+ use academy_core_course_contracts:: { CourseFeatureService , CoursePurchaseError } ;
4+ use academy_models:: course:: CourseId ;
45use aide:: {
56 axum:: { ApiRouter , routing} ,
67 transform:: TransformOperation ,
78} ;
89use axum:: {
910 Json ,
10- extract:: { Query , State } ,
11+ extract:: { Path , Query , State } ,
1112 http:: StatusCode ,
1213 response:: { IntoResponse , Response } ,
1314} ;
15+ use schemars:: JsonSchema ;
16+ use serde:: Deserialize ;
1417
1518use crate :: {
1619 docs:: TransformOperationExt ,
17- errors:: { internal_server_error, internal_server_error_docs} ,
18- models:: course:: { ApiCourseFilter , ApiCourseUserSummary } ,
20+ error_code,
21+ errors:: { auth_error, auth_error_docs, internal_server_error, internal_server_error_docs} ,
22+ extractors:: auth:: ApiToken ,
23+ models:: {
24+ OkResponse ,
25+ course:: { ApiCourseFilter , ApiCourseUserSummary } ,
26+ } ,
27+ routes:: coin:: NotEnoughCoinsError ,
1928} ;
2029
2130pub const TAG : & str = "Courses" ;
2231
2332pub fn router ( service : Arc < impl CourseFeatureService > ) -> ApiRouter < ( ) > {
2433 ApiRouter :: new ( )
2534 . api_route ( "/skills/courses" , routing:: get_with ( list, list_docs) )
35+ . api_route (
36+ "/skills/course_access/{course_id}" ,
37+ routing:: post_with ( purchase, purchase_docs) ,
38+ )
2639 . with_state ( service)
2740 . with_path_items ( |op| op. tag ( TAG ) )
2841}
@@ -48,3 +61,44 @@ fn list_docs(op: TransformOperation) -> TransformOperation {
4861 . add_response :: < Vec < ApiCourseUserSummary > > ( StatusCode :: OK , None )
4962 . with ( internal_server_error_docs)
5063}
64+
65+ #[ derive( Deserialize , JsonSchema ) ]
66+ struct PurchasePath {
67+ course_id : CourseId ,
68+ }
69+
70+ async fn purchase (
71+ service : State < Arc < impl CourseFeatureService > > ,
72+ token : ApiToken ,
73+ Path ( PurchasePath { course_id } ) : Path < PurchasePath > ,
74+ ) -> Response {
75+ match service. purchase ( & token. 0 , course_id) . await {
76+ Ok ( ( ) ) => Json ( OkResponse ) . into_response ( ) ,
77+ Err ( CoursePurchaseError :: CourseNotFound ) => CourseNotFoundError . into_response ( ) ,
78+ Err ( CoursePurchaseError :: CourseIsFree ) => CourseIsFreeError . into_response ( ) ,
79+ Err ( CoursePurchaseError :: AlreadyPurchased ) => AlreadyPurchasedError . into_response ( ) ,
80+ Err ( CoursePurchaseError :: NotEnoughCoins ) => NotEnoughCoinsError . into_response ( ) ,
81+ Err ( CoursePurchaseError :: Auth ( err) ) => auth_error ( err) ,
82+ Err ( CoursePurchaseError :: Other ( err) ) => internal_server_error ( err) ,
83+ }
84+ }
85+
86+ fn purchase_docs ( op : TransformOperation ) -> TransformOperation {
87+ op. summary ( "Purchase a course for the authenticated user" )
88+ . add_response :: < OkResponse > ( StatusCode :: OK , "The course has been purchased." )
89+ . add_error :: < CourseNotFoundError > ( )
90+ . add_error :: < CourseIsFreeError > ( )
91+ . add_error :: < AlreadyPurchasedError > ( )
92+ . add_error :: < NotEnoughCoinsError > ( )
93+ . with ( auth_error_docs)
94+ . with ( internal_server_error_docs)
95+ }
96+
97+ error_code ! {
98+ /// The course does not exist.
99+ CourseNotFoundError ( NOT_FOUND , "Course not found" ) ;
100+ /// The course is free.
101+ CourseIsFreeError ( FORBIDDEN , "Course is free" ) ;
102+ /// The user has already purchased this course.
103+ AlreadyPurchasedError ( FORBIDDEN , "Already purchased course" ) ;
104+ }
0 commit comments