add project RSS feed

Signed-off-by: Vlad-Stefan Harbuz <vlad@vladh.net>
This commit is contained in:
Vlad-Stefan Harbuz 2023-02-14 09:10:27 +00:00 committed by Drew DeVault
parent fce16cc33e
commit fb56d7ba60
9 changed files with 128 additions and 4 deletions

View File

@ -0,0 +1,32 @@
"""Expand external events
Revision ID: 9cfb231405a9
Revises: e748f55a827c
Create Date: 2022-11-14 18:00:00.000000
"""
# revision identifiers, used by Alembic.
revision = '9cfb231405a9'
down_revision = 'e748f55a827c'
from alembic import op
import sqlalchemy as sa
def upgrade():
op.execute("""
ALTER TABLE "event"
ADD COLUMN external_summary_plain character varying,
ADD COLUMN external_details_plain character varying,
ADD COLUMN external_url character varying;
""")
def downgrade():
op.execute("""
ALTER TABLE "event"
DROP COLUMN external_summary_plain,
DROP COLUMN external_details_plain,
DROP COLUMN external_url;
""")

View File

@ -1,7 +1,8 @@
import re
import string
from sqlalchemy import or_
from flask import Blueprint, Response, render_template, request, redirect, url_for, abort
from flask import Blueprint, Response, render_template, request, redirect, url_for, \
abort, make_response
from flask import session
from hubsrht.decorators import adminrequired
from hubsrht.projects import ProjectAccess, get_project
@ -135,6 +136,31 @@ def feed_GET(owner, project_name):
view="summary", owner=owner, project=project,
events=events, EventType=EventType, **pagination)
@projects.route("/<owner>/<project_name>/feed.rss")
def feed_rss_GET(owner, project_name):
owner, project = get_project(owner, project_name, ProjectAccess.read)
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)
res = make_response(render_template("project-feed-rss.html",
view="summary", owner=owner, project=project,
events=events, EventType=EventType, **pagination))
res.headers['Content-Type'] = 'text/xml; charset=utf-8'
return res
@projects.route("/<owner>/<project_name>/dismiss-checklist", methods=["POST"])
@loginrequired
def dismiss_checklist_POST(owner, project_name):

View File

@ -95,9 +95,12 @@ def git_repo(repo_id):
event.external_summary = (
f"<a href='{commit_url}'>{commit_sha}</a> " +
f"<code>{html.escape(commit_message)}</code>")
event.external_summary_plain = f"{commit_sha} - {commit_message}"
event.external_details = (
f"<a href='{pusher_url}'>{pusher_name}</a> pushed to " +
f"<a href='{repo.url()}'>{repo_name}</a> git")
event.external_details_plain = f"{pusher_name} pushed to {repo_name} git"
event.external_url = commit_url
repo.project.updated = datetime.utcnow()
db.session.add(event)
@ -243,15 +246,18 @@ def mailing_list(list_id):
elif event == "post:received":
event = Event()
sender = payload["sender"]
sender_name = "Unknown"
if sender:
sender = current_app.oauth_service.lookup_user(sender['name'])
event.user_id = sender.id
sender_name = sender.canonical_name
sender_url = f"<a href='{_listssrht}/{sender.canonical_name}'>{sender.canonical_name}</a>"
else:
msg = email.message_from_string(payload["envelope"],
policy=email.policy.SMTP)
sender = email.utils.parseaddr(msg['From'])
sender_url = sender[0] if sender[0] else sender[1]
sender_name = sender[0] if sender[0] else sender[1]
sender_url = sender_name
event.event_type = EventType.external_event
event.mailing_list_id = ml.id
@ -263,8 +269,11 @@ def mailing_list(list_id):
event.external_source = "todo.sr.ht"
event.external_summary = (
f"<a href='{archive_url}'>{html.escape(subject)}</a>")
event.external_summary_plain = subject
event.external_details = (
f"{sender_url} via <a href='{ml.url()}'>{ml.name}</a>")
event.external_details_plain = f"{sender_name} via {ml.name}"
event.external_url = archive_url
db.session.add(event)
db.session.commit()
@ -354,9 +363,12 @@ def todo_tracker(tracker_id):
event.external_summary = (
f"<a href='{ticket_url}'>#{ticket_id}</a> " +
f"{html.escape(ticket_subject)}")
event.external_summary_plain = f"#{ticket_id} {ticket_subject}"
event.external_details = (
f"{submitter_url} filed ticket on " +
f"<a href='{tracker.url()}'>{tracker.name}</a> todo")
event.external_details_plain = f"{submitter['canonical_name']} filed ticket on {tracker.name} todo"
event.external_url = ticket_url
db.session.add(event)
db.session.commit()
@ -404,9 +416,12 @@ def todo_ticket(tracker_id):
event.external_summary = (
f"<a href='{ticket_url}'>#{ticket_id}</a> " +
f"{html.escape(ticket_subject)}")
event.external_summary_plain = f"#{ticket_id} {ticket_subject}"
event.external_details = (
f"{participant_url} commented on " +
f"<a href='{tracker.url()}'>{tracker.name}</a> todo")
event.external_details_plain = f"{participant['canonical_name']} commented on {tracker.name} todo"
event.external_url = ticket_url
db.session.add(event)
db.session.commit()

View File

@ -0,0 +1,24 @@
{% macro event(event, project=False) %}
<item>
<pubDate>{{event.created}}</pubDate>
<guid>{{project.owner.canonical_name}}/{{project.name}}#{{event.id}}</guid>
{% if event.event_type == EventType.source_repo_added %}
<link>{{event.source_repo.url()}}</link>
<title>{{event.source_repo.owner.canonical_name}}/{{event.source_repo.name}}</title>
<description>New {{event.source_repo.repo_type.value}} repository added</description>
{% elif event.event_type == EventType.mailing_list_added %}
<link>{{event.mailing_list.url()}}</link>
<title>{{event.mailing_list.owner.canonical_name}}/{{event.mailing_list.name}}</title>
<description>New mailing list added</description>
{% elif event.event_type == EventType.tracker_added %}
<link>{{event.tracker.url()}}</link>
<title>{{event.tracker.owner.canonical_name}}/{{event.tracker.name}}</title>
<description>New ticket tracker added</description>
{% elif event.event_type == EventType.external_event %}
<link>{{event.external_url or ""}}</link>
<title>{{event.external_summary_plain or ""}}</title>
<description>{{event.external_details_plain or ""}}</description>
{% else %}
{% endif %}
</item>
{% endmacro %}

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
{% import "event-rss.html" as event_rss_tpl with context %}
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>{{project.name}} activity</title>
<link>{{url_for("projects.summary_GET",
owner=project.owner.canonical_name,
project_name=project.name)}}</link>
<description>{{project.owner.canonical_name}}/{{project.name}}</description>
<atom:link href="{{url_for('projects.feed_rss_GET',
owner=project.owner.canonical_name,
project_name=project.name)}}" rel="self" type="application/rss+xml" />
{% for event in events %}
{{ event_rss_tpl.event(event, project) }}
{% endfor %}
</channel>
</rss>

View File

@ -3,7 +3,6 @@
{% block title %}
<title>{{project.name}} activity</title>
{% endblock %}
{# TODO: RSS feed #}
{% block content %}
<div class="container">
<div class="row">

View File

@ -125,6 +125,11 @@
owner=owner, project_name=project.name)}}"
class="btn btn-link"
>View project feed&nbsp;{{icon("caret-right")}}</a>
<a
href="{{url_for("projects.feed_rss_GET",
owner=owner, project_name=project.name)}}"
class="btn btn-link"
>RSS&nbsp;{{icon("caret-right")}}</a>
</div>
</div>
</div>

View File

@ -48,3 +48,6 @@ class Event(Base):
external_source = sa.Column(sa.Unicode) # e.g. "lists.sr.ht"
external_summary = sa.Column(sa.Unicode) # markdown
external_details = sa.Column(sa.Unicode) # markdown
external_summary_plain = sa.Column(sa.Unicode) # plaintext
external_details_plain = sa.Column(sa.Unicode) # plaintext
external_url = sa.Column(sa.Unicode)

View File

@ -96,5 +96,8 @@ CREATE TABLE event (
tracker_id integer REFERENCES tracker(id) ON DELETE CASCADE,
external_source character varying,
external_summary character varying,
external_details character varying
external_details character varying,
external_summary_plain character varying,
external_details_plain character varying,
external_url character varying
);