diff --git a/data/database.py b/data/database.py index 413d2261c..c98444ec6 100644 --- a/data/database.py +++ b/data/database.py @@ -271,9 +271,22 @@ class LogEntry(BaseModel): metadata_json = TextField(default='{}') +class NotificationKind(BaseModel): + name = CharField(index=True) + + +class Notification(BaseModel): + uuid = CharField(default=uuid_generator, index=True) + kind = ForeignKeyField(NotificationKind, index=True) + notification_user = ForeignKeyField(User, index=True) + metadata_json = TextField(default='{}') + created = DateTimeField(default=datetime.now, index=True) + + all_models = [User, Repository, Image, AccessToken, Role, RepositoryPermission, Visibility, RepositoryTag, EmailConfirmation, FederatedLogin, LoginService, QueueItem, RepositoryBuild, Team, TeamMember, TeamRole, Webhook, LogEntryKind, LogEntry, PermissionPrototype, ImageStorage, - BuildTriggerService, RepositoryBuildTrigger] + BuildTriggerService, RepositoryBuildTrigger, NotificationKind, + Notification] diff --git a/data/model.py b/data/model.py index fd3b51855..9eec9f841 100644 --- a/data/model.py +++ b/data/model.py @@ -93,6 +93,12 @@ def create_user(username, password, email): new_user = User.create(username=username, password_hash=pw_hash, email=email) + + # If the password is None, then add a notification for the user to change + # their password ASAP. + if not pw_hash: + create_notification('password_required', new_user) + return new_user except Exception as ex: raise DataModelException(ex.message) @@ -662,6 +668,9 @@ def change_password(user, new_password): user.password_hash = pw_hash user.save() + # Remove any password required notifications for the user. + delete_notifications_by_kind(user, 'password_required') + def change_invoice_email(user, invoice_email): user.invoice_email = invoice_email @@ -1535,3 +1544,26 @@ def list_trigger_builds(namespace_name, repository_name, trigger_uuid, limit): return (list_repository_builds(namespace_name, repository_name, limit) .where(RepositoryBuildTrigger.uuid == trigger_uuid)) + + +def create_notification(kind, user, metadata={}): + kind_ref = NotificationKind.get(name=kind) + notification = Notification.create(kind=kind_ref, notification_user=user, + metadata_json=json.dumps(metadata)) + return notification + + +def list_notifications(user, kind=None): + query = (Notification.select() + .join(User) + .where(Notification.notification_user == user)) + + if kind: + query = query.join(NotificationKind).where(NotificationKind.name == kind) + + return query.order_by(Notification.created).desc() + + +def delete_notifications_by_kind(user, kind): + kind_ref = NotificationKind.get(name=kind) + Notification.delete().where(Notification.user == user, Notification.kind == kind_ref).execute() diff --git a/endpoints/api.py b/endpoints/api.py index 22a571be4..117ba4983 100644 --- a/endpoints/api.py +++ b/endpoints/api.py @@ -28,7 +28,7 @@ from auth.permissions import (ReadRepositoryPermission, ViewTeamPermission, UserPermission) from endpoints.common import (common_login, get_route_data, truthy_param, - start_build) + start_build, add_notification) from endpoints.trigger import (BuildTrigger, TriggerActivationException, TriggerDeactivationException, EmptyRepositoryException) @@ -2518,3 +2518,19 @@ def get_logs(namespace, start_time, end_time, performer_name=None, 'logs': [log_view(log) for log in logs] }) + +def notification_view(notification): + return { + 'kind': notification.kind.name, + 'created': notification.created, + 'metadata': json.loads(notification.metadata_json), + } + + +@api.route('/user/notifications', methods=['GET']) +@api_login_required +def list_user_notifications(): + notifications = model.list_notifications(current_user.db_user()) + return jsonify({ + 'notifications': [notification_view(notification) for notification in notifications] + }) diff --git a/endpoints/common.py b/endpoints/common.py index 0fc3dc3da..0f0e68df7 100644 --- a/endpoints/common.py +++ b/endpoints/common.py @@ -5,7 +5,7 @@ import urlparse import json from flask import session, make_response, render_template, request -from flask.ext.login import login_user, UserMixin +from flask.ext.login import login_user, UserMixin, current_user from flask.ext.principal import identity_changed from data import model @@ -120,13 +120,19 @@ app.jinja_env.globals['csrf_token'] = generate_csrf_token def render_page_template(name, **kwargs): - resp = make_response(render_template(name, route_data=get_route_data(), **kwargs)) resp.headers['X-FRAME-OPTIONS'] = 'DENY' return resp +def add_notification(kind, metadata=None, user=None): + if not user and current_user: + user = current_user.db_user() + + return model.create_notification(kind, user, metadata or {}) + + def start_build(repository, dockerfile_id, tags, build_name, subdir, manual, trigger=None): host = urlparse.urlparse(request.url).netloc diff --git a/endpoints/trigger.py b/endpoints/trigger.py index 41c32045a..82a3284ab 100644 --- a/endpoints/trigger.py +++ b/endpoints/trigger.py @@ -203,7 +203,7 @@ class GithubBuildTrigger(BuildTrigger): try: repo = gh_client.get_repo(source) - default_commit = repo.get_branch(repo.master_branch).commit + default_commit = repo.get_branch(repo.master_branch or 'master').commit commit_tree = repo.get_git_tree(default_commit.sha, recursive=True) return [os.path.dirname(elem.path) for elem in commit_tree.tree @@ -283,4 +283,4 @@ class GithubBuildTrigger(BuildTrigger): short_sha = GithubBuildTrigger.get_display_name(master_sha) ref = 'refs/heads/%s' % repo.master_branch - return self._prepare_build(config, repo, master_sha, short_sha, ref) \ No newline at end of file + return self._prepare_build(config, repo, master_sha, short_sha, ref) diff --git a/initdb.py b/initdb.py index a1e2fe646..561d98992 100644 --- a/initdb.py +++ b/initdb.py @@ -224,6 +224,11 @@ def initialize_database(): LogEntryKind.create(name='setup_repo_trigger') LogEntryKind.create(name='delete_repo_trigger') + NotificationKind.create(name='password_required') + NotificationKind.create(name='over_private_usage') + + NotificationKind.create(name='test_notification') + def wipe_database(): logger.debug('Wiping all data from the DB.') @@ -269,6 +274,8 @@ def populate_database(): outside_org.verified = True outside_org.save() + model.create_notification('test_notification', new_user_1, metadata={'some': 'value'}) + __generate_repository(new_user_4, 'randomrepo', 'Random repo repository.', False, [], (4, [], ['latest', 'prod'])) diff --git a/test/data/test.db b/test/data/test.db index 9779208a8..dd1b56708 100644 Binary files a/test/data/test.db and b/test/data/test.db differ