mirror of
https://github.com/lennart-k/rustical.git
synced 2025-12-14 19:32:29 +00:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8dfb47b28f | ||
|
|
eb720ded99 | ||
|
|
89ef7b2ced | ||
|
|
6e0129130e | ||
|
|
c646986c56 | ||
|
|
503cbe3699 |
4
.github/workflows/docker-publish.yml
vendored
4
.github/workflows/docker-publish.yml
vendored
@@ -41,12 +41,10 @@ jobs:
|
|||||||
# https://github.com/docker/metadata-action
|
# https://github.com/docker/metadata-action
|
||||||
- name: Extract Docker metadata
|
- name: Extract Docker metadata
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@96383f45573cb7f253c731d3b3ab81c87ef81934 # v5.0.0
|
uses: docker/metadata-action@v5
|
||||||
with:
|
with:
|
||||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||||
# As long as we don't have releases everything on the main branch shall be tagged as latest
|
|
||||||
tags: |
|
tags: |
|
||||||
type=raw,value=latest,enable={{is_default_branch}}
|
|
||||||
type=ref,event=branch
|
type=ref,event=branch
|
||||||
type=ref,event=pr
|
type=ref,event=pr
|
||||||
type=semver,pattern={{version}}
|
type=semver,pattern={{version}}
|
||||||
|
|||||||
22
Cargo.lock
generated
22
Cargo.lock
generated
@@ -2736,7 +2736,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustical"
|
name = "rustical"
|
||||||
version = "0.3.6"
|
version = "0.4.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"argon2",
|
"argon2",
|
||||||
@@ -2779,7 +2779,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustical_caldav"
|
name = "rustical_caldav"
|
||||||
version = "0.3.6"
|
version = "0.4.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"axum",
|
"axum",
|
||||||
@@ -2814,7 +2814,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustical_carddav"
|
name = "rustical_carddav"
|
||||||
version = "0.3.6"
|
version = "0.4.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"axum",
|
"axum",
|
||||||
@@ -2846,7 +2846,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustical_dav"
|
name = "rustical_dav"
|
||||||
version = "0.3.6"
|
version = "0.4.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"axum",
|
"axum",
|
||||||
@@ -2871,7 +2871,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustical_dav_push"
|
name = "rustical_dav_push"
|
||||||
version = "0.3.6"
|
version = "0.4.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"axum",
|
"axum",
|
||||||
@@ -2897,7 +2897,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustical_frontend"
|
name = "rustical_frontend"
|
||||||
version = "0.3.6"
|
version = "0.4.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"askama",
|
"askama",
|
||||||
"askama_web",
|
"askama_web",
|
||||||
@@ -2930,7 +2930,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustical_ical"
|
name = "rustical_ical"
|
||||||
version = "0.3.6"
|
version = "0.4.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"axum",
|
"axum",
|
||||||
"chrono",
|
"chrono",
|
||||||
@@ -2948,7 +2948,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustical_oidc"
|
name = "rustical_oidc"
|
||||||
version = "0.3.6"
|
version = "0.4.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"axum",
|
"axum",
|
||||||
@@ -2963,7 +2963,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustical_store"
|
name = "rustical_store"
|
||||||
version = "0.3.6"
|
version = "0.4.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
@@ -2997,7 +2997,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustical_store_sqlite"
|
name = "rustical_store_sqlite"
|
||||||
version = "0.3.6"
|
version = "0.4.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"chrono",
|
"chrono",
|
||||||
@@ -3017,7 +3017,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustical_xml"
|
name = "rustical_xml"
|
||||||
version = "0.3.6"
|
version = "0.4.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"quick-xml",
|
"quick-xml",
|
||||||
"thiserror 2.0.12",
|
"thiserror 2.0.12",
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
members = ["crates/*"]
|
members = ["crates/*"]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "0.3.6"
|
version = "0.4.2"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
description = "A CalDAV server"
|
description = "A CalDAV server"
|
||||||
repository = "https://github.com/lennart-k/rustical"
|
repository = "https://github.com/lennart-k/rustical"
|
||||||
|
|||||||
@@ -12,3 +12,12 @@ pub struct FrontendConfig {
|
|||||||
#[serde(default = "default_true")]
|
#[serde(default = "default_true")]
|
||||||
pub allow_password_login: bool,
|
pub allow_password_login: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for FrontendConfig {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
enabled: true,
|
||||||
|
allow_password_login: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -62,14 +62,14 @@ impl AddressObject {
|
|||||||
&self.vcf
|
&self.vcf
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_anniversary(&self) -> Option<CalDateTime> {
|
pub fn get_anniversary(&self) -> Option<(CalDateTime, bool)> {
|
||||||
let prop = self.vcard.get_property("ANNIVERSARY")?;
|
let prop = self.vcard.get_property("ANNIVERSARY")?.value.as_deref()?;
|
||||||
CalDateTime::parse_prop(prop, &HashMap::default()).ok()
|
CalDateTime::parse_vcard(prop).ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_birthday(&self) -> Option<CalDateTime> {
|
pub fn get_birthday(&self) -> Option<(CalDateTime, bool)> {
|
||||||
let prop = self.vcard.get_property("BDAY")?;
|
let prop = self.vcard.get_property("BDAY")?.value.as_deref()?;
|
||||||
CalDateTime::parse_prop(prop, &HashMap::default()).ok()
|
CalDateTime::parse_vcard(prop).ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_full_name(&self) -> Option<&str> {
|
pub fn get_full_name(&self) -> Option<&str> {
|
||||||
@@ -78,25 +78,27 @@ impl AddressObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_anniversary_object(&self) -> Result<Option<CalendarObject>, Error> {
|
pub fn get_anniversary_object(&self) -> Result<Option<CalendarObject>, Error> {
|
||||||
Ok(if let Some(anniversary) = self.get_anniversary() {
|
Ok(
|
||||||
let fullname = if let Some(name) = self.get_full_name() {
|
if let Some((anniversary, contains_year)) = self.get_anniversary() {
|
||||||
name
|
let fullname = if let Some(name) = self.get_full_name() {
|
||||||
} else {
|
name
|
||||||
return Ok(None);
|
} else {
|
||||||
};
|
return Ok(None);
|
||||||
let anniversary = anniversary.date();
|
};
|
||||||
let year = anniversary.year();
|
let anniversary = anniversary.date();
|
||||||
let anniversary_start = anniversary.format(LOCAL_DATE);
|
let year = contains_year.then_some(anniversary.year());
|
||||||
let anniversary_end = anniversary
|
let anniversary_start = anniversary.format(LOCAL_DATE);
|
||||||
.succ_opt()
|
let anniversary_end = anniversary
|
||||||
.unwrap_or(anniversary)
|
.succ_opt()
|
||||||
.format(LOCAL_DATE);
|
.unwrap_or(anniversary)
|
||||||
let uid = format!("{}-anniversary", self.get_id());
|
.format(LOCAL_DATE);
|
||||||
|
let uid = format!("{}-anniversary", self.get_id());
|
||||||
|
|
||||||
Some(CalendarObject::from_ics(
|
let year_suffix = year.map(|year| format!(" ({year})")).unwrap_or_default();
|
||||||
uid.clone(),
|
Some(CalendarObject::from_ics(
|
||||||
format!(
|
uid.clone(),
|
||||||
r#"BEGIN:VCALENDAR
|
format!(
|
||||||
|
r#"BEGIN:VCALENDAR
|
||||||
VERSION:2.0
|
VERSION:2.0
|
||||||
CALSCALE:GREGORIAN
|
CALSCALE:GREGORIAN
|
||||||
PRODID:-//github.com/lennart-k/rustical birthday calendar//EN
|
PRODID:-//github.com/lennart-k/rustical birthday calendar//EN
|
||||||
@@ -105,39 +107,42 @@ DTSTART;VALUE=DATE:{anniversary_start}
|
|||||||
DTEND;VALUE=DATE:{anniversary_end}
|
DTEND;VALUE=DATE:{anniversary_end}
|
||||||
UID:{uid}
|
UID:{uid}
|
||||||
RRULE:FREQ=YEARLY
|
RRULE:FREQ=YEARLY
|
||||||
SUMMARY:💍 {fullname} ({year})
|
SUMMARY:💍 {fullname}{year_suffix}
|
||||||
TRANSP:TRANSPARENT
|
TRANSP:TRANSPARENT
|
||||||
BEGIN:VALARM
|
BEGIN:VALARM
|
||||||
TRIGGER;VALUE=DURATION:-PT0M
|
TRIGGER;VALUE=DURATION:-PT0M
|
||||||
ACTION:DISPLAY
|
ACTION:DISPLAY
|
||||||
DESCRIPTION:💍 {fullname} ({year})
|
DESCRIPTION:💍 {fullname}{year_suffix}
|
||||||
END:VALARM
|
END:VALARM
|
||||||
END:VEVENT
|
END:VEVENT
|
||||||
END:VCALENDAR"#,
|
END:VCALENDAR"#,
|
||||||
),
|
),
|
||||||
)?)
|
)?)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
})
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_birthday_object(&self) -> Result<Option<CalendarObject>, Error> {
|
pub fn get_birthday_object(&self) -> Result<Option<CalendarObject>, Error> {
|
||||||
Ok(if let Some(birthday) = self.get_birthday() {
|
Ok(
|
||||||
let fullname = if let Some(name) = self.get_full_name() {
|
if let Some((birthday, contains_year)) = self.get_birthday() {
|
||||||
name
|
let fullname = if let Some(name) = self.get_full_name() {
|
||||||
} else {
|
name
|
||||||
return Ok(None);
|
} else {
|
||||||
};
|
return Ok(None);
|
||||||
let birthday = birthday.date();
|
};
|
||||||
let year = birthday.year();
|
let birthday = birthday.date();
|
||||||
let birthday_start = birthday.format(LOCAL_DATE);
|
let year = contains_year.then_some(birthday.year());
|
||||||
let birthday_end = birthday.succ_opt().unwrap_or(birthday).format(LOCAL_DATE);
|
let birthday_start = birthday.format(LOCAL_DATE);
|
||||||
let uid = format!("{}-birthday", self.get_id());
|
let birthday_end = birthday.succ_opt().unwrap_or(birthday).format(LOCAL_DATE);
|
||||||
|
let uid = format!("{}-birthday", self.get_id());
|
||||||
|
|
||||||
Some(CalendarObject::from_ics(
|
let year_suffix = year.map(|year| format!(" ({year})")).unwrap_or_default();
|
||||||
uid.clone(),
|
Some(CalendarObject::from_ics(
|
||||||
format!(
|
uid.clone(),
|
||||||
r#"BEGIN:VCALENDAR
|
format!(
|
||||||
|
r#"BEGIN:VCALENDAR
|
||||||
VERSION:2.0
|
VERSION:2.0
|
||||||
CALSCALE:GREGORIAN
|
CALSCALE:GREGORIAN
|
||||||
PRODID:-//github.com/lennart-k/rustical birthday calendar//EN
|
PRODID:-//github.com/lennart-k/rustical birthday calendar//EN
|
||||||
@@ -146,20 +151,21 @@ DTSTART;VALUE=DATE:{birthday_start}
|
|||||||
DTEND;VALUE=DATE:{birthday_end}
|
DTEND;VALUE=DATE:{birthday_end}
|
||||||
UID:{uid}
|
UID:{uid}
|
||||||
RRULE:FREQ=YEARLY
|
RRULE:FREQ=YEARLY
|
||||||
SUMMARY:🎂 {fullname} ({year})
|
SUMMARY:🎂 {fullname}{year_suffix}
|
||||||
TRANSP:TRANSPARENT
|
TRANSP:TRANSPARENT
|
||||||
BEGIN:VALARM
|
BEGIN:VALARM
|
||||||
TRIGGER;VALUE=DURATION:-PT0M
|
TRIGGER;VALUE=DURATION:-PT0M
|
||||||
ACTION:DISPLAY
|
ACTION:DISPLAY
|
||||||
DESCRIPTION:🎂 {fullname} ({year})
|
DESCRIPTION:🎂 {fullname}{year_suffix}
|
||||||
END:VALARM
|
END:VALARM
|
||||||
END:VEVENT
|
END:VEVENT
|
||||||
END:VCALENDAR"#,
|
END:VCALENDAR"#,
|
||||||
),
|
),
|
||||||
)?)
|
)?)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
})
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get significant dates associated with this address object
|
/// Get significant dates associated with this address object
|
||||||
|
|||||||
@@ -254,6 +254,16 @@ impl CalDateTime {
|
|||||||
if let Ok(date) = NaiveDate::parse_from_str(value, "%Y%m%d") {
|
if let Ok(date) = NaiveDate::parse_from_str(value, "%Y%m%d") {
|
||||||
return Ok(CalDateTime::Date(date, timezone));
|
return Ok(CalDateTime::Date(date, timezone));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Err(CalDateTimeError::InvalidDatetimeFormat(value.to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also returns whether the date contains a year
|
||||||
|
pub fn parse_vcard(value: &str) -> Result<(Self, bool), CalDateTimeError> {
|
||||||
|
if let Ok(datetime) = Self::parse(value, None) {
|
||||||
|
return Ok((datetime, true));
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(captures) = RE_VCARD_DATE_MM_DD.captures(value) {
|
if let Some(captures) = RE_VCARD_DATE_MM_DD.captures(value) {
|
||||||
// Because 1972 is a leap year
|
// Because 1972 is a leap year
|
||||||
let year = 1972;
|
let year = 1972;
|
||||||
@@ -261,13 +271,15 @@ impl CalDateTime {
|
|||||||
let month = captures.name("m").unwrap().as_str().parse().ok().unwrap();
|
let month = captures.name("m").unwrap().as_str().parse().ok().unwrap();
|
||||||
let day = captures.name("d").unwrap().as_str().parse().ok().unwrap();
|
let day = captures.name("d").unwrap().as_str().parse().ok().unwrap();
|
||||||
|
|
||||||
return Ok(CalDateTime::Date(
|
return Ok((
|
||||||
NaiveDate::from_ymd_opt(year, month, day)
|
CalDateTime::Date(
|
||||||
.ok_or(CalDateTimeError::ParseError(value.to_string()))?,
|
NaiveDate::from_ymd_opt(year, month, day)
|
||||||
timezone,
|
.ok_or(CalDateTimeError::ParseError(value.to_string()))?,
|
||||||
|
CalTimezone::Local,
|
||||||
|
),
|
||||||
|
false,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
Err(CalDateTimeError::InvalidDatetimeFormat(value.to_string()))
|
Err(CalDateTimeError::InvalidDatetimeFormat(value.to_string()))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -407,24 +419,33 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_vcard_date() {
|
fn test_vcard_date() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
CalDateTime::parse("19850412", None).unwrap(),
|
CalDateTime::parse_vcard("19850412").unwrap(),
|
||||||
CalDateTime::Date(
|
(
|
||||||
NaiveDate::from_ymd_opt(1985, 4, 12).unwrap(),
|
CalDateTime::Date(
|
||||||
crate::CalTimezone::Local
|
NaiveDate::from_ymd_opt(1985, 4, 12).unwrap(),
|
||||||
|
crate::CalTimezone::Local
|
||||||
|
),
|
||||||
|
true
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
CalDateTime::parse("1985-04-12", None).unwrap(),
|
CalDateTime::parse_vcard("1985-04-12").unwrap(),
|
||||||
CalDateTime::Date(
|
(
|
||||||
NaiveDate::from_ymd_opt(1985, 4, 12).unwrap(),
|
CalDateTime::Date(
|
||||||
crate::CalTimezone::Local
|
NaiveDate::from_ymd_opt(1985, 4, 12).unwrap(),
|
||||||
|
crate::CalTimezone::Local
|
||||||
|
),
|
||||||
|
true
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
CalDateTime::parse("--0412", None).unwrap(),
|
CalDateTime::parse_vcard("--0412").unwrap(),
|
||||||
CalDateTime::Date(
|
(
|
||||||
NaiveDate::from_ymd_opt(1972, 4, 12).unwrap(),
|
CalDateTime::Date(
|
||||||
crate::CalTimezone::Local
|
NaiveDate::from_ymd_opt(1972, 4, 12).unwrap(),
|
||||||
|
crate::CalTimezone::Local
|
||||||
|
),
|
||||||
|
false
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ pub struct Config {
|
|||||||
pub data_store: DataStoreConfig,
|
pub data_store: DataStoreConfig,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub http: HttpConfig,
|
pub http: HttpConfig,
|
||||||
|
#[serde(default)]
|
||||||
pub frontend: FrontendConfig,
|
pub frontend: FrontendConfig,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub oidc: Option<OidcConfig>,
|
pub oidc: Option<OidcConfig>,
|
||||||
|
|||||||
Reference in New Issue
Block a user