From 355ba99ca35fc7432dcdad219ff9d1f813628fdf Mon Sep 17 00:00:00 2001 From: Raphael Maenle Date: Wed, 27 May 2020 20:06:43 +0200 Subject: [PATCH] updates database design for mariadb --- backend | 2 +- config.py | 5 +- database/migrations/README | 1 + database/migrations/alembic.ini | 45 ++++++++ database/migrations/env.py | 96 +++++++++++++++++ database/migrations/script.py.mako | 24 +++++ database/migrations/versions/1e8205594ac1_.py | 34 ++++++ database/migrations/versions/3b829a27bc337.py | 23 ++++ database/migrations/versions/aeab4aff199b_.py | 72 +++++++++++++ database/models.py | 92 ++++++---------- docker/calendarwatch/.Dockerfile.swp | Bin 12288 -> 0 bytes docker/calendarwatch/Dockerfile | 8 +- docker/calendarwatch/docker-entrypoint.sh | 2 +- docker/docker-compose.yaml | 36 +++++-- server/__init__.py | 1 - server/googleHandler.py | 98 ++++++++++-------- server/routes.py | 35 +++---- server/static/css/.main.css.swp | Bin 16384 -> 0 bytes 18 files changed, 435 insertions(+), 139 deletions(-) create mode 100644 database/migrations/README create mode 100644 database/migrations/alembic.ini create mode 100644 database/migrations/env.py create mode 100644 database/migrations/script.py.mako create mode 100644 database/migrations/versions/1e8205594ac1_.py create mode 100644 database/migrations/versions/3b829a27bc337.py create mode 100644 database/migrations/versions/aeab4aff199b_.py delete mode 100644 docker/calendarwatch/.Dockerfile.swp delete mode 100644 server/static/css/.main.css.swp diff --git a/backend b/backend index 26cc342..fe1c216 160000 --- a/backend +++ b/backend @@ -1 +1 @@ -Subproject commit 26cc3425ee50ff516d75903d9e281b30860c497b +Subproject commit fe1c216c292b1e9c680e2be33103a9439cba5e03 diff --git a/config.py b/config.py index 3283aa4..d5ff55b 100644 --- a/config.py +++ b/config.py @@ -3,6 +3,5 @@ basedir = os.path.abspath(os.path.dirname(__file__)) class Config(object): # ... - SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or \ - 'sqlite:///' + os.path.join(basedir, 'app.db') - SQLALCHEMY_TRACK_MODIFICATIONS = False \ No newline at end of file + SQLALCHEMY_DATABASE_URI = 'mysql://user:pw@mariadb:3306/calendarwatch' + SQLALCHEMY_TRACK_MODIFICATIONS = False diff --git a/database/migrations/README b/database/migrations/README new file mode 100644 index 0000000..98e4f9c --- /dev/null +++ b/database/migrations/README @@ -0,0 +1 @@ +Generic single-database configuration. \ No newline at end of file diff --git a/database/migrations/alembic.ini b/database/migrations/alembic.ini new file mode 100644 index 0000000..f8ed480 --- /dev/null +++ b/database/migrations/alembic.ini @@ -0,0 +1,45 @@ +# A generic, single database configuration. + +[alembic] +# template used to generate migration files +# file_template = %%(rev)s_%%(slug)s + +# set to 'true' to run the environment during +# the 'revision' command, regardless of autogenerate +# revision_environment = false + + +# Logging configuration +[loggers] +keys = root,sqlalchemy,alembic + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARN +handlers = console +qualname = + +[logger_sqlalchemy] +level = WARN +handlers = +qualname = sqlalchemy.engine + +[logger_alembic] +level = INFO +handlers = +qualname = alembic + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(levelname)-5.5s [%(name)s] %(message)s +datefmt = %H:%M:%S diff --git a/database/migrations/env.py b/database/migrations/env.py new file mode 100644 index 0000000..9452179 --- /dev/null +++ b/database/migrations/env.py @@ -0,0 +1,96 @@ +from __future__ import with_statement + +import logging +from logging.config import fileConfig + +from sqlalchemy import engine_from_config +from sqlalchemy import pool + +from alembic import context + +# this is the Alembic Config object, which provides +# access to the values within the .ini file in use. +config = context.config + +# Interpret the config file for Python logging. +# This line sets up loggers basically. +fileConfig(config.config_file_name) +logger = logging.getLogger('alembic.env') + +# add your model's MetaData object here +# for 'autogenerate' support +# from myapp import mymodel +# target_metadata = mymodel.Base.metadata +from flask import current_app +config.set_main_option( + 'sqlalchemy.url', + str(current_app.extensions['migrate'].db.engine.url).replace('%', '%%')) +target_metadata = current_app.extensions['migrate'].db.metadata + +# other values from the config, defined by the needs of env.py, +# can be acquired: +# my_important_option = config.get_main_option("my_important_option") +# ... etc. + + +def run_migrations_offline(): + """Run migrations in 'offline' mode. + + This configures the context with just a URL + and not an Engine, though an Engine is acceptable + here as well. By skipping the Engine creation + we don't even need a DBAPI to be available. + + Calls to context.execute() here emit the given string to the + script output. + + """ + url = config.get_main_option("sqlalchemy.url") + context.configure( + url=url, target_metadata=target_metadata, literal_binds=True + ) + + with context.begin_transaction(): + context.run_migrations() + + +def run_migrations_online(): + """Run migrations in 'online' mode. + + In this scenario we need to create an Engine + and associate a connection with the context. + + """ + + # this callback is used to prevent an auto-migration from being generated + # when there are no changes to the schema + # reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html + def process_revision_directives(context, revision, directives): + if getattr(config.cmd_opts, 'autogenerate', False): + script = directives[0] + if script.upgrade_ops.is_empty(): + directives[:] = [] + logger.info('No changes in schema detected.') + + connectable = engine_from_config( + config.get_section(config.config_ini_section), + prefix='sqlalchemy.', + poolclass=pool.NullPool, + ) + + with connectable.connect() as connection: + context.configure( + connection=connection, + target_metadata=target_metadata, + process_revision_directives=process_revision_directives, + **current_app.extensions['migrate'].configure_args + ) + + with context.begin_transaction(): + context.run_migrations() + + +if context.is_offline_mode(): + run_migrations_offline() +else: + run_migrations_online() diff --git a/database/migrations/script.py.mako b/database/migrations/script.py.mako new file mode 100644 index 0000000..2c01563 --- /dev/null +++ b/database/migrations/script.py.mako @@ -0,0 +1,24 @@ +"""${message} + +Revision ID: ${up_revision} +Revises: ${down_revision | comma,n} +Create Date: ${create_date} + +""" +from alembic import op +import sqlalchemy as sa +${imports if imports else ""} + +# revision identifiers, used by Alembic. +revision = ${repr(up_revision)} +down_revision = ${repr(down_revision)} +branch_labels = ${repr(branch_labels)} +depends_on = ${repr(depends_on)} + + +def upgrade(): + ${upgrades if upgrades else "pass"} + + +def downgrade(): + ${downgrades if downgrades else "pass"} diff --git a/database/migrations/versions/1e8205594ac1_.py b/database/migrations/versions/1e8205594ac1_.py new file mode 100644 index 0000000..6422fbd --- /dev/null +++ b/database/migrations/versions/1e8205594ac1_.py @@ -0,0 +1,34 @@ +"""empty message + +Revision ID: 1e8205594ac1 +Revises: aeab4aff199b +Create Date: 2020-05-27 16:57:54.384047 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '1e8205594ac1' +down_revision = 'aeab4aff199b' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('user', sa.Column('userid', sa.String(length=64), nullable=True)) + op.create_index(op.f('ix_user_userid'), 'user', ['userid'], unique=True) + op.drop_index('ix_user_username', table_name='user') + op.create_index(op.f('ix_user_username'), 'user', ['username'], unique=False) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_index(op.f('ix_user_username'), table_name='user') + op.create_index('ix_user_username', 'user', ['username'], unique=True) + op.drop_index(op.f('ix_user_userid'), table_name='user') + op.drop_column('user', 'userid') + # ### end Alembic commands ### diff --git a/database/migrations/versions/3b829a27bc337.py b/database/migrations/versions/3b829a27bc337.py new file mode 100644 index 0000000..f204fe3 --- /dev/null +++ b/database/migrations/versions/3b829a27bc337.py @@ -0,0 +1,23 @@ +"""empty message + +Revision ID: 3b829a27bc337 +Revises: 1e8205594ac1 +Create Date: 2020-05-27 19:30:54.384047 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '3b829a27bc337' +down_revision = '1e8205594ac1' +branch_labels = None +depends_on = None + + +def upgrade(): + op.alter_column('User', 'google_credentials', existing_type=sa.Relationship(), new_column_name='google_token') + +def downgrade(): + op.alter_column('User', 'google_token', existing_type=sa.Relationship(), new_column_name='google_credentials') diff --git a/database/migrations/versions/aeab4aff199b_.py b/database/migrations/versions/aeab4aff199b_.py new file mode 100644 index 0000000..cbd1c07 --- /dev/null +++ b/database/migrations/versions/aeab4aff199b_.py @@ -0,0 +1,72 @@ +"""empty message + +Revision ID: aeab4aff199b +Revises: +Create Date: 2020-05-27 15:23:20.611265 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'aeab4aff199b' +down_revision = None +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('user', + sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), + sa.Column('username', sa.String(length=64), nullable=True), + sa.Column('email', sa.String(length=120), nullable=True), + sa.Column('profile_pic', sa.String(length=256), nullable=True), + sa.Column('password_hash', sa.String(length=128), nullable=True), + sa.PrimaryKeyConstraint('id') + ) + op.create_index(op.f('ix_user_email'), 'user', ['email'], unique=True) + op.create_index(op.f('ix_user_username'), 'user', ['username'], unique=True) + op.create_table('calendar', + sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), + sa.Column('user_id', sa.Integer(), nullable=False), + sa.Column('calendar_id', sa.String(length=256), nullable=False), + sa.Column('name', sa.String(length=256), nullable=True), + sa.Column('toggle', sa.String(length=8), nullable=True), + sa.Column('color', sa.String(length=16), nullable=True), + sa.ForeignKeyConstraint(['user_id'], ['user.id'], ), + sa.PrimaryKeyConstraint('id', 'calendar_id') + ) + op.create_index(op.f('ix_calendar_name'), 'calendar', ['name'], unique=False) + op.create_index(op.f('ix_calendar_user_id'), 'calendar', ['user_id'], unique=False) + op.create_table('device', + sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), + sa.Column('user_id', sa.Integer(), nullable=True), + sa.Column('deviceName', sa.String(length=64), nullable=True), + sa.ForeignKeyConstraint(['user_id'], ['user.id'], ), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('deviceName') + ) + op.create_table('google_token', + sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), + sa.Column('user_id', sa.Integer(), nullable=True), + sa.Column('token', sa.String(length=256), nullable=True), + sa.Column('refresh_token', sa.String(length=256), nullable=True), + sa.ForeignKeyConstraint(['user_id'], ['user.id'], ), + sa.PrimaryKeyConstraint('id') + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('google_token') + op.drop_table('device') + op.drop_index(op.f('ix_calendar_user_id'), table_name='calendar') + op.drop_index(op.f('ix_calendar_name'), table_name='calendar') + op.drop_table('calendar') + op.drop_index(op.f('ix_user_username'), table_name='user') + op.drop_index(op.f('ix_user_email'), table_name='user') + op.drop_table('user') + # ### end Alembic commands ### diff --git a/database/models.py b/database/models.py index 048fb0a..f5c9cac 100644 --- a/database/models.py +++ b/database/models.py @@ -8,13 +8,15 @@ def load_user(id): return User.query.get(id) class User(UserMixin, db.Model): - id = db.Column(db.String(64), primary_key=True) - username = db.Column(db.String(64), index=True, unique=True) + id = db.Column(db.Integer, primary_key=True, autoincrement=True) + userid = db.Column(db.String(64), index=True, unique=True) + username = db.Column(db.String(64), index=True) email = db.Column(db.String(120), index=True, unique=True) profile_pic = db.Column(db.String(256)) password_hash = db.Column(db.String(128)) - calendarJson = db.Column(db.String) - google_credentials = db.Column(db.String) + google_token = db.relationship('GoogleToken', uselist=False, backref = 'user') + calendars = db.relationship('Calendar', backref='user', lazy=True) + devices = db.relationship('Device', backref='user') def __repr__(self): return ''.format(self.username) @@ -25,53 +27,11 @@ class User(UserMixin, db.Model): def checkPassword(self, password): return check_password_hash(self.password_hash, password) - def setJson(self, jsonObject): - self.calendarJson = json.dumps(jsonObject) - db.session.commit() - - def getJson(self): - return json.loads(self.calendarJson) - - def setGoogleCredentials(self, credentials): - self.google_credentials = json.dumps(credentials) - db.session.commit() + def updateCalendar(self, calendar_id, toggle=None, color=None): - def getGoogleCredentials(self): - - if self.google_credentials is None: - print("no credentials", flush=True) - return None - return json.loads(self.google_credentials) - -class Calendar(db.Model): - usr_id = db.Column(db.String(21), index=True) - calendar_id = db.Column(db.String(256), primary_key=True) - name = db.Column(db.String(256), index=True) - toggle = db.Column(db.String(8)) - color = db.Column(db.String(16)) - - def getCalendars(self, user_id): - calendars = self.query.filter(Calendar.usr_id==user_id) - - return calendars - - def getCalendar(self, user_id, calendar_id): - calendars = self.query.filter(self.usr_id==user_id, self.calendar_id==calendar_id) - - calendar = None - for c in calendars: - calendar = c - - if not calendar: - return None - - return calendar - - @staticmethod - def updateCalendar(user_id, calendar_id, toggle=None, color=None): - - calendar = Calendar.query.filter(Calendar.usr_id==user_id, Calendar.calendar_id==calendar_id).first() - + for calendar in self.calendars: + if calendar.calendar_id == calendar_id: + break print("updating", flush=True) if(toggle != None): @@ -83,12 +43,28 @@ class Calendar(db.Model): calendar.color = color db.session.commit() - def create(self, user_id, calendar_id, name, color, toggle = 'True'): - newcal = Calendar(usr_id=user_id, calendar_id=calendar_id, name=name, toggle=toggle, color=color) - - db.session.add(newcal) - db.session.commit() - + def hasCalendar(self, calendar_id): + for calendar in self.calendars: + if calendar.calendar_id == calendar_id: + return True + + return False + +class GoogleToken(db.Model): + id = db.Column(db.Integer, primary_key=True, autoincrement=True) + user_id = db.Column(db.Integer, db.ForeignKey('user.id')) + token = db.Column(db.String(256)) + refresh_token = db.Column(db.String(256)) + class Device(db.Model): - id = db.Column(db.Integer, primary_key=True) - deviceId = db.Column(db.String(128), index=True) + id = db.Column(db.Integer, primary_key=True, autoincrement=True) + user_id = db.Column(db.Integer, db.ForeignKey('user.id')) + deviceName = db.Column(db.String(64), unique=True) + +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) + name = db.Column(db.String(256), index=True) + toggle = db.Column(db.String(8)) + color = db.Column(db.String(16)) diff --git a/docker/calendarwatch/.Dockerfile.swp b/docker/calendarwatch/.Dockerfile.swp deleted file mode 100644 index ce0c59c02763a4165bbec2ac17c5f916339f3335..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12288 zcmeI&O>fgM7zc2V;}9eg5KM03Cf3~8a-3GZjIRUV zfD@mF55S!ZPr7tyv>IpNZ|SMuo;ddJ7S-d{n}Z(jH1`;lyNo^C`SRiW=n?yTlQAz{ zCY7mXt}Uq%iJD!TvYb4x7mO%7N_B{0X;hv_KbIlSPAA^xf%;Km<1^*2&PFengA;Ak zpI?Lw2tZ(iz%AB(yw|EG9yT8Edpjo^axeq|2tWV=5P$##AOHaf{Hp@NZLyDZxEu8W zx9fWAx4Q14f&c^{009U<00Izz00bZa0SG|gFBFgoW81eG>r+(!|F6FPzujT%D@8)l zrnpb>gK|DmoKtv;>X{w|3Irek0SG_<0uX=z1Rwwb2>fpX-Tv`#bT~NZAMrwHzC9cqb$O@NdBR={4o7@b9*@exN5;|(@o22`hz(Er+-cY5Ixm7W zhNsr1M%9(DvJBa(Q?)Nfj2>SOIxXBHWHuME(X^@cCDl}FBWBX*LgBL> zL8L1D+D#mDdYOy8YF{?0Tk%{h*4LRBS)B3x>Q@}Se)ZhMnVK&6a`<$rQ!j(6d&Z_Z zuiNu5;q?&`OE=|bSGH0*+O^`lyL`2|($PwZV*A5EuYT}pyV((i(Ni%li=aIF3BI<; A@&Et; diff --git a/docker/calendarwatch/Dockerfile b/docker/calendarwatch/Dockerfile index 20a239d..29db076 100644 --- a/docker/calendarwatch/Dockerfile +++ b/docker/calendarwatch/Dockerfile @@ -1,13 +1,11 @@ FROM python:3.8-slim-buster RUN apt-get update && apt-get upgrade -RUN apt-get install -y cron RUN pip3 install flask Flask-SQLAlchemy flask_migrate flask_login flask_wtf python-dotenv -RUN apt-get install gcc libpcre3 libpcre3-dev -y +RUN apt-get install gcc libpcre3 libpcre3-dev libmariadbclient-dev -y RUN pip3 install uwsgi - RUN pip3 install email-validator -RUN pip3 install google google-oauth google-auth-oauthlib google-api-python-client - +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"] diff --git a/docker/calendarwatch/docker-entrypoint.sh b/docker/calendarwatch/docker-entrypoint.sh index a6a7281..5a544db 100755 --- a/docker/calendarwatch/docker-entrypoint.sh +++ b/docker/calendarwatch/docker-entrypoint.sh @@ -1,7 +1,7 @@ #!/bin/sh cd /home/calendarwatch # uwsgi --http-socket 0.0.0.0:8084 -w wsgi --protocol=https - +export FLASK_APP=/home/calendarwatch/server.py python3 server.py echo "server has been started" diff --git a/docker/docker-compose.yaml b/docker/docker-compose.yaml index c618c80..55f89a5 100644 --- a/docker/docker-compose.yaml +++ b/docker/docker-compose.yaml @@ -1,11 +1,29 @@ version: '3' services: - calendarwatch: - build: - context: ./calendarwatch - image: calendarwatch:latest - container_name: calendarwatch - volumes: - - ../:/home/calendarwatch - ports: - - "0.0.0.0:8084:8084" + calendarwatch: + build: + context: ./calendarwatch + image: calendarwatch:latest + container_name: calendarwatch + environment: + - FLASK_APP=/home/calendarwatch/server.py + volumes: + - ../:/home/calendarwatch + ports: + - "0.0.0.0:8084:8084" + + mariadb: + image: mariadb + container_name: maridab + environment: + - MYSQL_ROOT_PASSWORD=pw + - MYSQL_DATABASE=calendarwatch + - MYSQL_USER=user + - MYSQL_PASSWORD=pw + volumes: + - database:/var/lib/mysql + +volumes: + database: + driver: local + diff --git a/server/__init__.py b/server/__init__.py index 38c8864..f9de308 100644 --- a/server/__init__.py +++ b/server/__init__.py @@ -13,7 +13,6 @@ app = Flask(__name__, static_folder='static', template_folder='template') app.secret_key = os.environ.get("SECRET_KEY") or os.urandom(24) - app.config.from_object(Config) db = SQLAlchemy(app) diff --git a/server/googleHandler.py b/server/googleHandler.py index 02b6cda..4c65f5f 100644 --- a/server/googleHandler.py +++ b/server/googleHandler.py @@ -9,7 +9,6 @@ import flask # Python standard libraries import json import os -import sqlite3 # Third-party libraries import flask @@ -24,31 +23,47 @@ from flask_login import ( import requests from database.models import Calendar as dbCalendar - +from server import db # Configuration -CLIENT_SECRETS_FILE = "certificate/client_secret.json" +class GoogleClient(): + def __init__(self): + self.CLIENT_SECRETS_FILE = "certificate/client_secret.json" -# This OAuth 2.0 access scope allows for full read/write access to the -# authenticated user's account and requires requests to use an SSL connection. -SCOPES = ["https://www.googleapis.com/auth/userinfo.email", "https://www.googleapis.com/auth/userinfo.profile", "https://www.googleapis.com/auth/calendar.readonly", "openid"] -API_SERVICE_NAME = 'calendar' -API_VERSION = 'v3' + with open("/home/calendarwatch/certificate/google_client.json", encoding='utf-8') as json_file: + self.google_client = json.load(json_file) -GOOGLE_CLIENT_ID ="377787187748-shuvi4iq5bi4gdet6q3ioataimobs4lh.apps.googleusercontent.com" -GOOGLE_CLIENT_SECRET = "Hu_YWmKsVKUcLwyeINYzdKfZ" -GOOGLE_DISCOVERY_URL = ( - "https://accounts.google.com/.well-known/openid-configuration" -) + self.SCOPES = self.google_client.get('scopes') + self.API_SERVICE_NAME = 'calendar' + self.API_VERSION = 'v3' -# OAuth 2 client setup -client = WebApplicationClient(GOOGLE_CLIENT_ID) + # GOOGLE_CLIENT_ID ="377787187748-shuvi4iq5bi4gdet6q3ioataimobs4lh.apps.googleusercontent.com" + self.GOOGLE_CLIENT_ID = self.google_client.get('client_id') + # GOOGLE_CLIENT_SECRET = "Hu_YWmKsVKUcLwyeINYzdKfZ" + self.GOOGLE_CLIENT_SECRET = self.google_client.get('client_secret') + self.GOOGLE_DISCOVERY_URL = ( + "https://accounts.google.com/.well-known/openid-configuration" + ) + + # OAuth 2 client setup + self.client = WebApplicationClient(self.GOOGLE_CLIENT_ID) + def build_credentials(self, token, refresh_token): + data = {} + data['token'] = token + data['refresh_token'] = refresh_token + data['token_uri'] = self.google_client.get('token_uri') + data['client_id'] = self.google_client.get('client_id') + data['client_secret'] = self.google_client.get('client_secret') + data['scopes'] = self.google_client.get('scopes') + return data + +GC = GoogleClient() def login(): # Create flow instance to manage the OAuth 2.0 Authorization Grant Flow steps. flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file( - CLIENT_SECRETS_FILE, scopes=SCOPES) + GC.CLIENT_SECRETS_FILE, scopes=GC.SCOPES) # The URI created here must exactly match one of the authorized redirect URIs # for the OAuth 2.0 client, which you configured in the API Console. If this # value doesn't match an authorized URI, you will get a 'redirect_uri_mismatch' @@ -72,7 +87,7 @@ def verifyResponse(): state = flask.session['state'] flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file( - CLIENT_SECRETS_FILE, scopes=SCOPES, state=state) + GC.CLIENT_SECRETS_FILE, scopes=GC.SCOPES, state=state) flow.redirect_uri = flask.url_for('callback', _external=True) # Use the authorization server's response to fetch the OAuth 2.0 tokens. @@ -91,11 +106,11 @@ def verifyResponse(): def get_google_provider_cfg(): - return requests.get(GOOGLE_DISCOVERY_URL).json() + return requests.get(GC.GOOGLE_DISCOVERY_URL).json() def deleteAccount(user): result = requests.post('https://oauth2.googleapis.com/revoke', - params={'token': user.get('token')}, + params={'token': user.google_token.token}, headers = {'content-type': 'applixation/x-www-form-urlencoded'}) print(result, flush=True) return @@ -107,10 +122,10 @@ class Calendar: self.toggle=toggle self.calendarId = calendarId +# TODO move this to databas def calendarsFromDb(): - calendars = dbCalendar.getCalendars(dbCalendar, current_user.id) pyCalendars = [] - for calendar in calendars: + for calendar in current_user.calendars: name = (calendar.name[:16] + '..') if len(calendar.name)> 18 else calendar.name calendarId = calendar.calendar_id toggle = calendar.toggle @@ -121,20 +136,6 @@ def calendarsFromDb(): return pyCalendars -def getCalendarJson(): - if 'credentials' not in flask.session: - return flask.redirect('login/google') - - # Load credentials from the session. - credentials = google.oauth2.credentials.Credentials( - **flask.session['credentials']) - - with open('./userinfo/' + current_user.id + '/calendarevents.json', 'w') as outfile: - json.dump(todaysCal, outfile) - - return todaysCal - - def updateCalendars(): if 'credentials' not in flask.session: return flask.redirect('login/google') @@ -145,23 +146,36 @@ def updateCalendars(): # a = flask.session['credentials'] # print(a, flush=True) # print(current_user.getGoogleCredentials(), flush=True) - if current_user.getGoogleCredentials() == None: + if current_user.google_token == None: + print("notok", flush=True) return - - credentials = google.oauth2.credentials.Credentials(**current_user.getGoogleCredentials()) + + client_token = GC.build_credentials(current_user.google_token.token, + current_user.google_token.refresh_token) + credentials = google.oauth2.credentials.Credentials(**client_token) calendars = caltojson.getCalendarList(credentials) + print(calendars, flush=True) for calendar in calendars: - - if dbCalendar.getCalendar(dbCalendar, current_user.id, calendar.calendarId) == None: - dbCalendar.create(dbCalendar, current_user.id, calendar.calendarId, calendar.summary, calendar.color) - + print(calendar, flush=True) + if not current_user.hasCalendar(calendar.calendarId): + print("adding", flush=True) + c = dbCalendar(calendar_id=calendar.calendarId, + name = calendar.summary, + toggle = "False", + color = calendar.color) + db.session.add(c) + current_user.calendars.append(c) + + db.session.commit() print("updated Calendars") # Save credentials back to session in case access token was refreshed. # ACTION ITEM: In a production app, you likely want to save these # credentials in a persistent database instead. + # TODO add save updated token to database here flask.session['credentials'] = credentials_to_dict(credentials) + def credentials_to_dict(credentials): return {'token': credentials.token, 'refresh_token': credentials.refresh_token, diff --git a/server/routes.py b/server/routes.py index 88c6ede..2a9d410 100644 --- a/server/routes.py +++ b/server/routes.py @@ -21,7 +21,7 @@ import server.googleHandler as google from backend.Routine import Routine from server import login_manager, app, db from server.forms import LoginForm, RegistrationForm, DeviceForm -from database.models import User, Calendar, Device +from database.models import User, Calendar, Device, GoogleToken os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = '1' @@ -105,19 +105,18 @@ def register(): def deleteAccount(): if not current_user.is_authenticated: return redirect(url_for('account')) - print(current_user.getGoogleCredentials(), flush=True) - google.deleteAccount(current_user.getGoogleCredentials()) + # TODO fix google delete account + google.deleteAccount(current_user) - user = db.session.query(User).filter(User.id==current_user.id).first() - logout_user() - db.session.delete(user) + db.session.delete(current_user) db.session.commit() + logout_user() return redirect(url_for('account')) @app.route("/login/google") def googlelogin(): - if current_user.is_authenticated and current_user.getGoogleCredentials() != None: + if current_user.is_authenticated and current_user.google_credentials.refresh_token != None: return redirect(url_for('account')) authorization_url = google.login() @@ -127,35 +126,33 @@ def googlelogin(): @app.route("/login/google/callback") def callback(): session, credentials = google.verifyResponse() - - if current_user.is_authenticated and current_user.getGoogleCredentials == None: - current_user.setGoogleCredentials(credentials) userinfo = session.get('https://www.googleapis.com/userinfo/v2/me').json() # Create a user in your db with the information provided # by Google # Doesn't exist? Add it to the database. - if not User.query.get(userinfo['id']): + if not db.session.query(User).filter(User.userid==userinfo['id']).first(): + gc = GoogleToken(token=credentials.get("token"), + refresh_token=credentials.get("refresh_token")) + db.session.add(gc) newser = User( - id=userinfo['id'], + + userid=userinfo['id'], username=userinfo['name'], email=userinfo['email'], profile_pic=userinfo['picture'], - password_hash="" + password_hash="", + google_token = gc ) db.session.add(newser) db.session.commit() - user = User.query.get(userinfo['id']) + user = db.session.query(User).filter(User.userid==userinfo['id']).first() # Begin user session by logging the user in - print("login:" + user.id) - login_user(user) - # TODO currently not using the credentials anymore - if user.getGoogleCredentials() is None: - user.setGoogleCredentials(credentials) + return flask.redirect(flask.url_for('index')) @app.route("/logout") diff --git a/server/static/css/.main.css.swp b/server/static/css/.main.css.swp deleted file mode 100644 index 63ec28d96f26485f41f9ec00653bc46f113858b4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16384 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 TL+YivdG?pyb20*&ZLWU;M+5CL