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 }