Replace vendor JSON with common third parties API
The CreateVendorDialog previously loaded the entire @probo/vendors JSON bundle client-side and used MiniSearch for fuzzy search. This replaces it with a GraphQL query against the common_third_parties database table, searched server-side via ILIKE filtering. Backend: adds CommonThirdParty GraphQL type, a pkg/thirdparty service, and a commonThirdParties(name) root query. Frontend: splits into CommonThirdPartyCombobox (display) and an @inline fragment read on selection via readInlineData. Signed-off-by: Émile Ré <emile@getprobo.com>
This commit is contained in:
@@ -1,47 +0,0 @@
|
||||
// Copyright (c) 2025-2026 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.
|
||||
|
||||
import type { Vendor } from "@probo/vendors";
|
||||
import MiniSearch from "minisearch";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
|
||||
export function useVendorSearch() {
|
||||
const searchRef = useRef<(s: string) => Vendor[]>(() => []);
|
||||
const [query, setQuery] = useState("");
|
||||
const [vendors, setVendors] = useState<Vendor[]>([]);
|
||||
useEffect(() => {
|
||||
void import("@probo/vendors").then((module) => {
|
||||
const ms = new MiniSearch({
|
||||
fields: ["name"],
|
||||
storeFields: Object.keys(module.default[0]),
|
||||
searchOptions: {
|
||||
fuzzy: 0.1,
|
||||
prefix: true,
|
||||
},
|
||||
});
|
||||
ms.addAll(module.default.map(v => ({ ...v, id: v.name })));
|
||||
// @ts-expect-error not enough types to handle this case
|
||||
searchRef.current = ms.search.bind(ms);
|
||||
});
|
||||
}, []);
|
||||
|
||||
return {
|
||||
query,
|
||||
vendors: vendors.slice(0, 20),
|
||||
search: (s: string) => {
|
||||
setQuery(s);
|
||||
setVendors(searchRef.current(s));
|
||||
},
|
||||
};
|
||||
}
|
||||
66
apps/console/src/pages/organizations/vendors/dialogs/CommonThirdPartyCombobox.tsx
vendored
Normal file
66
apps/console/src/pages/organizations/vendors/dialogs/CommonThirdPartyCombobox.tsx
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
// Copyright (c) 2026 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.
|
||||
|
||||
import { faviconUrl } from "@probo/helpers";
|
||||
import { Avatar, ComboboxItem } from "@probo/ui";
|
||||
import type { PreloadedQuery } from "react-relay";
|
||||
import { graphql, usePreloadedQuery } from "react-relay";
|
||||
|
||||
import type {
|
||||
CommonThirdPartyComboboxQuery$data,
|
||||
CommonThirdPartyComboboxQuery,
|
||||
} from "#/__generated__/core/CommonThirdPartyComboboxQuery.graphql";
|
||||
|
||||
export type CommonThirdPartyRef
|
||||
= CommonThirdPartyComboboxQuery$data["commonThirdParties"][number];
|
||||
|
||||
export const commonThirdPartiesQuery = graphql`
|
||||
query CommonThirdPartyComboboxQuery($name: String!) {
|
||||
commonThirdParties(name: $name) {
|
||||
id
|
||||
name
|
||||
websiteUrl
|
||||
...CreateVendorDialog_commonThirdParty
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
interface CommonThirdPartyComboboxProps {
|
||||
queryRef: PreloadedQuery<CommonThirdPartyComboboxQuery>;
|
||||
onSelect: (thirdPartyRef: CommonThirdPartyRef) => void;
|
||||
}
|
||||
|
||||
export function CommonThirdPartyCombobox({
|
||||
queryRef,
|
||||
onSelect,
|
||||
}: CommonThirdPartyComboboxProps) {
|
||||
const data = usePreloadedQuery(commonThirdPartiesQuery, queryRef);
|
||||
|
||||
return (
|
||||
<>
|
||||
{data.commonThirdParties.map(thirdParty => (
|
||||
<ComboboxItem
|
||||
key={thirdParty.id}
|
||||
onClick={() => onSelect(thirdParty)}
|
||||
>
|
||||
<Avatar
|
||||
name={thirdParty.name}
|
||||
src={faviconUrl(thirdParty.websiteUrl)}
|
||||
/>
|
||||
{thirdParty.name}
|
||||
</ComboboxItem>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -12,10 +12,8 @@
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import { faviconUrl } from "@probo/helpers";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import {
|
||||
Avatar,
|
||||
Combobox,
|
||||
ComboboxItem,
|
||||
Dialog,
|
||||
@@ -24,11 +22,38 @@ import {
|
||||
IconPlusLarge,
|
||||
useDialogRef,
|
||||
} from "@probo/ui";
|
||||
import type { Vendor } from "@probo/vendors";
|
||||
import { type ReactNode } from "react";
|
||||
import { type ReactNode, Suspense, useState } from "react";
|
||||
import { useQueryLoader } from "react-relay";
|
||||
import { graphql, readInlineData } from "relay-runtime";
|
||||
|
||||
import type { CommonThirdPartyComboboxQuery } from "#/__generated__/core/CommonThirdPartyComboboxQuery.graphql";
|
||||
import type { CreateVendorDialog_commonThirdParty$key } from "#/__generated__/core/CreateVendorDialog_commonThirdParty.graphql";
|
||||
import { useCreateVendorMutation } from "#/hooks/graph/VendorGraph";
|
||||
import { useVendorSearch } from "#/hooks/useVendorSearch";
|
||||
|
||||
import {
|
||||
commonThirdPartiesQuery,
|
||||
CommonThirdPartyCombobox,
|
||||
type CommonThirdPartyRef,
|
||||
} from "./CommonThirdPartyCombobox";
|
||||
|
||||
const commonThirdPartyFragment = graphql`
|
||||
fragment CreateVendorDialog_commonThirdParty on CommonThirdParty @inline {
|
||||
name
|
||||
description
|
||||
category
|
||||
websiteUrl
|
||||
headquarterAddress
|
||||
legalName
|
||||
privacyPolicyUrl
|
||||
serviceLevelAgreementUrl
|
||||
dataProcessingAgreementUrl
|
||||
certifications
|
||||
securityPageUrl
|
||||
trustPageUrl
|
||||
statusPageUrl
|
||||
termsOfServiceUrl
|
||||
}
|
||||
`;
|
||||
|
||||
type Props = {
|
||||
children: ReactNode;
|
||||
@@ -42,35 +67,45 @@ export function CreateVendorDialog({
|
||||
connection,
|
||||
}: Props) {
|
||||
const { __ } = useTranslate();
|
||||
const { search, vendors, query } = useVendorSearch();
|
||||
const [createVendor] = useCreateVendorMutation();
|
||||
const dialogRef = useDialogRef();
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const [queryRef, loadQuery]
|
||||
= useQueryLoader<CommonThirdPartyComboboxQuery>(commonThirdPartiesQuery);
|
||||
|
||||
const onSelect = async (vendor: Vendor | string) => {
|
||||
const onSelect = async (thirdPartyRef: CommonThirdPartyRef | string) => {
|
||||
const input
|
||||
= typeof vendor === "string"
|
||||
= typeof thirdPartyRef === "string"
|
||||
? {
|
||||
organizationId,
|
||||
name: vendor,
|
||||
name: thirdPartyRef,
|
||||
category: null,
|
||||
}
|
||||
: {
|
||||
organizationId,
|
||||
name: vendor.name,
|
||||
description: vendor.description || null,
|
||||
headquarterAddress: vendor.headquarterAddress || null,
|
||||
legalName: vendor.legalName || null,
|
||||
websiteUrl: vendor.websiteUrl || null,
|
||||
category: vendor.category || null,
|
||||
privacyPolicyUrl: vendor.privacyPolicyUrl || null,
|
||||
serviceLevelAgreementUrl: vendor.serviceLevelAgreementUrl || null,
|
||||
dataProcessingAgreementUrl: vendor.dataProcessingAgreementUrl || null,
|
||||
certifications: vendor.certifications,
|
||||
countries: vendor.countries,
|
||||
securityPageUrl: vendor.securityPageUrl || null,
|
||||
trustPageUrl: vendor.trustPageUrl || null,
|
||||
statusPageUrl: vendor.statusPageUrl || null,
|
||||
termsOfServiceUrl: vendor.termsOfServiceUrl || null,
|
||||
};
|
||||
: (() => {
|
||||
const tp = readInlineData<CreateVendorDialog_commonThirdParty$key>(
|
||||
commonThirdPartyFragment,
|
||||
thirdPartyRef,
|
||||
);
|
||||
return {
|
||||
organizationId,
|
||||
name: tp.name,
|
||||
description: tp.description || null,
|
||||
headquarterAddress: tp.headquarterAddress || null,
|
||||
legalName: tp.legalName || null,
|
||||
websiteUrl: tp.websiteUrl || null,
|
||||
category: tp.category || null,
|
||||
privacyPolicyUrl: tp.privacyPolicyUrl || null,
|
||||
serviceLevelAgreementUrl:
|
||||
tp.serviceLevelAgreementUrl || null,
|
||||
dataProcessingAgreementUrl:
|
||||
tp.dataProcessingAgreementUrl || null,
|
||||
certifications: tp.certifications,
|
||||
securityPageUrl: tp.securityPageUrl || null,
|
||||
trustPageUrl: tp.trustPageUrl || null,
|
||||
statusPageUrl: tp.statusPageUrl || null,
|
||||
termsOfServiceUrl: tp.termsOfServiceUrl || null,
|
||||
};
|
||||
})();
|
||||
await createVendor({
|
||||
variables: {
|
||||
input,
|
||||
@@ -82,25 +117,32 @@ export function CreateVendorDialog({
|
||||
});
|
||||
};
|
||||
|
||||
const dialogRef = useDialogRef();
|
||||
const handleSearch = (name: string) => {
|
||||
setSearchQuery(name);
|
||||
if (name.trim().length >= 2) {
|
||||
loadQuery({ name: name.trim() });
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog ref={dialogRef} trigger={children} title={__("Add a vendor")}>
|
||||
<DialogContent className="p-6">
|
||||
<Combobox onSearch={search} placeholder={__("Type vendor's name")}>
|
||||
{vendors.map(vendor => (
|
||||
<ComboboxItem key={vendor.name} onClick={() => void onSelect(vendor)}>
|
||||
<Avatar name={vendor.name} src={faviconUrl(vendor.websiteUrl)} />
|
||||
{vendor.name}
|
||||
</ComboboxItem>
|
||||
))}
|
||||
{query.trim().length >= 2 && (
|
||||
<ComboboxItem onClick={() => void onSelect(query.trim())}>
|
||||
<Combobox onSearch={handleSearch} placeholder={__("Type vendor's name")}>
|
||||
{queryRef && (
|
||||
<Suspense>
|
||||
<CommonThirdPartyCombobox
|
||||
queryRef={queryRef}
|
||||
onSelect={thirdPartyRef => void onSelect(thirdPartyRef)}
|
||||
/>
|
||||
</Suspense>
|
||||
)}
|
||||
{searchQuery.trim().length >= 2 && (
|
||||
<ComboboxItem onClick={() => void onSelect(searchQuery.trim())}>
|
||||
<IconPlusLarge size={20} />
|
||||
{__("Create a new vendor")}
|
||||
{" "}
|
||||
:
|
||||
{query}
|
||||
{searchQuery}
|
||||
</ComboboxItem>
|
||||
)}
|
||||
</Combobox>
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"maps"
|
||||
"time"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
@@ -378,6 +379,7 @@ func (t CommonThirdParty) Delete(
|
||||
func (t *CommonThirdParties) LoadAll(
|
||||
ctx context.Context,
|
||||
conn pg.Querier,
|
||||
filter *CommonThirdPartyFilter,
|
||||
) error {
|
||||
q := `
|
||||
SELECT
|
||||
@@ -403,10 +405,18 @@ SELECT
|
||||
updated_at
|
||||
FROM
|
||||
common_third_parties
|
||||
WHERE
|
||||
%s
|
||||
ORDER BY name ASC
|
||||
LIMIT 20
|
||||
`
|
||||
|
||||
rows, err := conn.Query(ctx, q)
|
||||
q = fmt.Sprintf(q, filter.SQLFragment())
|
||||
|
||||
args := pgx.StrictNamedArgs{}
|
||||
maps.Copy(args, filter.SQLArguments())
|
||||
|
||||
rows, err := conn.Query(ctx, q, args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot query common third parties: %w", err)
|
||||
}
|
||||
|
||||
45
pkg/coredata/common_third_party_filter.go
Normal file
45
pkg/coredata/common_third_party_filter.go
Normal file
@@ -0,0 +1,45 @@
|
||||
// Copyright (c) 2026 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 coredata
|
||||
|
||||
import (
|
||||
"github.com/jackc/pgx/v5"
|
||||
)
|
||||
|
||||
type CommonThirdPartyFilter struct {
|
||||
name *string
|
||||
}
|
||||
|
||||
func NewCommonThirdPartyFilter(name *string) *CommonThirdPartyFilter {
|
||||
return &CommonThirdPartyFilter{name: name}
|
||||
}
|
||||
|
||||
func (f *CommonThirdPartyFilter) SQLFragment() string {
|
||||
return `(
|
||||
CASE
|
||||
WHEN @filter_name::text IS NOT NULL THEN
|
||||
name ILIKE '%' || @filter_name || '%'
|
||||
ELSE TRUE
|
||||
END
|
||||
)`
|
||||
}
|
||||
|
||||
func (f *CommonThirdPartyFilter) SQLArguments() pgx.StrictNamedArgs {
|
||||
args := pgx.StrictNamedArgs{"filter_name": nil}
|
||||
if f.name != nil {
|
||||
args["filter_name"] = *f.name
|
||||
}
|
||||
return args
|
||||
}
|
||||
@@ -69,6 +69,7 @@ import (
|
||||
"go.probo.inc/probo/pkg/server"
|
||||
"go.probo.inc/probo/pkg/server/trustedproxy"
|
||||
"go.probo.inc/probo/pkg/slack"
|
||||
"go.probo.inc/probo/pkg/thirdparty"
|
||||
"go.probo.inc/probo/pkg/trust"
|
||||
"go.probo.inc/probo/pkg/webhook"
|
||||
"golang.org/x/sync/errgroup"
|
||||
@@ -519,6 +520,8 @@ func (impl *Implm) Run(
|
||||
l.Named("access-review"),
|
||||
)
|
||||
|
||||
thirdPartyService := thirdparty.NewService(pgClient)
|
||||
|
||||
serverHandler, err := server.NewServer(
|
||||
server.Config{
|
||||
AllowedOrigins: impl.cfg.Api.Cors.AllowedOrigins,
|
||||
@@ -532,6 +535,7 @@ func (impl *Implm) Run(
|
||||
Mailman: mailmanService,
|
||||
CookieBanner: cookieBannerService,
|
||||
Geoloc: geolocService,
|
||||
ThirdParty: thirdPartyService,
|
||||
Slack: slackService,
|
||||
ConnectorRegistry: defaultConnectorRegistry,
|
||||
BaseURL: baseURL,
|
||||
|
||||
@@ -44,6 +44,7 @@ import (
|
||||
slack_v1 "go.probo.inc/probo/pkg/server/api/slack/v1"
|
||||
trust_v1 "go.probo.inc/probo/pkg/server/api/trust/v1"
|
||||
"go.probo.inc/probo/pkg/slack"
|
||||
"go.probo.inc/probo/pkg/thirdparty"
|
||||
"go.probo.inc/probo/pkg/trust"
|
||||
)
|
||||
|
||||
@@ -61,6 +62,7 @@ type (
|
||||
Mailman *mailman.Service
|
||||
CookieBanner *cookiebanner.Service
|
||||
Geoloc *geoloc.Service
|
||||
ThirdParty *thirdparty.Service
|
||||
Cookie securecookie.Config
|
||||
TokenSecret string
|
||||
ConnectorRegistry *connector.ConnectorRegistry
|
||||
@@ -190,6 +192,7 @@ func NewServer(cfg Config) (*Server, error) {
|
||||
cfg.ConnectorRegistry,
|
||||
cfg.BaseURL,
|
||||
cfg.CustomDomainCname,
|
||||
cfg.ThirdParty,
|
||||
),
|
||||
cookieBannerHandler: cookiebanner_v1.NewMux(
|
||||
cfg.Logger.Named("cookiebanner.v1"),
|
||||
|
||||
@@ -408,6 +408,22 @@ func (r *queryResolver) Viewer(ctx context.Context) (*types.Viewer, error) {
|
||||
return &types.Viewer{ID: viewerID}, nil
|
||||
}
|
||||
|
||||
// CommonThirdParties is the resolver for the commonThirdParties field.
|
||||
func (r *queryResolver) CommonThirdParties(ctx context.Context, name string) ([]*types.CommonThirdParty, error) {
|
||||
parties, err := r.thirdParty.Search(ctx, name)
|
||||
if err != nil {
|
||||
r.logger.ErrorCtx(ctx, "cannot search common third parties", log.Error(err))
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
|
||||
result := make([]*types.CommonThirdParty, len(parties))
|
||||
for i, p := range parties {
|
||||
result[i] = types.NewCommonThirdParty(p)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Mutation returns schema.MutationResolver implementation.
|
||||
func (r *Resolver) Mutation() schema.MutationResolver { return &mutationResolver{r} }
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ interface Node {
|
||||
type Query {
|
||||
node(id: ID!): Node!
|
||||
viewer: Viewer!
|
||||
commonThirdParties(name: String!): [CommonThirdParty!]!
|
||||
}
|
||||
|
||||
type Mutation
|
||||
|
||||
34
pkg/server/api/console/v1/graphql/common_third_party.graphql
Normal file
34
pkg/server/api/console/v1/graphql/common_third_party.graphql
Normal file
@@ -0,0 +1,34 @@
|
||||
# Copyright (c) 2026 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.
|
||||
|
||||
type CommonThirdParty
|
||||
@goModel(
|
||||
model: "go.probo.inc/probo/pkg/server/api/console/v1/types.CommonThirdParty"
|
||||
) {
|
||||
id: ID!
|
||||
name: String!
|
||||
description: String
|
||||
category: VendorCategory!
|
||||
websiteUrl: String
|
||||
headquarterAddress: String
|
||||
legalName: String
|
||||
privacyPolicyUrl: String
|
||||
serviceLevelAgreementUrl: String
|
||||
dataProcessingAgreementUrl: String
|
||||
certifications: [String!]!
|
||||
securityPageUrl: String
|
||||
trustPageUrl: String
|
||||
statusPageUrl: String
|
||||
termsOfServiceUrl: String
|
||||
}
|
||||
@@ -28,9 +28,21 @@ import (
|
||||
"go.probo.inc/probo/pkg/server/api/authz"
|
||||
"go.probo.inc/probo/pkg/server/api/console/v1/schema"
|
||||
"go.probo.inc/probo/pkg/server/gqlutils"
|
||||
"go.probo.inc/probo/pkg/thirdparty"
|
||||
)
|
||||
|
||||
func NewGraphQLHandler(iamSvc *iam.Service, proboSvc *probo.Service, esignSvc *esign.Service, accessReviewSvc *accessreview.Service, mailmanSvc *mailman.Service, cookieBannerSvc *cookiebanner.Service, connectorRegistry *connector.ConnectorRegistry, customDomainCname string, logger *log.Logger) http.Handler {
|
||||
func NewGraphQLHandler(
|
||||
iamSvc *iam.Service,
|
||||
proboSvc *probo.Service,
|
||||
esignSvc *esign.Service,
|
||||
accessReviewSvc *accessreview.Service,
|
||||
mailmanSvc *mailman.Service,
|
||||
cookieBannerSvc *cookiebanner.Service,
|
||||
connectorRegistry *connector.ConnectorRegistry,
|
||||
customDomainCname string,
|
||||
logger *log.Logger,
|
||||
thirdPartySvc *thirdparty.Service,
|
||||
) http.Handler {
|
||||
config := schema.Config{
|
||||
Resolvers: &Resolver{
|
||||
authorize: authz.NewAuthorizeFunc(iamSvc, logger),
|
||||
@@ -41,6 +53,7 @@ func NewGraphQLHandler(iamSvc *iam.Service, proboSvc *probo.Service, esignSvc *e
|
||||
mailman: mailmanSvc,
|
||||
cookieBanner: cookieBannerSvc,
|
||||
connectorRegistry: connectorRegistry,
|
||||
thirdParty: thirdPartySvc,
|
||||
customDomainCname: customDomainCname,
|
||||
logger: logger,
|
||||
},
|
||||
|
||||
@@ -41,6 +41,7 @@ import (
|
||||
"go.probo.inc/probo/pkg/server/api/authz"
|
||||
"go.probo.inc/probo/pkg/server/api/console/v1/dataloader"
|
||||
"go.probo.inc/probo/pkg/server/api/console/v1/types"
|
||||
"go.probo.inc/probo/pkg/thirdparty"
|
||||
)
|
||||
|
||||
type (
|
||||
@@ -53,6 +54,7 @@ type (
|
||||
mailman *mailman.Service
|
||||
cookieBanner *cookiebanner.Service
|
||||
connectorRegistry *connector.ConnectorRegistry
|
||||
thirdParty *thirdparty.Service
|
||||
logger *log.Logger
|
||||
customDomainCname string
|
||||
}
|
||||
@@ -71,6 +73,7 @@ func NewMux(
|
||||
connectorRegistry *connector.ConnectorRegistry,
|
||||
baseURL *baseurl.BaseURL,
|
||||
customDomainCname string,
|
||||
thirdPartySvc *thirdparty.Service,
|
||||
) *chi.Mux {
|
||||
r := chi.NewMux()
|
||||
|
||||
@@ -86,6 +89,7 @@ func NewMux(
|
||||
connectorRegistry,
|
||||
customDomainCname,
|
||||
logger,
|
||||
thirdPartySvc,
|
||||
)
|
||||
|
||||
r.Group(func(r chi.Router) {
|
||||
|
||||
58
pkg/server/api/console/v1/types/common_third_party.go
Normal file
58
pkg/server/api/console/v1/types/common_third_party.go
Normal file
@@ -0,0 +1,58 @@
|
||||
// Copyright (c) 2026 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 (
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
)
|
||||
|
||||
type CommonThirdParty struct {
|
||||
ID gid.GID `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description *string `json:"description,omitempty"`
|
||||
Category coredata.VendorCategory `json:"category"`
|
||||
WebsiteURL *string `json:"websiteUrl,omitempty"`
|
||||
HeadquarterAddress *string `json:"headquarterAddress,omitempty"`
|
||||
LegalName *string `json:"legalName,omitempty"`
|
||||
PrivacyPolicyURL *string `json:"privacyPolicyUrl,omitempty"`
|
||||
ServiceLevelAgreementURL *string `json:"serviceLevelAgreementUrl,omitempty"`
|
||||
DataProcessingAgreementURL *string `json:"dataProcessingAgreementUrl,omitempty"`
|
||||
Certifications []string `json:"certifications"`
|
||||
SecurityPageURL *string `json:"securityPageUrl,omitempty"`
|
||||
TrustPageURL *string `json:"trustPageUrl,omitempty"`
|
||||
StatusPageURL *string `json:"statusPageUrl,omitempty"`
|
||||
TermsOfServiceURL *string `json:"termsOfServiceUrl,omitempty"`
|
||||
}
|
||||
|
||||
func NewCommonThirdParty(c *coredata.CommonThirdParty) *CommonThirdParty {
|
||||
return &CommonThirdParty{
|
||||
ID: c.ID,
|
||||
Name: c.Name,
|
||||
Description: c.Description,
|
||||
Category: c.Category,
|
||||
WebsiteURL: c.WebsiteURL,
|
||||
HeadquarterAddress: c.HeadquarterAddress,
|
||||
LegalName: c.LegalName,
|
||||
PrivacyPolicyURL: c.PrivacyPolicyURL,
|
||||
ServiceLevelAgreementURL: c.ServiceLevelAgreementURL,
|
||||
DataProcessingAgreementURL: c.DataProcessingAgreementURL,
|
||||
Certifications: c.Certifications,
|
||||
SecurityPageURL: c.SecurityPageURL,
|
||||
TrustPageURL: c.TrustPageURL,
|
||||
StatusPageURL: c.StatusPageURL,
|
||||
TermsOfServiceURL: c.TermsOfServiceURL,
|
||||
}
|
||||
}
|
||||
@@ -42,6 +42,7 @@ import (
|
||||
trust_web "go.probo.inc/probo/pkg/server/trust"
|
||||
console_web "go.probo.inc/probo/pkg/server/web"
|
||||
"go.probo.inc/probo/pkg/slack"
|
||||
"go.probo.inc/probo/pkg/thirdparty"
|
||||
"go.probo.inc/probo/pkg/trust"
|
||||
"go.probo.inc/probo/pkg/uri"
|
||||
)
|
||||
@@ -60,6 +61,7 @@ type Config struct {
|
||||
Mailman *mailman.Service
|
||||
CookieBanner *cookiebanner.Service
|
||||
Geoloc *geoloc.Service
|
||||
ThirdParty *thirdparty.Service
|
||||
Cookie securecookie.Config
|
||||
TokenSecret string
|
||||
ConnectorRegistry *connector.ConnectorRegistry
|
||||
@@ -95,6 +97,7 @@ func NewServer(cfg Config) (*Server, error) {
|
||||
Mailman: cfg.Mailman,
|
||||
CookieBanner: cfg.CookieBanner,
|
||||
Geoloc: cfg.Geoloc,
|
||||
ThirdParty: cfg.ThirdParty,
|
||||
Cookie: cfg.Cookie,
|
||||
TokenSecret: cfg.TokenSecret,
|
||||
ConnectorRegistry: cfg.ConnectorRegistry,
|
||||
|
||||
49
pkg/thirdparty/service.go
vendored
Normal file
49
pkg/thirdparty/service.go
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
// Copyright (c) 2026 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 thirdparty
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"go.gearno.de/kit/pg"
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
pg *pg.Client
|
||||
}
|
||||
|
||||
func NewService(pgClient *pg.Client) *Service {
|
||||
return &Service{pg: pgClient}
|
||||
}
|
||||
|
||||
func (s *Service) Search(ctx context.Context, name string) ([]*coredata.CommonThirdParty, error) {
|
||||
var parties coredata.CommonThirdParties
|
||||
|
||||
filter := coredata.NewCommonThirdPartyFilter(&name)
|
||||
|
||||
err := s.pg.WithConn(
|
||||
ctx,
|
||||
func(ctx context.Context, conn pg.Querier) error {
|
||||
return parties.LoadAll(ctx, conn, filter)
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot search common third parties: %w", err)
|
||||
}
|
||||
|
||||
return parties, nil
|
||||
}
|
||||
Reference in New Issue
Block a user