all repos

onasty @ 7a1da0f

a one-time notes service

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

Olexandr Smirnov Olexandr Smirnov
ss2316544@gmail.com
refactor: don't pass note password in url (#167)..., 10 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
	var response []getNotesResponse
158
	for _, note := range notes {
159
		response = append(response, getNotesResponse{
160
			Content:              note.Content,
161
			Slug:                 note.Slug,
162
			BurnBeforeExpiration: note.BurnBeforeExpiration,
163
			HasPassword:          note.HasPassword,
164
			CreatedAt:            note.CreatedAt,
165
			ExpiresAt:            note.ExpiresAt,
166
			ReadAt:               note.ReadAt,
167
		})
168
	}
169
170
	c.JSON(http.StatusOK, response)
171
}
172
173
type updateNoteRequest struct {
174
	ExpiresAt            *time.Time `json:"expires_at,omitempty"`
175
	BurnBeforeExpiration *bool      `json:"burn_before_expiration,omitempty"`
176
}
177
178
func (a *APIV1) updateNoteHandler(c *gin.Context) {
179
	var req updateNoteRequest
180
	if err := c.ShouldBindJSON(&req); err != nil {
181
		newError(c, http.StatusBadRequest, "invalid request")
182
		return
183
	}
184
185
	// TODO: burn_before_expiration shouldn't be set if user has not set or specified expires_at
186
187
	if err := a.notesrv.UpdateExpirationTimeSettings(
188
		c.Request.Context(),
189
		dtos.PatchNote{
190
			BurnBeforeExpiration: req.BurnBeforeExpiration,
191
			ExpiresAt:            req.ExpiresAt,
192
		},
193
		c.Param("slug"),
194
		a.getUserID(c),
195
	); err != nil {
196
		errorResponse(c, err)
197
		return
198
	}
199
200
	c.Status(http.StatusOK)
201
}
202
203
func (a *APIV1) deleteNoteHandler(c *gin.Context) {
204
	if err := a.notesrv.DeleteBySlug(
205
		c.Request.Context(),
206
		c.Param("slug"),
207
		a.getUserID(c),
208
	); err != nil {
209
		errorResponse(c, err)
210
		return
211
	}
212
213
	c.Status(http.StatusNoContent)
214
}
215
216
type setNotePasswordRequest struct {
217
	Password string `json:"password"`
218
}
219
220
func (a *APIV1) setNotePasswordHandler(c *gin.Context) {
221
	var req setNotePasswordRequest
222
	if err := c.ShouldBindJSON(&req); err != nil {
223
		newError(c, http.StatusBadRequest, "invalid request")
224
		return
225
	}
226
227
	if err := a.notesrv.UpdatePassword(
228
		c.Request.Context(),
229
		c.Param("slug"),
230
		req.Password,
231
		a.getUserID(c),
232
	); err != nil {
233
		errorResponse(c, err)
234
		return
235
	}
236
237
	c.Status(http.StatusOK)
238
}