diff --git a/lib/twitter.py b/lib/twitter.py index 821a0a5..a03e15a 100644 --- a/lib/twitter.py +++ b/lib/twitter.py @@ -37,9 +37,9 @@ def receive_verifier(oauth_token, oauth_verifier, consumer_key=None, consumer_se acct = Account(twitter_id = remote_acct['id_str']) acct = db.session.merge(acct) - acct.remote_display_name = remote_acct['name'] - acct.remote_screen_name = remote_acct['screen_name'] - acct.remote_avatar_url = remote_acct['profile_image_url_https'] + acct.display_name = remote_acct['name'] + acct.screen_name = remote_acct['screen_name'] + acct.avatar_url = remote_acct['profile_image_url_https'] new_token.account = acct db.session.commit() diff --git a/migrations/versions/0cb99099c2dd_add_twitter_archives.py b/migrations/versions/0cb99099c2dd_add_twitter_archives.py deleted file mode 100644 index 277029b..0000000 --- a/migrations/versions/0cb99099c2dd_add_twitter_archives.py +++ /dev/null @@ -1,32 +0,0 @@ -"""add twitter archives - -Revision ID: 0cb99099c2dd -Revises: 92ffc9941fd9 -Create Date: 2017-07-30 23:13:48.949949 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = '0cb99099c2dd' -down_revision = '92ffc9941fd9' -branch_labels = None -depends_on = None - - -def upgrade(): - op.create_table('twitter_archives', - sa.Column('created_at', sa.DateTime(), server_default=sa.text('now()'), nullable=True), - sa.Column('updated_at', sa.DateTime(), server_default=sa.text('now()'), nullable=True), - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('account_id', sa.String(), nullable=False), - sa.Column('body', sa.LargeBinary(), nullable=False), - sa.ForeignKeyConstraint(['account_id'], ['accounts.id'], name=op.f('fk_twitter_archives_account_id_accounts')), - sa.PrimaryKeyConstraint('id', name=op.f('pk_twitter_archives')) - ) - - -def downgrade(): - op.drop_table('twitter_archives') diff --git a/migrations/versions/711770097f06_.py b/migrations/versions/711770097f06_.py deleted file mode 100644 index 37508bd..0000000 --- a/migrations/versions/711770097f06_.py +++ /dev/null @@ -1,28 +0,0 @@ -"""empty message - -Revision ID: 711770097f06 -Revises: e46c5fd68037 -Create Date: 2017-07-31 17:54:11.215759 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = '711770097f06' -down_revision = 'e46c5fd68037' -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.add_column('accounts', sa.Column('policy_ignore_favourites', sa.Boolean(), server_default='TRUE', nullable=True)) - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.drop_column('accounts', 'policy_ignore_favourites') - # ### end Alembic commands ### diff --git a/migrations/versions/7379bfac4379_.py b/migrations/versions/7379bfac4379_.py deleted file mode 100644 index e69cae5..0000000 --- a/migrations/versions/7379bfac4379_.py +++ /dev/null @@ -1,28 +0,0 @@ -"""empty message - -Revision ID: 7379bfac4379 -Revises: 995890c26959 -Create Date: 2017-08-01 23:35:17.924428 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = '7379bfac4379' -down_revision = '995890c26959' -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.add_column('posts', sa.Column('favourite', sa.Boolean(), server_default='FALSE', nullable=False)) - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.drop_column('posts', 'favourite') - # ### end Alembic commands ### diff --git a/migrations/versions/7afc95e24778_init.py b/migrations/versions/7afc95e24778_init.py new file mode 100644 index 0000000..e8a9306 --- /dev/null +++ b/migrations/versions/7afc95e24778_init.py @@ -0,0 +1,82 @@ +"""init + +Revision ID: 7afc95e24778 +Revises: +Create Date: 2017-08-03 11:51:08.190298 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '7afc95e24778' +down_revision = None +branch_labels = None +depends_on = None + + +def upgrade(): + op.create_table('accounts', + sa.Column('created_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False), + sa.Column('updated_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False), + sa.Column('id', sa.String(), nullable=False), + sa.Column('policy_enabled', sa.Boolean(), server_default='FALSE', nullable=False), + sa.Column('policy_keep_latest', sa.Integer(), server_default='0', nullable=False), + sa.Column('policy_keep_favourites', sa.Boolean(), server_default='TRUE', nullable=False), + sa.Column('policy_delete_every', sa.Interval(), server_default='0', nullable=False), + sa.Column('policy_keep_younger', sa.Interval(), server_default='0', nullable=False), + sa.Column('display_name', sa.String(), nullable=True), + sa.Column('screen_name', sa.String(), nullable=True), + sa.Column('avatar_url', sa.String(), nullable=True), + sa.Column('last_fetch', sa.DateTime(), server_default='epoch', nullable=True), + sa.Column('last_delete', sa.DateTime(), server_default='epoch', nullable=True), + sa.PrimaryKeyConstraint('id', name=op.f('pk_accounts')) + ) + op.create_table('oauth_tokens', + sa.Column('created_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False), + sa.Column('updated_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False), + sa.Column('token', sa.String(), nullable=False), + sa.Column('token_secret', sa.String(), nullable=False), + sa.Column('account_id', sa.String(), nullable=True), + sa.ForeignKeyConstraint(['account_id'], ['accounts.id'], name=op.f('fk_oauth_tokens_account_id_accounts'), onupdate='CASCADE', ondelete='CASCADE'), + sa.PrimaryKeyConstraint('token', name=op.f('pk_oauth_tokens')) + ) + op.create_table('posts', + sa.Column('created_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False), + sa.Column('updated_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False), + sa.Column('id', sa.String(), nullable=False), + sa.Column('body', sa.String(), nullable=True), + sa.Column('author_id', sa.String(), nullable=False), + sa.Column('favourite', sa.Boolean(), server_default='FALSE', nullable=False), + sa.ForeignKeyConstraint(['author_id'], ['accounts.id'], name=op.f('fk_posts_author_id_accounts'), onupdate='CASCADE', ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id', name=op.f('pk_posts')) + ) + op.create_table('sessions', + sa.Column('created_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False), + sa.Column('updated_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False), + sa.Column('id', sa.String(), nullable=False), + sa.Column('account_id', sa.String(), nullable=False), + sa.ForeignKeyConstraint(['account_id'], ['accounts.id'], name=op.f('fk_sessions_account_id_accounts'), onupdate='CASCADE', ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id', name=op.f('pk_sessions')) + ) + op.create_table('twitter_archives', + sa.Column('created_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False), + sa.Column('updated_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False), + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('account_id', sa.String(), nullable=False), + sa.Column('body', sa.LargeBinary(), nullable=False), + sa.Column('chunks', sa.Integer(), nullable=True), + sa.Column('chunks_successful', sa.Integer(), server_default='0', nullable=False), + sa.Column('chunks_failed', sa.Integer(), server_default='0', nullable=False), + sa.ForeignKeyConstraint(['account_id'], ['accounts.id'], name=op.f('fk_twitter_archives_account_id_accounts'), onupdate='CASCADE', ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id', name=op.f('pk_twitter_archives')) + ) + + +def downgrade(): + op.drop_table('twitter_archives') + op.drop_table('sessions') + op.drop_table('posts') + op.drop_table('oauth_tokens') + op.drop_table('accounts') diff --git a/migrations/versions/92ffc9941fd9_.py b/migrations/versions/92ffc9941fd9_.py deleted file mode 100644 index 05047f2..0000000 --- a/migrations/versions/92ffc9941fd9_.py +++ /dev/null @@ -1,66 +0,0 @@ -"""empty message - -Revision ID: 92ffc9941fd9 -Revises: -Create Date: 2017-07-29 12:31:47.687234 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = '92ffc9941fd9' -down_revision = None -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.create_table('accounts', - sa.Column('created_at', sa.DateTime(), server_default=sa.text('now()'), nullable=True), - sa.Column('updated_at', sa.DateTime(), server_default=sa.text('now()'), nullable=True), - sa.Column('id', sa.String(), nullable=False), - sa.Column('remote_display_name', sa.String(), nullable=True), - sa.Column('remote_screen_name', sa.String(), nullable=True), - sa.Column('remote_avatar_url', sa.String(), nullable=True), - sa.Column('last_fetch', sa.DateTime(), server_default='epoch', nullable=True), - sa.PrimaryKeyConstraint('id', name=op.f('pk_accounts')) - ) - op.create_table('oauth_tokens', - sa.Column('created_at', sa.DateTime(), server_default=sa.text('now()'), nullable=True), - sa.Column('updated_at', sa.DateTime(), server_default=sa.text('now()'), nullable=True), - sa.Column('token', sa.String(), nullable=False), - sa.Column('token_secret', sa.String(), nullable=False), - sa.Column('account_id', sa.String(), nullable=True), - sa.ForeignKeyConstraint(['account_id'], ['accounts.id'], name=op.f('fk_oauth_tokens_account_id_accounts')), - sa.PrimaryKeyConstraint('token', name=op.f('pk_oauth_tokens')) - ) - op.create_table('posts', - sa.Column('created_at', sa.DateTime(), server_default=sa.text('now()'), nullable=True), - sa.Column('updated_at', sa.DateTime(), server_default=sa.text('now()'), nullable=True), - sa.Column('id', sa.String(), nullable=False), - sa.Column('body', sa.String(), nullable=True), - sa.Column('author_id', sa.String(), nullable=True), - sa.ForeignKeyConstraint(['author_id'], ['accounts.id'], name=op.f('fk_posts_author_id_accounts')), - sa.PrimaryKeyConstraint('id', name=op.f('pk_posts')) - ) - op.create_table('sessions', - sa.Column('created_at', sa.DateTime(), server_default=sa.text('now()'), nullable=True), - sa.Column('updated_at', sa.DateTime(), server_default=sa.text('now()'), nullable=True), - sa.Column('id', sa.String(), nullable=False), - sa.Column('account_id', sa.String(), nullable=True), - sa.ForeignKeyConstraint(['account_id'], ['accounts.id'], name=op.f('fk_sessions_account_id_accounts')), - sa.PrimaryKeyConstraint('id', name=op.f('pk_sessions')) - ) - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.drop_table('sessions') - op.drop_table('posts') - op.drop_table('oauth_tokens') - op.drop_table('accounts') - # ### end Alembic commands ### diff --git a/migrations/versions/995890c26959_.py b/migrations/versions/995890c26959_.py deleted file mode 100644 index b9599a4..0000000 --- a/migrations/versions/995890c26959_.py +++ /dev/null @@ -1,30 +0,0 @@ -"""empty message - -Revision ID: 995890c26959 -Revises: e036b007017c -Create Date: 2017-08-01 23:24:22.223674 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = '995890c26959' -down_revision = 'e036b007017c' -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.add_column('accounts', sa.Column('policy_keep_favourites', sa.Boolean(), server_default='TRUE', nullable=True)) - op.drop_column('accounts', 'policy_ignore_favourites') - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.add_column('accounts', sa.Column('policy_ignore_favourites', sa.BOOLEAN(), server_default=sa.text('true'), autoincrement=False, nullable=True)) - op.drop_column('accounts', 'policy_keep_favourites') - # ### end Alembic commands ### diff --git a/migrations/versions/9fab742962ef_.py b/migrations/versions/9fab742962ef_.py deleted file mode 100644 index af79f73..0000000 --- a/migrations/versions/9fab742962ef_.py +++ /dev/null @@ -1,32 +0,0 @@ -"""empty message - -Revision ID: 9fab742962ef -Revises: 711770097f06 -Create Date: 2017-08-01 00:33:59.183437 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = '9fab742962ef' -down_revision = '711770097f06' -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.add_column('accounts', sa.Column('policy_delete_every', sa.Interval(), server_default='0', nullable=True)) - op.add_column('accounts', sa.Column('policy_keep_latest', sa.Integer(), server_default='0', nullable=True)) - op.add_column('accounts', sa.Column('policy_keep_younger', sa.Interval(), server_default='0', nullable=True)) - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.drop_column('accounts', 'policy_keep_younger') - op.drop_column('accounts', 'policy_keep_latest') - op.drop_column('accounts', 'policy_delete_every') - # ### end Alembic commands ### diff --git a/migrations/versions/e036b007017c_.py b/migrations/versions/e036b007017c_.py deleted file mode 100644 index 033dd65..0000000 --- a/migrations/versions/e036b007017c_.py +++ /dev/null @@ -1,28 +0,0 @@ -"""empty message - -Revision ID: e036b007017c -Revises: 9fab742962ef -Create Date: 2017-08-01 22:45:28.450097 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = 'e036b007017c' -down_revision = '9fab742962ef' -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.add_column('accounts', sa.Column('last_delete', sa.DateTime(), server_default='epoch', nullable=True)) - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.drop_column('accounts', 'last_delete') - # ### end Alembic commands ### diff --git a/migrations/versions/e46c5fd68037_.py b/migrations/versions/e46c5fd68037_.py deleted file mode 100644 index 13817e3..0000000 --- a/migrations/versions/e46c5fd68037_.py +++ /dev/null @@ -1,28 +0,0 @@ -"""empty message - -Revision ID: e46c5fd68037 -Revises: f11fe22d6169 -Create Date: 2017-07-31 17:10:09.833883 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = 'e46c5fd68037' -down_revision = 'f11fe22d6169' -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.add_column('accounts', sa.Column('policy_enabled', sa.Boolean(), server_default='FALSE', nullable=False)) - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.drop_column('accounts', 'policy_enabled') - # ### end Alembic commands ### diff --git a/migrations/versions/f11fe22d6169_.py b/migrations/versions/f11fe22d6169_.py deleted file mode 100644 index ce58479..0000000 --- a/migrations/versions/f11fe22d6169_.py +++ /dev/null @@ -1,32 +0,0 @@ -"""empty message - -Revision ID: f11fe22d6169 -Revises: 0cb99099c2dd -Create Date: 2017-07-31 01:07:39.741008 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = 'f11fe22d6169' -down_revision = '0cb99099c2dd' -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.add_column('twitter_archives', sa.Column('chunks', sa.Integer(), nullable=True)) - op.add_column('twitter_archives', sa.Column('chunks_failed', sa.Integer(), server_default='0', nullable=True)) - op.add_column('twitter_archives', sa.Column('chunks_successful', sa.Integer(), server_default='0', nullable=True)) - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.drop_column('twitter_archives', 'chunks_successful') - op.drop_column('twitter_archives', 'chunks_failed') - op.drop_column('twitter_archives', 'chunks') - # ### end Alembic commands ### diff --git a/model.py b/model.py index 1e15483..f06435a 100644 --- a/model.py +++ b/model.py @@ -8,8 +8,8 @@ from lib import decompose_interval from datetime import timedelta class TimestampMixin(object): - created_at = db.Column(db.DateTime, server_default=db.func.now()) - updated_at = db.Column(db.DateTime, server_default=db.func.now(), onupdate=db.func.now()) + created_at = db.Column(db.DateTime, server_default=db.func.now(), nullable=False) + updated_at = db.Column(db.DateTime, server_default=db.func.now(), onupdate=db.func.now(), nullable=False) def touch(self): self.updated_at=db.func.now() @@ -26,7 +26,7 @@ class RemoteIDMixin(object): if not self.id: return None if self.service != "twitter": - raise Exception("wrong service bucko") + raise Exception("tried to get twitter id for a {} {}".format(self.service, type(self))) return self.id.split(":")[1] @twitter_id.setter @@ -42,14 +42,14 @@ class Account(TimestampMixin, RemoteIDMixin): id = db.Column(db.String, primary_key=True) policy_enabled = db.Column(db.Boolean, server_default='FALSE', nullable=False) - policy_keep_latest = db.Column(db.Integer, server_default='0') - policy_keep_favourites = db.Column(db.Boolean, server_default='TRUE') - policy_delete_every = db.Column(db.Interval, server_default='0') - policy_keep_younger = db.Column(db.Interval, server_default='0') + policy_keep_latest = db.Column(db.Integer, server_default='0', nullable=False) + policy_keep_favourites = db.Column(db.Boolean, server_default='TRUE', nullable=False) + policy_delete_every = db.Column(db.Interval, server_default='0', nullable=False) + policy_keep_younger = db.Column(db.Interval, server_default='0', nullable=False) - remote_display_name = db.Column(db.String) - remote_screen_name = db.Column(db.String) - remote_avatar_url = db.Column(db.String) + display_name = db.Column(db.String) + screen_name = db.Column(db.String) + avatar_url = db.Column(db.String) last_fetch = db.Column(db.DateTime, server_default='epoch') last_delete = db.Column(db.DateTime, server_default='epoch') @@ -57,6 +57,9 @@ class Account(TimestampMixin, RemoteIDMixin): def touch_fetch(self): self.last_fetch = db.func.now() + def touch_delete(self): + self.last_delete = db.func.now() + @db.validates('policy_keep_younger', 'policy_delete_every') def validate_intervals(self, key, value): if not (value == timedelta(0) or value >= timedelta(minutes=1)): @@ -68,7 +71,7 @@ class Account(TimestampMixin, RemoteIDMixin): # backref: posts def __repr__(self): - return f"" + return f"" def post_count(self): return Post.query.with_parent(self).count() @@ -84,15 +87,17 @@ class OAuthToken(db.Model, TimestampMixin): token = db.Column(db.String, primary_key=True) token_secret = db.Column(db.String, nullable=False) - account_id = db.Column(db.String, db.ForeignKey('accounts.id')) + account_id = db.Column(db.String, db.ForeignKey('accounts.id', ondelete='CASCADE', onupdate='CASCADE'), nullable=True) account = db.relationship(Account, backref=db.backref('tokens', order_by=lambda: db.desc(OAuthToken.created_at))) + # note: account_id is nullable here because we don't know what account a token is for + # until we call /account/verify_credentials with it class Session(db.Model, TimestampMixin): __tablename__ = 'sessions' id = db.Column(db.String, primary_key=True, default=lambda: secrets.token_urlsafe()) - account_id = db.Column(db.String, db.ForeignKey('accounts.id')) + account_id = db.Column(db.String, db.ForeignKey('accounts.id', ondelete='CASCADE', onupdate='CASCADE'), nullable=False) account = db.relationship(Account, lazy='joined') class Post(db.Model, TimestampMixin, RemoteIDMixin): @@ -101,7 +106,7 @@ class Post(db.Model, TimestampMixin, RemoteIDMixin): id = db.Column(db.String, primary_key=True) body = db.Column(db.String) - author_id = db.Column(db.String, db.ForeignKey('accounts.id')) + author_id = db.Column(db.String, db.ForeignKey('accounts.id', ondelete='CASCADE', onupdate='CASCADE'), nullable=False) author = db.relationship(Account, backref=db.backref('posts', order_by=lambda: db.desc(Post.created_at))) @@ -110,16 +115,16 @@ class Post(db.Model, TimestampMixin, RemoteIDMixin): def __repr__(self): snippet = self.body if len(snippet) > 20: - snippet = snippet[:19] + "…" + snippet = snippet[:19] + "✂" return ''.format(self.id, snippet, self.author_id) class TwitterArchive(db.Model, TimestampMixin): __tablename__ = 'twitter_archives' id = db.Column(db.Integer, primary_key=True) - account_id = db.Column(db.String, db.ForeignKey('accounts.id'), nullable=False) + account_id = db.Column(db.String, db.ForeignKey('accounts.id', onupdate='CASCADE', ondelete='CASCADE'), nullable=False) account = db.relationship(Account, backref=db.backref('twitter_archives', order_by=lambda: db.desc(TwitterArchive.id))) body = db.deferred(db.Column(db.LargeBinary, nullable=False)) chunks = db.Column(db.Integer) - chunks_successful = db.Column(db.Integer, server_default='0') - chunks_failed = db.Column(db.Integer, server_default='0') + chunks_successful = db.Column(db.Integer, server_default='0', nullable=False) + chunks_failed = db.Column(db.Integer, server_default='0', nullable=False) diff --git a/tasks.py b/tasks.py index 6ff3ee7..ebb7966 100644 --- a/tasks.py +++ b/tasks.py @@ -135,7 +135,7 @@ def delete_from_account(account_id): post = random.choice(list((post for post in posts if not account.policy_keep_favourites or not post.favourite))) print("deleting {}".format(post)) lib.twitter.delete(post) - account.last_delete = db.func.now() + account.touch_delete() db.session.commit() diff --git a/templates/index.html b/templates/index.html index 20323ee..0df2142 100644 --- a/templates/index.html +++ b/templates/index.html @@ -2,8 +2,8 @@ {% if g.viewer %}

Hello, - - {{g.viewer.account.remote_display_name}}! + + {{g.viewer.account.display_name}}! Settings Log out