Compare commits

..

10 Commits

Author SHA1 Message Date
c3dcccb479 reloads page after successful form transmition in routes 2020-07-25 23:44:49 +02:00
5ec5ba488d adds remove button to all calendars 2020-07-25 19:45:37 +02:00
9ecaf0211f timezone fix to always use calendar timezone for event description 2020-07-25 19:28:30 +02:00
1f48689a32 Merge branch 'master' of git.maenle.net:raphael/calendarwatch_frontend 2020-07-25 18:10:04 +02:00
752c7c5577 adds ical input to calendar html templates and implements data handling
- the icalHandler in the backend is used in two instances. First, when
  the user adds a new ical calendar into the database, routes.py passes
  the information (name and url) to the ical Handler. The calendars are
  saved in the database as type 'ical'.

  Then, when a connected device pulls current events, all the calendars
  which are of type 'ical' are pulled and parsed for current events.
  See the backend commit for details on what happens there.

- All google Calendar related functions now have an additional check,
  to make sure that the calendar they are working on is of type 'Google'.
2020-07-25 18:05:55 +02:00
04c5410c41 migration scripts to newest database 2020-07-25 11:41:12 +02:00
524d2f1e1d adds calendar_type to calendar model and to calendar handling 2020-07-25 11:28:59 +02:00
2add28fa00 adds ical form and form visualization in calendar.html 2020-07-25 10:50:02 +02:00
38e16f92e8 device receives a timestamp at creation
no new devices are ever without a lastConnection timestamp
2020-07-17 10:42:27 +02:00
0284eb2fa8 adds cleanup functionality for old devices
- device now last field 'last connection' which gets updated
  at every server connection from the respective device
- manual cleanup script in database deletes all devices which
  have never been updated, or have a timestamp older than 30 days
2020-07-17 10:39:16 +02:00
9 changed files with 154 additions and 43 deletions

Submodule backend updated: 45cd71cc4b...f939127a0c

14
database/__init__.py Normal file
View File

@ -0,0 +1,14 @@
from server import db
import time
from database.models import Device
def cleanDevices():
allDevs = db.session.query(Device)
devices = allDevs.filter(Device.lastConnection <= int(round(time.time())) - 60*60*24*30).all()
devices += allDevs.filter(Device.lastConnection == None)
for device in devices:
print(device.deviceName)
db.session.delete(device)
db.session.commit()

View File

@ -0,0 +1,30 @@
"""empty message
Revision ID: 9882522aafa9
Revises: e5ef5e4a807b
Create Date: 2020-07-25 09:34:07.987380
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '9882522aafa9'
down_revision = 'e5ef5e4a807b'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('calendar', sa.Column('calendar_type', sa.String(length=32), nullable=True))
op.create_index(op.f('ix_calendar_calendar_type'), 'calendar', ['calendar_type'], unique=False)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_index(op.f('ix_calendar_calendar_type'), table_name='calendar')
op.drop_column('calendar', 'calendar_type')
# ### end Alembic commands ###

View File

@ -61,11 +61,13 @@ class Device(db.Model):
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
deviceName = db.Column(db.String(64), unique=True)
connection = db.Column(db.Boolean)
lastConnection = db.Column(db.BigInteger)
class Calendar(db.Model):
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), index=True, nullable=False)
calendar_id = db.Column(db.String(256), primary_key=True)
calendar_type = db.Column(db.String(32), index=True)
name = db.Column(db.String(256), index=True)
toggle = db.Column(db.String(8))
color = db.Column(db.String(16))

View File

@ -2,9 +2,10 @@ FROM python:3.8-slim-buster
RUN apt-get update && apt-get upgrade
RUN pip3 install flask Flask-SQLAlchemy flask_migrate flask_login flask_wtf python-dotenv
RUN apt-get install gcc libpcre3 libpcre3-dev libmariadbclient-dev -y
RUN pip3 install uwsgi email-validator RandomWords
RUN pip3 install uwsgi email-validator RandomWords ics
RUN pip3 install google google-oauth google-auth-oauthlib google-api-python-client mysqlclient
COPY docker-entrypoint.sh /usr/local/bin/
EXPOSE 8084
EXPOSE 3001
ENTRYPOINT ["docker-entrypoint.sh"]
# CMD tail -f /dev/null

View File

@ -46,3 +46,13 @@ class DeviceForm(FlaskForm):
device = Device.query.filter_by(deviceName=deviceName.data).first()
if device is None:
raise ValidationError('Device not Found')
class CalendarForm(FlaskForm):
iCalURL = StringField('New ical URL', validators=[DataRequired()])
calName = StringField('Calendar Name', validators=[DataRequired()])
submit = SubmitField('Add URL')
def validate_iCalURL (self, iCalURL):
return None

View File

@ -121,19 +121,23 @@ def deleteAccount(user):
def fetchCalendarEvents(user, calendars, startDate, endDate):
service = None
if user.google_token is not None:
client_token = GC.build_credentials(user.google_token.token,
user.google_token.refresh_token)
credentials = google.oauth2.credentials.Credentials(**client_token)
client_token = GC.build_credentials(user.google_token.token,
user.google_token.refresh_token)
credentials = google.oauth2.credentials.Credentials(**client_token)
service = build(GC.API_SERVICE_NAME, GC.API_VERSION, credentials=credentials)
service = build(GC.API_SERVICE_NAME, GC.API_VERSION, credentials=credentials)
all_events = []
for calendar in calendars:
if calendar.toggle == "True":
if (calendar.toggle == "True" and
calendar.calendar_type == "Google" and
service != None):
event_result = service.events().list(calendarId=calendar.calendar_id,
timeMin=startDate,
timeMax=endDate,
timeMin=startDate.isoformat(),
timeMax=endDate.isoformat(),
maxResults=10,
singleEvents=True,
orderBy='startTime').execute()
@ -156,7 +160,10 @@ def fetchCalendarEvents(user, calendars, startDate, endDate):
all_events.append(newEvent)
colors = service.colors().get().execute()
if service != None:
colors = service.colors().get().execute()
else:
colors = None
return all_events, colors
@ -178,6 +185,7 @@ def fetchCalendars():
calendar_list = service.calendarList().list(pageToken=page_token).execute()
for calendar in calendar_list['items']:
calendars.append(Calendar(name=calendar['summary'],
calType="Google",
calendarId=calendar['id'],
color=calendar['colorId']))
page_token = calendar_list.get('nextPageToken')

View File

@ -1,6 +1,7 @@
# Python standard libraries
import json
import os
import time
# Third-party libraries
import flask
@ -16,10 +17,11 @@ from flask_login import (
from random_words import RandomWords
import requests
import backend.icalHandler as ical
import server.googleHandler as google
from server import login_manager, app, db
from server.forms import LoginForm, RegistrationForm, DeviceForm
from server.forms import LoginForm, RegistrationForm, DeviceForm, CalendarForm
import backend
from database.models import User, Calendar, Device, GoogleToken
@ -93,11 +95,58 @@ def devices():
form=form)
@app.route("/calendar")
@login_required
@app.route("/calendar", methods=['GET', 'POST', 'DELETE'])
def calendar():
if not current_user.is_authenticated:
return flask.render_template('login.html')
calendars = backend.calendarsFromDb(current_user)
return flask.render_template('calendar.html', calendars=calendars)
form = CalendarForm()
if request.method == 'POST':
if request.form.get("submit") == "Remove":
calendar = db.session.query(Calendar).filter(Calendar.calendar_id==request.form.get("calendar")).first()
db.session.delete(calendar)
db.session.commit()
return flask.redirect(url_for('calendar'))
elif form.validate_on_submit():
ical.icalToCalendarDb(form.iCalURL.data, form.calName.data, current_user)
return flask.redirect(url_for('calendar'))
# otherwise it is a javascript POST
else:
try:
calId = request.json.get('calendar_id')
color = request.json.get('color', None)
toggle = request.json.get('toggle', None)
except:
return flask.render_template('calendar.html', calendars=calendars, form=form)
if color != None:
current_user.updateCalendar(calId, color=color)
if toggle != None:
current_user.updateCalendar(calId, toggle=toggle)
# toggle specific calendar of user
try:
calId = request.json.get('calendar_id')
color = request.json.get('color', None)
toggle = request.json.get('toggle', None)
except:
return flask.render_template('calendar.html', calendars=calendars, form=form)
if color != None:
current_user.updateCalendar(calId, color=color)
if toggle != None:
current_user.updateCalendar(calId, toggle=toggle)
# toggle specific calendar of user
return flask.render_template('calendar.html', calendars=calendars, form=form)
# POST
@app.route('/login/email', methods=['GET', 'POST'])
def emaillogin():
@ -204,6 +253,7 @@ def downloader(device):
if request_device.user_id == None:
return jsonify(kind="unregistered")
request_device.lastConnection=int(round(time.time()))
request_device.connection=True
db.session.commit()
request_user = db.session.query(User).filter(User.id==request_device.user_id).first()
@ -213,6 +263,7 @@ def downloader(device):
# TODO only pass along google calendars form user
startDate, endDate = backend.getTimeStamps()
events, colors = google.fetchCalendarEvents(request_user, request_user.calendars, startDate, endDate)
events.extend(ical.fetchCalendarEvents(request_user.calendars, startDate, endDate))
calendarjson = backend.generateJsonFromCalendarEntries(events, colors)
return jsonify(calendarjson)
@ -233,6 +284,7 @@ def generateDeviceFingerprint():
if not db.session.query(Device).filter(Device.deviceName==fingerprint).first():
# Save as new Device
device = Device(deviceName=fingerprint, connection=False)
device.lastConnection = int(round(time.time()))
db.session.add(device)
db.session.commit()
break;
@ -240,27 +292,3 @@ def generateDeviceFingerprint():
# Send to Device
return jsonify(deviceName=fingerprint)
# POST
@app.route('/calendar', methods = ['POST', 'DELETE'])
@login_required
def user():
if request.method == 'POST':
calId = request.json.get('calendar_id')
color = request.json.get('color', None)
toggle = request.json.get('toggle', None)
if color != None:
current_user.updateCalendar(calId, color=color)
if toggle != None:
current_user.updateCalendar(calId, toggle=toggle)
# toggle specific calendar of user
elif request.method == 'DELETE':
# do nothing
return 'NONE'
else:
# POST Error 405
print("405")
return 'OK'

View File

@ -9,6 +9,13 @@
{% for item in calendars %}
<div class="container">
<!--action button-->
<div style="width: 4rem; margin: 1rem;">
<form action="" method="post">
<input type="hidden" name="calendar" value={{ item.calendarId }}>
<input type="submit" name="submit" value="Remove">
</form>
</div>
<!--Name-->
<div style="width: 15rem; margin: 1rem;">{{ item.name }}</div>
@ -32,12 +39,23 @@
</div>
{% endfor %}
<div id=calendars class="container">
<a class="button" href="login/google">Google Calendar</a>
<a class="button" href="#" >Nextcloud Calendar</a>
</div>
<div class="container">
<a class="button addcalendar" href="/login/google" style="width: auto; margin: 4rem">Add Calendar</a>
<form action="" method="post">
<div class="container grey" style="margin-top: 3rem;">
<div>{{ form.hidden_tag() }}</div>
<div style="margin: 1rem">{{ form.calName.label }}</div>
<div style="margin: 1rem">{{ form.calName(size=24) }}</div>
<div style="margin: 1rem">{{ form.iCalURL.label }}</div>
<div style="margin: 1rem">{{ form.iCalURL(size=24) }}</div>
<div style="with: 8rem; margin: 1rem">{{ form.submit() }}</div>
{% for error in form.iCalURL.errors %}
<span style="color: red;">[{{ error }}]</span>
{% endfor %}
</div>
</form>
<!--div class="container">
<a class="button addcalendar" href="/login/google" style="width: auto; margin: 4rem">Add Calendar</a>
</div-->
<script type="text/javascript">