1// Copyright (C) 2019 Storj Labs, Inc.
2// See LICENSE for copying information.
3
4package consoleapi
5
6import (
7	"encoding/json"
8	"net/http"
9	"strconv"
10
11	"github.com/gorilla/mux"
12	"github.com/zeebo/errs"
13	"go.uber.org/zap"
14
15	"storj.io/common/uuid"
16	"storj.io/storj/storagenode/notifications"
17)
18
19// ErrNotificationsAPI - console notifications api error type.
20var ErrNotificationsAPI = errs.Class("consoleapi notifications")
21
22// Notifications is an api controller that exposes all notifications related api.
23type Notifications struct {
24	service *notifications.Service
25
26	log *zap.Logger
27}
28
29// Page contains notifications and related information.
30type Page struct {
31	Notifications []notifications.Notification `json:"notifications"`
32
33	Offset      uint64 `json:"offset"`
34	Limit       uint   `json:"limit"`
35	CurrentPage uint   `json:"currentPage"`
36	PageCount   uint   `json:"pageCount"`
37}
38
39// NewNotifications is a constructor for notifications controller.
40func NewNotifications(log *zap.Logger, service *notifications.Service) *Notifications {
41	return &Notifications{
42		log:     log,
43		service: service,
44	}
45}
46
47// ReadNotification updates specific notification in database as read.
48func (notification *Notifications) ReadNotification(w http.ResponseWriter, r *http.Request) {
49	ctx := r.Context()
50	var err error
51	defer mon.Task()(&ctx)(&err)
52
53	w.Header().Set(contentType, applicationJSON)
54
55	params := mux.Vars(r)
56	id, ok := params["id"]
57	if !ok {
58		notification.serveJSONError(w, http.StatusInternalServerError, ErrNotificationsAPI.Wrap(err))
59		return
60	}
61
62	notificationID, err := uuid.FromString(id)
63	if err != nil {
64		notification.serveJSONError(w, http.StatusInternalServerError, ErrNotificationsAPI.Wrap(err))
65		return
66	}
67
68	err = notification.service.Read(ctx, notificationID)
69	if err != nil {
70		notification.serveJSONError(w, http.StatusInternalServerError, ErrNotificationsAPI.Wrap(err))
71		return
72	}
73}
74
75// ReadAllNotifications updates all notifications in database as read.
76func (notification *Notifications) ReadAllNotifications(w http.ResponseWriter, r *http.Request) {
77	ctx := r.Context()
78	var err error
79	defer mon.Task()(&ctx)(&err)
80
81	w.Header().Set(contentType, applicationJSON)
82
83	err = notification.service.ReadAll(ctx)
84	if err != nil {
85		notification.serveJSONError(w, http.StatusInternalServerError, ErrNotificationsAPI.Wrap(err))
86		return
87	}
88}
89
90// ListNotifications returns listed page of notifications from database.
91func (notification *Notifications) ListNotifications(w http.ResponseWriter, r *http.Request) {
92	ctx := r.Context()
93	var err error
94	defer mon.Task()(&ctx)(&err)
95
96	w.Header().Set(contentType, applicationJSON)
97
98	limit, err := strconv.ParseUint(r.URL.Query().Get("limit"), 10, 32)
99	if err != nil {
100		notification.serveJSONError(w, http.StatusBadRequest, ErrNotificationsAPI.Wrap(err))
101		return
102	}
103
104	page, err := strconv.ParseUint(r.URL.Query().Get("page"), 10, 32)
105	if err != nil {
106		notification.serveJSONError(w, http.StatusBadRequest, ErrNotificationsAPI.Wrap(err))
107		return
108	}
109
110	cursor := notifications.Cursor{
111		Limit: uint(limit),
112		Page:  uint(page),
113	}
114
115	notificationList, err := notification.service.List(ctx, cursor)
116	if err != nil {
117		notification.serveJSONError(w, http.StatusInternalServerError, ErrNotificationsAPI.Wrap(err))
118		return
119	}
120
121	unreadCount, err := notification.service.UnreadAmount(ctx)
122	if err != nil {
123		notification.serveJSONError(w, http.StatusInternalServerError, ErrNotificationsAPI.Wrap(err))
124		return
125	}
126
127	var result struct {
128		Page        Page `json:"page"`
129		UnreadCount int  `json:"unreadCount"`
130		TotalCount  int  `json:"totalCount"`
131	}
132
133	result.Page.Notifications = notificationList.Notifications
134	result.Page.Limit = notificationList.Limit
135	result.Page.CurrentPage = notificationList.CurrentPage
136	result.Page.Offset = notificationList.Offset
137	result.Page.PageCount = notificationList.PageCount
138	result.UnreadCount = unreadCount
139	result.TotalCount = int(notificationList.TotalCount)
140
141	if err := json.NewEncoder(w).Encode(result); err != nil {
142		notification.log.Error("failed to encode json list notifications response", zap.Error(ErrNotificationsAPI.Wrap(err)))
143		return
144	}
145}
146
147// serveJSONError writes JSON error to response output stream.
148func (notification *Notifications) serveJSONError(w http.ResponseWriter, status int, err error) {
149	w.WriteHeader(status)
150
151	var response struct {
152		Error string `json:"error"`
153	}
154
155	response.Error = err.Error()
156
157	err = json.NewEncoder(w).Encode(response)
158	if err != nil {
159		notification.log.Error("failed to write json error response", zap.Error(ErrNotificationsAPI.Wrap(err)))
160		return
161	}
162}
163