all repos

onasty @ 86dba8ea0dc14c9a08dbb0baa9b0441168638328

a one-time notes service
6 files changed, 119 insertions(+), 1 deletions(-)
feat(api): get note metadata (#148)

* feat(api): get note metadata

* fix(note): get the metadata

* test: get note metadata

* fixup! fix(note): get the metadata

* fixup! fix(note): get the metadata
Author: Smirnov Oleksandr ss2316544@gmail.com
Committed by: GitHub noreply@github.com
Committed at: 2025-06-25 16:06:29 +0300
Parent: e4abd99
M e2e/apiv1_notes_test.go
···
        222
        222
         	)

      
        223
        223
         	e.Equal(httpResp.Code, http.StatusNotFound)

      
        224
        224
         }

      
        
        225
        +

      
        
        226
        +type apiv1NoteMetadataResponse struct {

      
        
        227
        +	CreatedAt   time.Time `json:"created_at"`

      
        
        228
        +	HasPassword bool      `json:"has_password"`

      
        
        229
        +}

      
        
        230
        +

      
        
        231
        +func (e *AppTestSuite) TestNoteV1_GetMetadata() {

      
        
        232
        +	// create note

      
        
        233
        +	httpResp := e.httpRequest(

      
        
        234
        +		http.MethodPost,

      
        
        235
        +		"/api/v1/note",

      
        
        236
        +		e.jsonify(apiv1NoteCreateRequest{Content: "content"}), //nolint:exhaustruct

      
        
        237
        +	)

      
        
        238
        +	e.Equal(http.StatusCreated, httpResp.Code)

      
        
        239
        +

      
        
        240
        +	var bodyCreated apiv1NoteCreateResponse

      
        
        241
        +	e.readBodyAndUnjsonify(httpResp.Body, &bodyCreated)

      
        
        242
        +

      
        
        243
        +	// get metadata

      
        
        244
        +	metaResp := e.httpRequest(http.MethodGet, "/api/v1/note/"+bodyCreated.Slug+"/meta", []byte{})

      
        
        245
        +	e.Equal(metaResp.Code, http.StatusOK)

      
        
        246
        +

      
        
        247
        +	var metadata apiv1NoteMetadataResponse

      
        
        248
        +	e.readBodyAndUnjsonify(metaResp.Body, &metadata)

      
        
        249
        +

      
        
        250
        +	e.False(metadata.HasPassword)

      
        
        251
        +	e.NotEmpty(metadata.CreatedAt)

      
        
        252
        +}

      
        
        253
        +

      
        
        254
        +func (e *AppTestSuite) TestNoteV1_GetMetadata_withPassword() {

      
        
        255
        +	// create note

      
        
        256
        +	httpResp := e.httpRequest(

      
        
        257
        +		http.MethodPost,

      
        
        258
        +		"/api/v1/note",

      
        
        259
        +		e.jsonify(apiv1NoteCreateRequest{ //nolint:exhaustruct

      
        
        260
        +			Content:  "content",

      
        
        261
        +			Password: "pass",

      
        
        262
        +		}),

      
        
        263
        +	)

      
        
        264
        +	e.Equal(http.StatusCreated, httpResp.Code)

      
        
        265
        +

      
        
        266
        +	var bodyCreated apiv1NoteCreateResponse

      
        
        267
        +	e.readBodyAndUnjsonify(httpResp.Body, &bodyCreated)

      
        
        268
        +

      
        
        269
        +	// get metadata

      
        
        270
        +	metaResp := e.httpRequest(http.MethodGet, "/api/v1/note/"+bodyCreated.Slug+"/meta", []byte{})

      
        
        271
        +	e.Equal(metaResp.Code, http.StatusOK)

      
        
        272
        +

      
        
        273
        +	var metadata apiv1NoteMetadataResponse

      
        
        274
        +	e.readBodyAndUnjsonify(metaResp.Body, &metadata)

      
        
        275
        +

      
        
        276
        +	e.True(metadata.HasPassword)

      
        
        277
        +	e.NotEmpty(metadata.CreatedAt)

      
        
        278
        +}

      
        
        279
        +

      
        
        280
        +func (e *AppTestSuite) TestNoteV1_GetMetadata_notFound() {

      
        
        281
        +	metaResp := e.httpRequest(http.MethodGet, "/api/v1/note/"+e.uuid()+"/meta", []byte{})

      
        
        282
        +	e.Equal(http.StatusNotFound, metaResp.Code)

      
        
        283
        +}

      
M internal/dtos/note.go
···
        15
        15
         	ExpiresAt time.Time

      
        16
        16
         }

      
        17
        17
         

      
        
        18
        +type NoteMetadata struct {

      
        
        19
        +	HasPassword bool

      
        
        20
        +	CreatedAt   time.Time

      
        
        21
        +}

      
        
        22
        +

      
        18
        23
         type CreateNote struct {

      
        19
        24
         	Content              string

      
        20
        25
         	UserID               uuid.UUID

      
M internal/service/notesrv/notesrv.go
···
        23
        23
         	Create(ctx context.Context, note dtos.CreateNote, userID uuid.UUID) (dtos.NoteSlug, error)

      
        24
        24
         

      
        25
        25
         	// GetBySlugAndRemoveIfNeeded returns note by slug, and removes if if needed.

      
        26
        
        -	// If notes is not found returns [models.ErrNoteNotFound].

      
        
        26
        +	// If note is not found returns [models.ErrNoteNotFound].

      
        27
        27
         	GetBySlugAndRemoveIfNeeded(

      
        28
        28
         		ctx context.Context,

      
        29
        29
         		input GetNoteBySlugInput,

      
        30
        30
         	) (dtos.GetNote, error)

      
        
        31
        +

      
        
        32
        +	// GetNoteMetadataBySlug returns note metadata by slug.

      
        
        33
        +	// If note is not found returns [models.ErrNoteNotFound].

      
        
        34
        +	GetNoteMetadataBySlug(ctx context.Context, slug dtos.NoteSlug) (dtos.NoteMetadata, error)

      
        31
        35
         

      
        32
        36
         	// GetAllByAuthorID returns all notes by author id.

      
        33
        37
         	GetAllByAuthorID(

      ···
        140
        144
         	}

      
        141
        145
         

      
        142
        146
         	return respNote, n.noterepo.RemoveBySlug(ctx, inp.Slug, time.Now())

      
        
        147
        +}

      
        
        148
        +

      
        
        149
        +func (n *NoteSrv) GetNoteMetadataBySlug(

      
        
        150
        +	ctx context.Context,

      
        
        151
        +	slug dtos.NoteSlug,

      
        
        152
        +) (dtos.NoteMetadata, error) {

      
        
        153
        +	note, err := n.noterepo.GetMetadataBySlug(ctx, slug)

      
        
        154
        +	return note, err

      
        143
        155
         }

      
        144
        156
         

      
        145
        157
         func (n *NoteSrv) GetAllByAuthorID(

      
M internal/store/psql/noterepo/noterepo.go
···
        21
        21
         	// Returns [models.ErrNoteNotFound] if note is not found.

      
        22
        22
         	GetBySlug(ctx context.Context, slug dtos.NoteSlug) (models.Note, error)

      
        23
        23
         

      
        
        24
        +	// GetMetadataBySlug gets note's metadata by its slug.

      
        
        25
        +	// Returns [models.ErrNoteNotFound] if note is not found.

      
        
        26
        +	GetMetadataBySlug(ctx context.Context, slug dtos.NoteSlug) (dtos.NoteMetadata, error)

      
        
        27
        +

      
        24
        28
         	// GetAllByAuthorID returns all notes with specified author.

      
        25
        29
         	GetAllByAuthorID(ctx context.Context, authorID uuid.UUID) ([]models.Note, error)

      
        26
        30
         

      ···
        112
        116
         	}

      
        113
        117
         

      
        114
        118
         	return note, err

      
        
        119
        +}

      
        
        120
        +

      
        
        121
        +func (s *NoteRepo) GetMetadataBySlug(

      
        
        122
        +	ctx context.Context,

      
        
        123
        +	slug dtos.NoteSlug,

      
        
        124
        +) (dtos.NoteMetadata, error) {

      
        
        125
        +	query := `--sql

      
        
        126
        +	select n.created_at, (n.password is not null and n.password <> '') has_password

      
        
        127
        +	from notes n

      
        
        128
        +	where slug = $1

      
        
        129
        +	`

      
        
        130
        +

      
        
        131
        +	var metadata dtos.NoteMetadata

      
        
        132
        +	err := s.db.QueryRow(ctx, query, slug).Scan(&metadata.CreatedAt, &metadata.HasPassword)

      
        
        133
        +	if errors.Is(err, pgx.ErrNoRows) {

      
        
        134
        +		return dtos.NoteMetadata{}, models.ErrNoteNotFound

      
        
        135
        +	}

      
        
        136
        +

      
        
        137
        +	return metadata, err

      
        115
        138
         }

      
        116
        139
         

      
        117
        140
         func (s *NoteRepo) GetAllByAuthorID(

      
M internal/transport/http/apiv1/apiv1.go
···
        59
        59
         	note := r.Group("/note")

      
        60
        60
         	{

      
        61
        61
         		note.GET("/:slug", a.getNoteBySlugHandler)

      
        
        62
        +		note.GET("/:slug/meta", a.getNoteMetadataByIDHandler)

      
        62
        63
         

      
        63
        64
         		possiblyAuthorized := note.Group("", a.couldBeAuthorizedMiddleware)

      
        64
        65
         		{

      
M internal/transport/http/apiv1/note.go
···
        92
        92
         	})

      
        93
        93
         }

      
        94
        94
         

      
        
        95
        +type getNoteMetadataBySlugResponse struct {

      
        
        96
        +	CreatedAt   time.Time `json:"created_at"`

      
        
        97
        +	HasPassword bool      `json:"has_password"`

      
        
        98
        +}

      
        
        99
        +

      
        
        100
        +func (a *APIV1) getNoteMetadataByIDHandler(c *gin.Context) {

      
        
        101
        +	meta, err := a.notesrv.GetNoteMetadataBySlug(c.Request.Context(), c.Param("slug"))

      
        
        102
        +	if err != nil {

      
        
        103
        +		errorResponse(c, err)

      
        
        104
        +		return

      
        
        105
        +	}

      
        
        106
        +

      
        
        107
        +	c.JSON(http.StatusOK, getNoteMetadataBySlugResponse{

      
        
        108
        +		CreatedAt:   meta.CreatedAt,

      
        
        109
        +		HasPassword: meta.HasPassword,

      
        
        110
        +	})

      
        
        111
        +}

      
        
        112
        +

      
        95
        113
         type getNotesResponse struct {

      
        96
        114
         	Content              string    `json:"content"`

      
        97
        115
         	Slug                 string    `json:"slug"`