Reorganize repository
This commit is contained in:
commit
8bc11233e2
29
README.md
Normal file
29
README.md
Normal file
@ -0,0 +1,29 @@
|
||||
## Meeting Planner Backend
|
||||
Find the best time to schedule a meeting across several timezones.
|
||||
Based on the availibity of all participants on specific days.
|
||||
|
||||
## How does it work?
|
||||
|
||||
#### For the meeting manager
|
||||
1. Enter a title and description(optional)
|
||||
2. Select the possible days on the calendar
|
||||
3. Select a deadline for responses
|
||||
4. Send invitations to potential participants
|
||||
5. Wait either for all participants to respond or for the deadline (whichever happens first)
|
||||
6. Choose and set the meeting date
|
||||
|
||||
Optional:
|
||||
- Choose the minimum of people for the meeting to take place (quorum)
|
||||
- Choose who needs to be there for the meeting to happen
|
||||
|
||||
#### For the participants
|
||||
1. Select your availibility on the calendar: you can choose between (Yes / No / Ideal)
|
||||
2. Wait for the meeting manager to confirm the meeting date
|
||||
|
||||
|
||||
## Code and architecture
|
||||
- [Backend](https://git.armada.digital/meeting-planner/backend)
|
||||
- Frontend (coming soon)
|
||||
- [API endpoints](./api-documentation.md)
|
||||
- [Database design](https://dbdiagram.io/d/5e769ab14495b02c3b88936f)
|
||||
- [Flowchart](https://app.diagrams.net/#Uhttps://git.armada.digital/meeting-scheduler-backend/)
|
637
api-documentation.md
Normal file
637
api-documentation.md
Normal file
@ -0,0 +1,637 @@
|
||||
## API Documentation
|
||||
___
|
||||
> Values required in **`bold`**.
|
||||
|
||||
### **Accounts** | `account`
|
||||
| field | data type | metadata |
|
||||
| :-------------| :---------------- | :-------------------------- |
|
||||
| id | unsigned integer | primary key, auto-increment |
|
||||
| username | varchar | required |
|
||||
| email | varchar | required |
|
||||
| password | varchar | required |
|
||||
| timezone | varchar | |
|
||||
| earliest_time | varchar | |
|
||||
| latest_time | varchar | |
|
||||
| created_at | datetime | generated by database |
|
||||
|
||||
#### Add an account
|
||||
**`POST /api/accounts`**
|
||||
|
||||
##### Request
|
||||
A json object for the account to register with **`username`**, **`email`**, **`password`**, `timezone`, `earliest_time` and `latest_time`.
|
||||
|
||||
```
|
||||
{
|
||||
"username": "jean",
|
||||
"email": "jean@example.com",
|
||||
"password": "really-strong-password",
|
||||
"timezone": "Europe/Brussels",
|
||||
"earliest_time":"09:30 AM",
|
||||
"latest_time":"10:00 PM"
|
||||
}
|
||||
```
|
||||
|
||||
##### Response
|
||||
A json object for the registered account with `id`, `username`, `email`, `timezone`, `earliest_time` and `latest_time`.
|
||||
|
||||
```
|
||||
{
|
||||
"status": 201,
|
||||
"data": {
|
||||
"id": 1,
|
||||
"username": "jean",
|
||||
"email": "jean@example.com",
|
||||
"timezone": "Europe/Brussels",
|
||||
"earliest_time":"09:30 AM",
|
||||
"latest_time":"10:00 PM"l
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Log in an account
|
||||
**`POST /api/accounts/login`**
|
||||
|
||||
##### Request
|
||||
A json object for the account to login with either **`email`** or **`username`**, and **`password`** .
|
||||
|
||||
```
|
||||
{
|
||||
"username": "jean",
|
||||
"password": "super-strong-password"
|
||||
}
|
||||
```
|
||||
|
||||
##### Response
|
||||
A json object for the registered account with `id`, `username`, `email`, `timezone`, `earliest_time` and `latest_time`.
|
||||
|
||||
```
|
||||
{
|
||||
"status": 201,
|
||||
"data": {
|
||||
"id": 1,
|
||||
"username": "jean",
|
||||
"email": "jean@example.com",
|
||||
"timezone": "Europe/Brussels",
|
||||
"earliest_time":"09:30 AM",
|
||||
"latest_time":"10:00 PM"l
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Update an account
|
||||
**`PUT /api/accounts/:id`**
|
||||
|
||||
##### Request
|
||||
A json object for the account to register with `id` and any of `username`, `email`, `password`, `timezone`, `earliest_time`, `latest_time`.
|
||||
|
||||
```
|
||||
{
|
||||
"id": 15,
|
||||
"earliest_time": "08:30 AM",
|
||||
"latest_time": "08:00 PM"
|
||||
}
|
||||
```
|
||||
|
||||
##### Response
|
||||
A json object for the registered account with `id`, `username`, `email`, `timezone`, `earliest_time` and `latest_time`.
|
||||
|
||||
```
|
||||
{
|
||||
"status": 200,
|
||||
"data": {
|
||||
"id": 1,
|
||||
"username": "jean",
|
||||
"email": "jean@example.com",
|
||||
"timezone": "Europe/Brussels",
|
||||
"earliest_time": "09:30 AM",
|
||||
"latest_time": "10:00 PM"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Delete an account
|
||||
**`DELETE /api/accounts/:id`**
|
||||
|
||||
##### Request
|
||||
A json object for the account to delete with `id`.
|
||||
|
||||
```
|
||||
{
|
||||
"id": 15
|
||||
}
|
||||
```
|
||||
|
||||
##### Response
|
||||
A json object for the deleted account with status code, message, `id`, `username` and `email`.
|
||||
|
||||
```
|
||||
{
|
||||
"status": 200,
|
||||
"message": " '`username`' account with email '`email`' has been successfully deleted."
|
||||
"data": {
|
||||
"id": 15,
|
||||
"username": "jean",
|
||||
"email": "jean@example.com"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Get a list of meetings for an account
|
||||
**`GET /api/accounts/:account_id/meetings`**
|
||||
|
||||
##### Request
|
||||
A json object with an `account_id`.
|
||||
|
||||
```
|
||||
{
|
||||
"id": 5
|
||||
}
|
||||
```
|
||||
|
||||
##### Response
|
||||
A json object for the specified account with an array of `meeting`.
|
||||
|
||||
```
|
||||
{
|
||||
"status": 200,
|
||||
"data": [{
|
||||
"id": "worldwide-strategy-meeting-for-11059",
|
||||
"title": "Worldwide strategy meeting for growth",
|
||||
"description": "Let's find the best ethical growth hacking technics together. Yeah, fun.",
|
||||
"start_time": "2022-02-16 20:00:00",
|
||||
"timezone": "Europe/Brussels",
|
||||
"duration": 60,
|
||||
"status": 1
|
||||
},
|
||||
{
|
||||
"id": "follow-up-with-tech-team-21850",
|
||||
"title": "Follow up with tech team",
|
||||
"duration": 120,
|
||||
"status": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### **Meetings** | `meeting`
|
||||
| field | data type | metadata |
|
||||
| :---------- | :-------- | :-------------------------------------------------------------------------------- |
|
||||
| id | varchar | primary key, first 30 chars of `title` with words separated by `-` + 5 random int |
|
||||
| title | varchar | required |
|
||||
| description | varchar | |
|
||||
| start_time | datetime | |
|
||||
| timezone | varchar | |
|
||||
| duration | int | required |
|
||||
| status | boolean | required: `0` (proposed) or `1` (confirmed) |
|
||||
| password | varchar | |
|
||||
| created_at | datetime | generated by database |
|
||||
|
||||
#### Add a meeting
|
||||
**`POST /api/meetings`**
|
||||
|
||||
##### Request
|
||||
A json object for the meeting to add with **`id`**, **`title`**, `description`, `start_time`, **`duration`** and `password`.
|
||||
|
||||
```
|
||||
{
|
||||
"id": "worldwide-strategy-meeting-for-11059",
|
||||
"title": "Worldwide strategy meeting for growth",
|
||||
"description": "Let's find the best ethical growth hacking technics together. Yeah, fun.",
|
||||
"duration": 90,
|
||||
"password": "generic-password"
|
||||
}
|
||||
```
|
||||
|
||||
##### Response
|
||||
A json object for the added meeting with `id`, `title`, `description`, `start_time`, `duration`, `status` and `password`.
|
||||
|
||||
```
|
||||
{
|
||||
"status": 201,
|
||||
"data": {
|
||||
"id": "worldwide-strategy-meeting-for-11059",
|
||||
"title": "Worldwide strategy meeting for growth",
|
||||
"description": "Let's find the best ethical growth hacking technics together. Yeah, fun.",
|
||||
"duration": 90,
|
||||
"status": 0
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Update a meeting
|
||||
**`PUT /api/meetings/:id`**
|
||||
|
||||
##### Request
|
||||
A json object for the meeting to update with `id`, `acount_id` and any of `title`, `description`, `start_time`, `timezone`, `duration`, `status` and `password`.
|
||||
|
||||
```
|
||||
{
|
||||
"id": "worldwide-strategy-meeting-for-11059",
|
||||
"account_id": 5,
|
||||
"start_time": "2022-02-16 20:00:00",
|
||||
"timezone": "Europe/Brussels",
|
||||
"status": 1
|
||||
}
|
||||
```
|
||||
|
||||
##### Response
|
||||
A json object for the updated meeting with `id`, `title`, `description`, `start_time`, `timezone`, `duration` and `status`.
|
||||
|
||||
```
|
||||
{
|
||||
"status": 200,
|
||||
"data": {
|
||||
"id": "worldwide-strategy-meeting-for-11059",
|
||||
"title": "Worldwide strategy meeting for growth",
|
||||
"description": "Let's find the best ethical growth hacking technics together. Yeah, fun.",
|
||||
"start_time": "2022-02-16 20:00:00",
|
||||
"timezone": "Europe/Brussels",
|
||||
"duration: 90,
|
||||
"status": 1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Delete a meeting
|
||||
**`DELETE /api/meetings/:id`**
|
||||
|
||||
##### Request
|
||||
A json object for the meeting to delete with `id` and `account_id`.
|
||||
|
||||
```
|
||||
{
|
||||
"id": "worldwide-strategy-meeting-for-11059",
|
||||
"account_id": 5
|
||||
}
|
||||
```
|
||||
|
||||
##### Response
|
||||
A json object for the deleted meeting with status code and message.
|
||||
|
||||
```
|
||||
{
|
||||
"status": 200,
|
||||
"message": "Meeting '`meeting_title`' has been successfully deleted."
|
||||
}
|
||||
```
|
||||
|
||||
#### Get a list of all participants for a meeting
|
||||
**`GET /api/meetings/:id/participants`**
|
||||
|
||||
##### Request
|
||||
A json object with an `account_id`.
|
||||
|
||||
```
|
||||
{
|
||||
"id": "worldwide-strategy-meeting-for-11059",
|
||||
"account_id": 5
|
||||
}
|
||||
```
|
||||
|
||||
##### Response
|
||||
A json object for the specified meeting with an array of `participant`.
|
||||
|
||||
```
|
||||
{
|
||||
"status": 200,
|
||||
"data": [{
|
||||
"id": 23,
|
||||
"account_id": 5,
|
||||
"meeting_id": "worldwide-strategy-meeting-for-11059",
|
||||
"earliest_time": "09:30 AM",
|
||||
"latest_time": "05:00 PM",
|
||||
"quorum": 1,
|
||||
"mandatory": 1,
|
||||
"host": 1,
|
||||
"answered": 1,
|
||||
"timezone": "Europe/Brussels"
|
||||
},
|
||||
{
|
||||
"id": 28,
|
||||
"account_id": 11,
|
||||
"meeting_id": "worldwide-strategy-meeting-for-11059",
|
||||
"earliest_time": "10:00 AM",
|
||||
"latest_time": "09:00 PM",
|
||||
"quorum": 0,
|
||||
"mandatory": 1,
|
||||
"host": 0,
|
||||
"answered": 1,
|
||||
"timezone": "Europe/Brussels"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### Get a list of complete availibility for a meeting
|
||||
**`GET /api/meetings/:id/availibility`**
|
||||
|
||||
##### Request
|
||||
A json object with a `meeting_id`.
|
||||
|
||||
```
|
||||
{
|
||||
"id": "worldwide-strategy-meeting-for-11059"
|
||||
}
|
||||
```
|
||||
|
||||
##### Response
|
||||
A json object for the specified meeting with an array of `participant`.
|
||||
|
||||
```
|
||||
{
|
||||
"status": 200,
|
||||
"data": {
|
||||
"meeting_id": "worldwide-strategy-meeting-for-11059",
|
||||
"availibility": [{
|
||||
"participant_id": 5,
|
||||
"possible_date_id": 21,
|
||||
"intervals": [{
|
||||
"preference": 0,
|
||||
"start_time": "2021-06-25 09:00:00",
|
||||
"end_time": "2021-06-25 13:00:00",
|
||||
"timezone": "Europe/Brussels"
|
||||
},
|
||||
{
|
||||
"preference": 1,
|
||||
"start_time": "2021-06-25 15:00:00",
|
||||
"end_time": "2021-06-25 20:00:00",
|
||||
"timezone": "Europe/Brussels"
|
||||
}]
|
||||
},
|
||||
{
|
||||
"participant_id": 56,
|
||||
"possible_date_id": 21,
|
||||
"intervals": [{
|
||||
"preference": 0,
|
||||
"start_time": "2021-06-25 08:00:00",
|
||||
"end_time": "2021-06-25 10:30:00",
|
||||
"timezone": "Europe/Brussels"
|
||||
}]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### **Possible Dates** | `possible_date`
|
||||
| field | data type | metadata |
|
||||
| :------------ | :-------- | :-------------------------- |
|
||||
| id | int | primary key, auto-increment |
|
||||
| meeting_id | varchar | foreign key, required |
|
||||
| possible_date | date | foreign key, required |
|
||||
|
||||
#### Add a possible date
|
||||
**`POST /api/possible-dates`**
|
||||
|
||||
##### Request
|
||||
A json object for the meeting to add with **`meeting_id`** and **`possible_date`**.
|
||||
|
||||
```
|
||||
{
|
||||
"meeting_id": "worldwide-strategy-meeting-for-11059",
|
||||
"possible_date": "2020-02-18"
|
||||
}
|
||||
```
|
||||
|
||||
##### Response
|
||||
A json object for the added meeting with `id`, `meeting_id` and `possible_date`.
|
||||
|
||||
```
|
||||
{
|
||||
"status": 201,
|
||||
"data": {
|
||||
"id": 5,
|
||||
"meeting_id": "worldwide-strategy-meeting-for-11059",
|
||||
"possible_date": "2020-02-18"
|
||||
}
|
||||
}
|
||||
```
|
||||
#### Delete a possible date
|
||||
**`DELETE /api/possible-dates/:id`**
|
||||
|
||||
##### Request
|
||||
A json object for the possible date to delete with `id` and `account_id`.
|
||||
|
||||
```
|
||||
{
|
||||
"id": 12,
|
||||
"account_id": 5
|
||||
}
|
||||
```
|
||||
|
||||
##### Response
|
||||
A json object for the deleted possible date with status code and message.
|
||||
|
||||
```
|
||||
{
|
||||
"status": 200,
|
||||
"message": "Date '`possible_date`' has been successfully removed for the '`meeting_title`'."
|
||||
}
|
||||
```
|
||||
|
||||
### **Participants** | `participant`
|
||||
| field | data type | metadata |
|
||||
| :------------ | :-------- | :-------------------------- |
|
||||
| account_id | int | primary key, auto-increment |
|
||||
| meeting_id | varchar | required |
|
||||
| earliest_time | datetime | |
|
||||
| latest_time | datetime | |
|
||||
| quorum | boolean | `0` (no) or `1` (yes) |
|
||||
| mandatory | boolean | `0` (no) or `1` (yes) |
|
||||
| host | boolean | `0` (no) or `1` (yes) |
|
||||
| answered | boolean | `0` (no) or `1` (yes) |
|
||||
| timezone | varchar | required |
|
||||
| created_at | datetime | generated by database |
|
||||
|
||||
#### Invite a participant
|
||||
**`POST /api/participants`**
|
||||
|
||||
##### Request
|
||||
A json object for the participant to add with **`account_id`**, **`meeting_id`**, **`earliest_time`**, **`latest_time`**, **`quorum`**, **`mandatory`**, **`host`**, **`answered`** and **`timezone`**.
|
||||
|
||||
```
|
||||
{
|
||||
"account_id": 5,
|
||||
"meeting_id": "worldwide-strategy-meeting-for-11059",
|
||||
"earliest_time": "08:30 AM",
|
||||
"latest_time": "08:00 PM",
|
||||
"quorum": 0,
|
||||
"mandatory": 1,
|
||||
"host": 0,
|
||||
"answered": 0,
|
||||
"timezone": "Europe/Brussels"
|
||||
}
|
||||
```
|
||||
|
||||
##### Response
|
||||
A json object for the participant with `id`, `account_id`, `meeting_id`, `earliest_time`, `latest_time`, `quorum`, `mandatory`, `host`, `answered` and `timezone`.
|
||||
|
||||
```
|
||||
{
|
||||
"status": 201,
|
||||
"data": {
|
||||
"id": 23,
|
||||
"account_id": 5,
|
||||
"meeting_id": "worldwide-strategy-meeting-for-11059",
|
||||
"earliest_time": "08:30 AM",
|
||||
"latest_time": "08:00 PM",
|
||||
"quorum": 0,
|
||||
"mandatory": 1,
|
||||
"host": 0,
|
||||
"answered": 0,
|
||||
"timezone": "Europe/Brussels"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Update a participant
|
||||
**`PUT /api/participants/:id`**
|
||||
|
||||
##### Request
|
||||
A json object for the participant to invite with `id` and any of `id`, `earliest_time`, `latest_time`, `quorum`, `mandatory`, `host`, `answered` and `timezone`.
|
||||
|
||||
```
|
||||
{
|
||||
"account_id": 5,
|
||||
"earliest_time": "09:30 AM",
|
||||
"latest_time": "05:00 PM",
|
||||
"quorum": 1,
|
||||
"host": 1,
|
||||
"answered": 1,
|
||||
}
|
||||
```
|
||||
|
||||
##### Response
|
||||
A json object for the participant with `id`, `account_id`, `meeting_id`, `earliest_time`, `latest_time`, `quorum`, `mandatory`, `host`, `answered` and `timezone`.
|
||||
|
||||
```
|
||||
{
|
||||
"status": 200,
|
||||
"data": {
|
||||
"id": 23,
|
||||
"account_id": 5,
|
||||
"meeting_id": "worldwide-strategy-meeting-for-11059",
|
||||
"earliest_time": "09:30 AM",
|
||||
"latest_time": "05:00 PM",
|
||||
"quorum": 1,
|
||||
"mandatory": 1,
|
||||
"host": 1,
|
||||
"answered": 1,
|
||||
"timezone": "Europe/Brussels"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Delete a participant
|
||||
**`DELETE /api/participants/:id`**
|
||||
|
||||
##### Request
|
||||
A json object for the participant to delete with `id`.
|
||||
|
||||
```
|
||||
{
|
||||
"id": 23
|
||||
}
|
||||
```
|
||||
|
||||
##### Response
|
||||
A json object for the deleted participant with status code and message.
|
||||
|
||||
```
|
||||
{
|
||||
"status": 200,
|
||||
"message": "The '`participant_username`' has been successfully removed from the meeting '`meeting_title`'."
|
||||
}
|
||||
```
|
||||
|
||||
### **Availibility** | `availibility`
|
||||
| field | data type | metadata |
|
||||
| :--------------- | :-------- | :--------------------------------- |
|
||||
| id | int | primary key, auto-increment |
|
||||
| participant_id | int | foreign key, required |
|
||||
| possible_date_id | int | foreign key, required |
|
||||
| preference | boolean | `0` (ideal) or `1` (yes), required |
|
||||
| start_time | timestamp | required |
|
||||
| end_time | timestamp | required |
|
||||
| timezone | varchar | required |
|
||||
| created_at | datetime | generated by database |
|
||||
|
||||
GET / DELETE / UPDATE
|
||||
|
||||
#### Add an availibility for a possible date
|
||||
**`POST /api/availibility`**
|
||||
|
||||
##### Request
|
||||
A json object with the availibility to add with **`participant_id`**, **`possible_date_id`** and an array of intervals with **`preference`**, **`start_time`**, **`end_time`** and **`timezone`**.
|
||||
|
||||
```
|
||||
{
|
||||
"participant_id": 5,
|
||||
"possible_date_id": 21,
|
||||
"intervals": [{
|
||||
"preference": 0,
|
||||
"start_time": "2021-06-25 09:00:00",
|
||||
"end_time": "2021-06-25 13:00:00",
|
||||
"timezone": "Europe/Brussels"
|
||||
},
|
||||
{
|
||||
"preference": 1,
|
||||
"start_time": "2021-06-25 15:00:00",
|
||||
"end_time": "2021-06-25 20:00:00",
|
||||
"timezone": "Europe/Brussels"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
##### Response
|
||||
A json object with the availibility to add with **`participant_id`**, **`possible_date_id`** and an array of intervals with **`id`**, **`preference`**, **`start_time`**, **`end_time`** and **`timezone`**.
|
||||
|
||||
```
|
||||
{
|
||||
"status": 201,
|
||||
"data": {
|
||||
"participant_id": 5,
|
||||
"possible_date_id": 21,
|
||||
"intervals": [{
|
||||
"id": 45,
|
||||
"preference": 0,
|
||||
"start_time": "2021-06-25 09:00:00",
|
||||
"end_time": "2021-06-25 20:00:00",
|
||||
"timezone": "Europe/Brussels"
|
||||
},
|
||||
{
|
||||
"id": 46,
|
||||
"preference": 1,
|
||||
"start_time": "2021-06-25 09:00:00",
|
||||
"end_time": "2021-06-25 20:00:00",
|
||||
"timezone": "Europe/Brussels"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Delete availibility for a possible date
|
||||
**`DELETE /api/availibility/:id`**
|
||||
|
||||
##### Request
|
||||
A json object for the availibility to delete with **`participant_id`** and **`possible_date_id`**.
|
||||
|
||||
```
|
||||
{
|
||||
"participant_id": 5,
|
||||
"possible_date_id": 21
|
||||
}
|
||||
```
|
||||
|
||||
##### Response
|
||||
A json object for the deleted availibility with status code and message.
|
||||
|
||||
```
|
||||
{
|
||||
"status": 200,
|
||||
"message": "Availibility successfully deleted for `participant_username` on `possible_date`"
|
||||
}
|
||||
```
|
1
meetingscheduler.drawio
Normal file
1
meetingscheduler.drawio
Normal file
@ -0,0 +1 @@
|
||||
<mxfile host="Electron" modified="2020-03-25T18:31:26.977Z" agent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/12.6.5 Chrome/80.0.3987.141 Electron/8.1.1 Safari/537.36" etag="QzSuGUuUtPoOGiynkDqc" version="12.6.5" type="device"><diagram id="C5RBs43oDa-KdzZeNtuy" name="App flow">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</diagram></mxfile>
|
23
research.md
Normal file
23
research.md
Normal file
@ -0,0 +1,23 @@
|
||||
## Proposed Tech stack
|
||||
- NodeJS
|
||||
- Express
|
||||
- Postgresql
|
||||
- Luxon
|
||||
|
||||
## How to deal with date and time in PostgreSQL?
|
||||
- use `datetime` datatype without timezone (datetime with timezone is using offset, which doesn't work well with DST)
|
||||
- store datetime in UTC
|
||||
- add timezone information separately, use `Full time zone name` (human friendly)
|
||||
- use `ISO 8601` format, which is human friendly to read and supported by every library
|
||||
|
||||
|
||||
See:
|
||||
- https://www.moesif.com/blog/technical/timestamp/manage-datetime-timestamp-timezones-in-api/
|
||||
- https://www.postgresql.org/docs/current/datatype-datetime.html
|
||||
|
||||
|
||||
## Inspiration
|
||||
- Framadate: https://framadate.org/
|
||||
- Omnipointment: https://omnipointment.com
|
||||
- When is Good: http://whenisgood.net/
|
||||
- Xoyondo: https://xoyondo.com/
|
Loading…
Reference in New Issue
Block a user