all repos

onasty @ 6e284908136d4766eeaf8cd85a6a2b394764af18

a one-time notes service

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

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