5 files changed,
40 insertions(+),
11 deletions(-)
Author:
Olexandr Smirnov
ss2316544@gmail.com
Committed by:
GitHub
noreply@github.com
Committed at:
2025-08-11 19:39:03 +0300
Parent:
e771742
M
e2e/e2e_utils_db_test.go
··· 1 1 package e2e_test 2 2 3 3 import ( 4 + "database/sql" 4 5 "errors" 5 6 "time" 6 7 ··· 8 9 "github.com/henvic/pgq" 9 10 "github.com/jackc/pgx/v5" 10 11 "github.com/olexsmir/onasty/internal/models" 12 + "github.com/olexsmir/onasty/internal/store/psqlutil" 11 13 ) 12 14 13 15 // getUserByEmail queries user from db by it's email ··· 114 116 SQL() 115 117 e.require.NoError(err) 116 118 119 + var readAt sql.NullTime 117 120 var note models.Note 118 121 err = e.postgresDB.QueryRow(e.ctx, query, args...). 119 - Scan(¬e.ID, ¬e.Content, ¬e.Slug, ¬e.BurnBeforeExpiration, ¬e.Password, ¬e.ReadAt, ¬e.CreatedAt, ¬e.ExpiresAt) 122 + Scan(¬e.ID, ¬e.Content, ¬e.Slug, ¬e.BurnBeforeExpiration, ¬e.Password, &readAt, ¬e.CreatedAt, ¬e.ExpiresAt) 120 123 if errors.Is(err, pgx.ErrNoRows) { 121 124 return models.Note{} //nolint:exhaustruct 122 125 } 126 + 127 + note.ReadAt = psqlutil.NullTimeToTime(readAt) 123 128 124 129 e.require.NoError(err) 125 130 return note
M
internal/store/psql/noterepo/noterepo.go
··· 2 2 3 3 import ( 4 4 "context" 5 + "database/sql" 5 6 "errors" 6 7 "time" 7 8 ··· 111 112 } 112 113 113 114 var note models.Note 115 + var readAt sql.NullTime 114 116 err = s.db.QueryRow(ctx, query, args...). 115 - Scan(¬e.Content, ¬e.Slug, ¬e.BurnBeforeExpiration, ¬e.ReadAt, ¬e.CreatedAt, ¬e.ExpiresAt) 116 - 117 + Scan(¬e.Content, ¬e.Slug, ¬e.BurnBeforeExpiration, &readAt, ¬e.CreatedAt, ¬e.ExpiresAt) 117 118 if errors.Is(err, pgx.ErrNoRows) { 118 119 return models.Note{}, models.ErrNoteNotFound 119 120 } 121 + 122 + note.ReadAt = psqlutil.NullTimeToTime(readAt) 120 123 121 124 return note, err 122 125 } ··· 131 134 where slug = $1 132 135 ` 133 136 134 - var readAt time.Time 137 + var readAt sql.NullTime 135 138 var metadata dtos.NoteMetadata 136 139 err := s.db.QueryRow(ctx, query, slug).Scan(&metadata.CreatedAt, &metadata.HasPassword, &readAt) 137 140 if errors.Is(err, pgx.ErrNoRows) { 138 141 return dtos.NoteMetadata{}, models.ErrNoteNotFound 139 142 } 140 143 141 - if !readAt.IsZero() { 144 + if !psqlutil.NullTimeToTime(readAt).IsZero() { 142 145 return dtos.NoteMetadata{}, models.ErrNoteNotFound 143 146 } 144 147 ··· 165 168 var notes []models.Note 166 169 for rows.Next() { 167 170 var note models.Note 171 + var readAt sql.NullTime 168 172 if err := rows.Scan(¬e.Content, ¬e.Slug, ¬e.BurnBeforeExpiration, ¬e.Password, 169 - ¬e.ReadAt, ¬e.CreatedAt, ¬e.ExpiresAt); err != nil { 173 + &readAt, ¬e.CreatedAt, ¬e.ExpiresAt); err != nil { 170 174 return nil, err 171 175 } 176 + 177 + note.ReadAt = psqlutil.NullTimeToTime(readAt) 172 178 notes = append(notes, note) 173 179 } 174 180 ··· 207 213 } 208 214 209 215 var note models.Note 216 + var readAt sql.NullTime 210 217 err = s.db.QueryRow(ctx, query, args...). 211 - Scan(¬e.Content, ¬e.Slug, ¬e.BurnBeforeExpiration, ¬e.ReadAt, ¬e.CreatedAt, ¬e.ExpiresAt) 218 + Scan(¬e.Content, ¬e.Slug, ¬e.BurnBeforeExpiration, &readAt, ¬e.CreatedAt, ¬e.ExpiresAt) 212 219 213 220 if errors.Is(err, pgx.ErrNoRows) { 214 221 return models.Note{}, models.ErrNoteNotFound 215 222 } 223 + 224 + note.ReadAt = psqlutil.NullTimeToTime(readAt) 216 225 217 226 return note, err 218 227 } ··· 255 264 Update("notes"). 256 265 Set("content", ""). 257 266 Set("read_at", readAt). 258 - Where(pgq.Eq{ 259 - "slug": slug, 260 - "read_at": time.Time{}, // check if time is null 261 - }). 267 + Where(pgq.Eq{"slug": slug}). 268 + Where("read_at is null"). 262 269 SQL() 263 270 if err != nil { 264 271 return err
M
internal/store/psqlutil/psqlutil.go
··· 2 2 3 3 import ( 4 4 "context" 5 + "database/sql" 5 6 "errors" 7 + "time" 6 8 7 9 "github.com/jackc/pgx/v5/pgconn" 8 10 "github.com/jackc/pgx/v5/pgxpool" ··· 39 41 } 40 42 return false 41 43 } 44 + 45 +// NullTimeToTime converts sql.NullTime to time.Time. 46 +// Returns zero [time.Time] if NullTime is not valid. 47 +func NullTimeToTime(t sql.NullTime) time.Time { 48 + if t.Valid { 49 + return t.Time 50 + } 51 + return time.Time{} 52 +}
A
migrations/20250811132505_note_nullable_read_at.down.sql
··· 1 +ALTER TABLE notes 2 + ALTER COLUMN "read_at" SET NOT NULL, 3 + ALTER COLUMN "read_at" SET DEFAULT '0001-01-01 00:00:00'::timestamptz;
A
migrations/20250811132505_note_nullable_read_at.up.sql
··· 1 +ALTER TABLE notes 2 + ALTER COLUMN "read_at" DROP NOT NULL, 3 + ALTER COLUMN "read_at" SET DEFAULT NULL;