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:
Émile Ré
2026-05-08 19:08:58 +04:00
parent caba84fd74
commit 7099a3d702
15 changed files with 388 additions and 87 deletions

View File

@@ -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));
},
};
}

View 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>
))}
</>
);
}

View File

@@ -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>

View File

@@ -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)
}

View 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
}

View File

@@ -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,

View File

@@ -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"),

View File

@@ -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} }

View File

@@ -25,6 +25,7 @@ interface Node {
type Query {
node(id: ID!): Node!
viewer: Viewer!
commonThirdParties(name: String!): [CommonThirdParty!]!
}
type Mutation

View 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
}

View File

@@ -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,
},

View File

@@ -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) {

View 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,
}
}

View File

@@ -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
View 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
}