Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement REST get segment, list segments, create segment #1418

Merged
merged 4 commits into from
Jan 8, 2025
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
203 changes: 203 additions & 0 deletions api-description/web-api.swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3268,6 +3268,194 @@ paths:
type: string
tags:
- push
/v1/segment:
get:
summary: Get
description: Get a segment.
operationId: web.v1.segment.get
responses:
"200":
description: A successful response.
schema:
$ref: '#/definitions/featureGetSegmentResponse'
"400":
description: Returned for bad requests that may have failed validation.
schema:
$ref: '#/definitions/googlerpcStatus'
examples:
application/json:
code: 3
details: []
message: invalid arguments error
"401":
description: Request could not be authenticated (authentication required).
schema:
$ref: '#/definitions/googlerpcStatus'
examples:
application/json:
code: 16
details: []
message: not authenticated
"503":
description: Returned for internal errors.
schema:
$ref: '#/definitions/googlerpcStatus'
examples:
application/json:
code: 13
details: []
message: internal
default:
description: An unexpected error response.
schema:
$ref: '#/definitions/googlerpcStatus'
parameters:
- name: id
in: query
required: true
type: string
- name: environmentId
in: query
required: true
type: string
tags:
- segment
post:
summary: Create
description: Create a segment.
operationId: web.v1.segment.create
responses:
"200":
description: A successful response.
schema:
$ref: '#/definitions/featureCreateSegmentResponse'
"400":
description: Returned for bad requests that may have failed validation.
schema:
$ref: '#/definitions/googlerpcStatus'
examples:
application/json:
code: 3
details: []
message: invalid arguments error
"401":
description: Request could not be authenticated (authentication required).
schema:
$ref: '#/definitions/googlerpcStatus'
examples:
application/json:
code: 16
details: []
message: not authenticated
"503":
description: Returned for internal errors.
schema:
$ref: '#/definitions/googlerpcStatus'
examples:
application/json:
code: 13
details: []
message: internal
default:
description: An unexpected error response.
schema:
$ref: '#/definitions/googlerpcStatus'
parameters:
- name: body
in: body
required: true
schema:
$ref: '#/definitions/featureCreateSegmentRequest'
tags:
- segment
/v1/segments:
get:
summary: List
description: List segments.
operationId: web.v1.segment.list
responses:
"200":
description: A successful response.
schema:
$ref: '#/definitions/featureListSegmentsResponse'
"400":
description: Returned for bad requests that may have failed validation.
schema:
$ref: '#/definitions/googlerpcStatus'
examples:
application/json:
code: 3
details: []
message: invalid arguments error
"401":
description: Request could not be authenticated (authentication required).
schema:
$ref: '#/definitions/googlerpcStatus'
examples:
application/json:
code: 16
details: []
message: not authenticated
"503":
description: Returned for internal errors.
schema:
$ref: '#/definitions/googlerpcStatus'
examples:
application/json:
code: 13
details: []
message: internal
default:
description: An unexpected error response.
schema:
$ref: '#/definitions/googlerpcStatus'
parameters:
- name: pageSize
in: query
required: false
type: string
format: int64
- name: cursor
in: query
required: false
type: string
- name: orderBy
in: query
required: false
type: string
enum:
- DEFAULT
- NAME
- CREATED_AT
- UPDATED_AT
default: DEFAULT
- name: orderDirection
in: query
required: false
type: string
enum:
- ASC
- DESC
default: ASC
- name: searchKeyword
in: query
required: false
type: string
- name: status
in: query
required: false
type: integer
format: int32
- name: isInUseStatus
in: query
required: false
type: boolean
- name: environmentId
in: query
required: true
type: string
tags:
- segment
/v1/subscription:
get:
summary: Get
Expand Down Expand Up @@ -5155,6 +5343,21 @@ definitions:
type: string
description:
type: string
featureCreateSegmentRequest:
type: object
properties:
command:
$ref: '#/definitions/featureCreateSegmentCommand'
description: deprecated
name:
type: string
environmentId:
type: string
description:
type: string
required:
- name
- environmentId
featureCreateSegmentResponse:
type: object
properties:
Expand Down
2 changes: 1 addition & 1 deletion manifests/bucketeer/charts/api/values.yaml

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion manifests/bucketeer/charts/web/values.yaml

Large diffs are not rendered by default.

125 changes: 124 additions & 1 deletion pkg/feature/api/segment.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,11 @@ import (
"errors"
"strconv"

"github.com/jinzhu/copier"
"go.uber.org/zap"
"google.golang.org/genproto/googleapis/rpc/errdetails"

domainevent "github.com/bucketeer-io/bucketeer/pkg/domainevent/domain"
"github.com/bucketeer-io/bucketeer/pkg/feature/command"
"github.com/bucketeer-io/bucketeer/pkg/feature/domain"
v2fs "github.com/bucketeer-io/bucketeer/pkg/feature/storage/v2"
Expand Down Expand Up @@ -49,6 +51,9 @@ func (s *FeatureService) CreateSegment(
if err != nil {
return nil, err
}
if req.Command == nil {
return s.createSegmentNoCommand(ctx, req, editor, localizer)
}
if err = validateCreateSegmentRequest(req.Command, localizer); err != nil {
s.logger.Error(
"Invalid argument",
Expand Down Expand Up @@ -128,7 +133,125 @@ func (s *FeatureService) CreateSegment(
return nil
})
if err != nil {
if err == v2fs.ErrSegmentAlreadyExists {
if errors.Is(err, v2fs.ErrSegmentAlreadyExists) {
dt, err := statusAlreadyExists.WithDetails(&errdetails.LocalizedMessage{
Locale: localizer.GetLocale(),
Message: localizer.MustLocalize(locale.AlreadyExistsError),
})
if err != nil {
return nil, statusInternal.Err()
}
return nil, dt.Err()
}
s.logger.Error(
"Failed to create segment",
log.FieldsFromImcomingContext(ctx).AddFields(
zap.Error(err),
zap.String("environmentId", req.EnvironmentId),
)...,
)
dt, err := statusInternal.WithDetails(&errdetails.LocalizedMessage{
Locale: localizer.GetLocale(),
Message: localizer.MustLocalize(locale.InternalServerError),
})
if err != nil {
return nil, statusInternal.Err()
}
return nil, dt.Err()
}
return &featureproto.CreateSegmentResponse{
Segment: segment.Segment,
}, nil
}

func (s *FeatureService) createSegmentNoCommand(
ctx context.Context,
req *featureproto.CreateSegmentRequest,
editor *eventproto.Editor,
localizer locale.Localizer,
) (*featureproto.CreateSegmentResponse, error) {
if err := validateCreateSegmentNoCommandRequest(req, localizer); err != nil {
s.logger.Info(
"Invalid argument",
log.FieldsFromImcomingContext(ctx).AddFields(
zap.Error(err),
zap.String("environmentId", req.EnvironmentId),
)...,
)
return nil, err
}
segment, err := domain.NewSegment(req.Name, req.Description)
if err != nil {
s.logger.Error(
"Failed to create segment",
log.FieldsFromImcomingContext(ctx).AddFields(
zap.Error(err),
zap.String("environmentId", req.EnvironmentId),
)...,
)
dt, err := statusInternal.WithDetails(&errdetails.LocalizedMessage{
Locale: localizer.GetLocale(),
Message: localizer.MustLocalize(locale.InternalServerError),
})
if err != nil {
return nil, statusInternal.Err()
}
return nil, dt.Err()
}
tx, err := s.mysqlClient.BeginTx(ctx)
if err != nil {
s.logger.Error(
"Failed to begin transaction",
log.FieldsFromImcomingContext(ctx).AddFields(
zap.Error(err),
)...,
)
dt, err := statusInternal.WithDetails(&errdetails.LocalizedMessage{
Locale: localizer.GetLocale(),
Message: localizer.MustLocalize(locale.InternalServerError),
})
if err != nil {
return nil, statusInternal.Err()
}
return nil, dt.Err()
}
err = s.mysqlClient.RunInTransaction(ctx, tx, func() error {
segmentStorage := v2fs.NewSegmentStorage(tx)
if err := segmentStorage.CreateSegment(ctx, segment, req.EnvironmentId); err != nil {
s.logger.Error(
"Failed to store segment",
log.FieldsFromImcomingContext(ctx).AddFields(
zap.Error(err),
zap.String("environmentId", req.EnvironmentId),
)...,
)
return err
}
prev := &domain.Segment{}
if err := copier.Copy(prev, segment); err != nil {
return err
}
e, err := domainevent.NewEvent(
editor,
eventproto.Event_SEGMENT,
segment.Id,
eventproto.Event_SEGMENT_CREATED,
&eventproto.SegmentCreatedEvent{
Id: segment.Id,
Name: segment.Name,
Description: segment.Description,
},
req.EnvironmentId,
segment.Segment,
prev,
)
if err != nil {
return nil
}
return s.domainPublisher.Publish(ctx, e)
})
if err != nil {
if errors.Is(err, v2fs.ErrSegmentAlreadyExists) {
dt, err := statusAlreadyExists.WithDetails(&errdetails.LocalizedMessage{
Locale: localizer.GetLocale(),
Message: localizer.MustLocalize(locale.AlreadyExistsError),
Expand Down
Loading
Loading