Compare commits

..

No commits in common. "master" and "dev" have entirely different histories.
master ... dev

15 changed files with 69 additions and 220 deletions

@ -1 +1 @@
Subproject commit f939127a0c9099f9c22c39bfaaed33b70b006fa2
Subproject commit 45cd71cc4bcddf23f46b1eddcffd8c7fda2b9d41

View File

@ -1,14 +0,0 @@
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

@ -1,30 +0,0 @@
"""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,13 +61,11 @@ 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,10 +2,9 @@ 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 ics
RUN pip3 install uwsgi email-validator RandomWords
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

@ -1,17 +1,5 @@
#!/bin/sh
cd /home/calendarwatch
# use flasks own uwsgi server for debugging:
# export FLASK_APP=/home/calendarwatch/server.py
# python3 server.py
# the --lazy flag forks() a new instance of the server
# instead of forking from the parent and copying the same mysql
# connection. If you don't do that, then multiple forks will use
# the same connection at the same time, causing the server to throw
# a 'connection has gone away' error.
# more here: https://serverfault.com/questions/407612/error-2006-mysql-server-has-gone-away
uwsgi --ini wsgi.ini --lazy
uwsgi --ini wsgi.ini
echo "server has been started"

View File

@ -46,13 +46,3 @@ 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,23 +121,19 @@ 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)
service = build(GC.API_SERVICE_NAME, GC.API_VERSION, credentials=credentials)
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)
all_events = []
for calendar in calendars:
if (calendar.toggle == "True" and
calendar.calendar_type == "Google" and
service != None):
if calendar.toggle == "True":
event_result = service.events().list(calendarId=calendar.calendar_id,
timeMin=startDate.isoformat(),
timeMax=endDate.isoformat(),
timeMin=startDate,
timeMax=endDate,
maxResults=10,
singleEvents=True,
orderBy='startTime').execute()
@ -160,10 +156,7 @@ def fetchCalendarEvents(user, calendars, startDate, endDate):
all_events.append(newEvent)
if service != None:
colors = service.colors().get().execute()
else:
colors = None
colors = service.colors().get().execute()
return all_events, colors
@ -185,7 +178,6 @@ 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,7 +1,6 @@
# Python standard libraries
import json
import os
import time
# Third-party libraries
import flask
@ -17,11 +16,10 @@ 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, CalendarForm
from server.forms import LoginForm, RegistrationForm, DeviceForm
import backend
from database.models import User, Calendar, Device, GoogleToken
@ -95,58 +93,11 @@ def devices():
form=form)
@app.route("/calendar", methods=['GET', 'POST', 'DELETE'])
@app.route("/calendar")
@login_required
def calendar():
if not current_user.is_authenticated:
return flask.render_template('login.html')
calendars = backend.calendarsFromDb(current_user)
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
return flask.render_template('calendar.html', calendars=calendars)
@app.route('/login/email', methods=['GET', 'POST'])
def emaillogin():
@ -253,7 +204,6 @@ 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()
@ -263,7 +213,6 @@ 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)
@ -284,7 +233,6 @@ 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;
@ -292,3 +240,27 @@ 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

@ -69,7 +69,7 @@ body
}
.grayblock .padded {
padding: 0rem;
padding: 3rem;
}
.horizontal {
@ -91,13 +91,11 @@ body
}
.vertical .content .image {
max-width: 90%;
width: 25rem;
}
.vertical .content .text {
max-width: 90%;
margin: auto;
margin-left: 2rem;
width: 26rem;
}
@ -108,6 +106,8 @@ body
.horizontal .image {
margin: 1rem;
margin-left: 4rem;
margin-right: 4rem;
height: 20rem;
border-radius: 1rem;
width: auto;
@ -156,7 +156,7 @@ body
background-color: #eaeaea;
overflow: hidden;
margin: auto;
display: block;
display: flex;
}
.navigation_rightside {
@ -165,7 +165,7 @@ body
.navigation a {
float: left;
display: block;
display: flex;
color: #333;
text-align: center;
padding: 14px 16px;
@ -211,7 +211,6 @@ body
display: block;
text-align: left;
}
}
/* Style page content */
@ -290,27 +289,25 @@ body
}
.container {
display: flex;
flex-wrap: wrap;
display: inline-flex;
justify-content: center;
align-items:center;
/* flex-direction: row; */
/*padding: 0px 2rem 0px 2rem;*/
flex-direction: row;
padding: 0px 2rem 0px 2rem;
}
.container .button {
padding: 1rem 1.5rem 1rem 1.5rem;
font-size: 2rem;
margin: 4rem 1rem 1rem 1rem;
margin: 4rem;
color: black;
text-decoration: none;
}
.container .preview {
width: 20rem;
height: auto;
height: 20rem;
margin: 1rem 3rem 4rem 3rem;
max-width: 100%;
}
.container .button.logout {
@ -331,9 +328,8 @@ body
}
.sub.container {
width: 40%;
width: 20rem;
justify-content: left;
display: flex;
}
.profile {
@ -353,7 +349,6 @@ body
.profile .name {
font-size: 3rem;
color: #333;
text-align: center;
}
.grey {
@ -371,10 +366,3 @@ body
border-radius: 5px;
margin-right: 1rem;
}
@media (max-width:800px) {
.sub.container {
justify-content: center;
}
}

View File

@ -2,32 +2,18 @@
{% block body%}
<div class="container">
<div style="width: 4rem;margin:1rem;"></div>
<div style="width: 10rem; margin: 1rem; font-weight: bold">Calendar</div>
<div style="display: inline-flex">
<div style="width: 5rem; margin: 1rem; padding-right: 1rem;font-weight: bold">Show on device</div>
<div style="width: 2rem; margin: 1rem;font-weight: bold">Color</div>
</div>
<div style="width: 15rem; margin: 1rem">Calendar</div>
<div style="width: 10rem; margin: 1rem; padding-right: 5rem">Show on device</div>
<div style="width: 2rem; margin: 1rem">Color</div>
</div>
{% for item in calendars %}
<div class="container" style="margin-top: 1.5rem">
<!--action button-->
{% if "ical" == item.calType %}
<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>
{% else %}
<div style="width: 4rem; margin: 1rem;"></div>
{% endif %}
<div class="container">
<!--Name-->
<div style="width: 10rem; margin-left: 1rem; margin-right: 1rem; margin-top: 0.5rem">{{ item.name }}</div>
<div style="display: inline-flex">
<div style="width: 15rem; margin: 1rem;">{{ item.name }}</div>
<!--Toggle-->
<div style="width: 5rem; margin-left: 1rem; margin-right: 1rem; margin-top: 0.5rem; padding-right: 1rem">
<div style="width: 10rem; margin: 1rem; padding-right: 5rem">
<!-- Rounded switch -->
<label class="switch">
<input class="toggle" id={{item.calendarId}} type="checkbox" toggled={{item.toggle}} onclick="toggleReaction(this)">
@ -36,38 +22,22 @@
</div>
<!--Color Selector-->
<div style="width: 2rem; margin-left: 1rem; margin-right: 1rem; margin-top: 0.5rem">
<div style="width: 2rem; margin: 1rem;">
<div class="colorPickSelector" id={{item.calendarId}} defaultColor={{item.color}}></div>
<!--svg height="20" width="20">
<circle cx="10" cy="10" r="10" stroke="black" stroke-width="0" fill={{ item.color }} />
</svg-->
</div>
</div>
</div>
{% endfor %}
<div id=calendars class="container">
<a class="button" href="login/google">Google Calendar</a>
<a class="button" href="#" >Nextcloud Calendar</a>
</div>
<form action="" method="post">
<div class="container grey" style="margin-top: 3rem;">
<div>{{ form.hidden_tag() }}</div>
<div style="display: flex">
<div style="margin: 1rem">{{ form.calName.label }}</div>
<div style="margin: 1rem">{{ form.calName(size=24) }}</div>
</div>
<div style="display: flex">
<div style="margin: 1rem">{{ form.iCalURL.label }}</div>
<div style="margin: 1rem">{{ form.iCalURL(size=24) }}</div>
</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">
<div class="container">
<a class="button addcalendar" href="/login/google" style="width: auto; margin: 4rem">Add Calendar</a>
</div-->
</div>
<script type="text/javascript">

View File

@ -1,7 +1,7 @@
<div id="footer">
<div class="footer container">
<div class="footer">
<p>made by Raphael Maenle </p>
<p><a href="mailto:raphael@maenle.net">raphael@maenle.net</a></p>
<p><a href="/privacy">privacy policy</a></p>

View File

@ -7,7 +7,7 @@
</div>
<h3 style="margin-left:10rem">Summary</h3>
<div style="margin-left:10rem; margin-right:10rem;">This Privacy Statement describes how Longitude handles your data and how the developer makes sure, that the user's information remains as secure as possible.
<div style="margin-left:10rem; margin-right:10rem;">This Privacy Statement descibes how Longitude handles your data and how the developer makes sure, that the users information remains as secure as possible.
This application does not share any user information with third parties and takes care to only save the minimum amount of information about the user.
The following chapters cover all essential points of interest about which information is saved and when it is removed from the server.
If you have any further questions or suggestions, please email us at <a href="mailto:raphael@maenle.net">raphael@maenle.net</a>.</div>
@ -15,7 +15,7 @@ If you have any further questions or suggestions, please email us at <a href="ma
<h3 style="margin-left:10rem">What Information is saved?</h3>
<div style="margin-left:10rem; margin-right:10rem;">
Longitude Calendar saves as little information about their users as possible. The application handles sensitive information only when directly prompted by the user or a device associated with the user. The service only provides this information to the user or a device associated with the user. The data saved in the Longidute Database is
Longitude Calendar saves as little information about their users as possible. The application handles sensitive information only when directly prompted by the user or a device associated with the user. The service only provides this information to the user or a device associated with the user. The data saved in the Longidute Databas is
<ul>
<li>Username and hashed password or alternatively</li>
<li>Google Username and Id with Google Login Token</li>

View File

@ -39,8 +39,6 @@
// content here
{% endblock %}
</div>
<div id="main">
</div>
{% include "footer.html" %}
</div>

View File

@ -12,15 +12,13 @@
<div class="grayblock horizontal">
<div class="content padded">
<div style='margin: 1rem'>
<!--Connect your <img src='/static/res/googlelogo.png' style='height: 3.2rem; vertical-align:middle; padding-Bottom: 0.1rem'/> Calendar...-->
Connect your Calendar..
Connect your <img src='/static/res/googlelogo.png' style='height: 2.2rem; vertical-align:middle; padding-Bottom: 0.1rem'/> Calendar...
</div>
<img class="image" src='/static/res/calendar.svg'/>
</div>
<div class="content padded">
<div style='margin: 1rem'>
<!--...with your <img src='/static/res/tizenlogo.png' style='height: 2rem; vertical-align:middle; padding-Bottom:0.3rem;'/> Watchface-->
..with your Tizen Watchface
...with your <img src='/static/res/tizenlogo.png' style='height: 2rem; vertical-align:middle; padding-Bottom:0.3rem;'/> Watchface
</div>
<img class="image" src='/static/res/watchface.svg'/>
</div>