2019-04-20 06:15:19 +02:00
// Copyright 2019 The Gitea Authors. All rights reserved.
2022-11-27 13:20:29 -05:00
// SPDX-License-Identifier: MIT
2019-04-20 06:15:19 +02:00
2016-12-30 16:49:54 -02:00
package user
import (
2022-05-25 01:51:53 +01:00
goctx "context"
2017-01-12 02:27:09 -02:00
"errors"
2016-12-30 16:49:54 -02:00
"fmt"
2020-04-24 04:57:38 +01:00
"net/http"
2021-10-16 11:34:07 +08:00
"net/url"
2017-01-02 16:31:50 -02:00
"strings"
2016-12-30 16:49:54 -02:00
2022-08-25 10:31:57 +08:00
activities_model "code.gitea.io/gitea/models/activities"
2022-09-29 21:09:14 +02:00
"code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues"
repo_model "code.gitea.io/gitea/models/repo"
2016-12-30 16:49:54 -02:00
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
2022-05-25 01:51:53 +01:00
"code.gitea.io/gitea/modules/log"
2017-01-12 02:27:09 -02:00
"code.gitea.io/gitea/modules/setting"
2022-05-03 21:38:34 +00:00
"code.gitea.io/gitea/modules/structs"
2022-09-29 21:09:14 +02:00
"code.gitea.io/gitea/modules/util"
issue_service "code.gitea.io/gitea/services/issue"
pull_service "code.gitea.io/gitea/services/pull"
2016-12-30 16:49:54 -02:00
)
const (
2022-09-29 21:09:14 +02:00
tplNotification base . TplName = "user/notification/notification"
tplNotificationDiv base . TplName = "user/notification/notification_div"
tplNotificationSubscriptions base . TplName = "user/notification/notification_subscriptions"
2016-12-30 16:49:54 -02:00
)
// GetNotificationCount is the middleware that sets the notification count in the context
2022-11-19 09:12:33 +01:00
func GetNotificationCount ( ctx * context . Context ) {
if strings . HasPrefix ( ctx . Req . URL . Path , "/api" ) {
2017-01-02 16:31:50 -02:00
return
}
2022-11-19 09:12:33 +01:00
if ! ctx . IsSigned {
2016-12-30 16:49:54 -02:00
return
}
2022-11-19 09:12:33 +01:00
ctx . Data [ "NotificationUnreadCount" ] = func ( ) int64 {
count , err := activities_model . GetNotificationCount ( ctx , ctx . Doer , activities_model . NotificationStatusUnread )
2020-04-24 04:57:38 +01:00
if err != nil {
2022-05-25 01:51:53 +01:00
if err != goctx . Canceled {
2022-11-19 09:12:33 +01:00
log . Error ( "Unable to GetNotificationCount for user:%-v: %v" , ctx . Doer , err )
2022-05-25 01:51:53 +01:00
}
2020-04-24 04:57:38 +01:00
return - 1
}
2016-12-30 16:49:54 -02:00
2020-04-24 04:57:38 +01:00
return count
}
2016-12-30 16:49:54 -02:00
}
// Notifications is the notifications page
2022-11-19 09:12:33 +01:00
func Notifications ( ctx * context . Context ) {
getNotifications ( ctx )
if ctx . Written ( ) {
2020-04-24 04:57:38 +01:00
return
}
2022-11-19 09:12:33 +01:00
if ctx . FormBool ( "div-only" ) {
ctx . Data [ "SequenceNumber" ] = ctx . FormString ( "sequence-number" )
ctx . HTML ( http . StatusOK , tplNotificationDiv )
2020-04-24 04:57:38 +01:00
return
}
2022-11-19 09:12:33 +01:00
ctx . HTML ( http . StatusOK , tplNotification )
2020-04-24 04:57:38 +01:00
}
2022-11-19 09:12:33 +01:00
func getNotifications ( ctx * context . Context ) {
2017-01-03 17:09:36 -02:00
var (
2022-11-19 09:12:33 +01:00
keyword = ctx . FormTrim ( "q" )
2022-08-25 10:31:57 +08:00
status activities_model . NotificationStatus
2022-11-19 09:12:33 +01:00
page = ctx . FormInt ( "page" )
perPage = ctx . FormInt ( "perPage" )
2017-01-03 17:09:36 -02:00
)
if page < 1 {
page = 1
}
if perPage < 1 {
perPage = 20
}
switch keyword {
2016-12-30 16:49:54 -02:00
case "read" :
2022-08-25 10:31:57 +08:00
status = activities_model . NotificationStatusRead
2016-12-30 16:49:54 -02:00
default :
2022-08-25 10:31:57 +08:00
status = activities_model . NotificationStatusUnread
2016-12-30 16:49:54 -02:00
}
2022-11-19 09:12:33 +01:00
total , err := activities_model . GetNotificationCount ( ctx , ctx . Doer , status )
2020-01-18 02:31:26 +08:00
if err != nil {
2022-11-19 09:12:33 +01:00
ctx . ServerError ( "ErrGetNotificationCount" , err )
2020-01-18 02:31:26 +08:00
return
}
// redirect to last page if request page is more than total pages
pager := context . NewPagination ( int ( total ) , perPage , page , 5 )
if pager . Paginater . Current ( ) < page {
2022-11-19 09:12:33 +01:00
ctx . Redirect ( fmt . Sprintf ( "%s/notifications?q=%s&page=%d" , setting . AppSubURL , url . QueryEscape ( ctx . FormString ( "q" ) ) , pager . Paginater . Current ( ) ) )
2020-01-18 02:31:26 +08:00
return
}
2022-08-25 10:31:57 +08:00
statuses := [ ] activities_model . NotificationStatus { status , activities_model . NotificationStatusPinned }
2022-11-19 09:12:33 +01:00
notifications , err := activities_model . NotificationsForUser ( ctx , ctx . Doer , statuses , page , perPage )
2016-12-30 16:49:54 -02:00
if err != nil {
2022-11-19 09:12:33 +01:00
ctx . ServerError ( "ErrNotificationsForUser" , err )
2016-12-30 16:49:54 -02:00
return
}
2020-03-29 20:51:14 +01:00
failCount := 0
2022-11-19 09:12:33 +01:00
repos , failures , err := notifications . LoadRepos ( ctx )
2019-11-12 16:33:34 +08:00
if err != nil {
2022-11-19 09:12:33 +01:00
ctx . ServerError ( "LoadRepos" , err )
2019-11-12 16:33:34 +08:00
return
}
2020-03-29 20:51:14 +01:00
notifications = notifications . Without ( failures )
2022-11-19 09:12:33 +01:00
if err := repos . LoadAttributes ( ) ; err != nil { // TODO
ctx . ServerError ( "LoadAttributes" , err )
2019-11-12 16:33:34 +08:00
return
}
2020-03-29 20:51:14 +01:00
failCount += len ( failures )
2019-11-12 16:33:34 +08:00
2022-11-19 09:12:33 +01:00
failures , err = notifications . LoadIssues ( ctx )
2020-03-29 20:51:14 +01:00
if err != nil {
2022-11-19 09:12:33 +01:00
ctx . ServerError ( "LoadIssues" , err )
2019-11-12 16:33:34 +08:00
return
}
2020-03-29 20:51:14 +01:00
notifications = notifications . Without ( failures )
failCount += len ( failures )
2022-11-19 09:12:33 +01:00
failures , err = notifications . LoadComments ( ctx )
2020-03-29 20:51:14 +01:00
if err != nil {
2022-11-19 09:12:33 +01:00
ctx . ServerError ( "LoadComments" , err )
2019-11-12 16:33:34 +08:00
return
}
2020-03-29 20:51:14 +01:00
notifications = notifications . Without ( failures )
failCount += len ( failures )
if failCount > 0 {
2022-11-19 09:12:33 +01:00
ctx . Flash . Error ( fmt . Sprintf ( "ERROR: %d notifications were removed due to missing parts - check the logs" , failCount ) )
2020-03-29 20:51:14 +01:00
}
2019-11-12 16:33:34 +08:00
2022-11-19 09:12:33 +01:00
ctx . Data [ "Title" ] = ctx . Tr ( "notifications" )
ctx . Data [ "Keyword" ] = keyword
ctx . Data [ "Status" ] = status
ctx . Data [ "Notifications" ] = notifications
2019-04-20 06:15:19 +02:00
2022-11-19 09:12:33 +01:00
pager . SetDefaultParams ( ctx )
ctx . Data [ "Page" ] = pager
2016-12-30 16:49:54 -02:00
}
2017-01-12 02:27:09 -02:00
// NotificationStatusPost is a route for changing the status of a notification
2022-11-19 09:12:33 +01:00
func NotificationStatusPost ( ctx * context . Context ) {
2017-01-12 02:27:09 -02:00
var (
2022-11-19 09:12:33 +01:00
notificationID = ctx . FormInt64 ( "notification_id" )
statusStr = ctx . FormString ( "status" )
2022-08-25 10:31:57 +08:00
status activities_model . NotificationStatus
2017-01-12 02:27:09 -02:00
)
switch statusStr {
case "read" :
2022-08-25 10:31:57 +08:00
status = activities_model . NotificationStatusRead
2017-01-12 02:27:09 -02:00
case "unread" :
2022-08-25 10:31:57 +08:00
status = activities_model . NotificationStatusUnread
2017-01-12 02:27:09 -02:00
case "pinned" :
2022-08-25 10:31:57 +08:00
status = activities_model . NotificationStatusPinned
2017-01-12 02:27:09 -02:00
default :
2022-11-19 09:12:33 +01:00
ctx . ServerError ( "InvalidNotificationStatus" , errors . New ( "Invalid notification status" ) )
2017-01-12 02:27:09 -02:00
return
}
2022-11-19 09:12:33 +01:00
if _ , err := activities_model . SetNotificationStatus ( ctx , notificationID , ctx . Doer , status ) ; err != nil {
ctx . ServerError ( "SetNotificationStatus" , err )
2017-01-12 02:27:09 -02:00
return
}
2022-11-19 09:12:33 +01:00
if ! ctx . FormBool ( "noredirect" ) {
url := fmt . Sprintf ( "%s/notifications?page=%s" , setting . AppSubURL , url . QueryEscape ( ctx . FormString ( "page" ) ) )
ctx . Redirect ( url , http . StatusSeeOther )
2020-04-24 04:57:38 +01:00
}
2022-11-19 09:12:33 +01:00
getNotifications ( ctx )
if ctx . Written ( ) {
2020-04-24 04:57:38 +01:00
return
}
2022-11-19 09:12:33 +01:00
ctx . Data [ "Link" ] = setting . AppURL + "notifications"
ctx . Data [ "SequenceNumber" ] = ctx . Req . PostFormValue ( "sequence-number" )
2020-04-24 04:57:38 +01:00
2022-11-19 09:12:33 +01:00
ctx . HTML ( http . StatusOK , tplNotificationDiv )
2017-01-12 02:27:09 -02:00
}
2017-12-07 12:52:57 +07:00
// NotificationPurgePost is a route for 'purging' the list of notifications - marking all unread as read
2022-11-19 09:12:33 +01:00
func NotificationPurgePost ( ctx * context . Context ) {
err := activities_model . UpdateNotificationStatuses ( ctx , ctx . Doer , activities_model . NotificationStatusUnread , activities_model . NotificationStatusRead )
2017-12-07 12:52:57 +07:00
if err != nil {
2022-11-19 09:12:33 +01:00
ctx . ServerError ( "UpdateNotificationStatuses" , err )
2017-12-07 12:52:57 +07:00
return
}
2022-11-19 09:12:33 +01:00
ctx . Redirect ( setting . AppSubURL + "/notifications" , http . StatusSeeOther )
2017-12-07 12:52:57 +07:00
}
2022-04-08 02:59:56 +08:00
2022-09-29 21:09:14 +02:00
// NotificationSubscriptions returns the list of subscribed issues
2022-11-19 09:12:33 +01:00
func NotificationSubscriptions ( ctx * context . Context ) {
page := ctx . FormInt ( "page" )
2022-09-29 21:09:14 +02:00
if page < 1 {
page = 1
}
2022-11-19 09:12:33 +01:00
sortType := ctx . FormString ( "sort" )
ctx . Data [ "SortType" ] = sortType
2022-09-29 21:09:14 +02:00
2022-11-19 09:12:33 +01:00
state := ctx . FormString ( "state" )
2022-09-29 21:09:14 +02:00
if ! util . IsStringInSlice ( state , [ ] string { "all" , "open" , "closed" } , true ) {
state = "all"
}
2022-11-19 09:12:33 +01:00
ctx . Data [ "State" ] = state
2022-09-29 21:09:14 +02:00
var showClosed util . OptionalBool
switch state {
case "all" :
showClosed = util . OptionalBoolNone
case "closed" :
showClosed = util . OptionalBoolTrue
case "open" :
showClosed = util . OptionalBoolFalse
}
var issueTypeBool util . OptionalBool
2022-11-19 09:12:33 +01:00
issueType := ctx . FormString ( "issueType" )
2022-09-29 21:09:14 +02:00
switch issueType {
case "issues" :
issueTypeBool = util . OptionalBoolFalse
case "pulls" :
issueTypeBool = util . OptionalBoolTrue
default :
issueTypeBool = util . OptionalBoolNone
}
2022-11-19 09:12:33 +01:00
ctx . Data [ "IssueType" ] = issueType
2022-09-29 21:09:14 +02:00
var labelIDs [ ] int64
2022-11-19 09:12:33 +01:00
selectedLabels := ctx . FormString ( "labels" )
ctx . Data [ "Labels" ] = selectedLabels
2022-09-29 21:09:14 +02:00
if len ( selectedLabels ) > 0 && selectedLabels != "0" {
var err error
labelIDs , err = base . StringsToInt64s ( strings . Split ( selectedLabels , "," ) )
if err != nil {
2022-11-19 09:12:33 +01:00
ctx . ServerError ( "StringsToInt64s" , err )
2022-09-29 21:09:14 +02:00
return
}
}
2022-11-19 09:12:33 +01:00
count , err := issues_model . CountIssues ( ctx , & issues_model . IssuesOptions {
SubscriberID : ctx . Doer . ID ,
2022-09-29 21:09:14 +02:00
IsClosed : showClosed ,
IsPull : issueTypeBool ,
LabelIDs : labelIDs ,
} )
if err != nil {
2022-11-19 09:12:33 +01:00
ctx . ServerError ( "CountIssues" , err )
2022-09-29 21:09:14 +02:00
return
}
2022-11-19 09:12:33 +01:00
issues , err := issues_model . Issues ( ctx , & issues_model . IssuesOptions {
2022-09-29 21:09:14 +02:00
ListOptions : db . ListOptions {
PageSize : setting . UI . IssuePagingNum ,
Page : page ,
} ,
2022-11-19 09:12:33 +01:00
SubscriberID : ctx . Doer . ID ,
2022-09-29 21:09:14 +02:00
SortType : sortType ,
IsClosed : showClosed ,
IsPull : issueTypeBool ,
LabelIDs : labelIDs ,
} )
if err != nil {
2022-11-19 09:12:33 +01:00
ctx . ServerError ( "Issues" , err )
2022-09-29 21:09:14 +02:00
return
}
2022-11-19 09:12:33 +01:00
commitStatuses , lastStatus , err := pull_service . GetIssuesAllCommitStatus ( ctx , issues )
2022-09-29 21:09:14 +02:00
if err != nil {
2022-11-19 09:12:33 +01:00
ctx . ServerError ( "GetIssuesAllCommitStatus" , err )
2022-09-29 21:09:14 +02:00
return
}
2022-11-19 09:12:33 +01:00
ctx . Data [ "CommitLastStatus" ] = lastStatus
ctx . Data [ "CommitStatuses" ] = commitStatuses
ctx . Data [ "Issues" ] = issues
2022-09-29 21:09:14 +02:00
2022-11-19 09:12:33 +01:00
ctx . Data [ "IssueRefEndNames" ] , ctx . Data [ "IssueRefURLs" ] = issue_service . GetRefEndNamesAndURLs ( issues , "" )
2022-09-29 21:09:14 +02:00
2022-11-19 09:12:33 +01:00
commitStatus , err := pull_service . GetIssuesLastCommitStatus ( ctx , issues )
2022-09-29 21:09:14 +02:00
if err != nil {
2022-11-19 09:12:33 +01:00
ctx . ServerError ( "GetIssuesLastCommitStatus" , err )
2022-09-29 21:09:14 +02:00
return
}
2022-11-19 09:12:33 +01:00
ctx . Data [ "CommitStatus" ] = commitStatus
2022-09-29 21:09:14 +02:00
issueList := issues_model . IssueList ( issues )
2022-11-19 09:12:33 +01:00
approvalCounts , err := issueList . GetApprovalCounts ( ctx )
2022-09-29 21:09:14 +02:00
if err != nil {
2022-11-19 09:12:33 +01:00
ctx . ServerError ( "ApprovalCounts" , err )
2022-09-29 21:09:14 +02:00
return
}
2022-11-19 09:12:33 +01:00
ctx . Data [ "ApprovalCounts" ] = func ( issueID int64 , typ string ) int64 {
2022-09-29 21:09:14 +02:00
counts , ok := approvalCounts [ issueID ]
if ! ok || len ( counts ) == 0 {
return 0
}
reviewTyp := issues_model . ReviewTypeApprove
if typ == "reject" {
reviewTyp = issues_model . ReviewTypeReject
} else if typ == "waiting" {
reviewTyp = issues_model . ReviewTypeRequest
}
for _ , count := range counts {
if count . Type == reviewTyp {
return count . Count
}
}
return 0
}
2022-11-19 09:12:33 +01:00
ctx . Data [ "Status" ] = 1
ctx . Data [ "Title" ] = ctx . Tr ( "notification.subscriptions" )
2022-09-29 21:09:14 +02:00
// redirect to last page if request page is more than total pages
pager := context . NewPagination ( int ( count ) , setting . UI . IssuePagingNum , page , 5 )
if pager . Paginater . Current ( ) < page {
2022-11-19 09:12:33 +01:00
ctx . Redirect ( fmt . Sprintf ( "/notifications/subscriptions?page=%d" , pager . Paginater . Current ( ) ) )
2022-09-29 21:09:14 +02:00
return
}
2022-11-19 09:12:33 +01:00
pager . AddParam ( ctx , "sort" , "SortType" )
pager . AddParam ( ctx , "state" , "State" )
ctx . Data [ "Page" ] = pager
2022-09-29 21:09:14 +02:00
2022-11-19 09:12:33 +01:00
ctx . HTML ( http . StatusOK , tplNotificationSubscriptions )
2022-09-29 21:09:14 +02:00
}
// NotificationWatching returns the list of watching repos
2022-11-19 09:12:33 +01:00
func NotificationWatching ( ctx * context . Context ) {
page := ctx . FormInt ( "page" )
2022-09-29 21:09:14 +02:00
if page < 1 {
page = 1
}
var orderBy db . SearchOrderBy
2022-11-19 09:12:33 +01:00
ctx . Data [ "SortType" ] = ctx . FormString ( "sort" )
switch ctx . FormString ( "sort" ) {
2022-09-29 21:09:14 +02:00
case "newest" :
orderBy = db . SearchOrderByNewest
case "oldest" :
orderBy = db . SearchOrderByOldest
case "recentupdate" :
orderBy = db . SearchOrderByRecentUpdated
case "leastupdate" :
orderBy = db . SearchOrderByLeastUpdated
case "reversealphabetically" :
orderBy = db . SearchOrderByAlphabeticallyReverse
case "alphabetically" :
orderBy = db . SearchOrderByAlphabetically
case "moststars" :
orderBy = db . SearchOrderByStarsReverse
case "feweststars" :
orderBy = db . SearchOrderByStars
case "mostforks" :
orderBy = db . SearchOrderByForksReverse
case "fewestforks" :
orderBy = db . SearchOrderByForks
default :
2022-11-19 09:12:33 +01:00
ctx . Data [ "SortType" ] = "recentupdate"
2022-09-29 21:09:14 +02:00
orderBy = db . SearchOrderByRecentUpdated
}
2022-11-19 09:12:33 +01:00
repos , count , err := repo_model . SearchRepository ( ctx , & repo_model . SearchRepoOptions {
2022-09-29 21:09:14 +02:00
ListOptions : db . ListOptions {
PageSize : setting . UI . User . RepoPagingNum ,
Page : page ,
} ,
2022-11-19 09:12:33 +01:00
Actor : ctx . Doer ,
Keyword : ctx . FormTrim ( "q" ) ,
2022-09-29 21:09:14 +02:00
OrderBy : orderBy ,
2022-11-19 09:12:33 +01:00
Private : ctx . IsSigned ,
WatchedByID : ctx . Doer . ID ,
2022-09-29 21:09:14 +02:00
Collaborate : util . OptionalBoolFalse ,
2022-11-19 09:12:33 +01:00
TopicOnly : ctx . FormBool ( "topic" ) ,
2022-09-29 21:09:14 +02:00
IncludeDescription : setting . UI . SearchRepoDescription ,
} )
if err != nil {
2022-11-19 09:12:33 +01:00
ctx . ServerError ( "SearchRepository" , err )
2022-09-29 21:09:14 +02:00
return
}
total := int ( count )
2022-11-19 09:12:33 +01:00
ctx . Data [ "Total" ] = total
ctx . Data [ "Repos" ] = repos
2022-09-29 21:09:14 +02:00
// redirect to last page if request page is more than total pages
pager := context . NewPagination ( total , setting . UI . User . RepoPagingNum , page , 5 )
2022-11-19 09:12:33 +01:00
pager . SetDefaultParams ( ctx )
ctx . Data [ "Page" ] = pager
2022-09-29 21:09:14 +02:00
2022-11-19 09:12:33 +01:00
ctx . Data [ "Status" ] = 2
ctx . Data [ "Title" ] = ctx . Tr ( "notification.watching" )
2022-09-29 21:09:14 +02:00
2022-11-19 09:12:33 +01:00
ctx . HTML ( http . StatusOK , tplNotificationSubscriptions )
2022-09-29 21:09:14 +02:00
}
2022-04-08 02:59:56 +08:00
// NewAvailable returns the notification counts
2022-05-03 21:38:34 +00:00
func NewAvailable ( ctx * context . Context ) {
2022-08-25 10:31:57 +08:00
ctx . JSON ( http . StatusOK , structs . NotificationCount { New : activities_model . CountUnread ( ctx , ctx . Doer . ID ) } )
2022-04-08 02:59:56 +08:00
}