Skip to content

Commit f09624f

Browse files
committed
feat(api-internal): implement applicationsList
1 parent a4bdfac commit f09624f

File tree

8 files changed

+248
-5
lines changed

8 files changed

+248
-5
lines changed

api-internal/src/generated.rs

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,21 @@ pub mod api {
154154
self.api = self.api.bind("/application.get", Method::POST, handler);
155155
self
156156
}
157+
158+
pub fn bind_applications_list<F, T, R>(mut self, handler: F) -> Self
159+
where
160+
F: Handler<T, R>,
161+
T: FromRequest + 'static,
162+
R: Future<
163+
Output = Result<
164+
super::paths::applications_list::Response,
165+
super::paths::applications_list::Error,
166+
>,
167+
> + 'static,
168+
{
169+
self.api = self.api.bind("/applications.list", Method::POST, handler);
170+
self
171+
}
157172
}
158173
}
159174

@@ -509,6 +524,12 @@ pub mod components {
509524
#[from]
510525
pub error: ApplicationGetError,
511526
}
527+
528+
#[derive(Debug, Serialize)]
529+
pub struct ApplicationsListSuccess {
530+
pub installed: Vec<super::schemas::Application>,
531+
pub available: Vec<super::schemas::Application>,
532+
}
512533
}
513534

514535
pub mod request_bodies {
@@ -1222,4 +1243,64 @@ pub mod paths {
12221243
}
12231244
}
12241245
}
1246+
1247+
pub mod applications_list {
1248+
use actix_swagger::ContentType;
1249+
use actix_web::http::StatusCode;
1250+
use actix_web::{HttpRequest, HttpResponse, Responder, ResponseError};
1251+
use serde::Serialize;
1252+
1253+
use super::responses;
1254+
1255+
#[derive(Debug, Serialize)]
1256+
#[serde(untagged)]
1257+
pub enum Response {
1258+
Ok(responses::ApplicationsListSuccess),
1259+
}
1260+
1261+
#[derive(Debug, Serialize, thiserror::Error)]
1262+
#[serde(untagged)]
1263+
pub enum Error {
1264+
#[error(transparent)]
1265+
InternalServerError(
1266+
#[from]
1267+
#[serde(skip)]
1268+
eyre::Report,
1269+
),
1270+
}
1271+
1272+
impl Responder for Response {
1273+
fn respond_to(self, _: &HttpRequest) -> HttpResponse {
1274+
match self {
1275+
Response::Ok(r) => HttpResponse::build(StatusCode::OK).json(r),
1276+
}
1277+
}
1278+
}
1279+
1280+
impl ResponseError for Error {
1281+
fn status_code(&self) -> StatusCode {
1282+
match self {
1283+
Error::InternalServerError(_) => StatusCode::INTERNAL_SERVER_ERROR,
1284+
}
1285+
}
1286+
1287+
fn error_response(&self) -> HttpResponse {
1288+
let content_type = match self {
1289+
Self::InternalServerError(_) => Some(ContentType::Json),
1290+
};
1291+
1292+
let mut res = &mut HttpResponse::build(self.status_code());
1293+
if let Some(content_type) = content_type {
1294+
res = res.content_type(content_type.to_string());
1295+
1296+
match content_type {
1297+
ContentType::Json => res.body(serde_json::to_string(self).unwrap()),
1298+
ContentType::FormData => res.body(serde_plain::to_string(self).unwrap()),
1299+
}
1300+
} else {
1301+
HttpResponse::build(self.status_code()).finish()
1302+
}
1303+
}
1304+
}
1305+
}
12251306
}

api-internal/src/main.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,8 @@ async fn main() -> eyre::Result<()> {
6464
.bind_session_delete(routes::session::delete::route)
6565
.bind_session_get(routes::session::get::route)
6666
.bind_account_edit(account::edit::route)
67-
.bind_application_get(routes::application::get::route),
67+
.bind_application_get(routes::application::get::route)
68+
.bind_applications_list(routes::application::list::route),
6869
)
6970
});
7071

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
use actix_web::web::Data;
2+
3+
use accesso_core::app::application::{Application as _, ApplicationsListError};
4+
use accesso_core::models;
5+
6+
use crate::generated::{
7+
components::{responses::ApplicationsListSuccess, schemas},
8+
paths::applications_list::{Error, Response},
9+
};
10+
use crate::session::Session;
11+
12+
pub async fn route(app: Data<accesso_app::App>, session: Session) -> Result<Response, Error> {
13+
let applications_list = app
14+
.applications_list(session.user.id)
15+
.await
16+
.map_err(map_applications_list_error)?;
17+
18+
Ok(Response::Ok(ApplicationsListSuccess {
19+
available: applications_list
20+
.available
21+
.iter()
22+
.map(map_application)
23+
.collect(),
24+
installed: applications_list
25+
.installed
26+
.iter()
27+
.map(map_application)
28+
.collect(),
29+
}))
30+
}
31+
32+
fn map_application(application: &models::Application) -> schemas::Application {
33+
schemas::Application {
34+
id: application.id,
35+
title: application.title.clone(),
36+
allowed_registrations: application.allowed_registrations,
37+
avatar: None,
38+
}
39+
}
40+
41+
fn map_applications_list_error(error: ApplicationsListError) -> Error {
42+
match error {
43+
ApplicationsListError::Unexpected(report) => Error::InternalServerError(report.into()),
44+
}
45+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
pub mod get;
2+
pub mod list;

app/src/application.rs

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
1-
use crate::{App, Service};
2-
use accesso_core::app::application::{Application, ApplicationGetError};
3-
use accesso_core::contracts::Repository;
4-
use accesso_core::models;
51
use async_trait::async_trait;
62
use uuid::Uuid;
73

4+
use accesso_core::app::application::{
5+
Application, ApplicationGetError, ApplicationsList, ApplicationsListError,
6+
};
7+
use accesso_core::contracts::Repository;
8+
use accesso_core::models;
9+
10+
use crate::{App, Service};
11+
812
#[async_trait]
913
impl Application for App {
1014
async fn application_get(
@@ -20,4 +24,26 @@ impl Application for App {
2024
None => Err(ApplicationGetError::ApplicationNotFound),
2125
}
2226
}
27+
28+
async fn applications_list(
29+
&self,
30+
user_id: Uuid,
31+
) -> Result<ApplicationsList, ApplicationsListError> {
32+
let db = self.get::<Service<dyn Repository>>()?;
33+
34+
let available_future = db.applications_allowed_to_register();
35+
let installed_future = db.applications_user_registered_in(user_id);
36+
37+
let mut available = available_future.await?;
38+
let installed = installed_future.await?;
39+
40+
for application in &installed {
41+
available.retain(|found| found.id != application.id);
42+
}
43+
44+
Ok(ApplicationsList {
45+
available,
46+
installed,
47+
})
48+
}
2349
}

core/src/app/application.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,16 @@ use uuid::Uuid;
66
#[async_trait]
77
pub trait Application {
88
async fn application_get(&self, id: Uuid) -> Result<models::Application, ApplicationGetError>;
9+
async fn applications_list(
10+
&self,
11+
user_id: Uuid,
12+
) -> Result<ApplicationsList, ApplicationsListError>;
13+
}
14+
15+
#[derive(Debug)]
16+
pub struct ApplicationsList {
17+
pub available: Vec<models::Application>,
18+
pub installed: Vec<models::Application>,
919
}
1020

1121
#[derive(Debug, thiserror::Error)]
@@ -21,3 +31,15 @@ impl From<UnexpectedDatabaseError> for ApplicationGetError {
2131
ApplicationGetError::Unexpected(e.into())
2232
}
2333
}
34+
35+
#[derive(Debug, thiserror::Error)]
36+
pub enum ApplicationsListError {
37+
#[error(transparent)]
38+
Unexpected(#[from] eyre::Report),
39+
}
40+
41+
impl From<UnexpectedDatabaseError> for ApplicationsListError {
42+
fn from(e: UnexpectedDatabaseError) -> Self {
43+
ApplicationsListError::Unexpected(e.into())
44+
}
45+
}

core/src/contracts/repo/application.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,15 @@ pub trait ApplicationRepo {
4646

4747
async fn application_list(&self) -> Result<Vec<Application>, UnexpectedDatabaseError>;
4848

49+
async fn applications_allowed_to_register(
50+
&self,
51+
) -> Result<Vec<Application>, UnexpectedDatabaseError>;
52+
53+
async fn applications_user_registered_in(
54+
&self,
55+
user_id: Uuid,
56+
) -> Result<Vec<Application>, UnexpectedDatabaseError>;
57+
4958
async fn application_create(
5059
&self,
5160
application: ApplicationForm,
@@ -72,6 +81,21 @@ impl ApplicationRepo for crate::contracts::MockDb {
7281
self.application.application_list().await
7382
}
7483

84+
async fn applications_allowed_to_register(
85+
&self,
86+
) -> Result<Vec<Application>, UnexpectedDatabaseError> {
87+
self.application.applications_allowed_to_register().await
88+
}
89+
90+
async fn applications_user_registered_in(
91+
&self,
92+
user_id: Uuid,
93+
) -> Result<Vec<Application>, UnexpectedDatabaseError> {
94+
self.application
95+
.applications_user_registered_in(user_id)
96+
.await
97+
}
98+
7599
async fn application_create(
76100
&self,
77101
application: ApplicationForm,

db/src/repos/client.rs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ use accesso_core::contracts::{
22
ApplicationCreateError, ApplicationForm, ApplicationRepo, UnexpectedDatabaseError,
33
};
44
use accesso_core::models;
5+
use accesso_core::models::Application;
6+
use uuid::Uuid;
57

68
use crate::entities;
79
use crate::Database;
@@ -49,6 +51,47 @@ impl ApplicationRepo for Database {
4951
.collect())
5052
}
5153

54+
async fn applications_allowed_to_register(
55+
&self,
56+
) -> Result<Vec<Application>, UnexpectedDatabaseError> {
57+
Ok(sqlx::query_as!(
58+
entities::Client,
59+
// language=PostgreSQL
60+
r#"
61+
SELECT id, is_dev, redirect_uri, secret_key, title, allowed_registrations
62+
FROM clients
63+
WHERE allowed_registrations = true AND is_dev = false
64+
"#
65+
)
66+
.fetch_all(&self.pool)
67+
.await?
68+
.into_iter()
69+
.map(|client| client.into())
70+
.collect())
71+
}
72+
73+
async fn applications_user_registered_in(
74+
&self,
75+
user_id: Uuid,
76+
) -> Result<Vec<Application>, UnexpectedDatabaseError> {
77+
Ok(sqlx::query_as!(
78+
entities::Client,
79+
// language=PostgreSQL
80+
r#"
81+
SELECT clients.id, is_dev, redirect_uri, secret_key, title, allowed_registrations
82+
FROM clients
83+
LEFT JOIN user_registrations ON clients.id = user_registrations.client_id
84+
WHERE user_registrations.user_id = $1
85+
"#,
86+
user_id,
87+
)
88+
.fetch_all(&self.pool)
89+
.await?
90+
.into_iter()
91+
.map(|client| client.into())
92+
.collect())
93+
}
94+
5295
async fn application_create(
5396
&self,
5497
application: ApplicationForm,

0 commit comments

Comments
 (0)