Documentation
¶
Overview ¶
Package crm mounts the Hanzo Cloud /v1/crm/* surface: a native-Go, per-org CRM (companies, contacts, opportunities) on Base/SQLite. It is the first slice of the "collapse the business apps into the unified cloud binary" program (universe/docs/architecture/unified-backend-go.md) — a native-Go port of the Twenty CRM core model, NOT a proxy to a NestJS backend.
The three entities are faithful to Twenty's `company` / `person` / `opportunity` standard objects, with Twenty's composite fields (FULL_NAME, EMAILS, CURRENCY, LINKS, ADDRESS) flattened to scalar columns for SQLite.
Tenant isolation is enforced SERVER-SIDE on every request: the org is c.Org() — the value SanitizeIdentity minted from the VALIDATED bearer owner claim (HIP-0026) — and NEVER a client-supplied header. Every store query filters WHERE org=?, so one tenant can never read or mutate another's data.
Surface (all org-scoped; /v1 only):
GET /v1/crm/summary per-org row counts (companies/contacts/opps)
GET /v1/crm/companies list companies -> {data:[…]}
POST /v1/crm/companies create a company -> Company (201)
GET /v1/crm/companies/:id company detail -> Company
PUT /v1/crm/companies/:id update a company -> Company
DELETE /v1/crm/companies/:id delete a company (+ clear refs)
GET /v1/crm/contacts list contacts (?companyId=) -> {data:[…]}
POST /v1/crm/contacts create a contact -> Contact (201)
GET /v1/crm/contacts/:id contact detail -> Contact
PUT /v1/crm/contacts/:id update a contact -> Contact
DELETE /v1/crm/contacts/:id delete a contact (+ clear refs)
GET /v1/crm/opportunities list opportunities (?stage=) -> {data:[…]}
POST /v1/crm/opportunities create an opportunity -> Opportunity (201)
GET /v1/crm/opportunities/:id opportunity detail -> Opportunity
PUT /v1/crm/opportunities/:id update an opportunity -> Opportunity
DELETE /v1/crm/opportunities/:id delete an opportunity
Order 131: binds /v1/crm/* before the AI subsystem's /v1/* catch-all (150). serve.go auto-registers GET /v1/crm/health.
Index ¶
- Constants
- func Mount(app cloud.Router, deps cloud.Deps) error
- func Shutdown() error
- type Application
- type Company
- type Contact
- type Opportunity
- type ScreenResult
- type StageEvent
- type Store
- func (s *Store) Close() error
- func (s *Store) CountApplications(ctx context.Context, org string) (int, error)
- func (s *Store) Counts(ctx context.Context, org string) (companies, contacts, opps int, err error)
- func (s *Store) CreateApplication(ctx context.Context, a Application) (Application, error)
- func (s *Store) CreateCompany(ctx context.Context, c Company) (Company, error)
- func (s *Store) CreateContact(ctx context.Context, c Contact) (Contact, error)
- func (s *Store) CreateOpportunity(ctx context.Context, o Opportunity) (Opportunity, error)
- func (s *Store) DeleteCompany(ctx context.Context, org, id string) (bool, error)
- func (s *Store) DeleteContact(ctx context.Context, org, id string) (bool, error)
- func (s *Store) DeleteOpportunity(ctx context.Context, org, id string) (bool, error)
- func (s *Store) FindApplicationByEmailCompany(ctx context.Context, org, email, company string) (Application, error)
- func (s *Store) GetApplication(ctx context.Context, org, id string) (Application, error)
- func (s *Store) GetCompany(ctx context.Context, org, id string) (Company, error)
- func (s *Store) GetContact(ctx context.Context, org, id string) (Contact, error)
- func (s *Store) GetOpportunity(ctx context.Context, org, id string) (Opportunity, error)
- func (s *Store) ListApplications(ctx context.Context, org, stage string, limit int) ([]Application, error)
- func (s *Store) ListCompanies(ctx context.Context, org string, limit int) ([]Company, error)
- func (s *Store) ListContacts(ctx context.Context, org, companyID string, limit int) ([]Contact, error)
- func (s *Store) ListOpportunities(ctx context.Context, org, stage string, limit int) ([]Opportunity, error)
- func (s *Store) UpdateApplication(ctx context.Context, a Application) (Application, error)
- func (s *Store) UpdateCompany(ctx context.Context, c Company) (Company, error)
- func (s *Store) UpdateContact(ctx context.Context, c Contact) (Contact, error)
- func (s *Store) UpdateOpportunity(ctx context.Context, o Opportunity) (Opportunity, error)
Constants ¶
const ( StageApplied = "applied" StageScreened = "screened" StageQualified = "qualified" StageCreditsOffered = "credits-offered" StageOnboarded = "onboarded" StageRejected = "rejected" )
Startup pipeline stages, in order. `rejected` is an off-pipeline terminal.
Variables ¶
This section is empty.
Functions ¶
Types ¶
type Application ¶
type Application struct {
ID string `json:"id"`
Org string `json:"-"`
Company string `json:"company"`
Website string `json:"website"`
ContactName string `json:"contactName"`
Email string `json:"email"`
Role string `json:"role"`
Stage string `json:"stage"`
Tier1 bool `json:"tier1"`
Metadata map[string]any `json:"metadata"`
Screen ScreenResult `json:"screen"`
Events []StageEvent `json:"events"`
CompanyID string `json:"companyId"`
ContactID string `json:"contactId"`
Reason string `json:"reason"`
CreatedAt int64 `json:"createdAt"`
UpdatedAt int64 `json:"updatedAt"`
}
Application is one startup-program submission plus its AI screen and pipeline state. Metadata carries the FULL submitted payload (all form fields, including arrays like tier1Investors/useCases); the promoted columns are query/display projections. Tier1 is deterministically derived at intake from the submitted fund list (independent of the AI screen's judgement).
type Company ¶
type Company struct {
ID string `json:"id"`
Org string `json:"-"`
Name string `json:"name"`
DomainName string `json:"domainName"`
Employees int64 `json:"employees"`
City string `json:"city"`
Country string `json:"country"`
ARR int64 `json:"arr"`
Currency string `json:"currency"`
ICP bool `json:"idealCustomerProfile"`
Linkedin string `json:"linkedinLink"`
XLink string `json:"xLink"`
CreatedAt int64 `json:"createdAt"`
UpdatedAt int64 `json:"updatedAt"`
}
Company is an org-scoped account record, faithful to Twenty's `company` standard object (composites flattened to scalar columns for SQLite): ARR is minor units (cents) of Currency; ICP is the ideal-customer-profile flag.
type Contact ¶
type Contact struct {
ID string `json:"id"`
Org string `json:"-"`
FirstName string `json:"firstName"`
LastName string `json:"lastName"`
Email string `json:"email"`
Phone string `json:"phone"`
JobTitle string `json:"jobTitle"`
City string `json:"city"`
CompanyID string `json:"companyId"`
Linkedin string `json:"linkedinLink"`
XLink string `json:"xLink"`
CreatedAt int64 `json:"createdAt"`
UpdatedAt int64 `json:"updatedAt"`
}
Contact is an org-scoped person record, faithful to Twenty's `person` standard object (FULL_NAME/EMAILS/PHONES composites flattened). CompanyID is an optional in-org relation to a Company.
type Opportunity ¶
type Opportunity struct {
ID string `json:"id"`
Org string `json:"-"`
Name string `json:"name"`
Amount int64 `json:"amount"`
Currency string `json:"currency"`
Stage string `json:"stage"`
CloseDate int64 `json:"closeDate"`
CompanyID string `json:"companyId"`
PointOfContact string `json:"pointOfContactId"`
CreatedAt int64 `json:"createdAt"`
UpdatedAt int64 `json:"updatedAt"`
}
Opportunity is an org-scoped deal record, faithful to Twenty's `opportunity` standard object. Amount is minor units (cents) of Currency; CloseDate is a unix second (0 == unset). Stage is validated against the default pipeline.
type ScreenResult ¶
type ScreenResult struct {
Status string `json:"status"` // pending | done | failed
Score int `json:"score"` // 0..100
Tier1Backed string `json:"tier1Backed"`
SuggestedCredits int `json:"suggestedCredits"` // 0 | 5000 | 25000 | 50000 | 150000
Summary string `json:"summary"`
DraftReply string `json:"draftReply"`
Model string `json:"model"`
ScreenedAt int64 `json:"screenedAt"`
Error string `json:"error,omitempty"`
}
ScreenResult is the AI screen stored on an application. Status is pending → done | failed; a failed/absent screen never blocks intake.
type StageEvent ¶
type StageEvent struct {
From string `json:"from"`
To string `json:"to"`
At int64 `json:"at"`
By string `json:"by"` // "system" or a staff user id
Note string `json:"note,omitempty"`
}
StageEvent is one entry in an application's append-only stage-transition log.
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store is the CRM database. ONE SQLite file ({DataDir}/crm.db) holds every org's records; tenant isolation is the `org` column, enforced on EVERY query. This mirrors clients/prompts and clients/eval exactly (the ONE storage pattern). MaxOpenConns(1) serializes writes against the single-writer file.
func (*Store) CountApplications ¶
CountApplications returns the org's application count (overview cards).
func (*Store) Counts ¶
Counts returns per-org row counts across the three entities — a real, non-fabricated summary for the CRM module's overview cards.
func (*Store) CreateApplication ¶
func (s *Store) CreateApplication(ctx context.Context, a Application) (Application, error)
func (*Store) CreateCompany ¶
func (*Store) CreateContact ¶
func (*Store) CreateOpportunity ¶
func (s *Store) CreateOpportunity(ctx context.Context, o Opportunity) (Opportunity, error)
func (*Store) DeleteCompany ¶
DeleteCompany removes a company and NULLs any dangling contact/opportunity refs to it within the same org (no orphaned foreign refs). One transaction.
func (*Store) DeleteContact ¶
DeleteContact removes a contact and clears any opportunity point-of-contact refs to it within the org. One transaction.
func (*Store) DeleteOpportunity ¶
func (*Store) FindApplicationByEmailCompany ¶
func (s *Store) FindApplicationByEmailCompany(ctx context.Context, org, email, company string) (Application, error)
FindApplicationByEmailCompany returns the org's application matching a case-insensitive (email, company) pair, or errNotFound. Basis for idempotent intake (a resubmission updates rather than duplicates).
func (*Store) GetApplication ¶
func (*Store) GetCompany ¶
func (*Store) GetContact ¶
func (*Store) GetOpportunity ¶
func (*Store) ListApplications ¶
func (s *Store) ListApplications(ctx context.Context, org, stage string, limit int) ([]Application, error)
ListApplications lists an org's applications, optionally filtered by pipeline stage (stage=="" means all). Newest first.
func (*Store) ListCompanies ¶
func (*Store) ListContacts ¶
func (s *Store) ListContacts(ctx context.Context, org, companyID string, limit int) ([]Contact, error)
ListContacts lists the org's contacts, optionally filtered to one company (companyID=="" means all). Most-recently-updated first.
func (*Store) ListOpportunities ¶
func (s *Store) ListOpportunities(ctx context.Context, org, stage string, limit int) ([]Opportunity, error)
ListOpportunities lists the org's opportunities, optionally filtered by stage (stage=="" means all). Most-recently-updated first.
func (*Store) UpdateApplication ¶
func (s *Store) UpdateApplication(ctx context.Context, a Application) (Application, error)
UpdateApplication persists the mutable columns (stage, tier1, metadata, screen, events, links, reason). ID/Org/CreatedAt are immutable keys.
func (*Store) UpdateCompany ¶
func (*Store) UpdateContact ¶
func (*Store) UpdateOpportunity ¶
func (s *Store) UpdateOpportunity(ctx context.Context, o Opportunity) (Opportunity, error)