all repos

onasty @ 7e5389df8b77bcb3308f4586bad32bbdae57c0d2

a one-time notes service

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

Smirnov Oleksandr Smirnov Oleksandr
ss2316544@gmail.com
feat: notes manipulations for the note authors (#117)..., 1 year ago
1
package apiv1
2
3
import (
4
	"errors"
5
	"io"
6
	"net/http"
7
	"time"
8
9
	"github.com/gin-gonic/gin"
10
	"github.com/olexsmir/onasty/internal/dtos"
11
	"github.com/olexsmir/onasty/internal/service/notesrv"
12
)
13
14
type createNoteRequest struct {
15
	Content              string    `json:"content"`
16
	Slug                 string    `json:"slug"`
17
	Password             string    `json:"password"`
18
	BurnBeforeExpiration bool      `json:"burn_before_expiration"`
19
	ExpiresAt            time.Time `json:"expires_at"`
20
}
21
22
type createNoteResponse struct {
23
	Slug string `json:"slug"`
24
}
25
26
func (a *APIV1) createNoteHandler(c *gin.Context) {
27
	var req createNoteRequest
28
	if err := c.ShouldBindJSON(&req); err != nil {
29
		newError(c, http.StatusBadRequest, "invalid request")
30
		return
31
	}
32
33
	// TODO: burn_before_expiration shouldn't be set if user has not set or specified expires_at
34
35
	slug, err := a.notesrv.Create(c.Request.Context(), dtos.CreateNote{
36
		Content:              req.Content,
37
		UserID:               a.getUserID(c),
38
		Slug:                 req.Slug,
39
		Password:             req.Password,
40
		BurnBeforeExpiration: req.BurnBeforeExpiration,
41
		CreatedAt:            time.Now(),
42
		ExpiresAt:            req.ExpiresAt,
43
	}, a.getUserID(c))
44
	if err != nil {
45
		errorResponse(c, err)
46
		return
47
	}
48
49
	c.JSON(http.StatusCreated, createNoteResponse{slug})
50
}
51
52
type getNoteBySlugRequest struct {
53
	Password string `json:"password"`
54
}
55
56
type getNoteBySlugResponse struct {
57
	Content   string    `json:"content"`
58
	ReadAt    time.Time `json:"read_at,omitzero"`
59
	CreatedAt time.Time `json:"created_at"`
60
	ExpiresAt time.Time `json:"expires_at,omitzero"`
61
}
62
63
func (a *APIV1) getNoteBySlugHandler(c *gin.Context) {
64
	var req getNoteBySlugRequest
65
	if err := c.ShouldBindJSON(&req); err != nil && !errors.Is(err, io.EOF) {
66
		newError(c, http.StatusBadRequest, "invalid request")
67
		return
68
	}
69
70
	note, err := a.notesrv.GetBySlugAndRemoveIfNeeded(
71
		c.Request.Context(),
72
		notesrv.GetNoteBySlugInput{
73
			Slug:     c.Param("slug"),
74
			Password: req.Password,
75
		},
76
	)
77
	if err != nil {
78
		errorResponse(c, err)
79
		return
80
	}
81
82
	status := http.StatusOK
83
	if !note.ReadAt.IsZero() {
84
		status = http.StatusNotFound
85
	}
86
87
	c.JSON(status, getNoteBySlugResponse{
88
		Content:   note.Content,
89
		ReadAt:    note.ReadAt,
90
		CreatedAt: note.CreatedAt,
91
		ExpiresAt: note.ExpiresAt,
92
	})
93
}
94
95
type getNotesResponse struct {
96
	Content              string    `json:"content"`
97
	Slug                 string    `json:"slug"`
98
	BurnBeforeExpiration bool      `json:"burn_before_expiration"`
99
	HasPassword          bool      `json:"has_password"`
100
	CreatedAt            time.Time `json:"created_at"`
101
	ExpiresAt            time.Time `json:"expires_at,omitzero"`
102
	ReadAt               time.Time `json:"read_at,omitzero"`
103
}
104
105
func (a *APIV1) getNotesHandler(c *gin.Context) {
106
	notes, err := a.notesrv.GetAllByAuthorID(c.Request.Context(), a.getUserID(c))
107
	if err != nil {
108
		errorResponse(c, err)
109
		return
110
	}
111
112
	var response []getNotesResponse
113
	for _, note := range notes {
114
		response = append(response, getNotesResponse{
115
			Content:              note.Content,
116
			Slug:                 note.Slug,
117
			BurnBeforeExpiration: note.BurnBeforeExpiration,
118
			HasPassword:          note.HasPassword,
119
			CreatedAt:            note.CreatedAt,
120
			ExpiresAt:            note.ExpiresAt,
121
			ReadAt:               note.ReadAt,
122
		})
123
	}
124
125
	c.JSON(http.StatusOK, response)
126
}
127
128
type updateNoteRequest struct {
129
	ExpiresAt            *time.Time `json:"expires_at,omitempty"`
130
	BurnBeforeExpiration *bool      `json:"burn_before_expiration,omitempty"`
131
}
132
133
func (a *APIV1) updateNoteHandler(c *gin.Context) {
134
	var req updateNoteRequest
135
	if err := c.ShouldBindJSON(&req); err != nil {
136
		newError(c, http.StatusBadRequest, "invalid request")
137
		return
138
	}
139
140
	// TODO: burn_before_expiration shouldn't be set if user has not set or specified expires_at
141
142
	if err := a.notesrv.UpdateExpirationTimeSettings(
143
		c.Request.Context(),
144
		dtos.PatchNote{
145
			BurnBeforeExpiration: req.BurnBeforeExpiration,
146
			ExpiresAt:            req.ExpiresAt,
147
		},
148
		c.Param("slug"),
149
		a.getUserID(c),
150
	); err != nil {
151
		errorResponse(c, err)
152
		return
153
	}
154
155
	c.Status(http.StatusOK)
156
}
157
158
func (a *APIV1) deleteNoteHandler(c *gin.Context) {
159
	if err := a.notesrv.DeleteBySlug(
160
		c.Request.Context(),
161
		c.Param("slug"),
162
		a.getUserID(c),
163
	); err != nil {
164
		errorResponse(c, err)
165
		return
166
	}
167
168
	c.Status(http.StatusNoContent)
169
}
170
171
type setNotePasswordRequest struct {
172
	Password string `json:"password"`
173
}
174
175
func (a *APIV1) setNotePasswordHandler(c *gin.Context) {
176
	var req setNotePasswordRequest
177
	if err := c.ShouldBindJSON(&req); err != nil {
178
		newError(c, http.StatusBadRequest, "invalid request")
179
		return
180
	}
181
182
	if err := a.notesrv.UpdatePassword(
183
		c.Request.Context(),
184
		c.Param("slug"),
185
		req.Password,
186
		a.getUserID(c),
187
	); err != nil {
188
		errorResponse(c, err)
189
		return
190
	}
191
192
	c.Status(http.StatusOK)
193
}