mirror of https://git.sr.ht/~sircmpwn/hub.sr.ht
Add basic mailing lists support
This commit is contained in:
parent
2aedc53e7f
commit
f0f753bfd3
|
@ -1,7 +1,8 @@
|
|||
from flask import Blueprint, render_template, request, redirect, url_for
|
||||
from hubsrht.projects import ProjectAccess, get_project
|
||||
from hubsrht.services import git
|
||||
from hubsrht.services import git, lists
|
||||
from hubsrht.types import Project, RepoType, SourceRepo, Visibility
|
||||
from hubsrht.types import MailingList
|
||||
from srht.config import get_origin
|
||||
from srht.database import db
|
||||
from srht.flask import paginate_query
|
||||
|
@ -163,3 +164,59 @@ def set_summary_repo(owner, project_name, repo_id):
|
|||
db.session.commit()
|
||||
return redirect(url_for("projects.summary_GET",
|
||||
owner=owner.canonical_name, project_name=project.name))
|
||||
|
||||
@projects.route("/<owner>/<project_name>/lists")
|
||||
@loginrequired
|
||||
def mailing_lists_GET(owner, project_name):
|
||||
owner, project = get_project(owner, project_name, ProjectAccess.read)
|
||||
mailing_lists = (MailingList.query
|
||||
.filter(MailingList.project_id == project.id)
|
||||
.order_by(MailingList.updated.desc()))
|
||||
mailing_lists, pagination = paginate_query(mailing_lists)
|
||||
return render_template("project-mailing-lists.html", view="mailing lists",
|
||||
owner=owner, project=project, mailing_lists=mailing_lists,
|
||||
**pagination)
|
||||
|
||||
@projects.route("/<owner>/<project_name>/lists/new")
|
||||
@loginrequired
|
||||
def mailing_lists_new_GET(owner, project_name):
|
||||
owner, project = get_project(owner, project_name, ProjectAccess.write)
|
||||
# TODO: Pagination
|
||||
mls = lists.get_lists(owner)
|
||||
mls = sorted(mls, key=lambda r: r["updated"], reverse=True)
|
||||
return render_template("project-lists-new.html", view="new-resource",
|
||||
owner=owner, project=project, lists=mls)
|
||||
|
||||
@projects.route("/<owner>/<project_name>/lists/new", methods=["POST"])
|
||||
@loginrequired
|
||||
def mailing_lists_new_POST(owner, project_name):
|
||||
owner, project = get_project(owner, project_name, ProjectAccess.write)
|
||||
valid = Validation(request)
|
||||
if "create" in valid:
|
||||
assert False # TODO: Create list
|
||||
if "from-template" in valid:
|
||||
assert False # TODO: Create lists from template
|
||||
|
||||
list_name = None
|
||||
for field in valid.source:
|
||||
if field.startswith("existing-"):
|
||||
list_name = field[len("existing-"):]
|
||||
break
|
||||
|
||||
mailing_list = lists.get_list(owner, list_name)
|
||||
ml = MailingList()
|
||||
ml.remote_id = mailing_list["id"]
|
||||
ml.project_id = project.id
|
||||
ml.owner_id = project.owner_id
|
||||
ml.name = mailing_list["name"]
|
||||
ml.description = mailing_list["description"]
|
||||
db.session.add(ml)
|
||||
|
||||
lists.ensure_mailing_list_webhooks(owner, list_name, {
|
||||
url_for("webhooks.mailing_list_update"): ["list:update", "list:delete"],
|
||||
})
|
||||
|
||||
db.session.commit()
|
||||
|
||||
return redirect(url_for("projects.summary_GET",
|
||||
owner=owner.canonical_name, project_name=project.name))
|
||||
|
|
|
@ -5,3 +5,7 @@ webhooks = Blueprint("webhooks", __name__)
|
|||
@webhooks.route("/webhooks/git-repo")
|
||||
def git_repo_update():
|
||||
pass # TODO
|
||||
|
||||
@webhooks.route("/webhooks/mailing-list")
|
||||
def mailing_list_update():
|
||||
pass # TODO
|
||||
|
|
|
@ -3,6 +3,7 @@ from srht.api import ensure_webhooks, get_authorization, get_results
|
|||
from srht.config import get_origin
|
||||
|
||||
_gitsrht = get_origin("git.sr.ht", external=True, default=None)
|
||||
_listsrht = get_origin("lists.sr.ht", external=True, default=None)
|
||||
|
||||
class GitService:
|
||||
def get_repos(self, user):
|
||||
|
@ -27,4 +28,20 @@ class GitService:
|
|||
def ensure_user_webhooks(self, user, config):
|
||||
ensure_webhooks(user, f"{_gitsrht}/api/user/webhooks", config)
|
||||
|
||||
class ListService():
|
||||
def get_lists(self, user):
|
||||
return get_results(f"{_listsrht}/api/lists", user)
|
||||
|
||||
def get_list(self, user, list_name):
|
||||
r = requests.get(f"{_listsrht}/api/lists/{list_name}",
|
||||
headers=get_authorization(user))
|
||||
if r.status_code != 200:
|
||||
raise Exception(r.json())
|
||||
return r.json()
|
||||
|
||||
def ensure_mailing_list_webhooks(self, user, list_name, config):
|
||||
url = f"{_listsrht}/api/user/{user.canonical_name}/lists/{list_name}/webhooks"
|
||||
ensure_webhooks(user, url, config)
|
||||
|
||||
git = GitService()
|
||||
lists = ListService()
|
||||
|
|
|
@ -0,0 +1,145 @@
|
|||
{% extends "project-resource-new.html" %}
|
||||
{% block content %}
|
||||
<form method="POST">
|
||||
{{csrf_token()}}
|
||||
<div class="row">
|
||||
{# TODO: Hide this option for any projects which already have lists #}
|
||||
<div class="col-lg-8">
|
||||
<h3>Use a common template</h3>
|
||||
<fieldset class="form-group">
|
||||
<div class="form-check">
|
||||
<label
|
||||
class="form-check-label"
|
||||
title="Only visible to you and your collaborators"
|
||||
>
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="radio"
|
||||
name="template"
|
||||
value="public-inbox"
|
||||
checked> public-inbox
|
||||
<small id="visibility-unlisted-help" class="form-text text-muted">
|
||||
Direct users to send patches & questions to a single "public
|
||||
inbox" shared with several other projects.
|
||||
</small>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<label
|
||||
class="form-check-label"
|
||||
title="Visible to anyone with the link, but not shown on your profile"
|
||||
>
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="radio"
|
||||
name="template"
|
||||
value="announce-devel">
|
||||
{{project.name}}-announce and
|
||||
{{project.name}}-devel
|
||||
<small id="visibility-unlisted-help" class="form-text text-muted">
|
||||
Separate mailing lists for low-frequency project announcements,
|
||||
and for patches & development.
|
||||
</small>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<label class="form-check-label">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="radio"
|
||||
name="template"
|
||||
value="announce-devel-discuss">
|
||||
{{project.name}}-announce,
|
||||
{{project.name}}-devel, and
|
||||
{{project.name}}-discuss
|
||||
<small id="visibility-public-help" class="form-text text-muted">
|
||||
One list for announcements, one for patches & development,
|
||||
and another for user support and discussion.
|
||||
</small>
|
||||
</label>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<div class="flex-grow-1 d-flex flex-row justify-content-end">
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn-primary align-self-end"
|
||||
name="from-template"
|
||||
>Create these mailing lists {{icon("caret-right")}}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-lg-8">
|
||||
<h3 style="margin-top: 1rem">
|
||||
Or create a new mailing list
|
||||
</h3>
|
||||
<div class="form-group">
|
||||
<label for="{{ typename }}">Name</label>
|
||||
<input
|
||||
type="text"
|
||||
name="list_name"
|
||||
id="name"
|
||||
class="form-control {{ valid.cls("list_name") }}"
|
||||
value="{{ list_name or project.name }}" />
|
||||
{{ valid.summary("list_name") }}
|
||||
<div class="form-group">
|
||||
<label for="list_description">Description</label>
|
||||
<input
|
||||
type="text"
|
||||
name="list_description"
|
||||
id="list_description"
|
||||
class="form-control {{valid.cls("list_description")}}"
|
||||
value="{{ list_description or project.description }}" />
|
||||
{{valid.summary("list_description")}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-grow-1 d-flex flex-row justify-content-end">
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn-primary align-self-end"
|
||||
name="new"
|
||||
>Create new mailing list {{icon("caret-right")}}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% if any(lists) %}
|
||||
<div class="row">
|
||||
<div class="col-lg-8">
|
||||
<h3 style="margin-top: 1rem">
|
||||
Or add an existing mailing list
|
||||
</h3>
|
||||
{# TODO: Pagination #}
|
||||
<div class="form-group">
|
||||
{# TODO: How exactly should this work #}
|
||||
<input
|
||||
name="search"
|
||||
type="text"
|
||||
placeholder="Search your mailing lists"
|
||||
class="form-control"
|
||||
value="{{ search if search else "" }}" />
|
||||
</div>
|
||||
<div class="event-list select-resource">
|
||||
{% for list in lists %}
|
||||
<div class="event">
|
||||
<h3>
|
||||
<button
|
||||
type="submit"
|
||||
name="existing-{{ list["name"] }}"
|
||||
class="pull-right btn btn-primary btn-lg"
|
||||
>Select list {{ icon("caret-right") }}</button>
|
||||
<a
|
||||
href="{{get_origin("lists.sr.ht",
|
||||
external=True)}}/{{ list["owner"]["canonical_name"] }}/{{list["name"]}}"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>{{ list["name"] }}</a>
|
||||
</h3>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</form>
|
||||
{% endblock %}
|
|
@ -0,0 +1,43 @@
|
|||
{% extends "layout.html" %}
|
||||
{% block body %}
|
||||
<div class="header-tabbed">
|
||||
<div class="container">
|
||||
{% include 'project-nav.html' %}
|
||||
</div>
|
||||
</div>
|
||||
{% if project.description %}
|
||||
<div class="header-extension">
|
||||
<div class="container">
|
||||
{{ project.description }}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-10 event-list">
|
||||
{% for mailing_list in mailing_lists %}
|
||||
<div class="event">
|
||||
<h4>
|
||||
<a
|
||||
href="{{mailing_list.url()}}"
|
||||
>~{{owner.username}}/{{mailing_list.name}}</a>
|
||||
</h4>
|
||||
{% if mailing_list.description %}
|
||||
{{ mailing_list.description | md }}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{{pagination()}}
|
||||
</div>
|
||||
{% if current_user and current_user.id == owner.id %}
|
||||
<div class="col-md-2">
|
||||
<a
|
||||
href="{{url_for("projects.mailing_lists_new_GET",
|
||||
owner=owner.canonical_name, project_name=project.name)}}"
|
||||
class="btn btn-primary btn-block"
|
||||
>Add mailing list {{icon('caret-right')}}</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -21,6 +21,13 @@
|
|||
project_name=project.name), "sources")}}
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if any(project.mailing_lists) %}
|
||||
<li class="nav-item">
|
||||
{{link(url_for("projects.mailing_lists_GET",
|
||||
owner=owner.canonical_name,
|
||||
project_name=project.name), "mailing lists")}}
|
||||
</li>
|
||||
{% endif %}
|
||||
{# TODO
|
||||
<li class="nav-item">
|
||||
<a
|
||||
|
@ -30,9 +37,6 @@
|
|||
<li class="nav-item">
|
||||
{{link("#", "documentation")}}
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
{{link("#", "mailing lists")}}
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
{{link("#", "tickets")}}
|
||||
</li>
|
||||
|
|
|
@ -34,6 +34,7 @@
|
|||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{% if any(repos) %}
|
||||
<div class="row">
|
||||
<div class="col-lg-8">
|
||||
<h3 style="margin-top: 1rem">
|
||||
|
@ -70,5 +71,6 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
|
|
@ -36,9 +36,7 @@
|
|||
href="{{url_for("projects.sources_new_GET",
|
||||
owner=owner.canonical_name, project_name=project.name)}}"
|
||||
class="btn btn-primary btn-block"
|
||||
>
|
||||
Add repository {{icon('caret-right')}}
|
||||
</a>
|
||||
>Add repository {{icon('caret-right')}}</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
|
|
@ -46,13 +46,26 @@
|
|||
{% endif %}
|
||||
</li>
|
||||
<li>
|
||||
{% if any(project.mailing_lists) %}
|
||||
{{icon('check', cls='text-success')}}
|
||||
Add mailing lists
|
||||
<br />
|
||||
<small class="text-muted">
|
||||
You can see the list of your mailing lists by clicking
|
||||
"mailing lists" on the project navigation.
|
||||
</small>
|
||||
{% else %}
|
||||
{{icon('plus-square', cls='text-info')}}
|
||||
<a href="#">Add mailing lists {{icon('arrow-right')}}</a>
|
||||
<a
|
||||
href="{{url_for("projects.mailing_lists_new_GET",
|
||||
owner=owner.canonical_name, project_name=project.name)}}"
|
||||
>Add mailing lists {{icon('arrow-right')}}</a>
|
||||
<br />
|
||||
<small class="text-muted">
|
||||
Mailing lists give users a means of asking questions about the
|
||||
project or sending patches to contribute to the source code.
|
||||
</small>
|
||||
{% endif %}
|
||||
</li>
|
||||
<li>
|
||||
{{icon('plus-square', cls='text-info')}}
|
||||
|
|
|
@ -12,3 +12,4 @@ class Visibility(Enum):
|
|||
|
||||
from hubsrht.types.project import Project
|
||||
from hubsrht.types.sourcerepo import SourceRepo, RepoType
|
||||
from hubsrht.types.mailinglist import MailingList
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
import sqlalchemy as sa
|
||||
from srht.config import get_origin
|
||||
from srht.database import Base
|
||||
|
||||
_listsrht = get_origin("lists.sr.ht", external=True, default=None)
|
||||
|
||||
class MailingList(Base):
|
||||
__tablename__ = "mailing_list"
|
||||
id = sa.Column(sa.Integer, primary_key=True)
|
||||
remote_id = sa.Column(sa.Integer, nullable=False)
|
||||
created = sa.Column(sa.DateTime, nullable=False)
|
||||
updated = sa.Column(sa.DateTime, nullable=False)
|
||||
|
||||
project_id = sa.Column(sa.Integer,
|
||||
sa.ForeignKey("project.id"), nullable=False)
|
||||
project = sa.orm.relationship("Project",
|
||||
backref=sa.orm.backref("mailing_lists"),
|
||||
foreign_keys=[project_id])
|
||||
|
||||
# Note: in theory this may eventually be different from the project owner(?)
|
||||
owner_id = sa.Column(sa.Integer,
|
||||
sa.ForeignKey("user.id"), nullable=False)
|
||||
owner = sa.orm.relationship("User")
|
||||
|
||||
name = sa.Column(sa.Unicode(128), nullable=False)
|
||||
description = sa.Column(sa.Unicode(512), nullable=False)
|
||||
|
||||
def url(self):
|
||||
return f"{_listsrht}/{self.owner.canonical_name}/{self.name}"
|
Loading…
Reference in New Issue