Skip to content

Commit b7b576b

Browse files
authored
feat: migrate Resource APIs to Connect RPC (#1174)
1 parent 9ef973b commit b7b576b

File tree

3 files changed

+997
-0
lines changed

3 files changed

+997
-0
lines changed

internal/api/v1beta1connect/errors.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,4 +54,5 @@ var (
5454
ErrOrgNotFound = errors.New("org doesn't exist")
5555
ErrGroupMinOwnerCount = errors.New("group must have at least one owner, consider adding another owner before removing")
5656
ErrPortalChangesKycCompleted = errors.New("customer portal changes not allowed: organization kyc completed")
57+
ErrResourceNotFound = errors.New("resource doesn't exist")
5758
)

internal/api/v1beta1connect/resource.go

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,14 @@ package v1beta1connect
22

33
import (
44
"context"
5+
"errors"
56

67
"connectrpc.com/connect"
8+
"github.com/raystack/frontier/core/audit"
79
"github.com/raystack/frontier/core/resource"
10+
"github.com/raystack/frontier/core/user"
811
"github.com/raystack/frontier/internal/bootstrap/schema"
12+
"github.com/raystack/frontier/pkg/metadata"
913
frontierv1beta1 "github.com/raystack/frontier/proto/v1beta1"
1014
"google.golang.org/protobuf/types/known/structpb"
1115
"google.golang.org/protobuf/types/known/timestamppb"
@@ -36,6 +40,205 @@ func (h *ConnectHandler) ListResources(ctx context.Context, request *connect.Req
3640
}), nil
3741
}
3842

43+
func (h *ConnectHandler) ListProjectResources(ctx context.Context, request *connect.Request[frontierv1beta1.ListProjectResourcesRequest]) (*connect.Response[frontierv1beta1.ListProjectResourcesResponse], error) {
44+
var resources []*frontierv1beta1.Resource
45+
namespaceID := schema.ParseNamespaceAliasIfRequired(request.Msg.GetNamespace())
46+
filters := resource.Filter{
47+
NamespaceID: namespaceID,
48+
ProjectID: request.Msg.GetProjectId(),
49+
}
50+
resourcesList, err := h.resourceService.List(ctx, filters)
51+
if err != nil {
52+
return nil, connect.NewError(connect.CodeInternal, ErrInternalServerError)
53+
}
54+
for _, r := range resourcesList {
55+
resourcePB, err := transformResourceToPB(r)
56+
if err != nil {
57+
return nil, connect.NewError(connect.CodeInternal, ErrInternalServerError)
58+
}
59+
resources = append(resources, resourcePB)
60+
}
61+
return connect.NewResponse(&frontierv1beta1.ListProjectResourcesResponse{
62+
Resources: resources,
63+
}), nil
64+
}
65+
66+
func (h *ConnectHandler) CreateProjectResource(ctx context.Context, request *connect.Request[frontierv1beta1.CreateProjectResourceRequest]) (*connect.Response[frontierv1beta1.CreateProjectResourceResponse], error) {
67+
if request.Msg.GetBody() == nil {
68+
return nil, connect.NewError(connect.CodeInvalidArgument, ErrBadRequest)
69+
}
70+
71+
var metaDataMap metadata.Metadata
72+
var err error
73+
if request.Msg.GetBody().GetMetadata() != nil {
74+
metaDataMap = metadata.Build(request.Msg.GetBody().GetMetadata().AsMap())
75+
}
76+
77+
parentProject, err := h.projectService.Get(ctx, request.Msg.GetProjectId())
78+
if err != nil {
79+
return nil, connect.NewError(connect.CodeInternal, ErrInternalServerError)
80+
}
81+
82+
principalType := schema.UserPrincipal
83+
principalID := request.Msg.GetBody().GetPrincipal()
84+
if ns, id, err := schema.SplitNamespaceAndResourceID(request.Msg.GetBody().GetPrincipal()); err == nil {
85+
principalType = ns
86+
principalID = id
87+
}
88+
namespaceID := schema.ParseNamespaceAliasIfRequired(request.Msg.GetBody().GetNamespace())
89+
newResource, err := h.resourceService.Create(ctx, resource.Resource{
90+
ID: request.Msg.GetId(),
91+
Name: request.Msg.GetBody().GetName(),
92+
Title: request.Msg.GetBody().GetTitle(),
93+
ProjectID: parentProject.ID,
94+
NamespaceID: namespaceID,
95+
PrincipalID: principalID,
96+
PrincipalType: principalType,
97+
Metadata: metaDataMap,
98+
})
99+
if err != nil {
100+
switch {
101+
case errors.Is(err, user.ErrInvalidEmail):
102+
return nil, connect.NewError(connect.CodeUnauthenticated, ErrUnauthenticated)
103+
case errors.Is(err, resource.ErrInvalidUUID),
104+
errors.Is(err, resource.ErrInvalidDetail):
105+
return nil, connect.NewError(connect.CodeInvalidArgument, ErrBadRequest)
106+
case errors.Is(err, resource.ErrConflict):
107+
return nil, connect.NewError(connect.CodeAlreadyExists, ErrConflictRequest)
108+
default:
109+
return nil, connect.NewError(connect.CodeInternal, ErrInternalServerError)
110+
}
111+
}
112+
113+
resourcePB, err := transformResourceToPB(newResource)
114+
if err != nil {
115+
return nil, connect.NewError(connect.CodeInternal, ErrInternalServerError)
116+
}
117+
118+
audit.GetAuditor(ctx, parentProject.Organization.ID).Log(audit.ResourceCreatedEvent, audit.Target{
119+
ID: newResource.ID,
120+
Type: newResource.NamespaceID,
121+
})
122+
return connect.NewResponse(&frontierv1beta1.CreateProjectResourceResponse{
123+
Resource: resourcePB,
124+
}), nil
125+
}
126+
127+
func (h *ConnectHandler) GetProjectResource(ctx context.Context, request *connect.Request[frontierv1beta1.GetProjectResourceRequest]) (*connect.Response[frontierv1beta1.GetProjectResourceResponse], error) {
128+
fetchedResource, err := h.resourceService.Get(ctx, request.Msg.GetId())
129+
if err != nil {
130+
switch {
131+
case errors.Is(err, resource.ErrNotExist),
132+
errors.Is(err, resource.ErrInvalidUUID),
133+
errors.Is(err, resource.ErrInvalidID):
134+
return nil, connect.NewError(connect.CodeNotFound, ErrResourceNotFound)
135+
default:
136+
return nil, connect.NewError(connect.CodeInternal, ErrInternalServerError)
137+
}
138+
}
139+
140+
resourcePB, err := transformResourceToPB(fetchedResource)
141+
if err != nil {
142+
return nil, connect.NewError(connect.CodeInternal, ErrInternalServerError)
143+
}
144+
145+
return connect.NewResponse(&frontierv1beta1.GetProjectResourceResponse{
146+
Resource: resourcePB,
147+
}), nil
148+
}
149+
150+
func (h *ConnectHandler) UpdateProjectResource(ctx context.Context, request *connect.Request[frontierv1beta1.UpdateProjectResourceRequest]) (*connect.Response[frontierv1beta1.UpdateProjectResourceResponse], error) {
151+
if request.Msg.GetBody() == nil {
152+
return nil, connect.NewError(connect.CodeInvalidArgument, ErrBadRequest)
153+
}
154+
155+
var metaDataMap metadata.Metadata
156+
var err error
157+
if request.Msg.GetBody().GetMetadata() != nil {
158+
metaDataMap = metadata.Build(request.Msg.GetBody().GetMetadata().AsMap())
159+
}
160+
161+
parentProject, err := h.projectService.Get(ctx, request.Msg.GetProjectId())
162+
if err != nil {
163+
return nil, connect.NewError(connect.CodeInternal, ErrInternalServerError)
164+
}
165+
166+
principalType := schema.UserPrincipal
167+
principalID := request.Msg.GetBody().GetPrincipal()
168+
if ns, id, err := schema.SplitNamespaceAndResourceID(request.Msg.GetBody().GetPrincipal()); err == nil {
169+
principalType = ns
170+
principalID = id
171+
}
172+
namespaceID := schema.ParseNamespaceAliasIfRequired(request.Msg.GetBody().GetNamespace())
173+
updatedResource, err := h.resourceService.Update(ctx, resource.Resource{
174+
ID: request.Msg.GetId(),
175+
ProjectID: parentProject.ID,
176+
NamespaceID: namespaceID,
177+
Name: request.Msg.GetBody().GetName(),
178+
PrincipalID: principalID,
179+
PrincipalType: principalType,
180+
Metadata: metaDataMap,
181+
})
182+
if err != nil {
183+
switch {
184+
case errors.Is(err, resource.ErrNotExist),
185+
errors.Is(err, resource.ErrInvalidUUID),
186+
errors.Is(err, resource.ErrInvalidID):
187+
return nil, connect.NewError(connect.CodeNotFound, ErrResourceNotFound)
188+
case errors.Is(err, resource.ErrInvalidDetail),
189+
errors.Is(err, resource.ErrInvalidURN):
190+
return nil, connect.NewError(connect.CodeInvalidArgument, ErrBadRequest)
191+
case errors.Is(err, resource.ErrConflict):
192+
return nil, connect.NewError(connect.CodeAlreadyExists, ErrConflictRequest)
193+
default:
194+
return nil, connect.NewError(connect.CodeInternal, ErrInternalServerError)
195+
}
196+
}
197+
198+
resourcePB, err := transformResourceToPB(updatedResource)
199+
if err != nil {
200+
return nil, connect.NewError(connect.CodeInternal, ErrInternalServerError)
201+
}
202+
203+
audit.GetAuditor(ctx, parentProject.Organization.ID).Log(audit.ResourceUpdatedEvent, audit.Target{
204+
ID: updatedResource.ID,
205+
Type: updatedResource.NamespaceID,
206+
})
207+
return connect.NewResponse(&frontierv1beta1.UpdateProjectResourceResponse{
208+
Resource: resourcePB,
209+
}), nil
210+
}
211+
212+
func (h *ConnectHandler) DeleteProjectResource(ctx context.Context, request *connect.Request[frontierv1beta1.DeleteProjectResourceRequest]) (*connect.Response[frontierv1beta1.DeleteProjectResourceResponse], error) {
213+
resourceToDel, err := h.resourceService.Get(ctx, request.Msg.GetId())
214+
if err != nil {
215+
switch {
216+
case errors.Is(err, resource.ErrNotExist),
217+
errors.Is(err, resource.ErrInvalidID),
218+
errors.Is(err, resource.ErrInvalidUUID):
219+
return nil, connect.NewError(connect.CodeNotFound, ErrResourceNotFound)
220+
default:
221+
return nil, connect.NewError(connect.CodeInternal, ErrInternalServerError)
222+
}
223+
}
224+
225+
parentProject, err := h.projectService.Get(ctx, resourceToDel.ProjectID)
226+
if err != nil {
227+
return nil, connect.NewError(connect.CodeInternal, ErrInternalServerError)
228+
}
229+
230+
err = h.resourceService.Delete(ctx, resourceToDel.NamespaceID, resourceToDel.ID)
231+
if err != nil {
232+
return nil, connect.NewError(connect.CodeInternal, ErrInternalServerError)
233+
}
234+
235+
audit.GetAuditor(ctx, parentProject.Organization.ID).Log(audit.ResourceDeletedEvent, audit.Target{
236+
ID: request.Msg.GetId(),
237+
Type: resourceToDel.NamespaceID,
238+
})
239+
return connect.NewResponse(&frontierv1beta1.DeleteProjectResourceResponse{}), nil
240+
}
241+
39242
func transformResourceToPB(from resource.Resource) (*frontierv1beta1.Resource, error) {
40243
var metadata *structpb.Struct
41244
var err error

0 commit comments

Comments
 (0)