all repos

onasty @ 3b5e67f

a one-time notes service

onasty/internal/transport/http/apiv1/note.go (view raw)

Olexandr Smirnov Olexandr Smirnov
ss2316544@gmail.com
feat: get all read and unread notes (#188)..., 9 months ago
1
package apiv1
2
3
import (
4
	"net/http"
5
	"time"
6
7
	"github.com/gin-gonic/gin"
8
	"github.com/olexsmir/onasty/internal/dtos"
9
	"github.com/olexsmir/onasty/internal/service/notesrv"
10
)
11
12
type createNoteRequest struct {
13
	Content              string    `json:"content"`
14
	Slug                 string    `json:"slug"`
15
	Password             string    `json:"password"`
16
	BurnBeforeExpiration bool      `json:"burn_before_expiration"`
17
	ExpiresAt            time.Time `json:"expires_at"`
18
}
19
20
type createNoteResponse struct {
21
	Slug string `json:"slug"`
22
}
23
24
func (a *APIV1) createNoteHandler(c *gin.Context) {
25
	var req createNoteRequest
26
	if err := c.ShouldBindJSON(&req); err != nil {
27
		newError(c, http.StatusBadRequest, "invalid request")
28
		return
29
	}
30
31
	// TODO: burn_before_expiration shouldn't be set if user has not set or specified expires_at
32
33
	slug, err := a.notesrv.Create(c.Request.Context(), dtos.CreateNote{
34
		Content:              req.Content,
35
		UserID:               a.getUserID(c),
36
		Slug:                 req.Slug,
37
		Password:             req.Password,
38
		BurnBeforeExpiration: req.BurnBeforeExpiration,
39
		CreatedAt:            time.Now(),
40
		ExpiresAt:            req.ExpiresAt,
41
	}, a.getUserID(c))
42
	if err != nil {
43
		errorResponse(c, err)
44
		return
45
	}
46
47
	c.JSON(http.StatusCreated, createNoteResponse{slug})
48
}
49
50
type getNoteBySlugResponse struct {
51
	Content              string    `json:"content"`
52
	ReadAt               time.Time `json:"read_at,omitzero"`
53
	BurnBeforeExpiration bool      `json:"burn_before_expiration"`
54
	CreatedAt            time.Time `json:"created_at"`
55
	ExpiresAt            time.Time `json:"expires_at,omitzero"`
56
}
57
58
func (a *APIV1) getNoteBySlugHandler(c *gin.Context) {
59
	note, err := a.notesrv.GetBySlugAndRemoveIfNeeded(
60
		c.Request.Context(),
61
		notesrv.GetNoteBySlugInput{
62
			Slug:     c.Param("slug"),
63
			Password: notesrv.EmptyPassword,
64
		},
65
	)
66
	if err != nil {
67
		errorResponse(c, err)
68
		return
69
	}
70
71
	status := http.StatusOK
72
	if !note.ReadAt.IsZero() {
73
		status = http.StatusNotFound
74
	}
75
76
	c.JSON(status, getNoteBySlugResponse{
77
		Content:              note.Content,
78
		ReadAt:               note.ReadAt,
79
		CreatedAt:            note.CreatedAt,
80
		ExpiresAt:            note.ExpiresAt,
81
		BurnBeforeExpiration: note.BurnBeforeExpiration,
82
	})
83
}
84
85
type getNoteBuySlugAndPasswordRequest struct {
86
	Password string `json:"password"`
87
}
88
89
func (a *APIV1) getNoteBySlugAndPasswordHandler(c *gin.Context) {
90
	var req getNoteBuySlugAndPasswordRequest
91
	if err := c.ShouldBindJSON(&req); err != nil {
92
		newError(c, http.StatusBadRequest, "invalid request")
93
		return
94
	}
95
96
	note, err := a.notesrv.GetBySlugAndRemoveIfNeeded(
97
		c.Request.Context(),
98
		notesrv.GetNoteBySlugInput{
99
			Slug:     c.Param("slug"),
100
			Password: req.Password,
101
		},
102
	)
103
	if err != nil {
104
		errorResponse(c, err)
105
		return
106
	}
107
108
	status := http.StatusOK
109
	if !note.ReadAt.IsZero() {
110
		status = http.StatusNotFound
111
	}
112
113
	c.JSON(status, getNoteBySlugResponse{
114
		Content:              note.Content,
115
		ReadAt:               note.ReadAt,
116
		CreatedAt:            note.CreatedAt,
117
		ExpiresAt:            note.ExpiresAt,
118
		BurnBeforeExpiration: note.BurnBeforeExpiration,
119
	})
120
}
121
122
type getNoteMetadataBySlugResponse struct {
123
	CreatedAt   time.Time `json:"created_at"`
124
	HasPassword bool      `json:"has_password"`
125
}
126
127
func (a *APIV1) getNoteMetadataByIDHandler(c *gin.Context) {
128
	meta, err := a.notesrv.GetNoteMetadataBySlug(c.Request.Context(), c.Param("slug"))
129
	if err != nil {
130
		errorResponse(c, err)
131
		return
132
	}
133
134
	c.JSON(http.StatusOK, getNoteMetadataBySlugResponse{
135
		CreatedAt:   meta.CreatedAt,
136
		HasPassword: meta.HasPassword,
137
	})
138
}
139
140
type getNotesResponse struct {
141
	Content              string    `json:"content"`
142
	Slug                 string    `json:"slug"`
143
	BurnBeforeExpiration bool      `json:"burn_before_expiration"`
144
	HasPassword          bool      `json:"has_password"`
145
	CreatedAt            time.Time `json:"created_at"`
146
	ExpiresAt            time.Time `json:"expires_at,omitzero"`
147
	ReadAt               time.Time `json:"read_at,omitzero"`
148
}
149
150
func (a *APIV1) getNotesHandler(c *gin.Context) {
151
	notes, err := a.notesrv.GetAllByAuthorID(c.Request.Context(), a.getUserID(c))
152
	if err != nil {
153
		errorResponse(c, err)
154
		return
155
	}
156
157
	c.JSON(http.StatusOK, mapNotesDTOToResponse(notes))
158
}
159
160
func (a *APIV1) getReadNotesHandler(c *gin.Context) {
161
	notes, err := a.notesrv.GetAllReadByAuthorID(c.Request.Context(), a.getUserID(c))
162
	if err != nil {
163
		errorResponse(c, err)
164
		return
165
	}
166
167
	c.JSON(http.StatusOK, mapNotesDTOToResponse(notes))
168
}
169
170
func (a *APIV1) getUnReadNotesHandler(c *gin.Context) {
171
	notes, err := a.notesrv.GetAllUnreadByAuthorID(c.Request.Context(), a.getUserID(c))
172
	if err != nil {
173
		errorResponse(c, err)
174
		return
175
	}
176
177
	c.JSON(http.StatusOK, mapNotesDTOToResponse(notes))
178
}
179
180
type updateNoteRequest struct {
181
	ExpiresAt            *time.Time `json:"expires_at,omitempty"`
182
	BurnBeforeExpiration *bool      `json:"burn_before_expiration,omitempty"`
183
}
184
185
func (a *APIV1) updateNoteHandler(c *gin.Context) {
186
	var req updateNoteRequest
187
	if err := c.ShouldBindJSON(&req); err != nil {
188
		newError(c, http.StatusBadRequest, "invalid request")
189
		return
190
	}
191
192
	// TODO: burn_before_expiration shouldn't be set if user has not set or specified expires_at
193
194
	if err := a.notesrv.UpdateExpirationTimeSettings(
195
		c.Request.Context(),
196
		dtos.PatchNote{
197
			BurnBeforeExpiration: req.BurnBeforeExpiration,
198
			ExpiresAt:            req.ExpiresAt,
199
		},
200
		c.Param("slug"),
201
		a.getUserID(c),
202
	); err != nil {
203
		errorResponse(c, err)
204
		return
205
	}
206
207
	c.Status(http.StatusOK)
208
}
209
210
func (a *APIV1) deleteNoteHandler(c *gin.Context) {
211
	if err := a.notesrv.DeleteBySlug(
212
		c.Request.Context(),
213
		c.Param("slug"),
214
		a.getUserID(c),
215
	); err != nil {
216
		errorResponse(c, err)
217
		return
218
	}
219
220
	c.Status(http.StatusNoContent)
221
}
222
223
type setNotePasswordRequest struct {
224
	Password string `json:"password"`
225
}
226
227
func (a *APIV1) setNotePasswordHandler(c *gin.Context) {
228
	var req setNotePasswordRequest
229
	if err := c.ShouldBindJSON(&req); err != nil {
230
		newError(c, http.StatusBadRequest, "invalid request")
231
		return
232
	}
233
234
	if err := a.notesrv.UpdatePassword(
235
		c.Request.Context(),
236
		c.Param("slug"),
237
		req.Password,
238
		a.getUserID(c),
239
	); err != nil {
240
		errorResponse(c, err)
241
		return
242
	}
243
244
	c.Status(http.StatusOK)
245
}
246
247
func mapNotesDTOToResponse(notes []dtos.NoteDetailed) []getNotesResponse {
248
	var response []getNotesResponse
249
	for _, note := range notes {
250
		response = append(response, getNotesResponse{
251
			Content:              note.Content,
252
			Slug:                 note.Slug,
253
			BurnBeforeExpiration: note.BurnBeforeExpiration,
254
			HasPassword:          note.HasPassword,
255
			CreatedAt:            note.CreatedAt,
256
			ExpiresAt:            note.ExpiresAt,
257
			ReadAt:               note.ReadAt,
258
		})
259
	}
260
261
	return response
262
}