package grpcserver import ( "context" "errors" "log/slog" "time" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "google.golang.org/protobuf/types/known/timestamppb" pb "git.wntrmute.dev/mc/mcns/gen/mcns/v1" "git.wntrmute.dev/mc/mcns/internal/db" ) type recordService struct { pb.UnimplementedRecordServiceServer db *db.DB logger *slog.Logger } func (s *recordService) ListRecords(_ context.Context, req *pb.ListRecordsRequest) (*pb.ListRecordsResponse, error) { if req.Zone == "" { return nil, status.Error(codes.InvalidArgument, "zone is required") } records, err := s.db.ListRecords(req.Zone, req.Name, req.Type) if errors.Is(err, db.ErrNotFound) { return nil, status.Error(codes.NotFound, "zone not found") } if err != nil { return nil, status.Error(codes.Internal, "failed to list records") } resp := &pb.ListRecordsResponse{} for _, r := range records { resp.Records = append(resp.Records, s.recordToProto(r)) } return resp, nil } func (s *recordService) GetRecord(_ context.Context, req *pb.GetRecordRequest) (*pb.Record, error) { if req.Id <= 0 { return nil, status.Error(codes.InvalidArgument, "id must be positive") } record, err := s.db.GetRecord(req.Id) if errors.Is(err, db.ErrNotFound) { return nil, status.Error(codes.NotFound, "record not found") } if err != nil { return nil, status.Error(codes.Internal, "failed to get record") } return s.recordToProto(*record), nil } func (s *recordService) CreateRecord(_ context.Context, req *pb.CreateRecordRequest) (*pb.Record, error) { if req.Zone == "" { return nil, status.Error(codes.InvalidArgument, "zone is required") } if req.Name == "" { return nil, status.Error(codes.InvalidArgument, "name is required") } if req.Type == "" { return nil, status.Error(codes.InvalidArgument, "type is required") } if req.Value == "" { return nil, status.Error(codes.InvalidArgument, "value is required") } record, err := s.db.CreateRecord(req.Zone, req.Name, req.Type, req.Value, int(req.Ttl)) if errors.Is(err, db.ErrNotFound) { return nil, status.Error(codes.NotFound, "zone not found") } if errors.Is(err, db.ErrConflict) { return nil, status.Error(codes.AlreadyExists, err.Error()) } if err != nil { return nil, status.Error(codes.InvalidArgument, err.Error()) } return s.recordToProto(*record), nil } func (s *recordService) UpdateRecord(_ context.Context, req *pb.UpdateRecordRequest) (*pb.Record, error) { if req.Id <= 0 { return nil, status.Error(codes.InvalidArgument, "id must be positive") } if req.Name == "" { return nil, status.Error(codes.InvalidArgument, "name is required") } if req.Type == "" { return nil, status.Error(codes.InvalidArgument, "type is required") } if req.Value == "" { return nil, status.Error(codes.InvalidArgument, "value is required") } record, err := s.db.UpdateRecord(req.Id, req.Name, req.Type, req.Value, int(req.Ttl)) if errors.Is(err, db.ErrNotFound) { return nil, status.Error(codes.NotFound, "record not found") } if errors.Is(err, db.ErrConflict) { return nil, status.Error(codes.AlreadyExists, err.Error()) } if err != nil { return nil, status.Error(codes.InvalidArgument, err.Error()) } return s.recordToProto(*record), nil } func (s *recordService) DeleteRecord(_ context.Context, req *pb.DeleteRecordRequest) (*pb.DeleteRecordResponse, error) { if req.Id <= 0 { return nil, status.Error(codes.InvalidArgument, "id must be positive") } err := s.db.DeleteRecord(req.Id) if errors.Is(err, db.ErrNotFound) { return nil, status.Error(codes.NotFound, "record not found") } if err != nil { return nil, status.Error(codes.Internal, "failed to delete record") } return &pb.DeleteRecordResponse{}, nil } func (s *recordService) recordToProto(r db.Record) *pb.Record { return &pb.Record{ Id: r.ID, Zone: r.ZoneName, Name: r.Name, Type: r.Type, Value: r.Value, Ttl: int32(r.TTL), CreatedAt: s.parseRecordTimestamp(r.CreatedAt), UpdatedAt: s.parseRecordTimestamp(r.UpdatedAt), } } func (s *recordService) parseRecordTimestamp(v string) *timestamppb.Timestamp { t, err := parseTime(v) if err != nil { s.logger.Warn("failed to parse record timestamp", "value", v, "error", err) return nil } return timestamppb.New(t) } func parseTime(s string) (time.Time, error) { return time.Parse("2006-01-02T15:04:05Z", s) }