fixes wrong handling of feeds - only one channel per feed is allowed

Thu, 13 May 2021 19:31:09 +0200

author
Mike Becker <universe@uap-core.de>
date
Thu, 13 May 2021 19:31:09 +0200
changeset 198
94f174d591ab
parent 197
0a2ad22ac656
child 199
59393c8cc557

fixes wrong handling of feeds - only one channel per feed is allowed

src/main/kotlin/de/uapcore/lightpit/Constants.kt file | annotate | diff | comparison | revisions
src/main/kotlin/de/uapcore/lightpit/RequestMapping.kt file | annotate | diff | comparison | revisions
src/main/kotlin/de/uapcore/lightpit/servlet/FeedServlet.kt file | annotate | diff | comparison | revisions
src/main/kotlin/de/uapcore/lightpit/servlet/ProjectServlet.kt file | annotate | diff | comparison | revisions
src/main/kotlin/de/uapcore/lightpit/viewmodel/Feeds.kt file | annotate | diff | comparison | revisions
src/main/resources/localization/strings.properties file | annotate | diff | comparison | revisions
src/main/resources/localization/strings_de.properties file | annotate | diff | comparison | revisions
src/main/webapp/WEB-INF/jsp/feed.jsp file | annotate | diff | comparison | revisions
src/main/webapp/WEB-INF/jsp/issues-feed.jsp file | annotate | diff | comparison | revisions
src/main/webapp/WEB-INF/jsp/project-feed.jsp file | annotate | diff | comparison | revisions
src/main/webapp/WEB-INF/jsp/site.jsp file | annotate | diff | comparison | revisions
     1.1 --- a/src/main/kotlin/de/uapcore/lightpit/Constants.kt	Thu May 13 18:01:56 2021 +0200
     1.2 +++ b/src/main/kotlin/de/uapcore/lightpit/Constants.kt	Thu May 13 19:31:09 2021 +0200
     1.3 @@ -62,6 +62,11 @@
     1.4      const val REQ_ATTR_BASE_HREF = "base_href"
     1.5  
     1.6      /**
     1.7 +     * Key for the request attribute containing the RSS feed href.
     1.8 +     */
     1.9 +    const val REQ_ATTR_FEED_HREF = "feed_href"
    1.10 +
    1.11 +    /**
    1.12       * Key for the request attribute containing the full path information (servlet path + path info).
    1.13       */
    1.14      const val REQ_ATTR_PATH = "requestPath"
     2.1 --- a/src/main/kotlin/de/uapcore/lightpit/RequestMapping.kt	Thu May 13 18:01:56 2021 +0200
     2.2 +++ b/src/main/kotlin/de/uapcore/lightpit/RequestMapping.kt	Thu May 13 19:31:09 2021 +0200
     2.3 @@ -86,6 +86,12 @@
     2.4              request.setAttribute(Constants.REQ_ATTR_REDIRECT_LOCATION, baseHref + value)
     2.5          }
     2.6  
     2.7 +    var feedPath = ""
     2.8 +        set(value) {
     2.9 +            field = value
    2.10 +            request.setAttribute(Constants.REQ_ATTR_FEED_HREF, baseHref + value)
    2.11 +        }
    2.12 +
    2.13      /**
    2.14       * The view object.
    2.15       *
    2.16 @@ -98,9 +104,21 @@
    2.17          }
    2.18  
    2.19      /**
    2.20 +     * Additional port info, if necessary.
    2.21 +     */
    2.22 +    private val portInfo =
    2.23 +        if ((request.scheme == "http" && request.serverPort == 80)
    2.24 +            || (request.scheme == "https" && request.serverPort == 443)
    2.25 +        ) "" else ":${request.serverPort}"
    2.26 +
    2.27 +    /**
    2.28       * The base path of this application.
    2.29       */
    2.30 -    val baseHref get() = "${request.scheme}://${request.serverName}:${request.serverPort}${request.contextPath}/"
    2.31 +    val baseHref get() = "${request.scheme}://${request.serverName}$portInfo${request.contextPath}/"
    2.32 +
    2.33 +    init {
    2.34 +        feedPath = "feed/projects.rss"
    2.35 +    }
    2.36  
    2.37      private fun String.withExt(ext: String) = if (endsWith(ext)) this else plus(ext)
    2.38      private fun jspPath(name: String) = Constants.JSP_PATH_PREFIX.plus(name).withExt(".jsp")
    2.39 @@ -108,10 +126,15 @@
    2.40      fun param(name: String): String? = request.getParameter(name)
    2.41      fun paramArray(name: String): Array<String> = request.getParameterValues(name) ?: emptyArray()
    2.42  
    2.43 -    fun forward(jsp: String) {
    2.44 +    private fun forward(jsp: String) {
    2.45          request.getRequestDispatcher(jspPath(jsp)).forward(request, response)
    2.46      }
    2.47  
    2.48 +    fun renderFeed(page: String? = null) {
    2.49 +        page?.let { contentPage = it }
    2.50 +        forward("feed")
    2.51 +    }
    2.52 +
    2.53      fun render(page: String? = null) {
    2.54          page?.let { contentPage = it }
    2.55          forward("site")
     3.1 --- a/src/main/kotlin/de/uapcore/lightpit/servlet/FeedServlet.kt	Thu May 13 18:01:56 2021 +0200
     3.2 +++ b/src/main/kotlin/de/uapcore/lightpit/servlet/FeedServlet.kt	Thu May 13 19:31:09 2021 +0200
     3.3 @@ -28,24 +28,39 @@
     3.4  import de.uapcore.lightpit.AbstractServlet
     3.5  import de.uapcore.lightpit.HttpRequest
     3.6  import de.uapcore.lightpit.dao.DataAccessObject
     3.7 -import de.uapcore.lightpit.entities.Issue
     3.8  import de.uapcore.lightpit.util.IssueFilter
     3.9  import de.uapcore.lightpit.util.IssueSorter
    3.10 +import de.uapcore.lightpit.util.SpecificFilter
    3.11  import de.uapcore.lightpit.viewmodel.IssueFeed
    3.12 +import de.uapcore.lightpit.viewmodel.ProjectFeed
    3.13  import javax.servlet.annotation.WebServlet
    3.14  
    3.15  @WebServlet(urlPatterns = ["/feed/*"])
    3.16  class FeedServlet : AbstractServlet() {
    3.17  
    3.18      init {
    3.19 -        get("/issues.rss", this::issues)
    3.20 +        get("/projects.rss", this::projects)
    3.21 +        get("/%project/issues.rss", this::issues)
    3.22 +    }
    3.23 +
    3.24 +    private fun projects(http: HttpRequest, dao: DataAccessObject) {
    3.25 +
    3.26 +        val projects = dao.listProjects()
    3.27 +
    3.28 +        http.view = ProjectFeed(projects)
    3.29 +        http.renderFeed("project-feed")
    3.30      }
    3.31  
    3.32      private fun issues(http: HttpRequest, dao: DataAccessObject) {
    3.33 +        val project = http.pathParams["project"]?.let { dao.findProjectByNode(it) }
    3.34 +        if (project == null) {
    3.35 +            http.response.sendError(404)
    3.36 +            return
    3.37 +        }
    3.38  
    3.39 -        val issues = dao.listIssues(IssueFilter()).sortedWith(IssueSorter.DEFAULT_ISSUE_SORTER)
    3.40 +        val issues = dao.listIssues(IssueFilter(SpecificFilter(project))).sortedWith(IssueSorter.DEFAULT_ISSUE_SORTER)
    3.41  
    3.42 -        http.view = IssueFeed(issues.groupBy(Issue::project))
    3.43 -        http.forward("issues-feed")
    3.44 +        http.view = IssueFeed(project, issues)
    3.45 +        http.renderFeed("issues-feed")
    3.46      }
    3.47  }
    3.48 \ No newline at end of file
     4.1 --- a/src/main/kotlin/de/uapcore/lightpit/servlet/ProjectServlet.kt	Thu May 13 18:01:56 2021 +0200
     4.2 +++ b/src/main/kotlin/de/uapcore/lightpit/servlet/ProjectServlet.kt	Thu May 13 19:31:09 2021 +0200
     4.3 @@ -145,12 +145,15 @@
     4.4          }
     4.5      }
     4.6  
     4.7 +    private fun feedPath(project: Project) = "feed/${project.node}/issues.rss"
     4.8 +
     4.9      data class PathInfos(
    4.10          val projectInfo: ProjectInfo,
    4.11          val version: Version?,
    4.12          val component: Component?
    4.13      ) {
    4.14 -        val issuesHref by lazyOf("projects/${projectInfo.project.node}/issues/${version?.node ?: "-"}/${component?.node ?: "-"}/")
    4.15 +        val project = projectInfo.project
    4.16 +        val issuesHref by lazyOf("projects/${project.node}/issues/${version?.node ?: "-"}/${component?.node ?: "-"}/")
    4.17      }
    4.18  
    4.19      private fun withPathInfo(http: HttpRequest, dao: DataAccessObject): PathInfos? {
    4.20 @@ -186,13 +189,14 @@
    4.21          withPathInfo(http, dao)?.run {
    4.22  
    4.23              val issues = dao.listIssues(IssueFilter(
    4.24 -                project = SpecificFilter(projectInfo.project),
    4.25 +                project = SpecificFilter(project),
    4.26                  version = version?.let { SpecificFilter(it) } ?: AllFilter(),
    4.27                  component = component?.let { SpecificFilter(it) } ?: AllFilter()
    4.28              )).sortedWith(DEFAULT_ISSUE_SORTER)
    4.29  
    4.30              with(http) {
    4.31                  view = ProjectDetails(projectInfo, issues, version, component)
    4.32 +                feedPath = feedPath(project)
    4.33                  navigationMenu = activeProjectNavMenu(
    4.34                      dao.listProjects(),
    4.35                      projectInfo,
    4.36 @@ -261,6 +265,7 @@
    4.37                  projectInfo,
    4.38                  dao.listVersionSummaries(projectInfo.project)
    4.39              )
    4.40 +            feedPath = feedPath(projectInfo.project)
    4.41              navigationMenu = activeProjectNavMenu(
    4.42                  dao.listProjects(),
    4.43                  projectInfo
    4.44 @@ -290,6 +295,7 @@
    4.45  
    4.46          with(http) {
    4.47              view = VersionEditView(projectInfo, version)
    4.48 +            feedPath = feedPath(projectInfo.project)
    4.49              navigationMenu = activeProjectNavMenu(
    4.50                  dao.listProjects(),
    4.51                  projectInfo,
    4.52 @@ -342,6 +348,7 @@
    4.53                  projectInfo,
    4.54                  dao.listComponentSummaries(projectInfo.project)
    4.55              )
    4.56 +            feedPath = feedPath(projectInfo.project)
    4.57              navigationMenu = activeProjectNavMenu(
    4.58                  dao.listProjects(),
    4.59                  projectInfo
    4.60 @@ -371,6 +378,7 @@
    4.61  
    4.62          with(http) {
    4.63              view = ComponentEditView(projectInfo, component, dao.listUsers())
    4.64 +            feedPath = feedPath(projectInfo.project)
    4.65              navigationMenu = activeProjectNavMenu(
    4.66                  dao.listProjects(),
    4.67                  projectInfo,
    4.68 @@ -426,7 +434,9 @@
    4.69              val comments = dao.listComments(issue)
    4.70  
    4.71              with(http) {
    4.72 -                view = IssueDetailView(issue, comments, projectInfo.project, version, component)
    4.73 +                view = IssueDetailView(issue, comments, project, version, component)
    4.74 +                // TODO: feed path for this particular issue
    4.75 +                feedPath = feedPath(projectInfo.project)
    4.76                  navigationMenu = activeProjectNavMenu(
    4.77                      dao.listProjects(),
    4.78                      projectInfo,
    4.79 @@ -443,7 +453,7 @@
    4.80          withPathInfo(http, dao)?.run {
    4.81              val issue = dao.findIssue(http.pathParams["issue"]?.toIntOrNull() ?: -1) ?: Issue(
    4.82                  -1,
    4.83 -                projectInfo.project,
    4.84 +                project,
    4.85              )
    4.86  
    4.87              // pre-select component, if available in the path info
    4.88 @@ -464,10 +474,11 @@
    4.89                      projectInfo.versions,
    4.90                      projectInfo.components,
    4.91                      dao.listUsers(),
    4.92 -                    projectInfo.project,
    4.93 +                    project,
    4.94                      version,
    4.95                      component
    4.96                  )
    4.97 +                feedPath = feedPath(projectInfo.project)
    4.98                  navigationMenu = activeProjectNavMenu(
    4.99                      dao.listProjects(),
   4.100                      projectInfo,
   4.101 @@ -505,7 +516,7 @@
   4.102              // TODO: throw validator exception instead of using defaults
   4.103              val issue = Issue(
   4.104                  http.param("id")?.toIntOrNull() ?: -1,
   4.105 -                projectInfo.project
   4.106 +                project
   4.107              ).apply {
   4.108                  component = dao.findComponent(http.param("component")?.toIntOrNull() ?: -1)
   4.109                  category = IssueCategory.valueOf(http.param("category") ?: "")
   4.110 @@ -522,9 +533,9 @@
   4.111                  eta = http.param("eta")?.let { if (it.isBlank()) null else Date.valueOf(it) }
   4.112  
   4.113                  affectedVersions = http.paramArray("affected")
   4.114 -                    .mapNotNull { param -> param.toIntOrNull()?.let { Version(it, projectInfo.project.id) } }
   4.115 +                    .mapNotNull { param -> param.toIntOrNull()?.let { Version(it, project.id) } }
   4.116                  resolvedVersions = http.paramArray("resolved")
   4.117 -                    .mapNotNull { param -> param.toIntOrNull()?.let { Version(it, projectInfo.project.id) } }
   4.118 +                    .mapNotNull { param -> param.toIntOrNull()?.let { Version(it, project.id) } }
   4.119              }
   4.120  
   4.121              val openId = if (issue.id < 0) {
     5.1 --- a/src/main/kotlin/de/uapcore/lightpit/viewmodel/Feeds.kt	Thu May 13 18:01:56 2021 +0200
     5.2 +++ b/src/main/kotlin/de/uapcore/lightpit/viewmodel/Feeds.kt	Thu May 13 19:31:09 2021 +0200
     5.3 @@ -28,6 +28,11 @@
     5.4  import de.uapcore.lightpit.entities.Issue
     5.5  import de.uapcore.lightpit.entities.Project
     5.6  
     5.7 +class ProjectFeed(
     5.8 +    val projects: List<Project>
     5.9 +) : View()
    5.10 +
    5.11  class IssueFeed(
    5.12 -    val issues: Map<Project, List<Issue>>
    5.13 +    val project: Project,
    5.14 +    val issues: List<Issue>
    5.15  ) : View()
    5.16 \ No newline at end of file
     6.1 --- a/src/main/resources/localization/strings.properties	Thu May 13 18:01:56 2021 +0200
     6.2 +++ b/src/main/resources/localization/strings.properties	Thu May 13 19:31:09 2021 +0200
     6.3 @@ -54,6 +54,11 @@
     6.4  error.message = Server Message
     6.5  error.returnLink = Return to
     6.6  error.timestamp = Timestamp
     6.7 +feed.issues.description=Feed about recently updated issues.
     6.8 +feed.issues.title=LightPIT Issues
     6.9 +feed.projects.description=Feed about tracked projects.
    6.10 +feed.projects.source=Issues
    6.11 +feed.projects.title=LightPIT Projects
    6.12  issue.affected-versions=Affected Versions
    6.13  issue.assignee=Assignee
    6.14  issue.category.Bug=Bug
    6.15 @@ -126,6 +131,4 @@
    6.16  version.status.Released=Released
    6.17  version.status.Unreleased=Unreleased
    6.18  version.status=Status
    6.19 -version=Version
    6.20 -feed.issues.title=LightPIT - Issues
    6.21 -feed.issues.description=Feed about recently updated issues.
    6.22 \ No newline at end of file
    6.23 +version=Version
    6.24 \ No newline at end of file
     7.1 --- a/src/main/resources/localization/strings_de.properties	Thu May 13 18:01:56 2021 +0200
     7.2 +++ b/src/main/resources/localization/strings_de.properties	Thu May 13 19:31:09 2021 +0200
     7.3 @@ -54,6 +54,11 @@
     7.4  error.message = Server Nachricht
     7.5  error.returnLink = Kehre zurück zu
     7.6  error.timestamp = Zeitstempel
     7.7 +feed.issues.description=Feed \u00fcber k\u00fcrzlich aktualisierte Vorg\u00e4nge.
     7.8 +feed.issues.title=LightPIT Vorg\u00e4nge
     7.9 +feed.projects.description=Feed \u00fcber verwaltete Projekte.
    7.10 +feed.projects.source=Vorg\u00e4nge
    7.11 +feed.projects.title=LightPIT Projekte
    7.12  issue.affected-versions=Betroffene Versionen
    7.13  issue.assignee=Zugewiesen
    7.14  issue.category.Bug=Fehler
    7.15 @@ -127,5 +132,3 @@
    7.16  version.status.Unreleased=Unver\u00f6ffentlicht
    7.17  version.status=Status 
    7.18  version=Version
    7.19 -feed.issues.title=LightPIT - Vorg\u00e4nge
    7.20 -feed.issues.description=Feed \u00fcber k\u00fcrzlich aktualisierte Vorg\u00e4nge.
     8.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     8.2 +++ b/src/main/webapp/WEB-INF/jsp/feed.jsp	Thu May 13 19:31:09 2021 +0200
     8.3 @@ -0,0 +1,36 @@
     8.4 +<%--
     8.5 +  ~ Copyright 2021 Mike Becker. All rights reserved.
     8.6 +  ~
     8.7 +  ~ Redistribution and use in source and binary forms, with or without
     8.8 +  ~ modification, are permitted provided that the following conditions are met:
     8.9 +  ~
    8.10 +  ~ 1. Redistributions of source code must retain the above copyright
    8.11 +  ~ notice, this list of conditions and the following disclaimer.
    8.12 +  ~
    8.13 +  ~ 2. Redistributions in binary form must reproduce the above copyright
    8.14 +  ~ notice, this list of conditions and the following disclaimer in the
    8.15 +  ~ documentation and/or other materials provided with the distribution.
    8.16 +  ~
    8.17 +  ~ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
    8.18 +  ~ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
    8.19 +  ~ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
    8.20 +  ~ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
    8.21 +  ~ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
    8.22 +  ~ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
    8.23 +  ~ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
    8.24 +  ~ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
    8.25 +  ~ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
    8.26 +  ~ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    8.27 +  --%>
    8.28 +<%@page contentType="application/rss+xml;charset=UTF-8" trimDirectiveWhitespaces="true" %>
    8.29 +<%@page import="de.uapcore.lightpit.Constants" %>
    8.30 +<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
    8.31 +<%@taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
    8.32 +<c:set scope="page" var="contentPage" value="${requestScope[Constants.REQ_ATTR_CONTENT_PAGE]}"/>
    8.33 +<c:set scope="request" var="baseHref" value="${requestScope[Constants.REQ_ATTR_BASE_HREF]}"/>
    8.34 +<fmt:setLocale scope="request" value="${pageContext.response.locale}"/>
    8.35 +<fmt:setBundle scope="request" basename="localization.strings"/>
    8.36 +<?xml version="1.0" encoding="utf-8"?>
    8.37 +<rss version="2.0">
    8.38 +    <c:import url="${contentPage}"/>
    8.39 +</rss>
     9.1 --- a/src/main/webapp/WEB-INF/jsp/issues-feed.jsp	Thu May 13 18:01:56 2021 +0200
     9.2 +++ b/src/main/webapp/WEB-INF/jsp/issues-feed.jsp	Thu May 13 19:31:09 2021 +0200
     9.3 @@ -22,37 +22,24 @@
     9.4    ~ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     9.5    ~ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     9.6    --%>
     9.7 -<%@ page contentType="application/rss+xml;charset=UTF-8" trimDirectiveWhitespaces="true" %>
     9.8 -<%@page import="de.uapcore.lightpit.Constants" %>
     9.9 +<%@page contentType="application/rss+xml;charset=UTF-8" pageEncoding="UTF-8" %>
    9.10  <%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
    9.11  <%@taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
    9.12  <jsp:useBean id="viewmodel" type="de.uapcore.lightpit.viewmodel.IssueFeed" scope="request"/>
    9.13 -<c:set scope="page" var="baseHref" value="${requestScope[Constants.REQ_ATTR_BASE_HREF]}"/>
    9.14 -<fmt:setLocale scope="request" value="${pageContext.response.locale}"/>
    9.15 -<fmt:setBundle scope="request" basename="localization.strings"/>
    9.16 -<?xml version="1.0" encoding="utf-8"?>
    9.17 -<rss version="2.0">
    9.18 -    <c:forEach items="${viewmodel.issues}" var="feed">
    9.19 -        <c:set var="project" value="${feed.key}"/>
    9.20 -        <c:set var="issues" value="${feed.value}"/>
    9.21 -        <channel>
    9.22 -            <title>
    9.23 -                <fmt:message key="feed.issues.title"/> - <c:out value="${project.name}"/>
    9.24 -            </title>
    9.25 -            <link>${baseHref}projects/${project.node}/</link>
    9.26 -            <description><fmt:message key="feed.issues.description"/></description>
    9.27 -            <language>${pageContext.response.locale.language}</language>
    9.28 +<channel>
    9.29 +    <title><c:out value="${viewmodel.project.name}"/> | <fmt:message key="feed.issues.title"/></title>
    9.30 +    <description><fmt:message key="feed.issues.description"/></description>
    9.31 +    <link>${baseHref}projects/${viewmodel.project.node}</link>
    9.32 +    <language>${pageContext.response.locale.language}</language>
    9.33  
    9.34 -            <c:forEach items="${issues}" var="issue">
    9.35 -                <item>
    9.36 -                    <title><c:if test="${not empty issue.component}"><c:out value="${issue.component.name}"/> - </c:if><c:out value="${issue.subject}"/></title>
    9.37 -                    <description><c:out value="${issue.description}"/></description>
    9.38 -                    <category><fmt:message key="issue.category.${issue.category}"/></category>
    9.39 -                    <link>${baseHref}projects/${issue.project.node}/issues/-/${empty issue.component ? '-' : issue.component.node}/${issue.id}</link>
    9.40 -                    <guid isPermaLink="true">${baseHref}projects/${issue.project.node}/issues/-/-/${issue.id}</guid>
    9.41 -                    <pubDate><fmt:formatDate value="${issue.updated}" pattern="EEE, dd MMM yyyy HH:mm:ss zzz" /></pubDate>
    9.42 -                </item>
    9.43 -            </c:forEach>
    9.44 -        </channel>
    9.45 +    <c:forEach items="${viewmodel.issues}" var="issue">
    9.46 +        <item>
    9.47 +            <title><c:if test="${not empty issue.component}"><c:out value="${issue.component.name}"/> - </c:if><c:out value="${issue.subject}"/></title>
    9.48 +            <description><c:out value="${issue.description}"/></description>
    9.49 +            <category><fmt:message key="issue.category.${issue.category}"/></category>
    9.50 +            <link>${baseHref}projects/${issue.project.node}/issues/-/${empty issue.component ? '-' : issue.component.node}/${issue.id}</link>
    9.51 +            <guid isPermaLink="true">${baseHref}projects/${issue.project.node}/issues/-/-/${issue.id}</guid>
    9.52 +            <pubDate><fmt:formatDate value="${issue.updated}" pattern="EEE, dd MMM yyyy HH:mm:ss zzz" /></pubDate>
    9.53 +        </item>
    9.54      </c:forEach>
    9.55 -</rss>
    9.56 \ No newline at end of file
    9.57 +</channel>
    10.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    10.2 +++ b/src/main/webapp/WEB-INF/jsp/project-feed.jsp	Thu May 13 19:31:09 2021 +0200
    10.3 @@ -0,0 +1,43 @@
    10.4 +<%--
    10.5 +  ~ Copyright 2021 Mike Becker. All rights reserved.
    10.6 +  ~
    10.7 +  ~ Redistribution and use in source and binary forms, with or without
    10.8 +  ~ modification, are permitted provided that the following conditions are met:
    10.9 +  ~
   10.10 +  ~ 1. Redistributions of source code must retain the above copyright
   10.11 +  ~ notice, this list of conditions and the following disclaimer.
   10.12 +  ~
   10.13 +  ~ 2. Redistributions in binary form must reproduce the above copyright
   10.14 +  ~ notice, this list of conditions and the following disclaimer in the
   10.15 +  ~ documentation and/or other materials provided with the distribution.
   10.16 +  ~
   10.17 +  ~ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
   10.18 +  ~ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   10.19 +  ~ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   10.20 +  ~ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
   10.21 +  ~ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
   10.22 +  ~ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
   10.23 +  ~ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
   10.24 +  ~ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
   10.25 +  ~ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
   10.26 +  ~ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   10.27 +  --%>
   10.28 +<%@page contentType="application/rss+xml;charset=UTF-8" pageEncoding="UTF-8" %>
   10.29 +<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
   10.30 +<%@taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
   10.31 +<jsp:useBean id="viewmodel" type="de.uapcore.lightpit.viewmodel.ProjectFeed" scope="request"/>
   10.32 +<channel>
   10.33 +    <title><fmt:message key="feed.projects.title"/></title>
   10.34 +    <description><fmt:message key="feed.projects.description"/></description>
   10.35 +    <link>${baseHref}projects/</link>
   10.36 +    <language>${pageContext.response.locale.language}</language>
   10.37 +
   10.38 +    <c:forEach items="${viewmodel.projects}" var="project">
   10.39 +        <item>
   10.40 +            <title><c:out value="${project.name}"/></title>
   10.41 +            <description><c:out value="${project.description}"/></description>
   10.42 +            <link>${baseHref}projects/${project.node}</link>
   10.43 +            <source url="${baseHref}feed/${project.node}/issues.rss"><fmt:message key="feed.projects.source"/></source>
   10.44 +        </item>
   10.45 +    </c:forEach>
   10.46 +</channel>
    11.1 --- a/src/main/webapp/WEB-INF/jsp/site.jsp	Thu May 13 18:01:56 2021 +0200
    11.2 +++ b/src/main/webapp/WEB-INF/jsp/site.jsp	Thu May 13 19:31:09 2021 +0200
    11.3 @@ -33,6 +33,9 @@
    11.4  <%-- Make the base href easily available at request scope --%>
    11.5  <c:set scope="page" var="baseHref" value="${requestScope[Constants.REQ_ATTR_BASE_HREF]}"/>
    11.6  
    11.7 +<%-- The feed URL for this page. --%>
    11.8 +<c:set scope="page" var="feedHref" value="${requestScope[Constants.REQ_ATTR_FEED_HREF]}"/>
    11.9 +
   11.10  <%-- Define an alias for the request path --%>
   11.11  <c:set scope="page" var="requestPath" value="${requestScope[Constants.REQ_ATTR_PATH]}"/>
   11.12  
   11.13 @@ -62,8 +65,7 @@
   11.14          <meta http-equiv="refresh" content="0; URL=${redirectLocation}">
   11.15      </c:if>
   11.16      <link rel="stylesheet" href="lightpit.css" type="text/css">
   11.17 -    <link rel="alternate" type="application/rss+xml"
   11.18 -          title="RSS" href="${baseHref}feed/issues.rss" />
   11.19 +    <link rel="alternate" type="application/rss+xml" title="RSS" href="${feedHref}" />
   11.20      <c:if test="${not empty extraCss}">
   11.21          <c:forEach items="${extraCss}" var="cssFile">
   11.22          <link rel="stylesheet" href="${cssFile}" type="text/css">

mercurial