Better handling of private or unlisted resources

This commit is contained in:
Drew DeVault 2020-04-29 13:25:49 -04:00
parent 8f5a77e19b
commit 7d1cb671f3
19 changed files with 198 additions and 16 deletions

View File

@ -6,21 +6,21 @@ The target database needs to exist, as defined in the config file.
Idempotent. If the tables already exist, they will not be re-created.
"""
#import hubsrht.alembic
import hubsrht.alembic
import hubsrht.types
#from alembic import command
#from alembic.config import Config
from alembic import command
from alembic.config import Config
from srht.config import cfg
from srht.database import DbSession
connection_string = cfg("hub.sr.ht", "connection-string")
#alembic_path = list(hubsrht.alembic.__path__)[0]
alembic_path = list(hubsrht.alembic.__path__)[0]
db = DbSession(connection_string)
db.create()
#config = Config()
#config.set_main_option("sqlalchemy.url", connection_string)
#config.set_main_option("script_location", alembic_path)
#command.stamp(config, "head")
config = Config()
config.set_main_option("sqlalchemy.url", connection_string)
config.set_main_option("script_location", alembic_path)
command.stamp(config, "head")

6
hubsrht-migrate Executable file
View File

@ -0,0 +1,6 @@
#!/usr/bin/env python3
import hubsrht.alembic
import srht.alembic
from srht.database import alembic
alembic("hub.sr.ht", hubsrht.alembic)
alembic("hub.sr.ht", srht.alembic)

1
hubsrht/alembic/README Normal file
View File

@ -0,0 +1 @@
Generic single-database configuration.

3
hubsrht/alembic/env.py Normal file
View File

@ -0,0 +1,3 @@
import hubsrht.types
from srht.database import alembic_env
alembic_env()

View File

@ -0,0 +1,22 @@
"""${message}
Revision ID: ${up_revision}
Revises: ${down_revision}
Create Date: ${create_date}
"""
# revision identifiers, used by Alembic.
revision = ${repr(up_revision)}
down_revision = ${repr(down_revision)}
from alembic import op
import sqlalchemy as sa
${imports if imports else ""}
def upgrade():
${upgrades if upgrades else "pass"}
def downgrade():
${downgrades if downgrades else "pass"}

View File

@ -0,0 +1,61 @@
"""Add visibility to resources
Revision ID: 7f512cdfc2f5
Revises: None
Create Date: 2020-04-29 12:44:42.864699
"""
# revision identifiers, used by Alembic.
revision = '7f512cdfc2f5'
down_revision = None
from alembic import op
import sqlalchemy as sa
from sqlalchemy.orm import sessionmaker
from hubsrht.app import app
from hubsrht.types import SourceRepo, Tracker, MailingList, RepoType, Visibility
from hubsrht.services import git, hg, todo, lists
Session = sessionmaker()
def upgrade():
op.add_column('tracker', sa.Column('visibility', sa.String(),
nullable=False, server_default="unlisted"))
op.add_column('mailing_list', sa.Column('visibility', sa.String(),
nullable=False, server_default="unlisted"))
op.add_column('source_repo', sa.Column('visibility', sa.String(),
nullable=False, server_default="unlisted"))
bind = op.get_bind()
session = Session(bind=bind)
with app.app_context():
for repo in session.query(SourceRepo).all():
if repo.repo_type == RepoType.git:
r = git.get_repo(repo.owner, repo.name)
elif repo.repo_type == RepoType.hg:
r = hg.get_repo(repo.owner, repo.name)
else:
assert False
repo.visibility = Visibility(r["visibility"])
for ml in session.query(MailingList).all():
m = lists.get_list(ml.owner, ml.name)
if any(m["permissions"]["nonsubscriber"]):
ml.visibility = Visibility.public
else:
ml.visibility = Visibility.unlisted
for tracker in session.query(Tracker).all():
t = todo.get_tracker(tracker.owner, tracker.name)
if any(t["default_permissions"]["anonymous"]):
tracker.visibility = Visibility.public
else:
tracker.visibility = Visibility.unlisted
session.commit()
def downgrade():
op.drop_column('tracker', 'visibility')
op.drop_column('mailing_list', 'visibility')
op.drop_column('source_repo', 'visibility')

View File

@ -3,7 +3,7 @@ from flask import Blueprint, render_template, request, redirect, url_for
from hubsrht.projects import ProjectAccess, get_project
from hubsrht.services import lists
from hubsrht.types import Event, EventType
from hubsrht.types import MailingList
from hubsrht.types import MailingList, Visibility
from srht.config import get_origin
from srht.database import db
from srht.flask import paginate_query
@ -19,6 +19,9 @@ def lists_GET(owner, project_name):
mailing_lists = (MailingList.query
.filter(MailingList.project_id == project.id)
.order_by(MailingList.updated.desc()))
if not current_user or current_user.id != owner.id:
mailing_lists = mailing_lists.filter(
MailingList.visibility == Visibility.public)
terms = request.args.get("search")
search_error = None
@ -105,6 +108,10 @@ Mailing list for end-user discussion and questions related to the
ml.owner_id = project.owner_id
ml.name = mailing_list["name"]
ml.description = mailing_list["description"]
if any(mailing_list["permissions"]["nonsubscriber"]):
ml.visibility = Visibility.public
else:
ml.visibility = Visibility.unlisted
db.session.add(ml)
db.session.flush()
@ -169,6 +176,10 @@ def new_POST(owner, project_name):
ml.owner_id = project.owner_id
ml.name = mailing_list["name"]
ml.description = mailing_list["description"]
if any(mailing_list["permissions"]["nonsubscriber"]):
ml.visibility = Visibility.public
else:
ml.visibility = Visibility.unlisted
db.session.add(ml)
db.session.flush()

View File

@ -1,10 +1,12 @@
import re
from sqlalchemy import or_
from flask import Blueprint, render_template, request, redirect, url_for
from hubsrht.decorators import adminrequired
from hubsrht.projects import ProjectAccess, get_project
from hubsrht.services import git, hg
from hubsrht.types import Feature, Event, EventType
from hubsrht.types import Project, RepoType, Visibility
from hubsrht.types import SourceRepo, MailingList, Tracker
from srht.database import db
from srht.flask import paginate_query
from srht.oauth import current_user, loginrequired
@ -33,8 +35,16 @@ def summary_GET(owner, project_name):
events = (Event.query
.filter(Event.project_id == project.id)
.order_by(Event.created.desc())
.limit(2)).all()
.order_by(Event.created.desc()))
if not current_user or current_user.id != owner.id:
events = (events
.outerjoin(SourceRepo)
.outerjoin(MailingList)
.outerjoin(Tracker)
.filter(or_(Event.source_repo == None, SourceRepo.visibility == Visibility.public),
or_(Event.mailing_list == None, MailingList.visibility == Visibility.public),
or_(Event.tracker == None, Tracker.visibility == Visibility.public)))
events = events.limit(2).all()
return render_template("project-summary.html", view="summary",
owner=owner, project=project,
@ -48,6 +58,16 @@ def feed_GET(owner, project_name):
events = (Event.query
.filter(Event.project_id == project.id)
.order_by(Event.created.desc()))
if not current_user or current_user.id != owner.id:
events = (events
.outerjoin(SourceRepo)
.outerjoin(MailingList)
.outerjoin(Tracker)
.filter(or_(Event.source_repo == None, SourceRepo.visibility == Visibility.public),
or_(Event.mailing_list == None, MailingList.visibility == Visibility.public),
or_(Event.tracker == None, Tracker.visibility == Visibility.public)))
events, pagination = paginate_query(events)
return render_template("project-feed.html",

View File

@ -2,10 +2,10 @@ from flask import Blueprint, render_template, request, redirect, url_for
from hubsrht.projects import ProjectAccess, get_project
from hubsrht.services import git, hg
from hubsrht.types import Event, EventType
from hubsrht.types import RepoType, SourceRepo
from hubsrht.types import RepoType, SourceRepo, Visibility
from srht.database import db
from srht.flask import paginate_query
from srht.oauth import loginrequired
from srht.oauth import current_user, loginrequired
from srht.search import search_by
from srht.validation import Validation
@ -17,6 +17,8 @@ def sources_GET(owner, project_name):
sources = (SourceRepo.query
.filter(SourceRepo.project_id == project.id)
.order_by(SourceRepo.updated.desc()))
if not current_user or current_user.id != owner.id:
sources = sources.filter(SourceRepo.visibility == Visibility.public)
terms = request.args.get("search")
search_error = None
@ -111,6 +113,7 @@ def git_new_POST(owner, project_name):
repo.name = git_repo["name"]
repo.description = git_repo["description"]
repo.repo_type = RepoType.git
repo.visibility = Visibility(repo["visibility"])
db.session.add(repo)
db.session.flush()
@ -170,6 +173,7 @@ def hg_new_POST(owner, project_name):
repo.name = hg_repo["name"]
repo.description = hg_repo["description"]
repo.repo_type = RepoType.hg
repo.visibility = Visibility(repo["visibility"])
db.session.add(repo)
db.session.flush()

View File

@ -4,7 +4,7 @@ from hubsrht.services import todo
from hubsrht.types import Event, EventType, Tracker
from srht.database import db
from srht.flask import paginate_query
from srht.oauth import loginrequired
from srht.oauth import current_user, loginrequired
from srht.search import search_by
from srht.validation import Validation
@ -16,6 +16,8 @@ def trackers_GET(owner, project_name):
trackers = (Tracker.query
.filter(Tracker.project_id == project.id)
.order_by(Tracker.updated.desc()))
if not current_user or current_user.id != owner.id:
trackers = trackers.filter(Tracker.visibility == Visibility.public)
terms = request.args.get("search")
search_error = None
@ -82,6 +84,10 @@ def new_POST(owner, project_name):
tracker.owner_id = owner.id
tracker.name = remote_tracker["name"]
tracker.description = remote_tracker["description"]
if any(remote_tracker["default_permissions"]["anonymous"]):
tracker.visibility = Visibility.public
else:
tracker.visibility = Visibility.unlisted
db.session.add(tracker)
db.session.flush()

View File

@ -1,5 +1,7 @@
from sqlalchemy import or_
from flask import Blueprint, render_template, request, abort
from hubsrht.types import User, Project, Visibility, Event, EventType
from hubsrht.types import SourceRepo, MailingList, Tracker
from srht.flask import paginate_query
from srht.oauth import current_user
from srht.search import search_by
@ -22,8 +24,15 @@ def summary_GET(username):
# TODO: ACLs
projects = projects.filter(Project.visibility == Visibility.public)
events = (events
.join(Project)
.join(Project, Event.project_id == Project.id)
.filter(Project.visibility == Visibility.public))
events = (events
.outerjoin(SourceRepo, Event.source_repo_id == SourceRepo.id)
.outerjoin(MailingList, Event.source_repo_id == MailingList.id)
.outerjoin(Tracker, Event.source_repo_id == Tracker.id)
.filter(or_(Event.source_repo == None, SourceRepo.visibility == Visibility.public),
or_(Event.mailing_list == None, MailingList.visibility == Visibility.public),
or_(Event.tracker == None, Tracker.visibility == Visibility.public)))
projects = projects.limit(5).all()
events, pagination = paginate_query(events)

View File

@ -3,7 +3,7 @@ import json
from datetime import datetime
from flask import Blueprint, request, current_app
from hubsrht.types import Event, EventType, MailingList, SourceRepo, RepoType
from hubsrht.types import Tracker, User
from hubsrht.types import Tracker, User, Visibility
from hubsrht.services import todo
from srht.config import get_origin
from srht.database import db
@ -34,6 +34,7 @@ def git_user(user_id):
return "I don't recognize this git repo.", 404
repo.name = payload["name"]
repo.description = payload["description"]
repo.visibility = Visibility(payload["visibility"])
repo.project.updated = datetime.utcnow()
db.session.commit()
return f"Updated local:{repo.id}/remote:{repo.remote_id}. Thanks!", 200
@ -113,6 +114,7 @@ def hg_user(user_id):
repo.name = payload["name"]
repo.description = payload["description"]
repo.project.updated = datetime.utcnow()
repo.visibility = Visibility(payload["visibility"])
db.session.commit()
return f"Updated local:{repo.id}/remote:{repo.remote_id}. Thanks!", 200
elif event == "repo:delete":
@ -142,6 +144,10 @@ def mailing_list(list_id):
if event == "list:update":
ml.name = payload["name"]
ml.description = payload["description"]
if any(payload["permissions"]["nonsubscriber"]):
ml.visibility = Visibility.public
else:
ml.visibility = Visibility.unlisted
ml.project.updated = datetime.utcnow()
db.session.commit()
return f"Updated local:{ml.id}/remote:{ml.remote_id}. Thanks!", 200
@ -200,6 +206,10 @@ def todo_user(user_id):
return "I don't recognize this tracker.", 404
tracker.name = payload["name"]
tracker.description = payload["description"]
if any(payload["default_permissions"]["anonymous"]):
tracker.visibility = Visibility.public
else:
tracker.visibility = Visibility.unlisted
tracker.project.updated = datetime.utcnow()
db.session.commit()
return f"Updated local:{tracker.id}/remote:{tracker.remote_id}. Thanks!", 200

View File

@ -3,6 +3,11 @@
<title>{{project.name}} mailing lists</title>
{% endblock %}
{% block resource_list %}
{% if not any(mailing_lists) %}
<div class="alert alert-info">
This project has no public mailing lists.
</div>
{% endif %}
{% for mailing_list in mailing_lists %}
<div class="event">
<h4>

View File

@ -1,5 +1,10 @@
{% extends "resource-list.html" %}
{% block resource_list %}
{% if not any(sources) %}
<div class="alert alert-info">
This project has no public sources.
</div>
{% endif %}
{% for repo in sources %}
<div class="event">
<h4>

View File

@ -1,5 +1,10 @@
{% extends "resource-list.html" %}
{% block resource_list %}
{% if not any(trackers) %}
<div class="alert alert-info">
This project has no public ticket trackers.
</div>
{% endif %}
{% for tracker in trackers %}
<div class="event">
<h4>

View File

@ -1,4 +1,6 @@
import sqlalchemy as sa
import sqlalchemy_utils as sau
from hubsrht.types import Visibility
from srht.config import get_origin
from srht.database import Base
@ -24,6 +26,8 @@ class MailingList(Base):
name = sa.Column(sa.Unicode(128), nullable=False)
description = sa.Column(sa.Unicode(512), nullable=False)
visibility = sa.Column(sau.ChoiceType(Visibility, impl=sa.String()),
nullable=False, server_default="unlisted")
def url(self):
return f"{_listsrht}/{self.owner.canonical_name}/{self.name}"

View File

@ -1,6 +1,7 @@
import sqlalchemy as sa
import sqlalchemy_utils as sau
from enum import Enum
from hubsrht.types import Visibility
from srht.config import get_origin
from srht.database import Base
@ -33,6 +34,8 @@ class SourceRepo(Base):
description = sa.Column(sa.Unicode(512), nullable=False)
repo_type = sa.Column(sau.ChoiceType(RepoType, impl=sa.String()),
nullable=False)
visibility = sa.Column(sau.ChoiceType(Visibility, impl=sa.String()),
nullable=False, server_default="unlisted")
def url(self):
origin = _gitsrht if self.repo_type == RepoType.git else _hgsrht

View File

@ -1,4 +1,6 @@
import sqlalchemy as sa
import sqlalchemy_utils as sau
from hubsrht.types import Visibility
from srht.config import get_origin
from srht.database import Base
@ -24,6 +26,8 @@ class Tracker(Base):
name = sa.Column(sa.Unicode(128), nullable=False)
description = sa.Column(sa.Unicode(512), nullable=False)
visibility = sa.Column(sau.ChoiceType(Visibility, impl=sa.String()),
nullable=False, server_default="unlisted")
def url(self):
return f"{_todosrht}/{self.owner.canonical_name}/{self.name}"

View File

@ -40,6 +40,8 @@ setup(
name = 'hubsrht',
packages = [
'hubsrht',
'hubsrht.alembic',
'hubsrht.alembic.versions',
'hubsrht.blueprints',
'hubsrht.types',
],
@ -59,5 +61,6 @@ setup(
},
scripts = [
'hubsrht-initdb',
'hubsrht-migrate',
]
)