2021-10-26 00:42:57 +02:00
|
|
|
package graph
|
|
|
|
|
|
|
|
// This file will be automatically regenerated based on the schema, any resolver implementations
|
|
|
|
// will be copied through when generating and any unknown code will be moved to the end.
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"database/sql"
|
2021-11-02 20:45:05 +01:00
|
|
|
"encoding/json"
|
|
|
|
"errors"
|
2021-10-26 00:42:57 +02:00
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/google/uuid"
|
|
|
|
"github.com/jordanknott/taskcafe/internal/db"
|
|
|
|
"github.com/jordanknott/taskcafe/internal/logger"
|
2021-11-02 20:45:05 +01:00
|
|
|
"github.com/jordanknott/taskcafe/internal/utils"
|
2021-10-31 00:20:41 +02:00
|
|
|
log "github.com/sirupsen/logrus"
|
2021-10-26 00:42:57 +02:00
|
|
|
)
|
|
|
|
|
2021-11-02 20:45:05 +01:00
|
|
|
func (r *mutationResolver) NotificationToggleRead(ctx context.Context, input NotificationToggleReadInput) (*Notified, error) {
|
|
|
|
userID, ok := GetUserID(ctx)
|
|
|
|
if !ok {
|
|
|
|
return &Notified{}, errors.New("unknown user ID")
|
|
|
|
}
|
|
|
|
notified, err := r.Repository.GetNotifiedByID(ctx, input.NotifiedID)
|
|
|
|
if err != nil {
|
|
|
|
log.WithError(err).Error("error while getting notified by ID")
|
|
|
|
return &Notified{}, err
|
|
|
|
}
|
|
|
|
readAt := time.Now().UTC()
|
|
|
|
read := true
|
|
|
|
if notified.Read {
|
|
|
|
read = false
|
|
|
|
err = r.Repository.MarkNotificationAsRead(ctx, db.MarkNotificationAsReadParams{
|
|
|
|
UserID: userID,
|
|
|
|
NotifiedID: input.NotifiedID,
|
|
|
|
Read: false,
|
|
|
|
ReadAt: sql.NullTime{
|
|
|
|
Valid: false,
|
|
|
|
Time: time.Time{},
|
|
|
|
},
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
err = r.Repository.MarkNotificationAsRead(ctx, db.MarkNotificationAsReadParams{
|
|
|
|
UserID: userID,
|
|
|
|
Read: true,
|
|
|
|
NotifiedID: input.NotifiedID,
|
|
|
|
ReadAt: sql.NullTime{
|
|
|
|
Valid: true,
|
|
|
|
Time: readAt,
|
|
|
|
},
|
|
|
|
})
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
log.WithError(err).Error("error while marking notification as read")
|
|
|
|
return &Notified{}, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return &Notified{
|
|
|
|
ID: notified.NotifiedID,
|
|
|
|
Read: read,
|
|
|
|
ReadAt: &readAt,
|
|
|
|
Notification: &db.Notification{
|
|
|
|
NotificationID: notified.NotificationID,
|
|
|
|
CausedBy: notified.CausedBy,
|
|
|
|
ActionType: notified.ActionType,
|
|
|
|
Data: notified.Data,
|
|
|
|
CreatedOn: notified.CreatedOn,
|
|
|
|
},
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2021-10-26 00:42:57 +02:00
|
|
|
func (r *notificationResolver) ID(ctx context.Context, obj *db.Notification) (uuid.UUID, error) {
|
|
|
|
return obj.NotificationID, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *notificationResolver) ActionType(ctx context.Context, obj *db.Notification) (ActionType, error) {
|
2021-11-02 20:45:05 +01:00
|
|
|
actionType := ActionType(obj.ActionType)
|
|
|
|
if !actionType.IsValid() {
|
|
|
|
log.WithField("ActionType", obj.ActionType).Error("ActionType is invalid")
|
|
|
|
return actionType, errors.New("ActionType is invalid")
|
|
|
|
}
|
|
|
|
return ActionType(obj.ActionType), nil // TODO
|
2021-10-26 00:42:57 +02:00
|
|
|
}
|
|
|
|
|
2021-10-26 21:42:04 +02:00
|
|
|
func (r *notificationResolver) CausedBy(ctx context.Context, obj *db.Notification) (*NotificationCausedBy, error) {
|
2021-10-31 00:20:41 +02:00
|
|
|
user, err := r.Repository.GetUserAccountByID(ctx, obj.CausedBy)
|
|
|
|
if err != nil {
|
|
|
|
if err == sql.ErrNoRows {
|
2021-11-02 20:45:05 +01:00
|
|
|
return nil, nil
|
2021-10-31 00:20:41 +02:00
|
|
|
}
|
|
|
|
log.WithError(err).Error("error while resolving Notification.CausedBy")
|
|
|
|
return &NotificationCausedBy{}, err
|
|
|
|
}
|
|
|
|
return &NotificationCausedBy{
|
|
|
|
Fullname: user.FullName,
|
|
|
|
Username: user.Username,
|
|
|
|
ID: obj.CausedBy,
|
|
|
|
}, nil
|
2021-10-26 21:42:04 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func (r *notificationResolver) Data(ctx context.Context, obj *db.Notification) ([]NotificationData, error) {
|
2021-11-02 20:45:05 +01:00
|
|
|
notifiedData := NotifiedData{}
|
|
|
|
err := json.Unmarshal(obj.Data, ¬ifiedData)
|
|
|
|
if err != nil {
|
|
|
|
return []NotificationData{}, err
|
|
|
|
}
|
|
|
|
data := []NotificationData{}
|
|
|
|
for key, value := range notifiedData.Data {
|
|
|
|
data = append(data, NotificationData{Key: key, Value: value})
|
|
|
|
}
|
|
|
|
return data, nil
|
2021-10-26 00:42:57 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func (r *notificationResolver) CreatedAt(ctx context.Context, obj *db.Notification) (*time.Time, error) {
|
2021-10-27 04:29:49 +02:00
|
|
|
return &obj.CreatedOn, nil
|
2021-10-26 00:42:57 +02:00
|
|
|
}
|
|
|
|
|
2021-10-26 21:42:04 +02:00
|
|
|
func (r *queryResolver) Notifications(ctx context.Context) ([]Notified, error) {
|
2021-10-26 00:42:57 +02:00
|
|
|
userID, ok := GetUserID(ctx)
|
|
|
|
logger.New(ctx).Info("fetching notifications")
|
|
|
|
if !ok {
|
2021-10-27 04:29:49 +02:00
|
|
|
return []Notified{}, nil
|
2021-10-26 00:42:57 +02:00
|
|
|
}
|
|
|
|
notifications, err := r.Repository.GetAllNotificationsForUserID(ctx, userID)
|
|
|
|
if err == sql.ErrNoRows {
|
2021-10-27 04:29:49 +02:00
|
|
|
return []Notified{}, nil
|
2021-10-26 00:42:57 +02:00
|
|
|
} else if err != nil {
|
2021-10-27 04:29:49 +02:00
|
|
|
return []Notified{}, err
|
2021-10-26 00:42:57 +02:00
|
|
|
}
|
2021-10-27 04:29:49 +02:00
|
|
|
userNotifications := []Notified{}
|
|
|
|
for _, notified := range notifications {
|
|
|
|
var readAt *time.Time
|
|
|
|
if notified.ReadAt.Valid {
|
|
|
|
readAt = ¬ified.ReadAt.Time
|
|
|
|
}
|
|
|
|
n := Notified{
|
|
|
|
ID: notified.NotifiedID,
|
|
|
|
Read: notified.Read,
|
|
|
|
ReadAt: readAt,
|
|
|
|
Notification: &db.Notification{
|
|
|
|
NotificationID: notified.NotificationID,
|
|
|
|
CausedBy: notified.CausedBy,
|
|
|
|
ActionType: notified.ActionType,
|
|
|
|
Data: notified.Data,
|
|
|
|
CreatedOn: notified.CreatedOn,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
userNotifications = append(userNotifications, n)
|
|
|
|
}
|
|
|
|
return userNotifications, nil
|
2021-10-26 00:42:57 +02:00
|
|
|
}
|
|
|
|
|
2021-11-02 20:45:05 +01:00
|
|
|
func (r *queryResolver) Notified(ctx context.Context, input NotifiedInput) (*NotifiedResult, error) {
|
|
|
|
userID, ok := GetUserID(ctx)
|
|
|
|
if !ok {
|
|
|
|
return &NotifiedResult{}, errors.New("userID is not found")
|
|
|
|
}
|
|
|
|
log.WithField("userID", userID).Info("fetching notified")
|
|
|
|
if input.Cursor != nil {
|
|
|
|
t, id, err := utils.DecodeCursor(*input.Cursor)
|
|
|
|
if err != nil {
|
|
|
|
log.WithError(err).Error("error decoding cursor")
|
|
|
|
return &NotifiedResult{}, err
|
|
|
|
}
|
2021-11-04 16:55:34 +01:00
|
|
|
enableRead := false
|
|
|
|
enableActionType := false
|
|
|
|
actionTypes := []string{}
|
|
|
|
switch input.Filter {
|
|
|
|
case NotificationFilterUnread:
|
|
|
|
enableRead = true
|
|
|
|
break
|
|
|
|
case NotificationFilterMentioned:
|
|
|
|
enableActionType = true
|
|
|
|
actionTypes = []string{"COMMENT_MENTIONED"}
|
|
|
|
break
|
|
|
|
case NotificationFilterAssigned:
|
|
|
|
enableActionType = true
|
|
|
|
actionTypes = []string{"TASK_ASSIGNED"}
|
|
|
|
break
|
|
|
|
}
|
2021-11-02 20:45:05 +01:00
|
|
|
n, err := r.Repository.GetNotificationsForUserIDCursor(ctx, db.GetNotificationsForUserIDCursorParams{
|
2021-11-04 16:55:34 +01:00
|
|
|
CreatedOn: t,
|
|
|
|
NotificationID: id,
|
|
|
|
LimitRows: int32(input.Limit + 1),
|
|
|
|
UserID: userID,
|
|
|
|
EnableUnread: enableRead,
|
|
|
|
EnableActionType: enableActionType,
|
|
|
|
ActionType: actionTypes,
|
2021-11-02 20:45:05 +01:00
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
log.WithError(err).Error("error decoding fetching notifications")
|
|
|
|
return &NotifiedResult{}, err
|
|
|
|
}
|
|
|
|
hasNextPage := false
|
|
|
|
log.WithFields(log.Fields{
|
|
|
|
"nLen": len(n),
|
|
|
|
"cursorTime": t,
|
|
|
|
"cursorId": id,
|
|
|
|
"limit": input.Limit,
|
|
|
|
}).Info("fetched notified")
|
2021-11-04 16:55:34 +01:00
|
|
|
var endCursor *db.GetNotificationsForUserIDCursorRow
|
|
|
|
if len(n) != 0 {
|
|
|
|
endCursor = &n[len(n)-1]
|
|
|
|
if len(n) == input.Limit+1 {
|
|
|
|
hasNextPage = true
|
|
|
|
n = n[:len(n)-1]
|
|
|
|
endCursor = &n[len(n)-1]
|
|
|
|
}
|
2021-11-02 20:45:05 +01:00
|
|
|
}
|
|
|
|
userNotifications := []Notified{}
|
|
|
|
for _, notified := range n {
|
|
|
|
var readAt *time.Time
|
|
|
|
if notified.ReadAt.Valid {
|
|
|
|
readAt = ¬ified.ReadAt.Time
|
|
|
|
}
|
|
|
|
n := Notified{
|
|
|
|
ID: notified.NotifiedID,
|
|
|
|
Read: notified.Read,
|
|
|
|
ReadAt: readAt,
|
|
|
|
Notification: &db.Notification{
|
|
|
|
NotificationID: notified.NotificationID,
|
|
|
|
CausedBy: notified.CausedBy,
|
|
|
|
ActionType: notified.ActionType,
|
|
|
|
Data: notified.Data,
|
|
|
|
CreatedOn: notified.CreatedOn,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
userNotifications = append(userNotifications, n)
|
|
|
|
}
|
2021-11-04 16:55:34 +01:00
|
|
|
var endCursorEncoded *string
|
|
|
|
if endCursor != nil {
|
|
|
|
eCur := utils.EncodeCursor(endCursor.CreatedOn, endCursor.NotificationID)
|
|
|
|
endCursorEncoded = &eCur
|
|
|
|
}
|
2021-11-02 20:45:05 +01:00
|
|
|
pageInfo := &PageInfo{
|
|
|
|
HasNextPage: hasNextPage,
|
2021-11-04 16:55:34 +01:00
|
|
|
EndCursor: endCursorEncoded,
|
2021-11-02 20:45:05 +01:00
|
|
|
}
|
|
|
|
log.WithField("pageInfo", pageInfo).Info("created page info")
|
|
|
|
return &NotifiedResult{
|
|
|
|
TotalCount: len(n) - 1,
|
|
|
|
PageInfo: pageInfo,
|
|
|
|
Notified: userNotifications,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
enableRead := false
|
|
|
|
enableActionType := false
|
|
|
|
actionTypes := []string{}
|
|
|
|
switch input.Filter {
|
|
|
|
case NotificationFilterUnread:
|
|
|
|
enableRead = true
|
|
|
|
break
|
|
|
|
case NotificationFilterMentioned:
|
|
|
|
enableActionType = true
|
|
|
|
actionTypes = []string{"COMMENT_MENTIONED"}
|
|
|
|
break
|
|
|
|
case NotificationFilterAssigned:
|
|
|
|
enableActionType = true
|
|
|
|
actionTypes = []string{"TASK_ASSIGNED"}
|
|
|
|
break
|
|
|
|
}
|
|
|
|
n, err := r.Repository.GetNotificationsForUserIDPaged(ctx, db.GetNotificationsForUserIDPagedParams{
|
|
|
|
LimitRows: int32(input.Limit + 1),
|
|
|
|
EnableUnread: enableRead,
|
|
|
|
EnableActionType: enableActionType,
|
|
|
|
ActionType: actionTypes,
|
|
|
|
UserID: userID,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
log.WithError(err).Error("error decoding fetching notifications")
|
|
|
|
return &NotifiedResult{}, err
|
|
|
|
}
|
|
|
|
hasNextPage := false
|
|
|
|
log.WithFields(log.Fields{
|
|
|
|
"nLen": len(n),
|
|
|
|
"limit": input.Limit,
|
|
|
|
}).Info("fetched notified")
|
2021-11-04 16:55:34 +01:00
|
|
|
var endCursor *db.GetNotificationsForUserIDPagedRow
|
|
|
|
if len(n) != 0 {
|
|
|
|
endCursor = &n[len(n)-1]
|
|
|
|
if len(n) == input.Limit+1 {
|
|
|
|
hasNextPage = true
|
|
|
|
n = n[:len(n)-1]
|
|
|
|
endCursor = &n[len(n)-1]
|
|
|
|
}
|
2021-11-02 20:45:05 +01:00
|
|
|
}
|
|
|
|
userNotifications := []Notified{}
|
|
|
|
for _, notified := range n {
|
|
|
|
var readAt *time.Time
|
|
|
|
if notified.ReadAt.Valid {
|
|
|
|
readAt = ¬ified.ReadAt.Time
|
|
|
|
}
|
|
|
|
n := Notified{
|
|
|
|
ID: notified.NotifiedID,
|
|
|
|
Read: notified.Read,
|
|
|
|
ReadAt: readAt,
|
|
|
|
Notification: &db.Notification{
|
|
|
|
NotificationID: notified.NotificationID,
|
|
|
|
CausedBy: notified.CausedBy,
|
|
|
|
ActionType: notified.ActionType,
|
|
|
|
Data: notified.Data,
|
|
|
|
CreatedOn: notified.CreatedOn,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
userNotifications = append(userNotifications, n)
|
|
|
|
}
|
2021-11-04 16:55:34 +01:00
|
|
|
var endCursorEncoded *string
|
|
|
|
if endCursor != nil {
|
|
|
|
eCur := utils.EncodeCursor(endCursor.CreatedOn, endCursor.NotificationID)
|
|
|
|
endCursorEncoded = &eCur
|
|
|
|
}
|
2021-11-02 20:45:05 +01:00
|
|
|
pageInfo := &PageInfo{
|
|
|
|
HasNextPage: hasNextPage,
|
2021-11-04 16:55:34 +01:00
|
|
|
EndCursor: endCursorEncoded,
|
2021-11-02 20:45:05 +01:00
|
|
|
}
|
|
|
|
log.WithField("pageInfo", pageInfo).Info("created page info")
|
|
|
|
return &NotifiedResult{
|
|
|
|
TotalCount: len(n),
|
|
|
|
PageInfo: pageInfo,
|
|
|
|
Notified: userNotifications,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *queryResolver) HasUnreadNotifications(ctx context.Context) (*HasUnreadNotificationsResult, error) {
|
|
|
|
userID, ok := GetUserID(ctx)
|
|
|
|
if !ok {
|
|
|
|
return &HasUnreadNotificationsResult{}, errors.New("userID is missing")
|
|
|
|
}
|
|
|
|
unread, err := r.Repository.HasUnreadNotification(ctx, userID)
|
|
|
|
if err != nil {
|
|
|
|
log.WithError(err).Error("error while fetching unread notifications")
|
|
|
|
return &HasUnreadNotificationsResult{}, err
|
|
|
|
}
|
|
|
|
return &HasUnreadNotificationsResult{
|
|
|
|
Unread: unread,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2021-10-26 21:42:04 +02:00
|
|
|
func (r *subscriptionResolver) NotificationAdded(ctx context.Context) (<-chan *Notified, error) {
|
2021-11-02 20:45:05 +01:00
|
|
|
notified := make(chan *Notified, 1)
|
|
|
|
|
|
|
|
userID, ok := GetUserID(ctx)
|
|
|
|
if !ok {
|
|
|
|
return notified, errors.New("userID is not found")
|
|
|
|
}
|
|
|
|
|
|
|
|
id := uuid.New().String()
|
|
|
|
go func() {
|
|
|
|
<-ctx.Done()
|
|
|
|
r.Notifications.Mu.Lock()
|
|
|
|
if _, ok := r.Notifications.Subscribers[userID.String()]; ok {
|
|
|
|
delete(r.Notifications.Subscribers[userID.String()], id)
|
|
|
|
}
|
|
|
|
r.Notifications.Mu.Unlock()
|
|
|
|
}()
|
|
|
|
|
|
|
|
r.Notifications.Mu.Lock()
|
|
|
|
if _, ok := r.Notifications.Subscribers[userID.String()]; !ok {
|
|
|
|
r.Notifications.Subscribers[userID.String()] = make(map[string]chan *Notified)
|
|
|
|
}
|
|
|
|
log.WithField("userID", userID).WithField("id", id).Info("adding new channel")
|
|
|
|
r.Notifications.Subscribers[userID.String()][id] = notified
|
|
|
|
r.Notifications.Mu.Unlock()
|
|
|
|
return notified, nil
|
2021-10-26 21:42:04 +02:00
|
|
|
}
|
|
|
|
|
2021-10-26 00:42:57 +02:00
|
|
|
// Notification returns NotificationResolver implementation.
|
|
|
|
func (r *Resolver) Notification() NotificationResolver { return ¬ificationResolver{r} }
|
|
|
|
|
|
|
|
type notificationResolver struct{ *Resolver }
|