Documentation
¶
Index ¶
- Variables
- type ConflictError
- type Note
- type NoteCursor
- type NoteResponse
- type NoteTombstone
- type NotesHandler
- func (h *NotesHandler) GetAll(c *echo.Context) error
- func (h *NotesHandler) GetByID(c *echo.Context) error
- func (h *NotesHandler) Purge(c *echo.Context) error
- func (h *NotesHandler) Restore(c *echo.Context) error
- func (h *NotesHandler) Trash(c *echo.Context) error
- func (h *NotesHandler) Upsert(c *echo.Context) error
- type NotesListResponse
- type NotesRepository
- func (r *NotesRepository) CountLiveNotes(ctx context.Context, userID uuid.UUID) (int64, error)
- func (r *NotesRepository) FindAll(ctx context.Context, userID uuid.UUID) ([]Note, error)
- func (r *NotesRepository) FindAllPaginated(ctx context.Context, userID uuid.UUID, limit int, afterUpdatedAt time.Time, ...) ([]Note, error)
- func (r *NotesRepository) FindAllTombstones(ctx context.Context, userID uuid.UUID) ([]NoteTombstone, error)
- func (r *NotesRepository) FindByID(ctx context.Context, noteID uuid.UUID, userID uuid.UUID) (*Note, error)
- func (r *NotesRepository) FindTombstonesSince(ctx context.Context, userID uuid.UUID, since time.Time) ([]NoteTombstone, error)
- func (r *NotesRepository) FindTombstonesSincePaginated(ctx context.Context, userID uuid.UUID, since time.Time, limit int, ...) ([]NoteTombstone, error)
- func (r *NotesRepository) FindUpdatedSince(ctx context.Context, userID uuid.UUID, since time.Time) ([]Note, error)
- func (r *NotesRepository) FindUpdatedSincePaginated(ctx context.Context, userID uuid.UUID, since time.Time, limit int, ...) ([]Note, error)
- func (r *NotesRepository) Purge(ctx context.Context, noteID uuid.UUID, userID uuid.UUID) error
- func (r *NotesRepository) PurgeExpiredTombstones(ctx context.Context, olderThan time.Time) (int64, error)
- func (r *NotesRepository) Restore(ctx context.Context, noteID uuid.UUID, userID uuid.UUID) error
- func (r *NotesRepository) Trash(ctx context.Context, noteID uuid.UUID, userID uuid.UUID) error
- func (r *NotesRepository) Upsert(ctx context.Context, note *Note) error
- type NotesService
- func (s *NotesService) GetNoteByID(ctx context.Context, noteID uuid.UUID, userID uuid.UUID) (*Note, error)
- func (s *NotesService) GetNotesSince(ctx context.Context, userID uuid.UUID, since *time.Time, cursor *NoteCursor, ...) ([]Note, []NoteTombstone, bool, error)
- func (s *NotesService) PurgeNote(ctx context.Context, noteID uuid.UUID, userID uuid.UUID) error
- func (s *NotesService) RestoreNote(ctx context.Context, noteID uuid.UUID, userID uuid.UUID) (*Note, error)
- func (s *NotesService) TrashNote(ctx context.Context, noteID uuid.UUID, userID uuid.UUID) error
- func (s *NotesService) UpsertNote(ctx context.Context, userID uuid.UUID, noteID uuid.UUID, ...) (*Note, error)
- type TombstoneResponse
- type UpsertNoteRequest
- type UserPlan
Constants ¶
This section is empty.
Variables ¶
var ErrNoteNotFound = errors.New("note not found")
var ErrQuotaExceeded = errors.New("quota exceeded")
ErrQuotaExceeded is returned by UpsertNote when a free-plan user attempts to create a new note beyond their plan limit.
Functions ¶
This section is empty.
Types ¶
type ConflictError ¶ added in v0.2.0
type ConflictError struct {
ServerNote *Note
}
ConflictError is returned when the server's stored version is newer than the client's base version. It carries the current server note so the handler can return it in the 409 body, allowing the client to resolve the conflict and re-push without an extra round trip.
func (*ConflictError) Error ¶ added in v0.2.0
func (e *ConflictError) Error() string
type Note ¶
type Note struct {
ID uuid.UUID `gorm:"column:note_id;primaryKey;type:uuid"`
UserID uuid.UUID `gorm:"column:user_id;type:uuid;not null;index:idx_user_updated,priority:1"`
User users.User `gorm:"constraint:OnDelete:CASCADE;"`
EncryptedPayload []byte `gorm:"column:encrypted_payload;type:bytea;not null"`
CreatedAt time.Time `gorm:"autoCreateTime"`
UpdatedAt time.Time `gorm:"autoUpdateTime;index:idx_user_updated,priority:2"`
TrashedAt *time.Time `gorm:"column:trashed_at;index"`
}
type NoteCursor ¶
type NoteCursor struct {
AfterUpdatedAt time.Time `json:"updated_at"`
AfterNoteID uuid.UUID `json:"note_id"`
}
NoteCursor is the position marker for cursor-based pagination of notes, ordered by (updated_at ASC, note_id ASC).
type NoteResponse ¶
type NoteTombstone ¶
type NotesHandler ¶
type NotesHandler struct {
// contains filtered or unexported fields
}
func NewNotesHandler ¶
func NewNotesHandler(service service) *NotesHandler
func (*NotesHandler) Purge ¶ added in v0.2.0
func (h *NotesHandler) Purge(c *echo.Context) error
Purge hard-deletes a note and creates a tombstone for sync clients.
func (*NotesHandler) Restore ¶ added in v0.2.0
func (h *NotesHandler) Restore(c *echo.Context) error
Restore clears the trashed_at timestamp on a note and returns the updated note.
type NotesListResponse ¶
type NotesListResponse struct {
Notes []NoteResponse `json:"notes"`
Tombstones []TombstoneResponse `json:"tombstones"`
NextCursor *string `json:"next_cursor"` // non-null when more pages exist
ServerTime *time.Time `json:"server_time"`
}
type NotesRepository ¶
type NotesRepository struct {
// contains filtered or unexported fields
}
func NewNotesRepository ¶
func NewNotesRepository(db *gorm.DB) *NotesRepository
func (*NotesRepository) CountLiveNotes ¶
CountLiveNotes returns the number of non-trashed notes for a user. Trashed notes (trashed_at IS NOT NULL) are excluded from the count so that a trashed note does not consume quota.
func (*NotesRepository) FindAllPaginated ¶
func (r *NotesRepository) FindAllPaginated(ctx context.Context, userID uuid.UUID, limit int, afterUpdatedAt time.Time, afterNoteID uuid.UUID) ([]Note, error)
FindAllPaginated returns up to limit notes for the user, ordered by (updated_at ASC, note_id ASC). Pass zero values for afterUpdatedAt and afterNoteID to start from the beginning.
func (*NotesRepository) FindAllTombstones ¶
func (r *NotesRepository) FindAllTombstones(ctx context.Context, userID uuid.UUID) ([]NoteTombstone, error)
func (*NotesRepository) FindTombstonesSince ¶
func (r *NotesRepository) FindTombstonesSince(ctx context.Context, userID uuid.UUID, since time.Time) ([]NoteTombstone, error)
func (*NotesRepository) FindTombstonesSincePaginated ¶ added in v0.2.0
func (r *NotesRepository) FindTombstonesSincePaginated(ctx context.Context, userID uuid.UUID, since time.Time, limit int, afterDeletedAt time.Time, afterNoteID uuid.UUID) ([]NoteTombstone, error)
FindTombstonesSincePaginated returns up to limit tombstones for the user whose deleted_at is strictly greater than since, ordered by (deleted_at ASC, note_id ASC). Pass zero values for afterDeletedAt and afterNoteID to start from the first page. Pass the last page's deleted_at and note_id to continue from a previous page.
func (*NotesRepository) FindUpdatedSince ¶
func (*NotesRepository) FindUpdatedSincePaginated ¶ added in v0.2.0
func (r *NotesRepository) FindUpdatedSincePaginated(ctx context.Context, userID uuid.UUID, since time.Time, limit int, afterUpdatedAt time.Time, afterNoteID uuid.UUID) ([]Note, error)
FindUpdatedSincePaginated returns up to limit notes for the user whose updated_at is strictly greater than since, ordered by (updated_at ASC, note_id ASC). Pass zero values for afterUpdatedAt and afterNoteID to start from the first page. Pass the last page's updated_at and note_id to continue from a previous page.
func (*NotesRepository) Purge ¶ added in v0.2.0
Purge hard-deletes a note row and creates a NoteTombstone within a single transaction. Returns ErrNoteNotFound if no matching row exists for the given noteID and userID.
func (*NotesRepository) PurgeExpiredTombstones ¶
func (*NotesRepository) Restore ¶ added in v0.2.0
Restore clears trashed_at on a note, making it live again. Returns ErrNoteNotFound if no matching row exists for the given noteID and userID.
type NotesService ¶
type NotesService struct {
// contains filtered or unexported fields
}
NotesService implements business logic for note operations.
func NewNotesService ¶
func NewNotesService(repository repository, usersRepo usersRepository, freeNoteLimit int) *NotesService
NewNotesService constructs a NotesService. usersRepo is used to look up user plans for quota enforcement. freeNoteLimit is the maximum number of live notes allowed for free-plan users.
func (*NotesService) GetNoteByID ¶
func (*NotesService) GetNotesSince ¶
func (s *NotesService) GetNotesSince(ctx context.Context, userID uuid.UUID, since *time.Time, cursor *NoteCursor, limit int) ([]Note, []NoteTombstone, bool, error)
GetNotesSince returns notes and tombstones for the user.
When since is non-nil (delta sync), notes updated after that time are returned paginated using cursor-based keyset pagination ordered by (updated_at ASC, note_id ASC). Tombstones are likewise paginated by (deleted_at ASC, note_id ASC). Pass a non-nil cursor to continue from a previous page. hasMore is true when there are additional pages to fetch.
When since is nil (full sync), notes are paginated using the same cursor-based mechanism but across all notes, and all tombstones are returned on the first page only.
func (*NotesService) PurgeNote ¶ added in v0.2.0
PurgeNote hard-deletes a note and creates a tombstone for sync clients.
func (*NotesService) RestoreNote ¶ added in v0.2.0
func (s *NotesService) RestoreNote(ctx context.Context, noteID uuid.UUID, userID uuid.UUID) (*Note, error)
RestoreNote clears the trashed_at timestamp and returns the updated note.
func (*NotesService) TrashNote ¶ added in v0.2.0
TrashNote soft-deletes a note by setting its trashed_at timestamp.
func (*NotesService) UpsertNote ¶
func (s *NotesService) UpsertNote(ctx context.Context, userID uuid.UUID, noteID uuid.UUID, encryptedPayload []byte, baseVersion *time.Time) (*Note, error)
UpsertNote creates or updates a note for the given user.
When baseVersion is non-nil, the operation is treated as a versioned update: a conflict check is performed and quota is never enforced (the user is not adding a new note). When baseVersion is nil, the method first checks whether the note already exists:
- If it exists, this is a re-push of an existing note — quota is not enforced and no conflict check is applied.
- If it does not exist, this is a new-note create — quota is enforced for free-plan users before the upsert is attempted.