148 lines
3.2 KiB
Go
148 lines
3.2 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"sync"
|
|
"time"
|
|
|
|
notesv1 "backend/gen/notes/v1"
|
|
|
|
"github.com/google/uuid"
|
|
"google.golang.org/grpc/codes"
|
|
"google.golang.org/grpc/status"
|
|
"google.golang.org/protobuf/types/known/emptypb"
|
|
"google.golang.org/protobuf/types/known/timestamppb"
|
|
)
|
|
|
|
// NoteService — реализация gRPC сервиса, сгенерированного из proto.
|
|
// Типы notesv1.Note, notesv1.CreateNoteRequest и т.д. —
|
|
// это всё автоматически сгенерированные из proto файла структуры.
|
|
// Если ты изменишь proto (добавишь поле, переименуешь) и
|
|
// перегенерируешь — этот код перестанет компилироваться,
|
|
// пока ты не обновишь реализацию. Вот в чём магия.
|
|
type NoteService struct {
|
|
notesv1.UnimplementedNoteServiceServer
|
|
mu sync.RWMutex
|
|
notes map[string]*notesv1.Note
|
|
}
|
|
|
|
func NewNoteService() *NoteService {
|
|
return &NoteService{
|
|
notes: make(map[string]*notesv1.Note),
|
|
}
|
|
}
|
|
|
|
func (s *NoteService) CreateNote(
|
|
ctx context.Context,
|
|
req *notesv1.CreateNoteRequest,
|
|
) (*notesv1.Note, error) {
|
|
if req.Title == "" {
|
|
return nil, status.Error(codes.InvalidArgument, "title is required")
|
|
}
|
|
|
|
now := timestamppb.New(time.Now())
|
|
note := ¬esv1.Note{
|
|
Id: uuid.New().String(),
|
|
Title: req.Title,
|
|
Content: req.Content,
|
|
CreatedAt: now,
|
|
UpdatedAt: now,
|
|
}
|
|
|
|
s.mu.Lock()
|
|
s.notes[note.Id] = note
|
|
s.mu.Unlock()
|
|
|
|
return note, nil
|
|
}
|
|
|
|
func (s *NoteService) GetNote(
|
|
ctx context.Context,
|
|
req *notesv1.GetNoteRequest,
|
|
) (*notesv1.Note, error) {
|
|
s.mu.RLock()
|
|
note, ok := s.notes[req.Id]
|
|
s.mu.RUnlock()
|
|
|
|
if !ok {
|
|
return nil, status.Errorf(codes.NotFound, "note %s not found", req.Id)
|
|
}
|
|
return note, nil
|
|
}
|
|
|
|
func (s *NoteService) UpdateNote(
|
|
ctx context.Context,
|
|
req *notesv1.UpdateNoteRequest,
|
|
) (*notesv1.Note, error) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
note, ok := s.notes[req.Id]
|
|
if !ok {
|
|
return nil, status.Errorf(codes.NotFound, "note %s not found", req.Id)
|
|
}
|
|
|
|
note.Title = req.Title
|
|
note.Content = req.Content
|
|
note.UpdatedAt = timestamppb.New(time.Now())
|
|
|
|
return note, nil
|
|
}
|
|
|
|
func (s *NoteService) DeleteNote(
|
|
ctx context.Context,
|
|
req *notesv1.DeleteNoteRequest,
|
|
) (*emptypb.Empty, error) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
if _, ok := s.notes[req.Id]; !ok {
|
|
return nil, status.Errorf(codes.NotFound, "note %s not found", req.Id)
|
|
}
|
|
|
|
delete(s.notes, req.Id)
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
func (s *NoteService) ListNotes(
|
|
ctx context.Context,
|
|
req *notesv1.ListNotesRequest,
|
|
) (*notesv1.ListNotesResponse, error) {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
|
|
all := make([]*notesv1.Note, 0, len(s.notes))
|
|
for _, n := range s.notes {
|
|
all = append(all, n)
|
|
}
|
|
|
|
// Простая пагинация
|
|
total := int32(len(all))
|
|
pageSize := req.PageSize
|
|
if pageSize <= 0 {
|
|
pageSize = 20
|
|
}
|
|
page := req.Page
|
|
if page <= 0 {
|
|
page = 1
|
|
}
|
|
|
|
start := (page - 1) * pageSize
|
|
if start >= total {
|
|
return ¬esv1.ListNotesResponse{
|
|
Notes: []*notesv1.Note{},
|
|
TotalCount: total,
|
|
}, nil
|
|
}
|
|
|
|
end := start + pageSize
|
|
if end > total {
|
|
end = total
|
|
}
|
|
|
|
return ¬esv1.ListNotesResponse{
|
|
Notes: all[start:end],
|
|
TotalCount: total,
|
|
}, nil
|
|
}
|