This is a Flask-based web application designed to manage events and visualize them on a dynamic timeline. This application supports integration with Grafana for visual analytics and the Giphy API for enhanced user interaction.
To get the application running locally, you have several options:
docker-compose up --build
The app will be available at http://127.0.0.1:5001/.
Alternatively, you can set up a development container or any other Python environment. After setting up, start the application with the following command:
flask run --debug
For production, you would use Gunicorn as a WSGI server:
gunicorn -w 4 -b "127.0.0.1:5000" "app:create_app()"
Ensure your environment has all the necessary dependencies installed as specified in the requirements.txt
file.
To integrate with Giphy, create a .env
file in the project root and add the following key:
GIPHY_API_TOKEN=<your_giphy_api_token>
To populate the application with sample data, run:
curl -X POST http://127.0.0.1:5000/batch-import -H "Content-Type: application/json" -d @testdata.json
- Purge Old Entries
- Set to automatically execute at the start of each month.
- Update Serial Entries
- Scheduled to run at the start of every new year.
These tasks use the APScheduler, with the scheduler API enabled for enhanced interaction through HTTP endpoints. More details and the API can be accessed here: APScheduler API Documentation.
Below are the available API endpoints with their respective usage:
-
Home
- GET
/
- Returns the main page of the application.
- GET
-
Timeline
- GET
/timeline?timeline-height=<height>&font-family=<font>&font-scale=<scale>&categories=<category_names>&load-past-images=<load>
- Displays a timeline of all entries. Allows optional
timeline-height
,font-family
,font-scale
,categories
, andload-past-images
query parameters to adjust the height of the timeline, set the font, apply a scale factor to the font size, filter entries by specified categories, and control the loading of images for past entries respectively.- Example:
/timeline?timeline-height=100%&font-family=Arial&font-scale=1.5&categories=Cake,Birthday&load-past-images=false
This would render a timeline with Arial font, increased by 1.5 times, showing only entries under 'Cake' and 'Birthday' categories, without loading images for past events.
- Example:
- GET
-
Create Entry
- POST
/create
- Creates a new entry. Requires form data including
date
,category
,title
, anddescription
.
- POST
-
Update Entry
- GET/POST
/update/<int:id>
- Retrieves an entry for editing or updates an entry if POST method is used.
- GET/POST
-
Delete Entry
- POST
/delete/<int:id>
- Deletes an entry by ID.
- POST
-
Toggle Entry Cancellation
- POST
/toggle_canceled/<int:id>
- Toggles the cancellation status of an entry by ID. The route changes the
canceled
state of the entry to eitherTrue
orFalse
depending on its current state. A successful operation will redirect back to the main page, updating the entry's status in the view.
- POST
-
API Data Access
- GET
/api/data
- Returns all entries in JSON format, including additional attributes such as
date_formatted
andindex
which help in sorting and formatting entries relative to the current date.
- GET
-
Export Data
- GET
/export-data
- Exports all entries and associated images as a zip file.
- GET
-
Batch Import
- POST
/batch-import
- Imports a batch of entries from a JSON file.
- POST
-
Update Serial Entries
- POST
/update-serial-entries
- Updates all entries linked to categories that are set to repeat annually, adjusting their dates to the current year.
- POST
-
Purge Old Entries
- POST
/purge-old-entries
- Deletes all entries where the date is in the past and the category is not marked as protected.
- POST
-
View and Manage Categories
- GET/POST
/categories
- Displays and allows management of categories including creation and update.
- GET/POST
-
Update Category
- POST
/categories/update/<int:id>
- Allows updating details for a specific category by ID.
- POST
-
Delete Category
- POST
/categories/delete/<int:id>
- Deletes a category if it is not associated with any entries.
- POST
Below is the database schema visualized using a Mermaid diagram:
classDiagram
class Category {
+int id
+string name : unique, not null
+string symbol : not null
+string color_hex : not null
+bool repeat_annually : default=false, not null
+bool display_celebration : default=false, not null
+bool is_protected : default=false, not null
+string last_updated_by : nullable [IP of last editor]
}
class Entry {
+int id
+string date : not null
+int category_id : ForeignKey, not null
+Category category : relationship
+string title : not null
+string description : nullable
+string image_filename : nullable
+string url : nullable
+bool cancelled : default=false, not null
+string last_updated_by : nullable [IP of last editor]
}
Category "1" o-- "*" Entry
This application supports integration with Grafana through a Simple JSON Datasource, enabling Grafana to pull data for visualization purposes. Here are the endpoints provided for Grafana:
-
Test Connection (
GET /grafana/
)- Confirms the data source connection is functional.
-
Search (
POST /grafana/search
)- Returns a list of categories that can be queried (e.g., 'cake', 'birthday').
-
Query (
POST /grafana/query
)- Retrieves timeseries data based on specified categories.
-
Annotations (
POST /grafana/annotations
)- Delivers event annotations for graph overlays based on specific queries.
-
Tag Keys (
POST /grafana/tag-keys
)- Provides tag keys for Grafana's ad hoc filtering capabilities.
-
Tag Values (
POST /grafana/tag-values
)- Supplies values for the selected tag keys for further filtering.
Query Grafana for timeseries data in the 'cake' category using this curl
command:
curl -X POST http://127.0.0.1:5000/grafana/query -H "Content-Type: application/json" -d '{"targets":[{"target": "Cake", "type": "timeserie"}]}'
This command will return timeseries data points for the 'cake' category if such data exists. Ensure the Grafana Simple JSON Datasource plugin is installed and properly configured to interact with these endpoints.
The Calendarium application features integration with the Giphy API to enhance the user interface with dynamic content. There are two different implementations of this integration, tailored to different deployment environments and security concerns.
In environments where the backend server has internet access, the application can directly interact with the Giphy API. This method involves the backend making API requests to Giphy and securely managing the API key via environment variables.
- Endpoint:
/search_gifs
- Method:
GET
- Function: This endpoint takes a search query as a parameter, directly calls the Giphy API, and returns the GIF data. This keeps the API key secure and not exposed to the client-side.
Example usage:
curl http://127.0.0.1:5000/search_gifs?q=cats
This implementation is found in the JavaScript file search_gifs.js
.
For environments where the backend does not have direct internet access, we employ a proxied approach. Here, the backend generates a URL with the API key, which is then used by the frontend to make the Giphy API call. This method ensures that the API key is not exposed in the client-side code.
- Backend Endpoint:
/get-giphy-url
- Method:
GET
- Function: This endpoint constructs the request URL including the API key and returns it to the client. The client then uses this URL to fetch GIF data directly from Giphy.
Example usage:
curl http://127.0.0.1:5000/get-giphy-url?q=cats
This implementation is located in the JavaScript file search_gifs_proxied.js
.
These methods are designed to accommodate different network security policies while maintaining functionality and protecting sensitive information.
This project uses Flickity, which is licensed under the GPLv3.
As such, modifications to the Flickity source code used in this project are documented in the repository. To comply with the GPLv3, all source code for this application is available under the same license. The full license text is included in the LICENSE file in this repository.
This project uses the icon "cracked glass" by Olena Panasovska from Noun Project licensed under CC BY 3.0. To meet the attribution requirements, this link points directly to the icon's detail page. Please refer to the Noun Project's guidelines for detailed information on how to properly attribute the creator in different formats and mediums.
Instead of Docker Hub, this project's Docker images are now built and pushed through GitHub Actions to the GitHub Container Registry.