Embded frontend inside go binary

This will simplify the self hosting of the platform.

Signed-off-by: gearnode <bryan@frimin.fr>
This commit is contained in:
gearnode
2025-03-09 15:30:28 +01:00
parent 2c82dbad39
commit 123a538d82
40 changed files with 527 additions and 262 deletions

View File

@@ -0,0 +1,215 @@
package console_v1
import (
"encoding/json"
"fmt"
"net/http"
"time"
"github.com/getprobo/probo/pkg/gid"
"github.com/getprobo/probo/pkg/usrmgr"
"github.com/go-chi/chi/v5"
)
type (
// RegisterRequest represents the request body for user registration
RegisterRequest struct {
Email string `json:"email"`
Password string `json:"password"`
FullName string `json:"fullName"`
}
// LoginRequest represents the request body for user login
LoginRequest struct {
Email string `json:"email"`
Password string `json:"password"`
}
// AuthResponse represents the response for successful authentication
AuthResponse struct {
User UserResponse `json:"user"`
Session SessionResponse `json:"session"`
}
// UserResponse represents user data in the authentication response
UserResponse struct {
ID gid.GID `json:"id"`
Email string `json:"email"`
FullName string `json:"fullName"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}
// SessionResponse represents session data in the authentication response
SessionResponse struct {
ID gid.GID `json:"id"`
ExpiresAt time.Time `json:"expiresAt"`
}
)
// RegisterHandler handles user registration
func RegisterHandler(usrmgrSvc *usrmgr.Service, authCfg AuthConfig) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// Parse request body
var req RegisterRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "Invalid request body", http.StatusBadRequest)
return
}
// Validate request
if req.Email == "" || req.Password == "" {
http.Error(w, "Email and password are required", http.StatusBadRequest)
return
}
// Register the user
user, err := usrmgrSvc.RegisterUser(
r.Context(),
usrmgr.RegisterUserParams{
Email: req.Email,
Password: req.Password,
FullName: req.FullName,
},
)
if err != nil {
http.Error(w, fmt.Sprintf("Failed to register user: %v", err), http.StatusInternalServerError)
return
}
// Log the user in
session, err := usrmgrSvc.Login(r.Context(), req.Email, req.Password)
if err != nil {
http.Error(w, fmt.Sprintf("Failed to login after registration: %v", err), http.StatusInternalServerError)
return
}
// Set the session cookie
setSessionCookie(w, session.ID.String(), authCfg)
// Return response
resp := AuthResponse{
User: UserResponse{
ID: user.ID,
Email: user.EmailAddress,
FullName: user.FullName,
CreatedAt: user.CreatedAt,
UpdatedAt: user.UpdatedAt,
},
Session: SessionResponse{
ID: session.ID,
ExpiresAt: session.ExpiredAt,
},
}
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(resp); err != nil {
http.Error(w, "Failed to encode response", http.StatusInternalServerError)
return
}
}
}
// LoginHandler handles user login
func LoginHandler(usrmgrSvc *usrmgr.Service, authCfg AuthConfig) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// Parse request body
var req LoginRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "Invalid request body", http.StatusBadRequest)
return
}
// Validate request
if req.Email == "" || req.Password == "" {
http.Error(w, "Email and password are required", http.StatusBadRequest)
return
}
// Login the user
session, err := usrmgrSvc.Login(r.Context(), req.Email, req.Password)
if err != nil {
http.Error(w, fmt.Sprintf("Failed to login: %v", err), http.StatusUnauthorized)
return
}
// Get the user
user, err := usrmgrSvc.GetUserBySession(r.Context(), session.ID)
if err != nil {
http.Error(w, fmt.Sprintf("Failed to get user: %v", err), http.StatusInternalServerError)
return
}
// Set the session cookie
setSessionCookie(w, session.ID.String(), authCfg)
// Return response
resp := AuthResponse{
User: UserResponse{
ID: user.ID,
Email: user.EmailAddress,
FullName: user.FullName,
CreatedAt: user.CreatedAt,
UpdatedAt: user.UpdatedAt,
},
Session: SessionResponse{
ID: session.ID,
ExpiresAt: session.ExpiredAt,
},
}
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(resp); err != nil {
http.Error(w, "Failed to encode response", http.StatusInternalServerError)
return
}
}
}
// LogoutHandler handles user logout
func LogoutHandler(usrmgrSvc *usrmgr.Service, authCfg AuthConfig) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// Get session from cookie
cookie, err := r.Cookie(authCfg.CookieName)
if err != nil || cookie.Value == "" {
http.Error(w, "No active session", http.StatusBadRequest)
return
}
// Verify the cookie signature
originalValue, err := verifyCookieValue(cookie.Value, authCfg.CookieSecret)
if err != nil {
http.Error(w, "Invalid session cookie", http.StatusBadRequest)
return
}
// Parse the session ID
sessionID, err := gid.ParseGID(originalValue)
if err != nil {
http.Error(w, "Invalid session ID", http.StatusBadRequest)
return
}
// Logout the user
err = usrmgrSvc.Logout(r.Context(), sessionID)
if err != nil {
http.Error(w, fmt.Sprintf("Failed to logout: %v", err), http.StatusInternalServerError)
return
}
// Clear the session cookie
clearSessionCookie(w, authCfg)
// Return success response
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]bool{"success": true})
}
}
// RegisterAuthRoutes registers the authentication routes
func RegisterAuthRoutes(r chi.Router, usrmgrSvc *usrmgr.Service, authCfg AuthConfig) {
r.Post("/auth/register", RegisterHandler(usrmgrSvc, authCfg))
r.Post("/auth/login", LoginHandler(usrmgrSvc, authCfg))
r.Post("/auth/logout", LogoutHandler(usrmgrSvc, authCfg))
}

View File

@@ -0,0 +1,38 @@
package console_v1
import (
"net/http"
)
// setSessionCookie sets a session cookie in the response
func setSessionCookie(w http.ResponseWriter, sessionID string, cfg AuthConfig) {
// Sign the session ID
signedValue := signCookieValue(sessionID, cfg.CookieSecret)
cookie := &http.Cookie{
Name: cfg.CookieName,
Value: signedValue,
Path: cfg.CookiePath,
Domain: cfg.CookieDomain,
Secure: cfg.CookieSecure,
HttpOnly: cfg.CookieHTTPOnly,
MaxAge: int(cfg.SessionDuration.Seconds()),
SameSite: http.SameSiteLaxMode,
}
http.SetCookie(w, cookie)
}
// clearSessionCookie clears the session cookie
func clearSessionCookie(w http.ResponseWriter, cfg AuthConfig) {
cookie := &http.Cookie{
Name: cfg.CookieName,
Value: "",
Path: cfg.CookiePath,
Domain: cfg.CookieDomain,
Secure: cfg.CookieSecure,
HttpOnly: cfg.CookieHTTPOnly,
MaxAge: -1,
SameSite: http.SameSiteLaxMode,
}
http.SetCookie(w, cookie)
}

View File

@@ -0,0 +1,50 @@
package console_v1
import (
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"fmt"
"strings"
)
func signCookieValue(value string, secret string) string {
if secret == "" {
panic(fmt.Errorf("cookie secret is not set"))
}
h := hmac.New(sha256.New, []byte(secret))
h.Write([]byte(value))
signature := base64.URLEncoding.EncodeToString(h.Sum(nil))
return fmt.Sprintf("%s.%s", value, signature)
}
func verifyCookieValue(signedValue string, secret string) (string, error) {
if secret == "" {
panic(fmt.Errorf("cookie secret is not set"))
}
parts := strings.Split(signedValue, ".")
if len(parts) != 2 {
return "", fmt.Errorf("invalid signed cookie format")
}
value, signature := parts[0], parts[1]
expectedSignedValue := signCookieValue(value, secret)
expectedParts := strings.Split(expectedSignedValue, ".")
if len(expectedParts) != 2 {
return "", fmt.Errorf("error computing signature")
}
expectedSignature := expectedParts[1]
if signature != expectedSignature {
return "", fmt.Errorf("cookie signature verification failed")
}
return value, nil
}

View File

@@ -0,0 +1,30 @@
schema: ["schema.graphql"]
exec:
filename: "schema/schema.go"
package: "schema"
model:
filename: "types/types.go"
package: "types"
resolver:
layout: "follow-schema"
dir: "."
package: "console_v1"
filename_template: "v1_resolver.go"
autobind: []
omit_panic_handler: true
call_argument_directives_with_null: true
models:
ID:
model:
- "github.com/getprobo/probo/pkg/server/api/console/v1/types.GIDScalar"
Datetime:
model:
- "github.com/99designs/gqlgen/graphql.Time"
CursorKey:
model:
- "github.com/getprobo/probo/pkg/server/api/console/v1/types.CursorKeyScalar"

View File

@@ -0,0 +1,178 @@
//go:generate go run github.com/99designs/gqlgen generate
// 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 console_v1
import (
"context"
"fmt"
"net/http"
"time"
"github.com/99designs/gqlgen/graphql"
"github.com/99designs/gqlgen/graphql/handler"
"github.com/99designs/gqlgen/graphql/handler/extension"
"github.com/99designs/gqlgen/graphql/handler/transport"
"github.com/99designs/gqlgen/graphql/playground"
"github.com/getprobo/probo/pkg/gid"
"github.com/getprobo/probo/pkg/probo"
"github.com/getprobo/probo/pkg/server/api/console/v1/schema"
"github.com/getprobo/probo/pkg/usrmgr"
"github.com/getprobo/probo/pkg/usrmgr/coredata"
"github.com/go-chi/chi/v5"
"github.com/vektah/gqlparser/v2/gqlerror"
)
type (
AuthConfig struct {
CookieName string
CookieSecure bool
CookieHTTPOnly bool
CookieDomain string
CookiePath string
SessionDuration time.Duration
CookieSecret string
}
Resolver struct {
proboSvc *probo.Service
usrmgrSvc *usrmgr.Service
authCfg AuthConfig
}
contextKey string
httpContext struct {
ResponseWriter http.ResponseWriter
Request *http.Request
}
)
const (
sessionContextKey contextKey = "session"
userContextKey contextKey = "user"
httpContextKey contextKey = "http"
)
// SessionFromContext retrieves the session from the context
func SessionFromContext(ctx context.Context) *coredata.Session {
session, _ := ctx.Value(sessionContextKey).(*coredata.Session)
return session
}
// UserFromContext retrieves the user from the context
func UserFromContext(ctx context.Context) *coredata.User {
user, _ := ctx.Value(userContextKey).(*coredata.User)
return user
}
func NewMux(proboSvc *probo.Service, usrmgrSvc *usrmgr.Service, authCfg AuthConfig) *chi.Mux {
r := chi.NewMux()
// Register authentication routes
RegisterAuthRoutes(r, usrmgrSvc, authCfg)
// GraphQL playground and query endpoint
r.Get("/", playground.Handler("GraphQL", "/console/v1/query"))
r.Post("/query", graphqlHandler(proboSvc, usrmgrSvc, authCfg))
return r
}
func graphqlHandler(proboSvc *probo.Service, usrmgrSvc *usrmgr.Service, authCfg AuthConfig) http.HandlerFunc {
var mb int64 = 1 << 20
es := schema.NewExecutableSchema(
schema.Config{
Resolvers: &Resolver{
proboSvc: proboSvc,
usrmgrSvc: usrmgrSvc,
authCfg: authCfg,
},
},
)
srv := handler.New(es)
srv.AddTransport(transport.POST{})
srv.AddTransport(transport.MultipartForm{
MaxMemory: 32 * mb,
MaxUploadSize: 50 * mb,
})
srv.Use(extension.Introspection{})
// Add operation middleware for authentication
srv.AroundOperations(func(ctx context.Context, next graphql.OperationHandler) graphql.ResponseHandler {
// Skip authentication for introspection queries
if op := graphql.GetOperationContext(ctx); op.OperationName == "IntrospectionQuery" {
return next(ctx)
}
// Get the user from context
user := UserFromContext(ctx)
if user == nil {
return func(ctx context.Context) *graphql.Response {
return &graphql.Response{
Errors: gqlerror.List{gqlerror.Errorf("authentication required")},
}
}
}
// Continue with the operation
return next(ctx)
})
return func(w http.ResponseWriter, r *http.Request) {
// Create HTTP context
httpCtx := &httpContext{
ResponseWriter: w,
Request: r,
}
ctx := context.WithValue(r.Context(), httpContextKey, httpCtx)
// Extract session from cookie
cookie, err := r.Cookie(authCfg.CookieName)
if err == nil && cookie.Value != "" {
// Verify the cookie signature
originalValue, err := verifyCookieValue(cookie.Value, authCfg.CookieSecret)
if err == nil {
// Parse the session ID
sessionID, err := gid.ParseGID(originalValue)
if err == nil {
// Get the session
session, err := usrmgrSvc.GetSession(r.Context(), sessionID)
if err == nil {
// Add session to context
ctx = context.WithValue(ctx, sessionContextKey, session)
// Get the user
user, err := usrmgrSvc.GetUserBySession(r.Context(), sessionID)
if err == nil {
// Add user to context
ctx = context.WithValue(ctx, userContextKey, user)
}
}
}
}
}
srv.ServeHTTP(w, r.WithContext(ctx))
if session := SessionFromContext(r.Context()); session != nil {
if err := usrmgrSvc.UpdateSession(r.Context(), session); err != nil {
panic(fmt.Errorf("failed to update session: %w", err))
}
}
}
}

View File

@@ -0,0 +1,702 @@
directive @goField(
forceResolver: Boolean
name: String
omittable: Boolean
) on INPUT_FIELD_DEFINITION | FIELD_DEFINITION
directive @goModel(
model: String
models: [String!]
) on OBJECT | INPUT_OBJECT | SCALAR | ENUM | INTERFACE | UNION
directive @goEnum(value: String) on ENUM_VALUE
scalar CursorKey
scalar Void
scalar Datetime
scalar Upload
interface Node {
id: ID!
}
enum ControlState
@goModel(model: "github.com/getprobo/probo/pkg/probo/coredata.ControlState") {
NOT_STARTED
@goEnum(
value: "github.com/getprobo/probo/pkg/probo/coredata.ControlStateNotStarted"
)
IN_PROGRESS
@goEnum(
value: "github.com/getprobo/probo/pkg/probo/coredata.ControlStateInProgress"
)
NOT_APPLICABLE
@goEnum(
value: "github.com/getprobo/probo/pkg/probo/coredata.ControlStateNotApplicable"
)
IMPLEMENTED
@goEnum(
value: "github.com/getprobo/probo/pkg/probo/coredata.ControlStateImplemented"
)
}
enum TaskState
@goModel(model: "github.com/getprobo/probo/pkg/probo/coredata.TaskState") {
TODO
@goEnum(value: "github.com/getprobo/probo/pkg/probo/coredata.TaskStateTodo")
DONE
@goEnum(value: "github.com/getprobo/probo/pkg/probo/coredata.TaskStateDone")
}
enum EvidenceState
@goModel(
model: "github.com/getprobo/probo/pkg/probo/coredata.EvidenceState"
) {
VALID
@goEnum(
value: "github.com/getprobo/probo/pkg/probo/coredata.EvidenceStateValid"
)
INVALID
@goEnum(
value: "github.com/getprobo/probo/pkg/probo/coredata.EvidenceStateInvalid"
)
EXPIRED
@goEnum(
value: "github.com/getprobo/probo/pkg/probo/coredata.EvidenceStateExpired"
)
}
enum PeopleKind
@goModel(model: "github.com/getprobo/probo/pkg/probo/coredata.PeopleKind") {
EMPLOYEE
@goEnum(
value: "github.com/getprobo/probo/pkg/probo/coredata.PeopleKindEmployee"
)
CONTRACTOR
@goEnum(
value: "github.com/getprobo/probo/pkg/probo/coredata.PeopleKindContractor"
)
}
type PageInfo {
hasNextPage: Boolean!
hasPreviousPage: Boolean!
startCursor: CursorKey
endCursor: CursorKey
}
type OrganizationConnection {
edges: [OrganizationEdge!]!
pageInfo: PageInfo!
}
type OrganizationEdge {
cursor: CursorKey!
node: Organization!
}
type Organization implements Node {
id: ID!
name: String!
logoUrl: String!
frameworks(
first: Int
after: CursorKey
last: Int
before: CursorKey
): FrameworkConnection! @goField(forceResolver: true)
vendors(
first: Int
after: CursorKey
last: Int
before: CursorKey
): VendorConnection! @goField(forceResolver: true)
peoples(
first: Int
after: CursorKey
last: Int
before: CursorKey
): PeopleConnection! @goField(forceResolver: true)
policies(
first: Int
after: CursorKey
last: Int
before: CursorKey
): PolicyConnection! @goField(forceResolver: true)
createdAt: Datetime!
updatedAt: Datetime!
}
type PeopleConnection {
edges: [PeopleEdge!]!
pageInfo: PageInfo!
}
type PeopleEdge {
cursor: CursorKey!
node: People!
}
type People implements Node {
id: ID!
fullName: String!
primaryEmailAddress: String!
additionalEmailAddresses: [String!]!
kind: PeopleKind!
createdAt: Datetime!
updatedAt: Datetime!
version: Int!
}
type VendorConnection {
edges: [VendorEdge!]!
pageInfo: PageInfo!
}
type VendorEdge {
cursor: CursorKey!
node: Vendor!
}
type Vendor implements Node {
id: ID!
name: String!
description: String!
serviceStartAt: Datetime!
serviceTerminationAt: Datetime
serviceCriticality: ServiceCriticality!
riskTier: RiskTier!
statusPageUrl: String
termsOfServiceUrl: String
privacyPolicyUrl: String
createdAt: Datetime!
updatedAt: Datetime!
version: Int!
}
type FrameworkConnection {
edges: [FrameworkEdge!]!
pageInfo: PageInfo!
}
type FrameworkEdge {
cursor: CursorKey!
node: Framework!
}
type Framework implements Node {
id: ID!
version: Int!
name: String!
description: String!
controls(
first: Int
after: CursorKey
last: Int
before: CursorKey
): ControlConnection! @goField(forceResolver: true)
createdAt: Datetime!
updatedAt: Datetime!
}
type ControlConnection {
edges: [ControlEdge!]!
pageInfo: PageInfo!
}
type ControlEdge {
cursor: CursorKey!
node: Control!
}
type Control implements Node {
id: ID!
version: Int!
category: String!
name: String!
description: String!
state: ControlState!
stateTransisions(
first: Int
after: CursorKey
last: Int
before: CursorKey
): ControlStateTransitionConnection! @goField(forceResolver: true)
tasks(
first: Int
after: CursorKey
last: Int
before: CursorKey
): TaskConnection! @goField(forceResolver: true)
createdAt: Datetime!
updatedAt: Datetime!
}
type ControlStateTransitionConnection {
edges: [ControlStateTransitionEdge!]!
pageInfo: PageInfo!
}
type ControlStateTransitionEdge {
cursor: CursorKey!
node: ControlStateTransition!
}
type ControlStateTransition {
id: ID!
fromState: ControlState
toState: ControlState!
reason: String
createdAt: Datetime!
updatedAt: Datetime!
}
type TaskConnection {
edges: [TaskEdge!]!
pageInfo: PageInfo!
}
type TaskEdge {
cursor: CursorKey!
node: Task!
}
type Task implements Node {
id: ID!
name: String!
description: String!
state: TaskState!
stateTransisions(
first: Int
after: CursorKey
last: Int
before: CursorKey
): TaskStateTransitionConnection! @goField(forceResolver: true)
evidences(
first: Int
after: CursorKey
last: Int
before: CursorKey
): EvidenceConnection! @goField(forceResolver: true)
createdAt: Datetime!
updatedAt: Datetime!
}
type TaskStateTransitionConnection {
edges: [TaskStateTransitionEdge!]!
pageInfo: PageInfo!
}
type TaskStateTransitionEdge {
cursor: CursorKey!
node: TaskStateTransition!
}
type TaskStateTransition {
id: ID!
fromState: TaskState
toState: TaskState!
reason: String
createdAt: Datetime!
updatedAt: Datetime!
}
type EvidenceConnection {
edges: [EvidenceEdge!]!
pageInfo: PageInfo!
}
type EvidenceEdge {
cursor: CursorKey!
node: Evidence!
}
type Evidence implements Node {
id: ID!
fileUrl: String! @goField(forceResolver: true)
mimeType: String!
size: Int!
state: EvidenceState!
filename: String!
stateTransisions(
first: Int
after: CursorKey
last: Int
before: CursorKey
): EvidenceStateTransitionConnection! @goField(forceResolver: true)
createdAt: Datetime!
updatedAt: Datetime!
}
type EvidenceStateTransitionConnection {
edges: [EvidenceStateTransitionEdge!]!
pageInfo: PageInfo!
}
type EvidenceStateTransitionEdge {
cursor: CursorKey!
node: EvidenceStateTransition!
}
type EvidenceStateTransition {
id: ID!
fromState: EvidenceState
toState: EvidenceState!
reason: String
createdAt: Datetime!
updatedAt: Datetime!
}
type User implements Node {
id: ID!
fullName: String!
email: String!
organizations(
first: Int
after: CursorKey
last: Int
before: CursorKey
): OrganizationConnection! @goField(forceResolver: true)
createdAt: Datetime!
updatedAt: Datetime!
}
type Session {
id: ID!
expiresAt: Datetime!
}
type Query {
node(id: ID!): Node!
viewer: User!
}
type Mutation {
createVendor(input: CreateVendorInput!): CreateVendorPayload!
updateVendor(input: UpdateVendorInput!): UpdateVendorPayload!
deleteVendor(input: DeleteVendorInput!): DeleteVendorPayload!
createPeople(input: CreatePeopleInput!): CreatePeoplePayload!
updatePeople(input: UpdatePeopleInput!): UpdatePeoplePayload!
deletePeople(input: DeletePeopleInput!): DeletePeoplePayload!
createOrganization(
input: CreateOrganizationInput!
): CreateOrganizationPayload!
deleteOrganization(
input: DeleteOrganizationInput!
): DeleteOrganizationPayload!
updateTaskState(input: UpdateTaskStateInput!): UpdateTaskStatePayload!
createTask(input: CreateTaskInput!): CreateTaskPayload!
deleteTask(input: DeleteTaskInput!): DeleteTaskPayload!
createFramework(input: CreateFrameworkInput!): CreateFrameworkPayload!
createControl(input: CreateControlInput!): CreateControlPayload!
updateFramework(input: UpdateFrameworkInput!): UpdateFrameworkPayload!
updateControl(input: UpdateControlInput!): UpdateControlPayload!
uploadEvidence(input: UploadEvidenceInput!): UploadEvidencePayload!
deleteEvidence(input: DeleteEvidenceInput!): DeleteEvidencePayload!
createPolicy(input: CreatePolicyInput!): CreatePolicyPayload!
updatePolicy(input: UpdatePolicyInput!): UpdatePolicyPayload!
deletePolicy(input: DeletePolicyInput!): DeletePolicyPayload!
}
input CreateVendorInput {
organizationId: ID!
name: String!
description: String!
serviceStartAt: Datetime!
serviceTerminationAt: Datetime
serviceCriticality: ServiceCriticality!
riskTier: RiskTier!
statusPageUrl: String
termsOfServiceUrl: String
privacyPolicyUrl: String
}
input DeleteVendorInput {
vendorId: ID!
}
input DeletePeopleInput {
peopleId: ID!
}
input CreatePeopleInput {
organizationId: ID!
fullName: String!
primaryEmailAddress: String!
additionalEmailAddresses: [String!]
kind: PeopleKind!
}
input UpdatePeopleInput {
id: ID!
expectedVersion: Int!
fullName: String
primaryEmailAddress: String
additionalEmailAddresses: [String!]
kind: PeopleKind
}
enum ServiceCriticality
@goModel(
model: "github.com/getprobo/probo/pkg/probo/coredata.ServiceCriticality"
) {
LOW
@goEnum(
value: "github.com/getprobo/probo/pkg/probo/coredata.ServiceCriticalityLow"
)
MEDIUM
@goEnum(
value: "github.com/getprobo/probo/pkg/probo/coredata.ServiceCriticalityMedium"
)
HIGH
@goEnum(
value: "github.com/getprobo/probo/pkg/probo/coredata.ServiceCriticalityHigh"
)
}
enum RiskTier
@goModel(model: "github.com/getprobo/probo/pkg/probo/coredata.RiskTier") {
CRITICAL
@goEnum(
value: "github.com/getprobo/probo/pkg/probo/coredata.RiskTierCritical"
)
SIGNIFICANT
@goEnum(
value: "github.com/getprobo/probo/pkg/probo/coredata.RiskTierSignificant"
)
GENERAL
@goEnum(
value: "github.com/getprobo/probo/pkg/probo/coredata.RiskTierGeneral"
)
}
input UpdateVendorInput {
id: ID!
expectedVersion: Int!
name: String
description: String
serviceStartAt: Datetime
serviceTerminationAt: Datetime
serviceCriticality: ServiceCriticality
riskTier: RiskTier
statusPageUrl: String
termsOfServiceUrl: String
privacyPolicyUrl: String
}
type CreatePeoplePayload {
peopleEdge: PeopleEdge!
}
type CreateVendorPayload {
vendorEdge: VendorEdge!
}
type DeleteVendorPayload {
deletedVendorId: ID!
}
type DeletePeoplePayload {
deletedPeopleId: ID!
}
input CreateOrganizationInput {
name: String!
}
input DeleteOrganizationInput {
organizationId: ID!
}
type CreateOrganizationPayload {
organizationEdge: OrganizationEdge!
}
type DeleteOrganizationPayload {
deletedOrganizationId: ID!
}
input UpdateTaskStateInput {
taskId: ID!
state: TaskState!
}
type UpdateTaskStatePayload {
task: Task!
}
input CreateTaskInput {
controlId: ID!
name: String!
description: String!
}
type CreateTaskPayload {
taskEdge: TaskEdge!
}
input DeleteTaskInput {
taskId: ID!
}
type DeleteTaskPayload {
deletedTaskId: ID!
}
input CreateFrameworkInput {
organizationId: ID!
name: String!
description: String!
}
input UpdateFrameworkInput {
id: ID!
expectedVersion: Int!
name: String
description: String
}
type CreateFrameworkPayload {
frameworkEdge: FrameworkEdge!
}
input CreateControlInput {
frameworkId: ID!
name: String!
description: String!
category: String!
}
type CreateControlPayload {
controlEdge: ControlEdge!
}
type UpdateFrameworkPayload {
framework: Framework!
}
type UpdateVendorPayload {
vendor: Vendor!
}
type UpdatePeoplePayload {
people: People!
}
input UpdateControlInput {
id: ID!
expectedVersion: Int!
name: String
description: String
category: String
state: ControlState
}
type UpdateControlPayload {
control: Control!
}
input UploadEvidenceInput {
taskId: ID!
name: String!
file: Upload!
}
type UploadEvidencePayload {
evidenceEdge: EvidenceEdge!
}
input DeleteEvidenceInput {
evidenceId: ID!
}
type DeleteEvidencePayload {
deletedEvidenceId: ID!
}
enum PolicyStatus
@goModel(model: "github.com/getprobo/probo/pkg/probo/coredata.PolicyStatus") {
DRAFT
@goEnum(
value: "github.com/getprobo/probo/pkg/probo/coredata.PolicyStatusDraft"
)
ACTIVE
@goEnum(
value: "github.com/getprobo/probo/pkg/probo/coredata.PolicyStatusActive"
)
}
input CreatePolicyInput {
organizationId: ID!
name: String!
content: String!
status: PolicyStatus!
reviewDate: Datetime
ownerId: ID!
}
input UpdatePolicyInput {
id: ID!
expectedVersion: Int!
name: String
content: String
status: PolicyStatus
reviewDate: Datetime
ownerId: ID
}
input DeletePolicyInput {
policyId: ID!
}
type CreatePolicyPayload {
policyEdge: PolicyEdge!
}
type UpdatePolicyPayload {
policy: Policy!
}
type DeletePolicyPayload {
deletedPolicyId: ID!
}
type Policy implements Node {
id: ID!
version: Int!
name: String!
status: PolicyStatus!
content: String!
reviewDate: Datetime
owner: People! @goField(forceResolver: true)
createdAt: Datetime!
updatedAt: Datetime!
}
type PolicyConnection {
edges: [PolicyEdge!]!
pageInfo: PageInfo!
}
type PolicyEdge {
cursor: CursorKey!
node: Policy!
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,53 @@
// 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 types
import (
"github.com/getprobo/probo/pkg/page"
"github.com/getprobo/probo/pkg/probo/coredata"
)
func NewControlConnection(p *page.Page[*coredata.Control]) *ControlConnection {
var edges = make([]*ControlEdge, len(p.Data))
for i := range edges {
edges[i] = NewControlEdge(p.Data[i])
}
return &ControlConnection{
Edges: edges,
PageInfo: NewPageInfo(p),
}
}
func NewControlEdge(c *coredata.Control) *ControlEdge {
return &ControlEdge{
Cursor: c.CursorKey(),
Node: NewControl(c),
}
}
func NewControl(c *coredata.Control) *Control {
return &Control{
ID: c.ID,
Version: c.Version,
Category: c.Category,
Name: c.Name,
Description: c.Description,
State: c.State,
CreatedAt: c.CreatedAt,
UpdatedAt: c.UpdatedAt,
}
}

View File

@@ -0,0 +1,58 @@
// 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 types
import (
"github.com/getprobo/probo/pkg/page"
"github.com/getprobo/probo/pkg/probo/coredata"
)
func NewControlStateTransitionConnection(
p *page.Page[*coredata.ControlStateTransition],
) *ControlStateTransitionConnection {
var edges = make([]*ControlStateTransitionEdge, len(p.Data))
for i := range edges {
edges[i] = NewControlStateTransitionEdge(p.Data[i])
}
return &ControlStateTransitionConnection{
Edges: edges,
PageInfo: NewPageInfo(p),
}
}
func NewControlStateTransitionEdge(cst *coredata.ControlStateTransition) *ControlStateTransitionEdge {
return &ControlStateTransitionEdge{
Cursor: cst.CursorKey(),
Node: NewControlStateTransition(cst),
}
}
func NewControlStateTransition(cst *coredata.ControlStateTransition) *ControlStateTransition {
var fromState *coredata.ControlState
if cst.FromState != nil {
fromState = cst.FromState
}
return &ControlStateTransition{
ID: cst.ID,
FromState: fromState,
ToState: cst.ToState,
Reason: cst.Reason,
CreatedAt: cst.CreatedAt,
UpdatedAt: cst.UpdatedAt,
}
}

View File

@@ -0,0 +1,69 @@
// 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 types
import (
"errors"
"io"
"strconv"
"github.com/99designs/gqlgen/graphql"
"github.com/getprobo/probo/pkg/page"
)
func NewCursor(
first *int,
after *page.CursorKey,
last *int,
before *page.CursorKey,
) *page.Cursor {
var (
size int
from *page.CursorKey
direction = page.Head
)
if first != nil {
size = *first
direction = page.Head
from = after
} else if last != nil {
size = *last
direction = page.Tail
from = before
}
return page.NewCursor(size, from, direction)
}
func MarshalCursorKeyScalar(ck page.CursorKey) graphql.Marshaler {
return graphql.WriterFunc(func(w io.Writer) {
_, _ = w.Write([]byte(strconv.Quote(ck.String())))
})
}
func UnmarshalCursorKeyScalar(v interface{}) (page.CursorKey, error) {
s, ok := v.(string)
if !ok {
return page.CursorKeyNil, errors.New("must be a string")
}
ck, err := page.ParseCursorKey(s)
if err != nil {
return page.CursorKeyNil, err
}
return ck, nil
}

View File

@@ -0,0 +1,53 @@
// 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 types
import (
"github.com/getprobo/probo/pkg/page"
"github.com/getprobo/probo/pkg/probo/coredata"
)
func NewEvidenceConnection(p *page.Page[*coredata.Evidence]) *EvidenceConnection {
var edges = make([]*EvidenceEdge, len(p.Data))
for i := range edges {
edges[i] = NewEvidenceEdge(p.Data[i])
}
return &EvidenceConnection{
Edges: edges,
PageInfo: NewPageInfo(p),
}
}
func NewEvidenceEdge(e *coredata.Evidence) *EvidenceEdge {
return &EvidenceEdge{
Cursor: e.CursorKey(),
Node: NewEvidence(e),
}
}
func NewEvidence(e *coredata.Evidence) *Evidence {
return &Evidence{
ID: e.ID,
State: e.State,
FileURL: "",
Filename: e.Filename,
MimeType: e.MimeType,
Size: int(e.Size),
CreatedAt: e.CreatedAt,
UpdatedAt: e.UpdatedAt,
}
}

View File

@@ -0,0 +1,58 @@
// 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 types
import (
"github.com/getprobo/probo/pkg/page"
"github.com/getprobo/probo/pkg/probo/coredata"
)
func NewEvidenceStateTransitionConnection(
p *page.Page[*coredata.EvidenceStateTransition],
) *EvidenceStateTransitionConnection {
var edges = make([]*EvidenceStateTransitionEdge, len(p.Data))
for i := range edges {
edges[i] = NewEvidenceStateTransitionEdge(p.Data[i])
}
return &EvidenceStateTransitionConnection{
Edges: edges,
PageInfo: NewPageInfo(p),
}
}
func NewEvidenceStateTransitionEdge(est *coredata.EvidenceStateTransition) *EvidenceStateTransitionEdge {
return &EvidenceStateTransitionEdge{
Cursor: est.CursorKey(),
Node: NewEvidenceStateTransition(est),
}
}
func NewEvidenceStateTransition(est *coredata.EvidenceStateTransition) *EvidenceStateTransition {
var fromState *coredata.EvidenceState
if est.FromState != nil {
fromState = est.FromState
}
return &EvidenceStateTransition{
ID: est.ID,
FromState: fromState,
ToState: est.ToState,
Reason: est.Reason,
CreatedAt: est.CreatedAt,
UpdatedAt: est.UpdatedAt,
}
}

View File

@@ -0,0 +1,51 @@
// 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 types
import (
"github.com/getprobo/probo/pkg/page"
"github.com/getprobo/probo/pkg/probo/coredata"
)
func NewFrameworkConnection(p *page.Page[*coredata.Framework]) *FrameworkConnection {
var edges = make([]*FrameworkEdge, len(p.Data))
for i := range edges {
edges[i] = NewFrameworkEdge(p.Data[i])
}
return &FrameworkConnection{
Edges: edges,
PageInfo: NewPageInfo(p),
}
}
func NewFrameworkEdge(f *coredata.Framework) *FrameworkEdge {
return &FrameworkEdge{
Cursor: f.CursorKey(),
Node: NewFramework(f),
}
}
func NewFramework(f *coredata.Framework) *Framework {
return &Framework{
ID: f.ID,
Version: f.Version,
Name: f.Name,
Description: f.Description,
CreatedAt: f.CreatedAt,
UpdatedAt: f.UpdatedAt,
}
}

View File

@@ -0,0 +1,44 @@
// 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 types
import (
"errors"
"io"
"strconv"
"github.com/99designs/gqlgen/graphql"
"github.com/getprobo/probo/pkg/gid"
)
func MarshalGIDScalar(id gid.GID) graphql.Marshaler {
return graphql.WriterFunc(func(w io.Writer) {
w.Write([]byte(strconv.Quote(id.String())))
})
}
func UnmarshalGIDScalar(v interface{}) (gid.GID, error) {
s, ok := v.(string)
if !ok {
return gid.Nil, errors.New("must be a string")
}
id, err := gid.ParseGID(s)
if err != nil {
return gid.Nil, err
}
return id, nil
}

View File

@@ -0,0 +1,35 @@
// 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 types
import (
"github.com/getprobo/probo/pkg/probo/coredata"
)
func NewOrganization(o *coredata.Organization) *Organization {
return &Organization{
ID: o.ID,
Name: o.Name,
LogoURL: o.LogoURL,
CreatedAt: o.CreatedAt,
UpdatedAt: o.UpdatedAt,
}
}
func NewOrganizationEdge(o *coredata.Organization) *OrganizationEdge {
return &OrganizationEdge{
Node: NewOrganization(o),
}
}

View File

@@ -0,0 +1,39 @@
// 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 types
import (
"gearno.de/ref"
"github.com/getprobo/probo/pkg/page"
)
func NewPageInfo[T page.Paginable](p *page.Page[T]) *PageInfo {
var (
startCursor *page.CursorKey
endCursor *page.CursorKey
)
if len(p.Data) > 0 {
startCursor = ref.Ref(p.First().CursorKey())
endCursor = ref.Ref(p.Last().CursorKey())
}
return &PageInfo{
HasNextPage: p.Info.HasNext,
HasPreviousPage: p.Info.HasPrev,
StartCursor: startCursor,
EndCursor: endCursor,
}
}

View File

@@ -0,0 +1,53 @@
// 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 types
import (
"github.com/getprobo/probo/pkg/page"
"github.com/getprobo/probo/pkg/probo/coredata"
)
func NewPeopleConnection(p *page.Page[*coredata.People]) *PeopleConnection {
var edges = make([]*PeopleEdge, len(p.Data))
for i := range edges {
edges[i] = NewPeopleEdge(p.Data[i])
}
return &PeopleConnection{
Edges: edges,
PageInfo: NewPageInfo(p),
}
}
func NewPeopleEdge(p *coredata.People) *PeopleEdge {
return &PeopleEdge{
Cursor: p.CursorKey(),
Node: NewPeople(p),
}
}
func NewPeople(p *coredata.People) *People {
return &People{
ID: p.ID,
FullName: p.FullName,
PrimaryEmailAddress: p.PrimaryEmailAddress,
AdditionalEmailAddresses: p.AdditionalEmailAddresses,
Kind: p.Kind,
CreatedAt: p.CreatedAt,
UpdatedAt: p.UpdatedAt,
Version: p.Version,
}
}

View File

@@ -0,0 +1,52 @@
// 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 types
import (
"github.com/getprobo/probo/pkg/page"
"github.com/getprobo/probo/pkg/probo/coredata"
)
func NewPolicy(policy *coredata.Policy) *Policy {
return &Policy{
ID: policy.ID,
Version: policy.Version,
Name: policy.Name,
Content: policy.Content,
CreatedAt: policy.CreatedAt,
UpdatedAt: policy.UpdatedAt,
Status: policy.Status,
ReviewDate: policy.ReviewDate,
}
}
func NewPolicyEdge(policy *coredata.Policy) *PolicyEdge {
return &PolicyEdge{
Cursor: policy.CursorKey(),
Node: NewPolicy(policy),
}
}
func NewPolicyConnection(page *page.Page[*coredata.Policy]) *PolicyConnection {
edges := make([]*PolicyEdge, len(page.Data))
for i, policy := range page.Data {
edges[i] = NewPolicyEdge(policy)
}
return &PolicyConnection{
Edges: edges,
PageInfo: NewPageInfo(page),
}
}

View File

@@ -0,0 +1,51 @@
// 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 types
import (
"github.com/getprobo/probo/pkg/page"
"github.com/getprobo/probo/pkg/probo/coredata"
)
func NewTaskConnection(p *page.Page[*coredata.Task]) *TaskConnection {
var edges = make([]*TaskEdge, len(p.Data))
for i := range edges {
edges[i] = NewTaskEdge(p.Data[i])
}
return &TaskConnection{
Edges: edges,
PageInfo: NewPageInfo(p),
}
}
func NewTaskEdge(t *coredata.Task) *TaskEdge {
return &TaskEdge{
Cursor: t.CursorKey(),
Node: NewTask(t),
}
}
func NewTask(t *coredata.Task) *Task {
return &Task{
ID: t.ID,
Name: t.Name,
Description: t.Description,
State: t.State,
CreatedAt: t.CreatedAt,
UpdatedAt: t.UpdatedAt,
}
}

View File

@@ -0,0 +1,58 @@
// 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 types
import (
"github.com/getprobo/probo/pkg/page"
"github.com/getprobo/probo/pkg/probo/coredata"
)
func NewTaskStateTransitionConnection(
p *page.Page[*coredata.TaskStateTransition],
) *TaskStateTransitionConnection {
var edges = make([]*TaskStateTransitionEdge, len(p.Data))
for i := range edges {
edges[i] = NewTaskStateTransitionEdge(p.Data[i])
}
return &TaskStateTransitionConnection{
Edges: edges,
PageInfo: NewPageInfo(p),
}
}
func NewTaskStateTransitionEdge(tst *coredata.TaskStateTransition) *TaskStateTransitionEdge {
return &TaskStateTransitionEdge{
Cursor: tst.CursorKey(),
Node: NewTaskStateTransition(tst),
}
}
func NewTaskStateTransition(tst *coredata.TaskStateTransition) *TaskStateTransition {
var fromState *coredata.TaskState
if tst.FromState != nil {
fromState = tst.FromState
}
return &TaskStateTransition{
ID: tst.ID,
FromState: fromState,
ToState: tst.ToState,
Reason: tst.Reason,
CreatedAt: tst.CreatedAt,
UpdatedAt: tst.UpdatedAt,
}
}

View File

@@ -0,0 +1,522 @@
// Code generated by github.com/99designs/gqlgen, DO NOT EDIT.
package types
import (
"time"
"github.com/99designs/gqlgen/graphql"
"github.com/getprobo/probo/pkg/gid"
"github.com/getprobo/probo/pkg/page"
"github.com/getprobo/probo/pkg/probo/coredata"
)
type Node interface {
IsNode()
GetID() gid.GID
}
type Control struct {
ID gid.GID `json:"id"`
Version int `json:"version"`
Category string `json:"category"`
Name string `json:"name"`
Description string `json:"description"`
State coredata.ControlState `json:"state"`
StateTransisions *ControlStateTransitionConnection `json:"stateTransisions"`
Tasks *TaskConnection `json:"tasks"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}
func (Control) IsNode() {}
func (this Control) GetID() gid.GID { return this.ID }
type ControlConnection struct {
Edges []*ControlEdge `json:"edges"`
PageInfo *PageInfo `json:"pageInfo"`
}
type ControlEdge struct {
Cursor page.CursorKey `json:"cursor"`
Node *Control `json:"node"`
}
type ControlStateTransition struct {
ID gid.GID `json:"id"`
FromState *coredata.ControlState `json:"fromState,omitempty"`
ToState coredata.ControlState `json:"toState"`
Reason *string `json:"reason,omitempty"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}
type ControlStateTransitionConnection struct {
Edges []*ControlStateTransitionEdge `json:"edges"`
PageInfo *PageInfo `json:"pageInfo"`
}
type ControlStateTransitionEdge struct {
Cursor page.CursorKey `json:"cursor"`
Node *ControlStateTransition `json:"node"`
}
type CreateControlInput struct {
FrameworkID gid.GID `json:"frameworkId"`
Name string `json:"name"`
Description string `json:"description"`
Category string `json:"category"`
}
type CreateControlPayload struct {
ControlEdge *ControlEdge `json:"controlEdge"`
}
type CreateFrameworkInput struct {
OrganizationID gid.GID `json:"organizationId"`
Name string `json:"name"`
Description string `json:"description"`
}
type CreateFrameworkPayload struct {
FrameworkEdge *FrameworkEdge `json:"frameworkEdge"`
}
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"`
PrimaryEmailAddress string `json:"primaryEmailAddress"`
AdditionalEmailAddresses []string `json:"additionalEmailAddresses,omitempty"`
Kind coredata.PeopleKind `json:"kind"`
}
type CreatePeoplePayload struct {
PeopleEdge *PeopleEdge `json:"peopleEdge"`
}
type CreatePolicyInput struct {
OrganizationID gid.GID `json:"organizationId"`
Name string `json:"name"`
Content string `json:"content"`
Status coredata.PolicyStatus `json:"status"`
ReviewDate *time.Time `json:"reviewDate,omitempty"`
OwnerID gid.GID `json:"ownerId"`
}
type CreatePolicyPayload struct {
PolicyEdge *PolicyEdge `json:"policyEdge"`
}
type CreateTaskInput struct {
ControlID gid.GID `json:"controlId"`
Name string `json:"name"`
Description string `json:"description"`
}
type CreateTaskPayload struct {
TaskEdge *TaskEdge `json:"taskEdge"`
}
type CreateVendorInput struct {
OrganizationID gid.GID `json:"organizationId"`
Name string `json:"name"`
Description string `json:"description"`
ServiceStartAt time.Time `json:"serviceStartAt"`
ServiceTerminationAt *time.Time `json:"serviceTerminationAt,omitempty"`
ServiceCriticality coredata.ServiceCriticality `json:"serviceCriticality"`
RiskTier coredata.RiskTier `json:"riskTier"`
StatusPageURL *string `json:"statusPageUrl,omitempty"`
TermsOfServiceURL *string `json:"termsOfServiceUrl,omitempty"`
PrivacyPolicyURL *string `json:"privacyPolicyUrl,omitempty"`
}
type CreateVendorPayload struct {
VendorEdge *VendorEdge `json:"vendorEdge"`
}
type DeleteEvidenceInput struct {
EvidenceID gid.GID `json:"evidenceId"`
}
type DeleteEvidencePayload struct {
DeletedEvidenceID gid.GID `json:"deletedEvidenceId"`
}
type DeleteOrganizationInput struct {
OrganizationID gid.GID `json:"organizationId"`
}
type DeleteOrganizationPayload struct {
DeletedOrganizationID gid.GID `json:"deletedOrganizationId"`
}
type DeletePeopleInput struct {
PeopleID gid.GID `json:"peopleId"`
}
type DeletePeoplePayload struct {
DeletedPeopleID gid.GID `json:"deletedPeopleId"`
}
type DeletePolicyInput struct {
PolicyID gid.GID `json:"policyId"`
}
type DeletePolicyPayload struct {
DeletedPolicyID gid.GID `json:"deletedPolicyId"`
}
type DeleteTaskInput struct {
TaskID gid.GID `json:"taskId"`
}
type DeleteTaskPayload struct {
DeletedTaskID gid.GID `json:"deletedTaskId"`
}
type DeleteVendorInput struct {
VendorID gid.GID `json:"vendorId"`
}
type DeleteVendorPayload struct {
DeletedVendorID gid.GID `json:"deletedVendorId"`
}
type Evidence struct {
ID gid.GID `json:"id"`
FileURL string `json:"fileUrl"`
MimeType string `json:"mimeType"`
Size int `json:"size"`
State coredata.EvidenceState `json:"state"`
Filename string `json:"filename"`
StateTransisions *EvidenceStateTransitionConnection `json:"stateTransisions"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}
func (Evidence) IsNode() {}
func (this Evidence) GetID() gid.GID { return this.ID }
type EvidenceConnection struct {
Edges []*EvidenceEdge `json:"edges"`
PageInfo *PageInfo `json:"pageInfo"`
}
type EvidenceEdge struct {
Cursor page.CursorKey `json:"cursor"`
Node *Evidence `json:"node"`
}
type EvidenceStateTransition struct {
ID gid.GID `json:"id"`
FromState *coredata.EvidenceState `json:"fromState,omitempty"`
ToState coredata.EvidenceState `json:"toState"`
Reason *string `json:"reason,omitempty"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}
type EvidenceStateTransitionConnection struct {
Edges []*EvidenceStateTransitionEdge `json:"edges"`
PageInfo *PageInfo `json:"pageInfo"`
}
type EvidenceStateTransitionEdge struct {
Cursor page.CursorKey `json:"cursor"`
Node *EvidenceStateTransition `json:"node"`
}
type Framework struct {
ID gid.GID `json:"id"`
Version int `json:"version"`
Name string `json:"name"`
Description string `json:"description"`
Controls *ControlConnection `json:"controls"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}
func (Framework) IsNode() {}
func (this Framework) GetID() gid.GID { return this.ID }
type FrameworkConnection struct {
Edges []*FrameworkEdge `json:"edges"`
PageInfo *PageInfo `json:"pageInfo"`
}
type FrameworkEdge struct {
Cursor page.CursorKey `json:"cursor"`
Node *Framework `json:"node"`
}
type Mutation struct {
}
type Organization struct {
ID gid.GID `json:"id"`
Name string `json:"name"`
LogoURL string `json:"logoUrl"`
Frameworks *FrameworkConnection `json:"frameworks"`
Vendors *VendorConnection `json:"vendors"`
Peoples *PeopleConnection `json:"peoples"`
Policies *PolicyConnection `json:"policies"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}
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"`
StartCursor *page.CursorKey `json:"startCursor,omitempty"`
EndCursor *page.CursorKey `json:"endCursor,omitempty"`
}
type People struct {
ID gid.GID `json:"id"`
FullName string `json:"fullName"`
PrimaryEmailAddress string `json:"primaryEmailAddress"`
AdditionalEmailAddresses []string `json:"additionalEmailAddresses"`
Kind coredata.PeopleKind `json:"kind"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
Version int `json:"version"`
}
func (People) IsNode() {}
func (this People) GetID() gid.GID { return this.ID }
type PeopleConnection struct {
Edges []*PeopleEdge `json:"edges"`
PageInfo *PageInfo `json:"pageInfo"`
}
type PeopleEdge struct {
Cursor page.CursorKey `json:"cursor"`
Node *People `json:"node"`
}
type Policy struct {
ID gid.GID `json:"id"`
Version int `json:"version"`
Name string `json:"name"`
Status coredata.PolicyStatus `json:"status"`
Content string `json:"content"`
ReviewDate *time.Time `json:"reviewDate,omitempty"`
Owner *People `json:"owner"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}
func (Policy) IsNode() {}
func (this Policy) GetID() gid.GID { return this.ID }
type PolicyConnection struct {
Edges []*PolicyEdge `json:"edges"`
PageInfo *PageInfo `json:"pageInfo"`
}
type PolicyEdge struct {
Cursor page.CursorKey `json:"cursor"`
Node *Policy `json:"node"`
}
type Query struct {
}
type Session struct {
ID gid.GID `json:"id"`
ExpiresAt time.Time `json:"expiresAt"`
}
type Task struct {
ID gid.GID `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
State coredata.TaskState `json:"state"`
StateTransisions *TaskStateTransitionConnection `json:"stateTransisions"`
Evidences *EvidenceConnection `json:"evidences"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}
func (Task) IsNode() {}
func (this Task) GetID() gid.GID { return this.ID }
type TaskConnection struct {
Edges []*TaskEdge `json:"edges"`
PageInfo *PageInfo `json:"pageInfo"`
}
type TaskEdge struct {
Cursor page.CursorKey `json:"cursor"`
Node *Task `json:"node"`
}
type TaskStateTransition struct {
ID gid.GID `json:"id"`
FromState *coredata.TaskState `json:"fromState,omitempty"`
ToState coredata.TaskState `json:"toState"`
Reason *string `json:"reason,omitempty"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}
type TaskStateTransitionConnection struct {
Edges []*TaskStateTransitionEdge `json:"edges"`
PageInfo *PageInfo `json:"pageInfo"`
}
type TaskStateTransitionEdge struct {
Cursor page.CursorKey `json:"cursor"`
Node *TaskStateTransition `json:"node"`
}
type UpdateControlInput struct {
ID gid.GID `json:"id"`
ExpectedVersion int `json:"expectedVersion"`
Name *string `json:"name,omitempty"`
Description *string `json:"description,omitempty"`
Category *string `json:"category,omitempty"`
State *coredata.ControlState `json:"state,omitempty"`
}
type UpdateControlPayload struct {
Control *Control `json:"control"`
}
type UpdateFrameworkInput struct {
ID gid.GID `json:"id"`
ExpectedVersion int `json:"expectedVersion"`
Name *string `json:"name,omitempty"`
Description *string `json:"description,omitempty"`
}
type UpdateFrameworkPayload struct {
Framework *Framework `json:"framework"`
}
type UpdatePeopleInput struct {
ID gid.GID `json:"id"`
ExpectedVersion int `json:"expectedVersion"`
FullName *string `json:"fullName,omitempty"`
PrimaryEmailAddress *string `json:"primaryEmailAddress,omitempty"`
AdditionalEmailAddresses []string `json:"additionalEmailAddresses,omitempty"`
Kind *coredata.PeopleKind `json:"kind,omitempty"`
}
type UpdatePeoplePayload struct {
People *People `json:"people"`
}
type UpdatePolicyInput struct {
ID gid.GID `json:"id"`
ExpectedVersion int `json:"expectedVersion"`
Name *string `json:"name,omitempty"`
Content *string `json:"content,omitempty"`
Status *coredata.PolicyStatus `json:"status,omitempty"`
ReviewDate *time.Time `json:"reviewDate,omitempty"`
OwnerID *gid.GID `json:"ownerId,omitempty"`
}
type UpdatePolicyPayload struct {
Policy *Policy `json:"policy"`
}
type UpdateTaskStateInput struct {
TaskID gid.GID `json:"taskId"`
State coredata.TaskState `json:"state"`
}
type UpdateTaskStatePayload struct {
Task *Task `json:"task"`
}
type UpdateVendorInput struct {
ID gid.GID `json:"id"`
ExpectedVersion int `json:"expectedVersion"`
Name *string `json:"name,omitempty"`
Description *string `json:"description,omitempty"`
ServiceStartAt *time.Time `json:"serviceStartAt,omitempty"`
ServiceTerminationAt *time.Time `json:"serviceTerminationAt,omitempty"`
ServiceCriticality *coredata.ServiceCriticality `json:"serviceCriticality,omitempty"`
RiskTier *coredata.RiskTier `json:"riskTier,omitempty"`
StatusPageURL *string `json:"statusPageUrl,omitempty"`
TermsOfServiceURL *string `json:"termsOfServiceUrl,omitempty"`
PrivacyPolicyURL *string `json:"privacyPolicyUrl,omitempty"`
}
type UpdateVendorPayload struct {
Vendor *Vendor `json:"vendor"`
}
type UploadEvidenceInput struct {
TaskID gid.GID `json:"taskId"`
Name string `json:"name"`
File graphql.Upload `json:"file"`
}
type UploadEvidencePayload struct {
EvidenceEdge *EvidenceEdge `json:"evidenceEdge"`
}
type User struct {
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"`
Description string `json:"description"`
ServiceStartAt time.Time `json:"serviceStartAt"`
ServiceTerminationAt *time.Time `json:"serviceTerminationAt,omitempty"`
ServiceCriticality coredata.ServiceCriticality `json:"serviceCriticality"`
RiskTier coredata.RiskTier `json:"riskTier"`
StatusPageURL *string `json:"statusPageUrl,omitempty"`
TermsOfServiceURL *string `json:"termsOfServiceUrl,omitempty"`
PrivacyPolicyURL *string `json:"privacyPolicyUrl,omitempty"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
Version int `json:"version"`
}
func (Vendor) IsNode() {}
func (this Vendor) GetID() gid.GID { return this.ID }
type VendorConnection struct {
Edges []*VendorEdge `json:"edges"`
PageInfo *PageInfo `json:"pageInfo"`
}
type VendorEdge struct {
Cursor page.CursorKey `json:"cursor"`
Node *Vendor `json:"node"`
}

View File

@@ -0,0 +1,29 @@
// 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 types
import (
"github.com/getprobo/probo/pkg/usrmgr/coredata"
)
func NewUser(u *coredata.User) *User {
return &User{
ID: u.ID,
Email: u.EmailAddress,
FullName: u.FullName,
CreatedAt: u.CreatedAt,
UpdatedAt: u.UpdatedAt,
}
}

View File

@@ -0,0 +1,58 @@
// 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 types
import (
"github.com/getprobo/probo/pkg/page"
"github.com/getprobo/probo/pkg/probo/coredata"
)
func NewVendorConnection(p *page.Page[*coredata.Vendor]) *VendorConnection {
var edges = make([]*VendorEdge, len(p.Data))
for i := range edges {
edges[i] = NewVendorEdge(p.Data[i])
}
return &VendorConnection{
Edges: edges,
PageInfo: NewPageInfo(p),
}
}
func NewVendorEdge(v *coredata.Vendor) *VendorEdge {
return &VendorEdge{
Cursor: v.CursorKey(),
Node: NewVendor(v),
}
}
func NewVendor(v *coredata.Vendor) *Vendor {
return &Vendor{
ID: v.ID,
Name: v.Name,
Description: v.Description,
CreatedAt: v.CreatedAt,
UpdatedAt: v.UpdatedAt,
ServiceStartAt: v.ServiceStartAt,
ServiceTerminationAt: v.ServiceTerminationAt,
ServiceCriticality: v.ServiceCriticality,
RiskTier: v.RiskTier,
StatusPageURL: v.StatusPageURL,
TermsOfServiceURL: v.TermsOfServiceURL,
PrivacyPolicyURL: v.PrivacyPolicyURL,
Version: v.Version,
}
}

View File

@@ -0,0 +1,654 @@
package console_v1
// This file will be automatically regenerated based on the schema, any resolver implementations
// will be copied through when generating and any unknown code will be moved to the end.
// Code generated by github.com/99designs/gqlgen version v0.17.63
import (
"context"
"fmt"
"time"
"github.com/getprobo/probo/pkg/gid"
"github.com/getprobo/probo/pkg/page"
"github.com/getprobo/probo/pkg/probo"
"github.com/getprobo/probo/pkg/probo/coredata"
"github.com/getprobo/probo/pkg/server/api/console/v1/schema"
"github.com/getprobo/probo/pkg/server/api/console/v1/types"
"github.com/vektah/gqlparser/v2/gqlerror"
)
// StateTransisions is the resolver for the stateTransisions field.
func (r *controlResolver) StateTransisions(ctx context.Context, obj *types.Control, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.ControlStateTransitionConnection, error) {
cursor := types.NewCursor(first, after, last, before)
page, err := r.proboSvc.ListControlStateTransitions(ctx, obj.ID, cursor)
if err != nil {
return nil, fmt.Errorf("cannot list control tasks: %w", err)
}
return types.NewControlStateTransitionConnection(page), nil
}
// Tasks is the resolver for the tasks field.
func (r *controlResolver) Tasks(ctx context.Context, obj *types.Control, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.TaskConnection, error) {
cursor := types.NewCursor(first, after, last, before)
page, err := r.proboSvc.ListControlTasks(ctx, obj.ID, cursor)
if err != nil {
return nil, fmt.Errorf("cannot list control tasks: %w", err)
}
return types.NewTaskConnection(page), nil
}
// FileURL is the resolver for the fileUrl field.
func (r *evidenceResolver) FileURL(ctx context.Context, obj *types.Evidence) (string, error) {
fileURL, err := r.proboSvc.GetEvidenceFileURL(ctx, obj.ID, 15*time.Minute)
if err != nil {
return "", fmt.Errorf("cannot generate file URL: %w", err)
}
return *fileURL, nil
}
// StateTransisions is the resolver for the stateTransisions field.
func (r *evidenceResolver) StateTransisions(ctx context.Context, obj *types.Evidence, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.EvidenceStateTransitionConnection, error) {
cursor := types.NewCursor(first, after, last, before)
page, err := r.proboSvc.ListEvidenceStateTransitions(ctx, obj.ID, cursor)
if err != nil {
return nil, fmt.Errorf("cannot list evidence state transitions: %w", err)
}
return types.NewEvidenceStateTransitionConnection(page), nil
}
// Controls is the resolver for the controls field.
func (r *frameworkResolver) Controls(ctx context.Context, obj *types.Framework, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.ControlConnection, error) {
cursor := types.NewCursor(first, after, last, before)
page, err := r.proboSvc.ListFrameworkControls(ctx, obj.ID, cursor)
if err != nil {
return nil, fmt.Errorf("cannot list framework controls: %w", err)
}
return types.NewControlConnection(page), nil
}
// CreateVendor is the resolver for the createVendor field.
func (r *mutationResolver) CreateVendor(ctx context.Context, input types.CreateVendorInput) (*types.CreateVendorPayload, error) {
vendor, err := r.proboSvc.CreateVendor(ctx, probo.CreateVendorRequest{
OrganizationID: input.OrganizationID,
Name: input.Name,
Description: input.Description,
ServiceStartAt: input.ServiceStartAt,
ServiceTerminationAt: input.ServiceTerminationAt,
ServiceCriticality: input.ServiceCriticality,
RiskTier: input.RiskTier,
StatusPageURL: input.StatusPageURL,
TermsOfServiceURL: input.TermsOfServiceURL,
PrivacyPolicyURL: input.PrivacyPolicyURL,
})
if err != nil {
return nil, fmt.Errorf("cannot create vendor: %w", err)
}
return &types.CreateVendorPayload{
VendorEdge: types.NewVendorEdge(vendor),
}, nil
}
// UpdateVendor is the resolver for the updateVendor field.
func (r *mutationResolver) UpdateVendor(ctx context.Context, input types.UpdateVendorInput) (*types.UpdateVendorPayload, error) {
vendor, err := r.proboSvc.UpdateVendor(ctx, probo.UpdateVendorRequest{
ID: input.ID,
ExpectedVersion: input.ExpectedVersion,
Name: input.Name,
Description: input.Description,
ServiceStartAt: input.ServiceStartAt,
ServiceTerminationAt: input.ServiceTerminationAt,
ServiceCriticality: input.ServiceCriticality,
RiskTier: input.RiskTier,
StatusPageURL: input.StatusPageURL,
TermsOfServiceURL: input.TermsOfServiceURL,
PrivacyPolicyURL: input.PrivacyPolicyURL,
})
if err != nil {
return nil, fmt.Errorf("cannot update vendor: %w", err)
}
return &types.UpdateVendorPayload{
Vendor: types.NewVendor(vendor),
}, nil
}
// DeleteVendor is the resolver for the deleteVendor field.
func (r *mutationResolver) DeleteVendor(ctx context.Context, input types.DeleteVendorInput) (*types.DeleteVendorPayload, error) {
err := r.proboSvc.DeleteVendor(ctx, input.VendorID)
if err != nil {
return nil, fmt.Errorf("cannot delete vendor: %w", err)
}
return &types.DeleteVendorPayload{
DeletedVendorID: input.VendorID,
}, nil
}
// CreatePeople is the resolver for the createPeople field.
func (r *mutationResolver) CreatePeople(ctx context.Context, input types.CreatePeopleInput) (*types.CreatePeoplePayload, error) {
people, err := r.proboSvc.CreatePeople(ctx, probo.CreatePeopleRequest{
OrganizationID: input.OrganizationID,
FullName: input.FullName,
PrimaryEmailAddress: input.PrimaryEmailAddress,
AdditionalEmailAddresses: []string{},
Kind: input.Kind,
})
if err != nil {
return nil, fmt.Errorf("cannot create people: %w", err)
}
return &types.CreatePeoplePayload{
PeopleEdge: types.NewPeopleEdge(people),
}, nil
}
// UpdatePeople is the resolver for the updatePeople field.
func (r *mutationResolver) UpdatePeople(ctx context.Context, input types.UpdatePeopleInput) (*types.UpdatePeoplePayload, error) {
people, err := r.proboSvc.UpdatePeople(ctx, probo.UpdatePeopleRequest{
ID: input.ID,
ExpectedVersion: input.ExpectedVersion,
FullName: input.FullName,
PrimaryEmailAddress: input.PrimaryEmailAddress,
AdditionalEmailAddresses: &input.AdditionalEmailAddresses,
Kind: input.Kind,
})
if err != nil {
return nil, fmt.Errorf("cannot update people: %w", err)
}
return &types.UpdatePeoplePayload{
People: types.NewPeople(people),
}, nil
}
// DeletePeople is the resolver for the deletePeople field.
func (r *mutationResolver) DeletePeople(ctx context.Context, input types.DeletePeopleInput) (*types.DeletePeoplePayload, error) {
err := r.proboSvc.DeletePeople(ctx, input.PeopleID)
if err != nil {
return nil, fmt.Errorf("cannot delete people: %w", err)
}
return &types.DeletePeoplePayload{
DeletedPeopleID: input.PeopleID,
}, 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"))
}
// UpdateTaskState is the resolver for the updateTaskState field.
func (r *mutationResolver) UpdateTaskState(ctx context.Context, input types.UpdateTaskStateInput) (*types.UpdateTaskStatePayload, error) {
task, err := r.proboSvc.UpdateTaskState(ctx, probo.UpdateTaskStateRequest{
TaskID: input.TaskID,
State: input.State,
Reason: nil,
})
if err != nil {
return nil, fmt.Errorf("cannot update task state: %w", err)
}
return &types.UpdateTaskStatePayload{
Task: types.NewTask(task),
}, nil
}
// CreateTask is the resolver for the createTask field.
func (r *mutationResolver) CreateTask(ctx context.Context, input types.CreateTaskInput) (*types.CreateTaskPayload, error) {
task, err := r.proboSvc.CreateTask(ctx, probo.CreateTaskRequest{
ControlID: input.ControlID,
Name: input.Name,
Description: input.Description,
})
if err != nil {
return nil, fmt.Errorf("cannot create task: %w", err)
}
return &types.CreateTaskPayload{
TaskEdge: types.NewTaskEdge(task),
}, nil
}
// DeleteTask is the resolver for the deleteTask field.
func (r *mutationResolver) DeleteTask(ctx context.Context, input types.DeleteTaskInput) (*types.DeleteTaskPayload, error) {
err := r.proboSvc.DeleteTask(ctx, input.TaskID)
if err != nil {
return nil, fmt.Errorf("cannot delete task: %w", err)
}
return &types.DeleteTaskPayload{
DeletedTaskID: input.TaskID,
}, nil
}
// CreateFramework is the resolver for the createFramework field.
func (r *mutationResolver) CreateFramework(ctx context.Context, input types.CreateFrameworkInput) (*types.CreateFrameworkPayload, error) {
framework, err := r.proboSvc.CreateFramework(ctx, probo.CreateFrameworkRequest{
OrganizationID: input.OrganizationID,
Name: input.Name,
Description: input.Description,
})
if err != nil {
return nil, fmt.Errorf("cannot create framework: %w", err)
}
return &types.CreateFrameworkPayload{
FrameworkEdge: types.NewFrameworkEdge(framework),
}, nil
}
// CreateControl is the resolver for the createControl field.
func (r *mutationResolver) CreateControl(ctx context.Context, input types.CreateControlInput) (*types.CreateControlPayload, error) {
control, err := r.proboSvc.CreateControl(ctx, probo.CreateControlRequest{
FrameworkID: input.FrameworkID,
Name: input.Name,
Description: input.Description,
Category: input.Category,
})
if err != nil {
return nil, fmt.Errorf("cannot create control: %w", err)
}
return &types.CreateControlPayload{
ControlEdge: types.NewControlEdge(control),
}, nil
}
// UpdateFramework is the resolver for the updateFramework field.
func (r *mutationResolver) UpdateFramework(ctx context.Context, input types.UpdateFrameworkInput) (*types.UpdateFrameworkPayload, error) {
var name, description *string
if input.Name != nil {
name = input.Name
}
if input.Description != nil {
description = input.Description
}
framework, err := r.proboSvc.UpdateFramework(ctx, probo.UpdateFrameworkRequest{
ID: input.ID,
ExpectedVersion: input.ExpectedVersion,
Name: name,
Description: description,
})
if err != nil {
return nil, fmt.Errorf("cannot update framework: %w", err)
}
return &types.UpdateFrameworkPayload{
Framework: types.NewFramework(framework),
}, nil
}
// UpdateControl is the resolver for the updateControl field.
func (r *mutationResolver) UpdateControl(ctx context.Context, input types.UpdateControlInput) (*types.UpdateControlPayload, error) {
var name, description, category *string
var state *coredata.ControlState
if input.Name != nil {
name = input.Name
}
if input.Description != nil {
description = input.Description
}
if input.Category != nil {
category = input.Category
}
if input.State != nil {
state = input.State
}
control, err := r.proboSvc.UpdateControl(ctx, probo.UpdateControlRequest{
ID: input.ID,
ExpectedVersion: input.ExpectedVersion,
Name: name,
Description: description,
Category: category,
State: state,
})
if err != nil {
return nil, fmt.Errorf("cannot update control: %w", err)
}
return &types.UpdateControlPayload{
Control: types.NewControl(control),
}, nil
}
// UploadEvidence is the resolver for the uploadEvidence field.
func (r *mutationResolver) UploadEvidence(ctx context.Context, input types.UploadEvidenceInput) (*types.UploadEvidencePayload, error) {
req := probo.CreateEvidenceRequest{
TaskID: input.TaskID,
Name: input.Name,
File: input.File.File,
}
evidence, err := r.proboSvc.CreateEvidence(ctx, req)
if err != nil {
return nil, fmt.Errorf("failed to create evidence: %w", err)
}
return &types.UploadEvidencePayload{
EvidenceEdge: types.NewEvidenceEdge(evidence),
}, nil
}
// DeleteEvidence is the resolver for the deleteEvidence field.
func (r *mutationResolver) DeleteEvidence(ctx context.Context, input types.DeleteEvidenceInput) (*types.DeleteEvidencePayload, error) {
err := r.proboSvc.DeleteEvidence(ctx, input.EvidenceID)
if err != nil {
return nil, fmt.Errorf("failed to delete evidence: %w", err)
}
return &types.DeleteEvidencePayload{
DeletedEvidenceID: input.EvidenceID,
}, nil
}
// CreatePolicy is the resolver for the createPolicy field.
func (r *mutationResolver) CreatePolicy(ctx context.Context, input types.CreatePolicyInput) (*types.CreatePolicyPayload, error) {
policy, err := r.proboSvc.Policies.Create(ctx, probo.CreatePolicyRequest{
OrganizationID: input.OrganizationID,
Name: input.Name,
Content: input.Content,
Status: input.Status,
ReviewDate: input.ReviewDate,
OwnerID: input.OwnerID,
})
if err != nil {
return nil, fmt.Errorf("cannot create policy: %w", err)
}
return &types.CreatePolicyPayload{
PolicyEdge: types.NewPolicyEdge(policy),
}, nil
}
// UpdatePolicy is the resolver for the updatePolicy field.
func (r *mutationResolver) UpdatePolicy(ctx context.Context, input types.UpdatePolicyInput) (*types.UpdatePolicyPayload, error) {
policy, err := r.proboSvc.Policies.Update(ctx, probo.UpdatePolicyRequest{
ID: input.ID,
ExpectedVersion: input.ExpectedVersion,
Name: input.Name,
Content: input.Content,
Status: input.Status,
ReviewDate: input.ReviewDate,
OwnerID: input.OwnerID,
})
if err != nil {
return nil, fmt.Errorf("cannot update policy: %w", err)
}
return &types.UpdatePolicyPayload{
Policy: types.NewPolicy(policy),
}, nil
}
// DeletePolicy is the resolver for the deletePolicy field.
func (r *mutationResolver) DeletePolicy(ctx context.Context, input types.DeletePolicyInput) (*types.DeletePolicyPayload, error) {
err := r.proboSvc.Policies.Delete(ctx, input.PolicyID)
if err != nil {
return nil, fmt.Errorf("cannot delete policy: %w", err)
}
return &types.DeletePolicyPayload{
DeletedPolicyID: input.PolicyID,
}, nil
}
// 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)
page, err := r.proboSvc.ListOrganizationFrameworks(ctx, obj.ID, cursor)
if err != nil {
return nil, fmt.Errorf("cannot list organization frameworks: %w", err)
}
return types.NewFrameworkConnection(page), nil
}
// Vendors is the resolver for the vendors field.
func (r *organizationResolver) Vendors(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.VendorConnection, error) {
cursor := types.NewCursor(first, after, last, before)
page, err := r.proboSvc.ListOrganizationVendors(ctx, obj.ID, cursor)
if err != nil {
return nil, fmt.Errorf("cannot list organization vendors: %w", err)
}
return types.NewVendorConnection(page), nil
}
// Peoples is the resolver for the peoples field.
func (r *organizationResolver) Peoples(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.PeopleConnection, error) {
cursor := types.NewCursor(first, after, last, before)
page, err := r.proboSvc.ListOrganizationPeoples(ctx, obj.ID, cursor)
if err != nil {
return nil, fmt.Errorf("cannot list organization peoples: %w", err)
}
return types.NewPeopleConnection(page), nil
}
// Policies is the resolver for the policies field.
func (r *organizationResolver) Policies(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.PolicyConnection, error) {
cursor := types.NewCursor(first, after, last, before)
page, err := r.proboSvc.Policies.ListByOrganization(ctx, obj.ID, cursor)
if err != nil {
return nil, fmt.Errorf("cannot list organization policies: %w", err)
}
return types.NewPolicyConnection(page), nil
}
// Owner is the resolver for the owner field.
func (r *policyResolver) Owner(ctx context.Context, obj *types.Policy) (*types.People, error) {
policy, err := r.proboSvc.Policies.Get(ctx, obj.ID)
if err != nil {
return nil, fmt.Errorf("cannot get policy: %w", err)
}
// Get the owner
owner, err := r.proboSvc.GetPeople(ctx, policy.OwnerID)
if err != nil {
return nil, fmt.Errorf("cannot get owner: %w", err)
}
return types.NewPeople(owner), nil
}
// Node is the resolver for the node field.
func (r *queryResolver) Node(ctx context.Context, id gid.GID) (types.Node, error) {
switch id.EntityType() {
case coredata.OrganizationEntityType:
organization, err := r.proboSvc.GetOrganization(ctx, id)
if err != nil {
return nil, err
}
return types.NewOrganization(organization), nil
case coredata.PeopleEntityType:
people, err := r.proboSvc.GetPeople(ctx, id)
if err != nil {
return nil, err
}
return types.NewPeople(people), nil
case coredata.VendorEntityType:
vendor, err := r.proboSvc.GetVendor(ctx, id)
if err != nil {
return nil, err
}
return types.NewVendor(vendor), nil
case coredata.FrameworkEntityType:
framework, err := r.proboSvc.GetFramework(ctx, id)
if err != nil {
return nil, err
}
return types.NewFramework(framework), nil
case coredata.ControlEntityType:
control, err := r.proboSvc.GetControl(ctx, id)
if err != nil {
return nil, err
}
return types.NewControl(control), nil
case coredata.TaskEntityType:
task, err := r.proboSvc.GetTask(ctx, id)
if err != nil {
return nil, err
}
return types.NewTask(task), nil
case coredata.EvidenceEntityType:
evidence, err := r.proboSvc.GetEvidence(ctx, id)
if err != nil {
return nil, err
}
return types.NewEvidence(evidence), nil
case coredata.PolicyEntityType:
policy, err := r.proboSvc.Policies.Get(ctx, id)
if err != nil {
return nil, err
}
return types.NewPolicy(policy), nil
default:
}
return nil, gqlerror.Errorf("node %q not found", id)
}
// Viewer is the resolver for the viewer field.
func (r *queryResolver) Viewer(ctx context.Context) (*types.User, error) {
user := UserFromContext(ctx)
return types.NewUser(user), nil
}
// StateTransisions is the resolver for the stateTransisions field.
func (r *taskResolver) StateTransisions(ctx context.Context, obj *types.Task, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.TaskStateTransitionConnection, error) {
cursor := types.NewCursor(first, after, last, before)
page, err := r.proboSvc.ListTaskStateTransitions(ctx, obj.ID, cursor)
if err != nil {
return nil, fmt.Errorf("cannot list control tasks: %w", err)
}
return types.NewTaskStateTransitionConnection(page), nil
}
// Evidences is the resolver for the evidences field.
func (r *taskResolver) Evidences(ctx context.Context, obj *types.Task, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.EvidenceConnection, error) {
cursor := types.NewCursor(first, after, last, before)
page, err := r.proboSvc.ListTaskEvidences(ctx, obj.ID, cursor)
if err != nil {
return nil, fmt.Errorf("cannot list organization frameworks: %w", err)
}
return types.NewEvidenceConnection(page), nil
}
// Organizations is the resolver for the organizations field.
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 connection
if len(organizationIDs) == 0 {
return &types.OrganizationConnection{
Edges: []*types.OrganizationEdge{},
PageInfo: &types.PageInfo{},
}, nil
}
// Get the organization details for each organization ID
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)
}
edges = append(edges, types.NewOrganizationEdge(organization))
}
return &types.OrganizationConnection{
Edges: edges,
PageInfo: &types.PageInfo{},
}, nil
}
// Control returns schema.ControlResolver implementation.
func (r *Resolver) Control() schema.ControlResolver { return &controlResolver{r} }
// Evidence returns schema.EvidenceResolver implementation.
func (r *Resolver) Evidence() schema.EvidenceResolver { return &evidenceResolver{r} }
// Framework returns schema.FrameworkResolver implementation.
func (r *Resolver) Framework() schema.FrameworkResolver { return &frameworkResolver{r} }
// Mutation returns schema.MutationResolver implementation.
func (r *Resolver) Mutation() schema.MutationResolver { return &mutationResolver{r} }
// Organization returns schema.OrganizationResolver implementation.
func (r *Resolver) Organization() schema.OrganizationResolver { return &organizationResolver{r} }
// Policy returns schema.PolicyResolver implementation.
func (r *Resolver) Policy() schema.PolicyResolver { return &policyResolver{r} }
// Query returns schema.QueryResolver implementation.
func (r *Resolver) Query() schema.QueryResolver { return &queryResolver{r} }
// Task returns schema.TaskResolver implementation.
func (r *Resolver) Task() schema.TaskResolver { return &taskResolver{r} }
// User returns schema.UserResolver implementation.
func (r *Resolver) User() schema.UserResolver { return &userResolver{r} }
type controlResolver struct{ *Resolver }
type evidenceResolver struct{ *Resolver }
type frameworkResolver struct{ *Resolver }
type mutationResolver struct{ *Resolver }
type organizationResolver struct{ *Resolver }
type policyResolver struct{ *Resolver }
type queryResolver struct{ *Resolver }
type taskResolver struct{ *Resolver }
type userResolver struct{ *Resolver }