Skip to content

Commit

Permalink
feat: add route for searching events (#144)
Browse files Browse the repository at this point in the history
* feat: add route for searching events

* chore: generate TS types

* fix: small fixes here and there

* chore: rename

* chore: rename (2)

* fix: regen types
  • Loading branch information
GuillaumeDecMeetsMore authored Nov 13, 2024
1 parent 038304f commit ad97707
Show file tree
Hide file tree
Showing 20 changed files with 512 additions and 4 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 22 additions & 0 deletions clients/javascript/lib/eventClient.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { NitteiBaseClient } from './baseClient'
import type {
SearchEventsAPIResponse,
SearchEventsRequestBody,
} from './gen_types'
import type { CalendarEventDTO } from './gen_types/CalendarEventDTO'
import type { CalendarEventResponse } from './gen_types/CalendarEventResponse'
import type { CreateEventRequestBody } from './gen_types/CreateEventRequestBody'
Expand Down Expand Up @@ -71,6 +75,24 @@ export class NitteiEventClient extends NitteiBaseClient {
}
}

/**
* Search events given the options
* @param options - options - see {@link SearchEventsRequestBody} for more details
* @returns - the events found
*/
public async searchEvents(
options: SearchEventsRequestBody
): Promise<SearchEventsAPIResponse> {
const res = await this.post<SearchEventsAPIResponse>(
'/events/search',
options
)

return {
events: res.events.map(convertEventDates),
}
}

public async findByMeta(
meta: {
key: string
Expand Down
15 changes: 15 additions & 0 deletions clients/javascript/lib/gen_types/DateTimeQuery.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.

/**
* Query parameters for searching on a date time
*/
export type DateTimeQuery = {
/**
* Optional "greater than or equal" query (UTC)
*/
gte?: Date
/**
* Optional "less than or equal" query (UTC)
*/
lte?: Date
}
17 changes: 17 additions & 0 deletions clients/javascript/lib/gen_types/IDQuery.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.

/**
* Query parameters for searching on an ID
*/
export type IDQuery = {
/**
* Optional String (equality test)
* This is not a UUID, but a string as we allow any type of ID in this field
*/
eq?: string
/**
* Optional bool (existence test)
* If "eq" is provided, this field is ignored
*/
exists?: boolean
}
12 changes: 12 additions & 0 deletions clients/javascript/lib/gen_types/SearchEventsAPIResponse.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 { CalendarEventDTO } from './CalendarEventDTO'

/**
* API response for getting events by calendars
*/
export type SearchEventsAPIResponse = {
/**
* List of calendar events retrieved
*/
events: Array<CalendarEventDTO>
}
44 changes: 44 additions & 0 deletions clients/javascript/lib/gen_types/SearchEventsRequestBody.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { DateTimeQuery } from './DateTimeQuery'
import type { ID } from './ID'
import type { IDQuery } from './IDQuery'
import type { JsonValue } from './serde_json/JsonValue'

/**
* Query parameters for searching events
*/
export type SearchEventsRequestBody = {
/**
* User ID
*/
userId: ID
/**
* Optional list of calendar UUIDs
* If not provided, all calendars will be used
*/
calendarIds?: Array<ID>
/**
* Optional query on parent ID
*/
parentId?: IDQuery
/**
* Optional query on start time - "lower than or equal", or "great than or equal" (UTC)
*/
startTime?: DateTimeQuery
/**
* Optional query on end time - "lower than or equal", or "great than or equal" (UTC)
*/
endTime?: DateTimeQuery
/**
* Optional list of event status
*/
status?: Array<string>
/**
* Optioanl query on updated at - "lower than or equal", or "great than or equal" (UTC)
*/
updatedAt?: DateTimeQuery
/**
* Optional list of metadata key-value pairs
*/
metadata?: JsonValue
}
4 changes: 4 additions & 0 deletions clients/javascript/lib/gen_types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export * from './CreateEventRequestBody'
export * from './CreateServiceEventIntendRequestBody'
export * from './CreateServiceRequestBody'
export * from './CreateUserRequestBody'
export * from './DateTimeQuery'
export * from './EventInstance'
export * from './EventWithInstancesDTO'
export * from './GetCalendarEventsAPIResponse'
Expand All @@ -46,6 +47,7 @@ export * from './GetUsersByMetaAPIResponse'
export * from './GoogleCalendarAccessRole'
export * from './GoogleCalendarListEntry'
export * from './ID'
export * from './IDQuery'
export * from './IntegrationProvider'
export * from './MultipleFreeBusyAPIResponse'
export * from './MultipleFreeBusyRequestBody'
Expand All @@ -67,6 +69,8 @@ export * from './ScheduleDTO'
export * from './ScheduleRule'
export * from './ScheduleRuleInterval'
export * from './ScheduleRuleVariant'
export * from './SearchEventsAPIResponse'
export * from './SearchEventsRequestBody'
export * from './ServiceBookingSlotDTO'
export * from './ServiceBookingSlotsDateDTO'
export * from './ServiceDTO'
Expand Down
6 changes: 6 additions & 0 deletions crates/api/src/event/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ mod get_event_instances;
mod get_events_by_calendars;
mod get_events_by_meta;
pub mod get_upcoming_reminders;
mod search_events;
mod subscribers;
pub mod sync_event_reminders;
mod update_event;
Expand All @@ -16,6 +17,7 @@ use delete_event::{delete_event_admin_controller, delete_event_controller};
use get_event::{get_event_admin_controller, get_event_controller};
use get_event_instances::{get_event_instances_admin_controller, get_event_instances_controller};
use get_events_by_meta::get_events_by_meta_controller;
use search_events::search_events_controller;
use update_event::{update_event_admin_controller, update_event_controller};

// Configure the routes for the event module
Expand All @@ -37,6 +39,10 @@ pub fn configure_routes(cfg: &mut web::ServiceConfig) {
// Get events by metadata
cfg.route("/events/meta", web::get().to(get_events_by_meta_controller));

// Search events
// /!\ This is a POST route
cfg.route("/events/search", web::post().to(search_events_controller));

// Get a specific event by external id
cfg.route(
"/user/events/external_id/{external_id}",
Expand Down
159 changes: 159 additions & 0 deletions crates/api/src/event/search_events.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
use actix_web::{web, HttpRequest, HttpResponse};
use nittei_api_structs::{dtos::CalendarEventDTO, search_events::*};
use nittei_domain::{DateTimeQuery, IDQuery, ID};
use nittei_infra::{NitteiContext, SearchEventsParams};

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

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

let body = body.0;
let usecase = SearchEventsUseCase {
account_id: account.id,
user_id: body.user_id,
calendar_ids: body.calendar_ids,
parent_id: body.parent_id,
start_time: body.start_time,
end_time: body.end_time,
status: body.status,
updated_at: body.updated_at,
metadata: body.metadata,
};

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

#[derive(Debug)]
pub struct SearchEventsUseCase {
/// Account ID
pub account_id: ID,

/// User ID
pub user_id: ID,

/// Optional list of calendar UUIDs
/// If not provided, all calendars will be used
pub calendar_ids: Option<Vec<ID>>,

/// Optional query on parent ID
pub parent_id: Option<IDQuery>,

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

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

/// Optional list of event status
pub status: Option<Vec<String>>,

/// Optioanl query on updated at - "lower than or equal", or "great than or equal" (UTC)
pub updated_at: Option<DateTimeQuery>,

/// Optional list of metadata key-value pairs
pub metadata: Option<serde_json::Value>,
}

#[derive(Debug)]
pub struct UseCaseResponse {
pub events: Vec<CalendarEventDTO>,
}

#[derive(Debug)]
pub enum UseCaseError {
InternalError,
BadRequest(String),
NotFound(String, String),
}

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

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

type Error = UseCaseError;

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

async fn execute(&mut self, ctx: &NitteiContext) -> Result<UseCaseResponse, UseCaseError> {
if let Some(calendar_ids) = &self.calendar_ids {
if calendar_ids.is_empty() {
return Err(UseCaseError::BadRequest(
"calendar_ids cannot be empty".into(),
));
}

let calendars = ctx
.repos
.calendars
.find_multiple(calendar_ids.iter().collect())
.await
.map_err(|_| UseCaseError::InternalError)?;

// Check that all calendars exist and belong to the same account
if calendars.is_empty()
|| calendars.len() != calendar_ids.len()
|| !calendars
.iter()
.all(|cal| cal.account_id == self.account_id)
{
return Err(UseCaseError::NotFound(
"Calendars not found".to_string(),
calendar_ids
.iter()
.map(|c| c.to_string())
.collect::<Vec<String>>()
.join(","),
));
}
}

let res = ctx
.repos
.events
.search_events(SearchEventsParams {
user_id: self.user_id.clone(),
calendar_ids: self.calendar_ids.clone(),
parent_id: self.parent_id.clone(),
start_time: self.start_time.clone(),
end_time: self.end_time.clone(),
status: self.status.clone(),
updated_at: self.updated_at.clone(),
metadata: self.metadata.clone(),
})
.await;

match res {
Ok(events) => Ok(UseCaseResponse {
events: events.into_iter().map(CalendarEventDTO::new).collect(),
}),
Err(_) => Err(UseCaseError::InternalError),
}
}
}
Loading

0 comments on commit ad97707

Please sign in to comment.