Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add api structs + "create" route #162

Merged
22 changes: 22 additions & 0 deletions clients/javascript/lib/gen_types/CreateEventGroupRequestBody.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { ID } from './ID'

/**
* Request body for creating an event
*/
export type CreateEventGroupRequestBody = {
/**
* UUID of the calendar where the event group will be created
*/
calendarId: ID
/**
* Optional parent event ID
* This is useful for external applications that need to link Nittei's events to a wider data model (e.g. a project, an order, etc.)
*/
parentId?: string
/**
* Optional external event ID
* This is useful for external applications that need to link Nittei's events to their own data models
*/
externalId?: string
}
38 changes: 38 additions & 0 deletions clients/javascript/lib/gen_types/EventGroup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { ID } from './ID'

/**
* Group of calendar events
*/
export type EventGroup = {
/**
* Unique ID
*/
id: ID
/**
* Calendar ID to which the group belongs
*/
calendarId: ID
/**
* User ID
*/
userId: ID
/**
* Account ID
*/
accountId: ID
/**
* Parent ID - this is an ID external to the system
* It allows to link groups of events together to an outside entity
*/
parentId: string | null
/**
* External ID - this is an ID external to the system
* It allows to link a group of events to an outside entity
*/
externalId: string | null
/**
* List of event IDs in the group
*/
eventIds: Array<ID>
}
28 changes: 28 additions & 0 deletions clients/javascript/lib/gen_types/EventGroupDTO.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { ID } from './ID'

/**
* Calendar event object
*/
export type EventGroupDTO = {
/**
* UUID of the event
*/
id: ID
/**
* Optional parent event ID
*/
parentId: string | null
/**
* Optional external ID
*/
externalId: string | null
/**
* UUID of the calendar
*/
calendarId: ID
/**
* UUID of the user
*/
userId: ID
}
12 changes: 12 additions & 0 deletions clients/javascript/lib/gen_types/EventGroupResponse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { EventGroupDTO } from './EventGroupDTO'

/**
* Calendar event response object
*/
export type EventGroupResponse = {
/**
* Calendar event retrieved
*/
eventGroup: EventGroupDTO
}
4 changes: 4 additions & 0 deletions clients/javascript/lib/gen_types/SearchEventsRequestBody.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ export type SearchEventsRequestBody = {
* Optional query on parent ID
*/
parentId?: IDQuery
/**
* Optional query on group ID
*/
groupId?: IDQuery
/**
* Optional query on start time - "lower than or equal", or "great than or equal" (UTC)
*/
Expand Down
18 changes: 18 additions & 0 deletions clients/javascript/lib/gen_types/UpdateEventGroupRequestBody.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.

/**
* Request body for updating an event
*/
export type UpdateEventGroupRequestBody = {
/**
* Optional parent event ID
* This is useful for external applications that need to link Nittei's events to a wider data model (e.g. a project, an order, etc.)
*/
parentId?: string
/**
* Optional external event ID
* This is useful for external applications that need to link Nittei's events to their own data models
* Default is None
*/
externalId?: string
}
5 changes: 5 additions & 0 deletions clients/javascript/lib/gen_types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,15 @@ export * from './CalendarSettingsDTO'
export * from './CreateAccountRequestBody'
export * from './CreateAccountResponseBody'
export * from './CreateCalendarRequestBody'
export * from './CreateEventGroupRequestBody'
export * from './CreateEventRequestBody'
export * from './CreateServiceEventIntendRequestBody'
export * from './CreateServiceRequestBody'
export * from './CreateUserRequestBody'
export * from './DateTimeQuery'
export * from './EventGroup'
export * from './EventGroupDTO'
export * from './EventGroupResponse'
export * from './EventInstance'
export * from './EventWithInstancesDTO'
export * from './GetCalendarEventsAPIResponse'
Expand Down Expand Up @@ -87,6 +91,7 @@ export * from './TimePlan'
export * from './TimeSpan'
export * from './UpdateCalendarRequestBody'
export * from './UpdateCalendarSettings'
export * from './UpdateEventGroupRequestBody'
export * from './UpdateEventRequestBody'
export * from './UpdateServiceRequestBody'
export * from './UpdateServiceUserRequestBody'
Expand Down
1 change: 1 addition & 0 deletions crates/api/src/event/create_event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ impl UseCase for CreateEventUseCase {
account_id: self.user.account_id.clone(),
reminders: self.reminders.clone(),
service_id: self.service_id.clone(),
group_id: None,
metadata: self.metadata.clone(),
};

Expand Down
5 changes: 5 additions & 0 deletions crates/api/src/event/search_events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ pub async fn search_events_controller(
user_id: body.user_id,
calendar_ids: body.calendar_ids,
parent_id: body.parent_id,
group_id: body.group_id,
start_time: body.start_time,
end_time: body.end_time,
status: body.status,
Expand Down Expand Up @@ -52,6 +53,9 @@ pub struct SearchEventsUseCase {
/// Optional query on parent ID
pub parent_id: Option<IDQuery>,

/// Optional query on the group ID
pub group_id: Option<IDQuery>,

/// Optional query on start time - "lower than or equal", or "great than or equal" (UTC)
pub start_time: Option<DateTimeQuery>,

Expand Down Expand Up @@ -141,6 +145,7 @@ impl UseCase for SearchEventsUseCase {
user_id: self.user_id.clone(),
calendar_ids: self.calendar_ids.clone(),
parent_id: self.parent_id.clone(),
group_id: self.group_id.clone(),
start_time: self.start_time.clone(),
end_time: self.end_time.clone(),
status: self.status.clone(),
Expand Down
175 changes: 175 additions & 0 deletions crates/api/src/event_group/create_event_group.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
use actix_web::{web, HttpRequest, HttpResponse};
use nittei_api_structs::create_event_group::*;
use nittei_domain::{event_group::EventGroup, User, ID};
use nittei_infra::NitteiContext;

use crate::{
error::NitteiError,
shared::{
auth::{account_can_modify_user, protect_account_route},
usecase::{execute, UseCase},
},
};

pub async fn create_event_group_admin_controller(
http_req: HttpRequest,
path_params: web::Path<PathParams>,
body: actix_web_validator::Json<RequestBody>,
ctx: web::Data<NitteiContext>,
) -> Result<HttpResponse, NitteiError> {
let account = protect_account_route(&http_req, &ctx).await?;
let user = account_can_modify_user(&account, &path_params.user_id, &ctx).await?;

let body = body.0;
let usecase = CreateEventGroupUseCase {
parent_id: body.parent_id,
external_id: body.external_id,
user,
calendar_id: body.calendar_id,
};

execute(usecase, &ctx)
.await
.map(|group| HttpResponse::Created().json(APIResponse::new(group)))
.map_err(NitteiError::from)
}

#[derive(Debug, Default)]
pub struct CreateEventGroupUseCase {
pub calendar_id: ID,
pub user: User,
pub parent_id: Option<String>,
pub external_id: Option<String>,
}

#[derive(Debug, PartialEq)]
pub enum UseCaseError {
NotFound(ID),
StorageError,
}

impl From<UseCaseError> for NitteiError {
fn from(e: UseCaseError) -> Self {
match e {
UseCaseError::NotFound(calendar_id) => Self::NotFound(format!(
"The calendar with id: {}, was not found.",
calendar_id
)),
UseCaseError::StorageError => Self::InternalError,
}
}
}

impl From<anyhow::Error> for UseCaseError {
fn from(_: anyhow::Error) -> Self {
UseCaseError::StorageError
}
}

#[async_trait::async_trait(?Send)]
impl UseCase for CreateEventGroupUseCase {
type Response = EventGroup;

type Error = UseCaseError;

const NAME: &'static str = "CreateEvent";

async fn execute(&mut self, ctx: &NitteiContext) -> Result<Self::Response, Self::Error> {
let calendar = ctx
.repos
.calendars
.find(&self.calendar_id)
.await
.map_err(|_| UseCaseError::StorageError)?;
let calendar = match calendar {
Some(calendar) if calendar.user_id == self.user.id => calendar,
_ => return Err(UseCaseError::NotFound(self.calendar_id.clone())),
};

let g = EventGroup {
id: Default::default(),
parent_id: self.parent_id.clone(),
external_id: self.external_id.clone(),
calendar_id: calendar.id.clone(),
user_id: self.user.id.clone(),
account_id: self.user.account_id.clone(),
event_ids: vec![],
};

ctx.repos.event_groups.insert(&g).await?;

Ok(g)
}
}

#[cfg(test)]
mod test {
use nittei_domain::{Account, Calendar, User};
use nittei_infra::setup_context;

use super::*;

struct TestContext {
ctx: NitteiContext,
calendar: Calendar,
user: User,
}

async fn setup() -> TestContext {
let ctx = setup_context().await.unwrap();
let account = Account::default();
ctx.repos.accounts.insert(&account).await.unwrap();
let user = User::new(account.id.clone(), None);
ctx.repos.users.insert(&user).await.unwrap();
let calendar = Calendar::new(&user.id, &account.id, None, None);
ctx.repos.calendars.insert(&calendar).await.unwrap();

TestContext {
user,
calendar,
ctx,
}
}

#[actix_web::main]
#[test]
async fn creates_event_group() {
let TestContext {
ctx,
calendar,
user,
} = setup().await;

let mut usecase = CreateEventGroupUseCase {
calendar_id: calendar.id.clone(),
user,
..Default::default()
};

let res = usecase.execute(&ctx).await;

assert!(res.is_ok());
}

#[actix_web::main]
#[test]
async fn rejects_invalid_calendar_id() {
let TestContext {
ctx,
calendar: _,
user,
} = setup().await;

let mut usecase = CreateEventGroupUseCase {
user,
..Default::default()
};

let res = usecase.execute(&ctx).await;
assert!(res.is_err());
assert_eq!(
res.unwrap_err(),
UseCaseError::NotFound(usecase.calendar_id)
);
}
}
13 changes: 13 additions & 0 deletions crates/api/src/event_group/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
mod create_event_group;

use actix_web::web;
use create_event_group::create_event_group_admin_controller;

// Configure the routes for the event_group module
pub fn configure_routes(cfg: &mut web::ServiceConfig) {
// Create an event for a user (admin route)
cfg.route(
"/user/{user_id}/event_group",
web::post().to(create_event_group_admin_controller),
);
}
2 changes: 2 additions & 0 deletions crates/api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ mod account;
mod calendar;
mod error;
mod event;
mod event_group;
mod http_logger;
mod job_schedulers;
mod schedule;
Expand Down Expand Up @@ -41,6 +42,7 @@ pub fn configure_server_api(cfg: &mut web::ServiceConfig) {
account::configure_routes(cfg);
calendar::configure_routes(cfg);
event::configure_routes(cfg);
event_group::configure_routes(cfg);
schedule::configure_routes(cfg);
service::configure_routes(cfg);
status::configure_routes(cfg);
Expand Down
Loading