diff --git a/README.md b/README.md
index 29040ee..31b4055 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,2 @@
-# reminder
\ No newline at end of file
+# reminder
+A simple python Flask application which adds reminders to every event in a given ICS file.
diff --git a/apache_sample_config.conf b/apache_sample_config.conf
new file mode 100644
index 0000000..9a275dc
--- /dev/null
+++ b/apache_sample_config.conf
@@ -0,0 +1,44 @@
+
+ ServerAdmin postmaster@finnchristiansen.de
+ DocumentRoot /var/www/vhosts/reminder.pimux.de
+ ServerName reminder.pimux.de
+
+ ErrorLog ${APACHE_LOG_DIR}/reminder.pimux.de_error.log
+ CustomLog ${APACHE_LOG_DIR}/reminder.pimux.de_access.log combined
+ RewriteEngine On
+ RewriteCond %{HTTPS} off
+ RewriteCond %{REQUEST_URI} !^/\.well-known/acme-challenge/.*
+ RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI}
+ RewriteCond %{SERVER_NAME} =reminder.pimux.de
+ RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent]
+
+
+
+ ServerAdmin postmaster@finnchristiansen.de
+ ServerName reminder.pimux.de
+ DocumentRoot /var/www/vhosts/reminder.pimux.de/
+
+ ErrorLog ${APACHE_LOG_DIR}/reminder.pimux.de_error.log
+ CustomLog ${APACHE_LOG_DIR}/reminder.pimux.de_access.log combined
+
+ WSGIDaemonProcess reminder user=www-data group=www-data threads=5
+ WSGIScriptAlias / /var/www/vhosts/reminder.pimux.de/index.py
+ WSGIScriptReloading On
+ WSGIPassAuthorization On
+
+ Alias /static/ /var/www/vhosts/reminder.pimux.de/static/
+
+
+ WSGIProcessGroup reminder
+ WSGIApplicationGroup %{GLOBAL}
+ Order deny,allow
+ Allow from all
+
+
+ SSLEngine on
+ SSLCACertificateFile /etc/letsencrypt/live/reminder.pimux.de/chain.pem
+ SSLCertificateFile /etc/letsencrypt/live/reminder.pimux.de/fullchain.pem
+ SSLCertificateKeyFile /etc/letsencrypt/live/reminder.pimux.de/privkey.pem
+ Include /etc/letsencrypt/options-ssl-apache.conf
+
+
diff --git a/index.py b/index.py
new file mode 100644
index 0000000..b1d2883
--- /dev/null
+++ b/index.py
@@ -0,0 +1,57 @@
+from flask import Flask, render_template, request, send_file
+from icalendar import Calendar, Alarm
+from flask_wtf import FlaskForm
+#from flask_wtf.file import FileField, FileRequired
+from wtforms import FileField, IntegerField, SubmitField
+from wtforms.validators import InputRequired
+from wtforms.widgets.html5 import NumberInput
+import tempfile
+
+application = Flask(__name__)
+application.config['SECRET_KEY'] = 'secret'
+
+
+@application.route("/", methods=['GET', 'POST'])
+def addreminder():
+ form = IcsForm()
+ content = render_template('index.html', form=form)
+ file = form.icsfile.data
+ if not file:
+ return content
+
+ hours = form.hours.data
+ minutes = form.minutes.data
+ try:
+ calendar = Calendar.from_ical(file.read())
+ except ValueError:
+ error = 'can\'t read file'
+ return render_template('index.html', form=form, error=error)
+
+ for component in calendar.walk('VEVENT'):
+ valarm_found = False
+ for k, v in component.property_items():
+ if k == 'BEGIN' and v == 'VALARM':
+ valarm_found = True
+
+ if not valarm_found:
+ alarm = Alarm()
+ alarm.add('ACTION', 'DISPLAY')
+ alarm.add('DESCRIPTION', component.get('SUMMARY'))
+ alarm.add('TRIGGER;VALUE=DURATION', '-PT%dH%dM' % (hours, minutes))
+ component.add_component(alarm)
+
+ new_ics = tempfile.TemporaryFile()
+ new_ics.write(calendar.to_ical())
+ new_ics.seek(0)
+ new_filename = file.filename.rstrip('.ics')+'_with_reminders'+'.ics'
+ return send_file(new_ics, as_attachment=True,
+ attachment_filename=new_filename)
+
+
+class IcsForm(FlaskForm):
+ icsfile = FileField('ICS File', validators=[InputRequired()])
+ hours = IntegerField('Hours', default=0,
+ widget=NumberInput(step=1, min=0, max=24))
+ minutes = IntegerField('Minutes', default=0,
+ widget=NumberInput(step=5, min=0, max=55))
+ submit = SubmitField('Submit')
diff --git a/static/css/style.css b/static/css/style.css
new file mode 100644
index 0000000..6a13954
--- /dev/null
+++ b/static/css/style.css
@@ -0,0 +1,153 @@
+body {
+ background-color: #EEEEEE;
+ font-family: sans-serif;
+ text-align: center;
+ margin: 0px;
+}
+
+main {
+ padding: 12px;
+}
+
+h1 {
+ font-size: 1.25em;
+}
+
+label {
+ text-align: left;
+}
+
+ol {
+ text-align: left;
+}
+
+a:link,
+a:visited {
+ text-decoration: none;
+ color: #0066cc;
+}
+
+a:hover,
+a:active {
+ text-decoration: underline;
+ color: #0066cc;
+}
+
+div.form {
+ width: 420px;
+ margin: 0 auto;
+}
+
+div.form-group {
+ display: flex;
+ justify-content: space-between;
+ margin-bottom: 24px;
+}
+
+div.form label {
+ font-size: 18px;
+}
+
+div.form input[type="number"] {
+ width: 80px;
+}
+
+footer {
+ width: 100vw;
+ position: absolute;
+ bottom: 0px;
+ padding: 10px 0px;
+}
+
+input[type="submit"] {
+ width: 150px;
+ color: white;
+ background-color: #0066cc;
+ padding: 14px 20px;
+ margin: 18px;
+ border: none;
+ border-radius: 4px;
+ cursor: pointer;
+ font-weight: bold;
+ font-size: 18px;
+}
+
+input[type="submit"]:hover {
+ background-color: #0044aa;
+}
+
+.file {
+ position: relative;
+ display: inline-block;
+ cursor: pointer;
+ height: 2.5rem;
+ width: 100%;
+ margin-bottom: 16px;
+}
+
+.file input {
+ min-width: 14rem;
+ margin: 0;
+ filter: alpha(opacity=0);
+ opacity: 0;
+}
+.file-custom::before {
+ position: absolute;
+ top: -.075rem;
+ right: -.075rem;
+ bottom: -.075rem;
+ z-index: 6;
+ display: block;
+ content: "Browse";
+ height: 2.5rem;
+ padding: .5rem 1rem;
+ line-height: 1.5;
+ color: #555;
+ background-color: #eee;
+ border: .075rem solid #ddd;
+ border-radius: 0 .25rem .25rem 0;
+}
+
+.file-custom {
+ position: absolute;
+ top: 0;
+ right: 0;
+ left: 0;
+ z-index: 5;
+ height: 2.5rem;
+ padding: .5rem 1rem;
+ line-height: 1.5;
+ color: #555;
+ background-color: #fff;
+ border: .075rem solid #ddd;
+ border-radius: .25rem;
+ box-shadow: inset 0 .2rem .4rem rgba(0,0,0,.05);
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+}
+
+*, ::before, ::after {
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+}
+.file-custom::after {
+ content: attr(data-content);
+}
+
+div.error {
+ color: #721c24;
+ background-color: #f8d7da;
+ padding: .75rem 1.25rem;
+ border: 1px solid #f5c6cb;
+ border-radius: .25rem;
+}
+
+@media all and (max-width: 500px) {
+ div.form {
+ width: 100%;
+ margin: 0 auto;
+ }
+}
diff --git a/static/js/reminder.js b/static/js/reminder.js
new file mode 100644
index 0000000..9034e19
--- /dev/null
+++ b/static/js/reminder.js
@@ -0,0 +1,6 @@
+document.addEventListener("DOMContentLoaded", function(event) {
+ document.getElementById('icsfile').addEventListener("change", function(e){
+ var filename = this.value.replace(/^.*[\\\/]/, '');
+ document.getElementById('filename').setAttribute('data-content', filename);
+ });
+});
diff --git a/templates/base.html b/templates/base.html
new file mode 100644
index 0000000..037e7d2
--- /dev/null
+++ b/templates/base.html
@@ -0,0 +1,21 @@
+
+
+
+
+
+ Add reminders to ICS files
+
+
+
+
+
+
+{% block body %}
+{% endblock %}
+
+
+
+
+
diff --git a/templates/index.html b/templates/index.html
new file mode 100644
index 0000000..722c408
--- /dev/null
+++ b/templates/index.html
@@ -0,0 +1,33 @@
+{% extends 'base.html' %}
+{% block body %}
+Add reminders to ICS files
+
+{% endblock %}