Skip to content

Commit 26e78db

Browse files
authored
Add an automatic birthday calendar (#190)
1 parent df5d271 commit 26e78db

File tree

17 files changed

+785
-45
lines changed

17 files changed

+785
-45
lines changed

.env

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,10 @@ WEBDAV_ENABLED=false
7272
# Do we allow calendars to be public ?
7373
PUBLIC_CALENDARS_ENABLED=true
7474

75+
# For Birthday calendars, what should be the reminder offset ?
76+
# (The default is PT9H, 9am on the day of the event)
77+
BIRTHDAY_REMINDER_OFFSET=PT9H
78+
7579
# What mail is used as the sender for invites ?
7680
INVITE_FROM_ADDRESS=[email protected]
7781

README.md

Lines changed: 73 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ Davis
66
[![Latest release][release_badge]][release_link]
77
[![Sponsor me][sponsor_badge]][sponsor_link]
88

9-
A simple, fully translatable and full-featured DAV server, admin interface and frontend based on `sabre/dav`, built with [Symfony 7](https://symfony.com/) and [Bootstrap 5](https://getbootstrap.com/), initially inspired by [Baïkal](https://github.com/sabre-io/Baikal) (_see dependencies table below for more detail_)
9+
A modern, simple, feature-packed, fully translatable DAV server, admin interface and frontend based on `sabre/dav`, built with [Symfony 7](https://symfony.com/) and [Bootstrap 5](https://getbootstrap.com/), initially inspired by [Baïkal](https://github.com/sabre-io/Baikal) (_see dependencies table below for more detail_)
1010

1111
### Web admin dashboard
1212

@@ -18,6 +18,12 @@ Supports **Basic authentication**, as well as **IMAP** and **LDAP** (_via extern
1818

1919
The underlying server implementation supports (*non-exhaustive list*) CalDAV, CardDAV, WebDAV, calendar sharing, scheduling, mail notifications, and server-side subscriptions (*depending on the capabilities of the client*).
2020

21+
### Additional features ✨
22+
23+
- Subscriptions (to be added via the client, such as the macOS calendar, for instance)
24+
- Public calendars, available to anyone with the link
25+
- Automatic birthday calendar, updated on the fly when birthdates change in your contacts
26+
2127
### Deployment
2228

2329
Easily containerisable (_`Dockerfile` and sample `docker-compose` configuration file provided_).
@@ -61,17 +67,17 @@ Dependencies
6167

6268
1. Retrieve the dependencies:
6369

64-
```
65-
composer install
66-
```
70+
```
71+
composer install
72+
```
6773
6874
2. At least put the correct credentials to your database (driver and url) in your `.env.local` file so you can easily create the necessary tables.
6975
7076
3. Run the migrations to create all the necessary tables:
7177
72-
```
73-
bin/console doctrine:migrations:migrate
74-
```
78+
```
79+
bin/console doctrine:migrations:migrate
80+
```
7581
7682
**Davis** can also be used with a pre-existing MySQL database (_for instance, one previously managed by Baïkal_). See the paragraph "Migrating from Baikal" for more info.
7783
@@ -87,14 +93,14 @@ Create your own `.env.local` file to change the necessary variables, if you plan
8793
>
8894
> If your installation is behind a web server like Apache or Nginx, you can setup the env vars directly in your Apache or Nginx configuration (see below). Skip this part in this case.
8995
90-
a. The database driver and url (_you should already have it configured since you created the database previously_)
96+
**a. The database driver and url** (_you should already have it configured since you created the database previously_)
9197
9298
```shell
9399
DATABASE_DRIVER=mysql # or postgresql, or sqlite
94-
DATABASE_URL=mysql://db_user:db_pass@host:3306/db_name?serverVersion=mariadb-10.6.10&charset=utf8mb4
100+
DATABASE_URL=mysql://db_user:db_pass@host:3306/db_name?serverVersion=10.9.3-MariaDB&charset=utf8mb4
95101
```
96102

97-
b. The admin password for the backend
103+
**b. The admin password for the backend**
98104

99105
```shell
100106
ADMIN_LOGIN=admin
@@ -105,15 +111,15 @@ ADMIN_PASSWORD=test
105111
>
106112
> You can bypass auth entirely if you use a third party authorization provider such as Authelia. In that case, set the `ADMIN_AUTH_BYPASS` env var to `true` (case-sensitive, this is actually the string `true`, not a boolean) to allow full access to the dashboard. This does not change the behaviour of the DAV server.
107113
108-
c. The auth Realm and method for HTTP auth
114+
**c. The auth Realm and method for HTTP auth**
109115

110116
```shell
111117
AUTH_REALM=SabreDAV
112118
AUTH_METHOD=Basic # can be "Basic", "IMAP" or "LDAP"
113119
```
114120
> See [the following paragraph](#specific-environment-variables-for-imap-and-ldap-authentication-methods) for more information if you choose either IMAP or LDAP.
115121
116-
d. The global flags to enable CalDAV, CardDAV and WebDAV. You can also disable the option to have calendars public
122+
**d. The global flags to enable CalDAV, CardDAV and WebDAV**. You can also disable the option to have calendars public
117123

118124
```shell
119125
CALDAV_ENABLED=true
@@ -128,14 +134,34 @@ PUBLIC_CALENDARS_ENABLED=true
128134
> By default, `PUBLIC_CALENDARS_ENABLED` is true. That doesn't mean that all calendars are public by default — it just means that you have an option, upon calendar creation, to set the calendar public (but it's not public by default).
129135
130136

131-
e. The email address that your invites are going to be sent from
137+
**e. The email address that your invites are going to be sent from**
132138

133139
```shell
134140
135141
```
136142

137-
f. The paths for the WebDAV installation
143+
**f. The reminder offset for all birthdays**
138144

145+
You must specify a relative duration, as specified in [the RFC 5545 spec](https://www.rfc-editor.org/rfc/rfc5545.html#section-3.3.6)
146+
147+
```shell
148+
BIRTHDAY_REMINDER_OFFSET=PT9H
149+
```
150+
151+
If you don't want a reminder for birthday events, set it to the `false` value (lowercase):
152+
153+
```shell
154+
BIRTHDAY_REMINDER_OFFSET=false
155+
```
156+
157+
> [!NOTE]
158+
>
159+
> By default, if the env var is not set or empty, we use `PT9H` (9am on the date of the birthday).
160+
161+
**g. The paths for the WebDAV installation**
162+
163+
> [!TIP]
164+
>
139165
> I recommend that you use absolute directories so you know exactly where your files reside.
140166
141167
```shell
@@ -152,15 +178,15 @@ WEBDAV_HOMES_DIR=
152178
>
153179
> By default, home directories are disabled totally (the env var is set to an empty string). If needed, it is recommended to use a folder that is **NOT** a child of the public dir, such as `/webdav/homes` for instance, so that users cannot access other users' homes.
154180
155-
g. The log file path
181+
**h. The log file path**
156182

157183
You can use an absolute file path here, and you can use Symfony's `%kernel.logs_dir%` and `%kernel.environment%` placeholders if needed (as in the default value). Setting it to `/dev/null` will disable logging altogether.
158184

159185
```shell
160186
LOG_FILE_PATH="%kernel.logs_dir%/%kernel.environment%.log"
161187
```
162188

163-
h. The timezone you want for the app
189+
**i. The timezone you want for the app**
164190

165191
This must comply with the [official list](https://www.php.net/manual/en/timezones.php)
166192

@@ -230,29 +256,27 @@ If you're migrating from Baïkal, then you will likely want to do the following
230256
231257
1. Get a backup of your data (without the `CREATE` statements, but with complete `INSERT` statements):
232258
233-
```shell
234-
mysqldump -u root -p --no-create-info --complete-insert baikal > baikal_to_davis.sql # baikal is the actual name of your database
235-
```
236-
259+
```shell
260+
mysqldump -u root -p --no-create-info --complete-insert baikal > baikal_to_davis.sql # baikal is the actual name of your database
261+
```
237262
238263
2. Create a new database for Davis (let's name it `davis`) and create the base schema:
239264
240-
```shell
241-
bin/console doctrine:migrations:migrate 'DoctrineMigrations\Version20191030113307' --no-interaction
242-
```
243-
265+
```shell
266+
bin/console doctrine:migrations:migrate 'DoctrineMigrations\Version20191030113307' --no-interaction
267+
```
244268
245269
3. Reimport the data back:
246270
247-
```
248-
mysql -uroot -p davis < baikal_to_davis.sql
249-
```
271+
```
272+
mysql -uroot -p davis < baikal_to_davis.sql
273+
```
250274
251275
4. Run the necessary remaining migrations:
252276
253-
```
254-
bin/console doctrine:migrations:migrate
255-
```
277+
```
278+
bin/console doctrine:migrations:migrate
279+
```
256280
257281
# 🌐 Access / Webserver
258282
@@ -262,7 +286,7 @@ The administration interface is available at `/dashboard`. You need to login to
262286
263287
The main endpoint for CalDAV, WebDAV or CardDAV is at `/dav`.
264288
265-
> [!NOTE]
289+
> [!TIP]
266290
>
267291
> For shared hosting, the `symfony/apache-pack` is included and provides a standard `.htaccess` file in the public directory so redirections should work out of the box.
268292
@@ -316,7 +340,7 @@ dav.domain.tld {
316340
SetEnv APP_ENV prod
317341
SetEnv APP_SECRET <app-secret-id>
318342
SetEnv DATABASE_DRIVER "mysql"
319-
SetEnv DATABASE_URL "mysql://db_user:db_pass@host:3306/db_name?serverVersion=mariadb-10.6.10&charset=utf8mb4"
343+
SetEnv DATABASE_URL "mysql://db_user:db_pass@host:3306/db_name?serverVersion=10.9.3-MariaDB&charset=utf8mb4"
320344
# ... etc
321345
</VirtualHost>
322346
```
@@ -345,7 +369,7 @@ server {
345369
fastcgi_param APP_ENV prod;
346370
fastcgi_param APP_SECRET <app-secret-id>;
347371
fastcgi_param DATABASE_DRIVER "mysql";
348-
fastcgi_param DATABASE_URL "mysql://db_user:db_pass@host:3306/db_name?serverVersion=mariadb-10.6.10&charset=utf8mb4";
372+
fastcgi_param DATABASE_URL "mysql://db_user:db_pass@host:3306/db_name?serverVersion=10.9.3-MariaDB&charset=utf8mb4";
349373
# ... etc ...
350374
351375
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
@@ -429,15 +453,15 @@ docker pull ghcr.io/tchapi/davis-standalone:v4.4.0
429453
430454
### Edge image
431455
432-
The edge image is built from the tip of the main branch:
456+
The edge image is generally built from the tip of the main branch, but might sometimes be used for specific branch testing:
433457
434458
```
435459
docker pull ghcr.io/tchapi/davis:edge
436460
```
437461
438462
> [!WARNING]
439463
>
440-
> The `edge` image must not be considered stable. Use only release images for production.
464+
> The `edge` image must not be considered stable. **Use only release images for production setups**.
441465
442466
## Full stack
443467
@@ -531,7 +555,12 @@ Depending on how you run Davis, logs are either:
531555
532556
> [!NOTE]
533557
>
534-
> It's `./var/log` (relative to the Davis installation), not `/var/log`
558+
> It's `./var/log` (relative to the Davis installation), not `/var/log`.
559+
>
560+
> To tail the aplication log on Docker, do:
561+
> ```
562+
> docker exec -it davis tail /var/www/davis/var/log/prod.log
563+
> ```
535564
536565
### I have a "Bad timezone configuration env var" error on the dashboard
537566
@@ -580,6 +609,14 @@ Check if your instance can reach your LDAP server:
580609
- Check that the `LDAP_DN_PATTERN` filter is compliant with your LDAP service
581610
- Example: `uid=%u,ou=people,dc=domain,dc=com`: [LLDAP](https://github.com/lldap/lldap) uses `people` instead of `users`.
582611
612+
### The birthday calendar is not synced / not up to date
613+
614+
An update event might have been missed. In this case, it's easy to resync all contacts by issuing the command:
615+
616+
```
617+
bin/console dav:sync-birthday-calendar
618+
```
619+
583620
# 📚 Libraries used
584621
585622
- Symfony 7 (Licence : MIT)

config/services.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ parameters:
99
timezone: '%env(APP_TIMEZONE)%'
1010
public_calendars_enabled: '%env(default:default_public_calendars_enabled:bool:PUBLIC_CALENDARS_ENABLED)%'
1111
default_public_calendars_enabled: "true"
12+
birthday_reminder_offset: '%env(default:default_birthday_reminder_offset:BIRTHDAY_REMINDER_OFFSET)%'
13+
default_birthday_reminder_offset: "PT9H"
14+
caldav_enabled: "%env(bool:CALDAV_ENABLED)%"
15+
carddav_enabled: "%env(bool:CARDDAV_ENABLED)%"
1216

1317
services:
1418
# default configuration for services in *this* file
@@ -68,6 +72,10 @@ services:
6872
tags:
6973
- { name: monolog.processor }
7074

75+
App\Services\BirthdayService:
76+
arguments:
77+
$birthdayReminderOffset: "%birthday_reminder_offset%"
78+
7179
when@dev:
7280
services:
7381
Symfony\Component\HttpKernel\Profiler\Profiler: '@profiler'

docker/.env

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ CARDDAV_ENABLED=true
66
WEBDAV_ENABLED=false
77
PUBLIC_CALENDARS_ENABLED=true
88

9+
BIRTHDAY_REMINDER_OFFSET=PT9H
10+
911
APP_TIMEZONE=Europe/Paris
1012

1113
LOG_FILE_PATH="%kernel.logs_dir%/%kernel.environment%.log"
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace DoctrineMigrations;
6+
7+
use Doctrine\DBAL\Schema\Schema;
8+
use Doctrine\Migrations\AbstractMigration;
9+
10+
/**
11+
* Add birthday calendar.
12+
*/
13+
final class Version20250421163214 extends AbstractMigration
14+
{
15+
public function getDescription(): string
16+
{
17+
return 'Add birthday calendars';
18+
}
19+
20+
public function up(Schema $schema): void
21+
{
22+
$engine = $this->connection->getDatabasePlatform()->getName();
23+
24+
if ('mysql' === $engine) {
25+
$this->addSql('ALTER TABLE addressbooks ADD included_in_birthday_calendar TINYINT(1) DEFAULT 0');
26+
} elseif ('postgresql' === $engine) {
27+
$this->addSql('ALTER TABLE addressbooks ADD COLUMN included_in_birthday_calendar BOOLEAN DEFAULT FALSE;');
28+
} elseif ('sqlite' === $engine) {
29+
$this->addSql('ALTER TABLE addressbooks ADD COLUMN included_in_birthday_calendar INTEGER DEFAULT 0;');
30+
}
31+
}
32+
33+
public function down(Schema $schema): void
34+
{
35+
if ('mysql' === $this->connection->getDatabasePlatform()->getName()) {
36+
$this->addSql('ALTER TABLE addressbooks DROP included_in_birthday_calendar');
37+
} else {
38+
$this->addSql('ALTER TABLE addressbooks DROP COLUMN included_in_birthday_calendar');
39+
}
40+
}
41+
}

0 commit comments

Comments
 (0)