JMAP Registry API implementation - part 2

This commit is contained in:
mdecimus
2026-02-24 17:19:44 +01:00
parent ec148c5550
commit 2fea9a46b0
61 changed files with 1746 additions and 1992 deletions

View File

@@ -21,20 +21,24 @@ use common::{
auth::{AccountInfo, AccountTenantIds},
storage::index::ObjectIndexBuilder,
};
use registry::{
pickle::Pickle,
schema::structs::{Task, TaskCalendarAlarmEmail, TaskCalendarAlarmNotification, TaskStatus},
types::{EnumImpl, datetime::UTCDateTime},
};
use store::{
IterateParams, U16_LEN, U32_LEN, U64_LEN, ValueKey,
IterateParams, SerializeInfallible, U32_LEN, ValueKey,
roaring::RoaringBitmap,
write::{
AlignedBytes, Archive, BatchBuilder, IndexPropertyClass, TaskEpoch, TaskQueueClass,
ValueClass,
key::{DeserializeBigEndian, KeySerializer},
now,
AlignedBytes, Archive, BatchBuilder, IndexPropertyClass, Operation, TaskQueueClass,
ValueClass, ValueOp, key::DeserializeBigEndian, now,
},
};
use trc::AddContext;
use types::{
collection::{Collection, VanishedCollection},
field::CalendarNotificationField,
id::Id,
};
pub trait ItipAutoExpunge: Sync + Send {
@@ -526,52 +530,70 @@ impl DestroyArchive<Archive<&ArchivedCalendarEventNotification>> {
}
impl CalendarAlarm {
pub fn write_task(&self, batch: &mut BatchBuilder) {
match &self.typ {
pub fn build_write_ops(&self, account_id: u32, document_id: u32) -> [Operation; 2] {
let task = match &self.typ {
CalendarAlarmType::Email {
event_start,
event_start_tz,
event_end,
event_end_tz,
} => {
batch.set(
ValueClass::TaskQueue(TaskQueueClass::SendAlarm {
due: TaskEpoch::new(self.alarm_time as u64),
event_id: self.event_id,
alarm_id: self.alarm_id,
is_email_alert: true,
}),
KeySerializer::new((U64_LEN * 2) + (U16_LEN * 2))
.write(*event_start as u64)
.write(*event_end as u64)
.write(*event_start_tz)
.write(*event_end_tz)
.finalize(),
);
}
} => Task::CalendarAlarmEmail(TaskCalendarAlarmEmail {
account_id: account_id.into(),
document_id: document_id.into(),
alarm_id: self.alarm_id.into(),
event_id: self.event_id.into(),
event_end: UTCDateTime::from_timestamp(*event_end),
event_end_tz: (*event_end_tz).into(),
event_start: UTCDateTime::from_timestamp(*event_start),
event_start_tz: (*event_start_tz).into(),
status: TaskStatus::at(self.alarm_time),
}),
CalendarAlarmType::Display { recurrence_id } => {
batch.set(
ValueClass::TaskQueue(TaskQueueClass::SendAlarm {
due: TaskEpoch::new(self.alarm_time as u64),
event_id: self.event_id,
alarm_id: self.alarm_id,
is_email_alert: false,
}),
KeySerializer::new(U64_LEN)
.write(recurrence_id.unwrap_or_default() as u64)
.finalize(),
);
Task::CalendarAlarmNotification(TaskCalendarAlarmNotification {
account_id: account_id.into(),
document_id: document_id.into(),
alarm_id: self.alarm_id.into(),
event_id: self.event_id.into(),
recurrence_id: *recurrence_id,
status: TaskStatus::at(self.alarm_time),
})
}
};
let id = Id::from_parts(account_id, document_id).id();
[
Operation::Value {
class: ValueClass::TaskQueue(TaskQueueClass::Due {
id,
due: self.alarm_time as u64,
}),
op: ValueOp::Set(task.object_type().to_id().serialize()),
},
Operation::Value {
class: ValueClass::TaskQueue(TaskQueueClass::Task { id }),
op: ValueOp::Set(task.to_pickled_vec()),
},
]
}
pub fn write_task(&self, batch: &mut BatchBuilder) {
let account_id = batch.last_account_id().unwrap();
let document_id = batch.last_document_id().unwrap();
for op in self.build_write_ops(account_id, document_id) {
batch.any_op(op);
}
}
pub fn delete_task(&self, batch: &mut BatchBuilder) {
batch.clear(ValueClass::TaskQueue(TaskQueueClass::SendAlarm {
due: TaskEpoch::new(self.alarm_time as u64),
event_id: self.event_id,
alarm_id: self.alarm_id,
is_email_alert: matches!(self.typ, CalendarAlarmType::Email { .. }),
}));
let account_id = batch.last_account_id().unwrap();
let document_id = batch.last_document_id().unwrap();
let id = Id::from_parts(account_id, document_id).id();
batch
.clear(ValueClass::TaskQueue(TaskQueueClass::Task { id }))
.clear(ValueClass::TaskQueue(TaskQueueClass::Due {
id,
due: self.alarm_time as u64,
}));
}
}

View File

@@ -4,7 +4,7 @@
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use crate::scheduling::{ArchivedItipSummary, ItipMessage, ItipMessages};
use crate::scheduling::{ItipMessage, ItipMessages, ItipSummary};
use calcard::{
common::{IanaString, PartialDateTime},
icalendar::{
@@ -14,11 +14,10 @@ use calcard::{
},
};
use common::PROD_ID;
use store::{
Serialize,
write::{Archiver, BatchBuilder, TaskEpoch, TaskQueueClass, ValueClass},
use registry::schema::structs::{
Task, TaskCalendarItipContents, TaskCalendarItipMessage, TaskStatus,
};
use trc::AddContext;
use store::write::BatchBuilder;
pub(crate) fn itip_build_envelope(method: ICalendarMethod) -> ICalendarComponent {
ICalendarComponent {
@@ -273,28 +272,26 @@ pub(crate) fn can_attendee_modify_property(
impl ItipMessages {
pub fn new(messages: Vec<ItipMessage<ICalendar>>) -> Self {
ItipMessages {
messages: messages.into_iter().map(|m| m.into()).collect(),
messages: messages
.into_iter()
.map(|m| TaskCalendarItipContents {
from: m.from,
i_calendar_data: m.message.to_string(),
is_from_organizer: m.from_organizer,
summary: serde_json::to_string(&m.summary).unwrap_or_default(),
to: m.to,
})
.collect(),
}
}
pub fn queue(self, batch: &mut BatchBuilder) -> trc::Result<()> {
let due = TaskEpoch::now().with_random_sequence_id();
batch.set(
ValueClass::TaskQueue(TaskQueueClass::SendImip {
due,
is_payload: false,
}),
vec![],
);
batch.set(
ValueClass::TaskQueue(TaskQueueClass::SendImip {
due,
is_payload: true,
}),
Archiver::new(self)
.serialize()
.caused_by(trc::location!())?,
);
batch.schedule_task(Task::CalendarItipMessage(TaskCalendarItipMessage {
account_id: batch.last_account_id().unwrap().into(),
document_id: batch.last_document_id().unwrap().into(),
messages: self.messages,
status: TaskStatus::now(),
}));
Ok(())
}
@@ -312,13 +309,13 @@ impl From<ItipMessage<ICalendar>> for ItipMessage<String> {
}
}
impl ArchivedItipSummary {
impl ItipSummary {
pub fn method(&self) -> &str {
match self {
ArchivedItipSummary::Invite(_) => ICalendarMethod::Request.as_str(),
ArchivedItipSummary::Update { method, .. } => method.as_str(),
ArchivedItipSummary::Cancel(_) => ICalendarMethod::Cancel.as_str(),
ArchivedItipSummary::Rsvp { .. } => ICalendarMethod::Reply.as_str(),
ItipSummary::Invite(_) => ICalendarMethod::Request.as_str(),
ItipSummary::Update { method, .. } => method.as_str(),
ItipSummary::Cancel(_) => ICalendarMethod::Cancel.as_str(),
ItipSummary::Rsvp { .. } => ICalendarMethod::Reply.as_str(),
}
}
}

View File

@@ -14,6 +14,7 @@ use calcard::{
ICalendarStatus, ICalendarUserTypes, ICalendarValue, Uri,
},
};
use registry::schema::structs::TaskCalendarItipContents;
use std::{fmt::Display, hash::Hash};
pub mod attendee;
@@ -144,7 +145,7 @@ pub enum ItipError {
AutoAddDisabled,
}
#[derive(Debug, rkyv::Archive, rkyv::Deserialize, rkyv::Serialize)]
#[derive(Debug)]
pub struct ItipMessage<T> {
pub from: String,
pub from_organizer: bool,
@@ -153,7 +154,7 @@ pub struct ItipMessage<T> {
pub message: T,
}
#[derive(Debug, Clone, rkyv::Archive, rkyv::Deserialize, rkyv::Serialize)]
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub enum ItipSummary {
Invite(Vec<ItipField>),
Update {
@@ -168,13 +169,14 @@ pub enum ItipSummary {
},
}
#[derive(Debug, Clone, Hash, PartialEq, Eq, rkyv::Archive, rkyv::Deserialize, rkyv::Serialize)]
#[derive(Debug, Clone, Hash, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct ItipField {
pub name: ICalendarProperty,
pub value: ItipValue,
}
#[derive(Debug, Clone, Hash, PartialEq, Eq, rkyv::Archive, rkyv::Deserialize, rkyv::Serialize)]
#[derive(Debug, Clone, Hash, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(tag = "type", content = "value")]
pub enum ItipValue {
Text(String),
Time(ItipTime),
@@ -182,22 +184,21 @@ pub enum ItipValue {
Participants(Vec<ItipParticipant>),
}
#[derive(Debug, Clone, Hash, PartialEq, Eq, rkyv::Archive, rkyv::Deserialize, rkyv::Serialize)]
#[derive(Debug, Clone, Hash, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct ItipTime {
pub start: i64,
pub tz_id: u16,
}
#[derive(Debug, Clone, Hash, PartialEq, Eq, rkyv::Archive, rkyv::Deserialize, rkyv::Serialize)]
#[derive(Debug, Clone, Hash, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct ItipParticipant {
pub email: String,
pub name: Option<String>,
pub is_organizer: bool,
}
#[derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize)]
pub struct ItipMessages {
pub messages: Vec<ItipMessage<String>>,
pub messages: Vec<TaskCalendarItipContents>,
}
impl Attendee<'_> {