Add create organization

Signed-off-by: gearnode <bryan@frimin.fr>
This commit is contained in:
gearnode
2025-02-26 10:05:56 +01:00
parent 3ee3b37dac
commit b8eb0198db
19 changed files with 2213 additions and 389 deletions

View File

@@ -85,6 +85,16 @@ type PageInfo {
endCursor: CursorKey
}
type OrganizationConnection {
edges: [OrganizationEdge!]!
pageInfo: PageInfo!
}
type OrganizationEdge {
cursor: CursorKey!
node: Organization!
}
type Organization implements Node {
id: ID!
name: String!
@@ -342,13 +352,17 @@ type EvidenceStateTransition {
updatedAt: Datetime!
}
# Authentication types
type User {
type User implements Node {
id: ID!
fullName: String!
email: String!
organizations: [Organization!]! @goField(forceResolver: true)
organizations(
first: Int
after: CursorKey
last: Int
before: CursorKey
): OrganizationConnection! @goField(forceResolver: true)
createdAt: Datetime!
updatedAt: Datetime!
@@ -371,6 +385,12 @@ type Mutation {
createPeople(input: CreatePeopleInput!): CreatePeoplePayload!
updatePeople(input: UpdatePeopleInput!): People!
deletePeople(input: DeletePeopleInput!): DeletePeoplePayload!
createOrganization(
input: CreateOrganizationInput!
): CreateOrganizationPayload!
deleteOrganization(
input: DeleteOrganizationInput!
): DeleteOrganizationPayload!
}
input CreateVendorInput {
@@ -474,3 +494,19 @@ type DeleteVendorPayload {
type DeletePeoplePayload {
deletedPeopleId: ID!
}
input CreateOrganizationInput {
name: String!
}
input DeleteOrganizationInput {
organizationId: ID!
}
type CreateOrganizationPayload {
organizationEdge: OrganizationEdge!
}
type DeleteOrganizationPayload {
deletedOrganizationId: ID!
}

File diff suppressed because it is too large Load Diff

View File

@@ -27,3 +27,9 @@ func NewOrganization(o *coredata.Organization) *Organization {
UpdatedAt: o.UpdatedAt,
}
}
func NewOrganizationEdge(o *coredata.Organization) *OrganizationEdge {
return &OrganizationEdge{
Node: NewOrganization(o),
}
}

View File

@@ -59,6 +59,14 @@ type ControlStateTransitionEdge struct {
Node *ControlStateTransition `json:"node"`
}
type CreateOrganizationInput struct {
Name string `json:"name"`
}
type CreateOrganizationPayload struct {
OrganizationEdge *OrganizationEdge `json:"organizationEdge"`
}
type CreatePeopleInput struct {
OrganizationID gid.GID `json:"organizationId"`
FullName string `json:"fullName"`
@@ -88,6 +96,14 @@ type CreateVendorPayload struct {
VendorEdge *VendorEdge `json:"vendorEdge"`
}
type DeleteOrganizationInput struct {
OrganizationID gid.GID `json:"organizationId"`
}
type DeleteOrganizationPayload struct {
DeletedOrganizationID gid.GID `json:"deletedOrganizationId"`
}
type DeletePeopleInput struct {
PeopleID gid.GID `json:"peopleId"`
}
@@ -186,6 +202,16 @@ type Organization struct {
func (Organization) IsNode() {}
func (this Organization) GetID() gid.GID { return this.ID }
type OrganizationConnection struct {
Edges []*OrganizationEdge `json:"edges"`
PageInfo *PageInfo `json:"pageInfo"`
}
type OrganizationEdge struct {
Cursor page.CursorKey `json:"cursor"`
Node *Organization `json:"node"`
}
type PageInfo struct {
HasNextPage bool `json:"hasNextPage"`
HasPreviousPage bool `json:"hasPreviousPage"`
@@ -292,14 +318,17 @@ type UpdateVendorInput struct {
}
type User struct {
ID gid.GID `json:"id"`
FullName string `json:"fullName"`
Email string `json:"email"`
Organizations []*Organization `json:"organizations"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
ID gid.GID `json:"id"`
FullName string `json:"fullName"`
Email string `json:"email"`
Organizations *OrganizationConnection `json:"organizations"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}
func (User) IsNode() {}
func (this User) GetID() gid.GID { return this.ID }
type Vendor struct {
ID gid.GID `json:"id"`
Name string `json:"name"`

View File

@@ -169,6 +169,30 @@ func (r *mutationResolver) DeletePeople(ctx context.Context, input types.DeleteP
}, nil
}
// CreateOrganization is the resolver for the createOrganization field.
func (r *mutationResolver) CreateOrganization(ctx context.Context, input types.CreateOrganizationInput) (*types.CreateOrganizationPayload, error) {
organization, err := r.proboSvc.CreateOrganization(ctx, probo.CreateOrganizationRequest{
Name: input.Name,
})
if err != nil {
return nil, fmt.Errorf("cannot create organization: %w", err)
}
err = r.usrmgrSvc.AddUserToOrganization(ctx, UserFromContext(ctx).ID, organization.ID)
if err != nil {
return nil, fmt.Errorf("cannot add user to organization: %w", err)
}
return &types.CreateOrganizationPayload{
OrganizationEdge: types.NewOrganizationEdge(organization),
}, nil
}
// DeleteOrganization is the resolver for the deleteOrganization field.
func (r *mutationResolver) DeleteOrganization(ctx context.Context, input types.DeleteOrganizationInput) (*types.DeleteOrganizationPayload, error) {
panic(fmt.Errorf("not implemented: DeleteOrganization - deleteOrganization"))
}
// Frameworks is the resolver for the frameworks field.
func (r *organizationResolver) Frameworks(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.FrameworkConnection, error) {
cursor := types.NewCursor(first, after, last, before)
@@ -300,29 +324,35 @@ func (r *taskResolver) Evidences(ctx context.Context, obj *types.Task, first *in
}
// Organizations is the resolver for the organizations field.
func (r *userResolver) Organizations(ctx context.Context, obj *types.User) ([]*types.Organization, error) {
func (r *userResolver) Organizations(ctx context.Context, obj *types.User, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.OrganizationConnection, error) {
// Get the user's organization IDs
organizationIDs, err := r.usrmgrSvc.GetUserOrganizations(ctx, obj.ID)
if err != nil {
return nil, fmt.Errorf("failed to get user organizations: %w", err)
}
// If the user doesn't have any organizations, return an empty slice
// If the user doesn't have any organizations, return an empty connection
if len(organizationIDs) == 0 {
return []*types.Organization{}, nil
return &types.OrganizationConnection{
Edges: []*types.OrganizationEdge{},
PageInfo: &types.PageInfo{},
}, nil
}
// Get the organization details for each organization ID
var organizations []*types.Organization
var edges []*types.OrganizationEdge
for _, organizationID := range organizationIDs {
organization, err := r.proboSvc.GetOrganization(ctx, organizationID)
if err != nil {
return nil, fmt.Errorf("failed to get organization details: %w", err)
}
organizations = append(organizations, types.NewOrganization(organization))
edges = append(edges, types.NewOrganizationEdge(organization))
}
return organizations, nil
return &types.OrganizationConnection{
Edges: edges,
PageInfo: &types.PageInfo{},
}, nil
}
// Control returns schema.ControlResolver implementation.

View File

@@ -29,7 +29,7 @@ type (
Organization struct {
ID gid.GID
Name string
LogoURL string
LogoURL string
CreatedAt time.Time
UpdatedAt time.Time
}
@@ -82,3 +82,33 @@ LIMIT 1;
return nil
}
func (o *Organization) Insert(
ctx context.Context,
conn pg.Conn,
) error {
q := `
INSERT INTO organizations (
id,
name,
logo_url,
created_at,
updated_at
) VALUES (@id, @name, @logo_url, @created_at, @updated_at)
`
args := pgx.NamedArgs{
"id": o.ID,
"name": o.Name,
"logo_url": o.LogoURL,
"created_at": o.CreatedAt,
"updated_at": o.UpdatedAt,
}
_, err := conn.Exec(ctx, q, args)
if err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,66 @@
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
package probo
import (
"context"
"fmt"
"time"
"github.com/getprobo/probo/pkg/gid"
"github.com/getprobo/probo/pkg/probo/coredata"
"go.gearno.de/kit/pg"
)
type (
CreateOrganizationRequest struct {
Name string
}
)
func (s Service) CreateOrganization(
ctx context.Context,
req CreateOrganizationRequest,
) (*coredata.Organization, error) {
now := time.Now()
organizationID, err := gid.NewGID(coredata.OrganizationEntityType)
if err != nil {
return nil, fmt.Errorf("cannot create organization global id: %w", err)
}
organization := &coredata.Organization{
ID: organizationID,
Name: req.Name,
CreatedAt: now,
UpdatedAt: now,
}
err = s.pg.WithConn(
ctx,
func(conn pg.Conn) error {
if err := organization.Insert(ctx, conn); err != nil {
return fmt.Errorf("cannot insert control: %w", err)
}
return nil
},
)
if err != nil {
return nil, err
}
return organization, nil
}