package store // import ( // "context" // "fmt" // "strings" // "time" // "github.com/google/uuid" // "git.warky.dev/wdevs/amcs/internal/generatedmodels" // ext "git.warky.dev/wdevs/amcs/internal/types" // ) // func (db *DB) AddProfessionalContact(ctx context.Context, c ext.ProfessionalContact) (ext.ProfessionalContact, error) { // if c.Tags == nil { // c.Tags = []string{} // } // row := db.pool.QueryRow(ctx, ` // insert into professional_contacts (name, company, title, email, phone, linkedin_url, how_we_met, tags, notes, follow_up_date) // values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) // returning id, created_at, updated_at // `, c.Name, nullStr(c.Company), nullStr(c.Title), nullStr(c.Email), nullStr(c.Phone), // nullStr(c.LinkedInURL), nullStr(c.HowWeMet), c.Tags, nullStr(c.Notes), c.FollowUpDate) // created := c // var model generatedmodels.ModelPublicProfessionalContacts // if err := row.Scan(&model.ID, &model.CreatedAt, &model.UpdatedAt); err != nil { // return ext.ProfessionalContact{}, fmt.Errorf("insert contact: %w", err) // } // created.ID = model.ID.UUID() // created.CreatedAt = model.CreatedAt.Time() // created.UpdatedAt = model.UpdatedAt.Time() // return created, nil // } // func (db *DB) SearchContacts(ctx context.Context, query string, tags []string) ([]ext.ProfessionalContact, error) { // args := []any{} // conditions := []string{} // if q := strings.TrimSpace(query); q != "" { // args = append(args, "%"+q+"%") // idx := len(args) // conditions = append(conditions, fmt.Sprintf( // "(name ILIKE $%[1]d OR company ILIKE $%[1]d OR title ILIKE $%[1]d OR notes ILIKE $%[1]d)", idx)) // } // if len(tags) > 0 { // args = append(args, tags) // conditions = append(conditions, fmt.Sprintf("tags @> $%d", len(args))) // } // q := `select id, name, company, title, email, phone, linkedin_url, how_we_met, tags::text[], notes, last_contacted, follow_up_date, created_at, updated_at from professional_contacts` // if len(conditions) > 0 { // q += " where " + strings.Join(conditions, " and ") // } // q += " order by name" // rows, err := db.pool.Query(ctx, q, args...) // if err != nil { // return nil, fmt.Errorf("search contacts: %w", err) // } // defer rows.Close() // return scanContacts(rows) // } // func (db *DB) GetContact(ctx context.Context, id uuid.UUID) (ext.ProfessionalContact, error) { // row := db.pool.QueryRow(ctx, ` // select id, name, company, title, email, phone, linkedin_url, how_we_met, tags::text[], notes, last_contacted, follow_up_date, created_at, updated_at // from professional_contacts where id = $1 // `, id) // var model generatedmodels.ModelPublicProfessionalContacts // var tags []string // if err := row.Scan(&model.ID, &model.Name, &model.Company, &model.Title, &model.Email, &model.Phone, // &model.LinkedinURL, &model.HowWeMet, &tags, &model.Notes, &model.LastContacted, &model.FollowUpDate, // &model.CreatedAt, &model.UpdatedAt); err != nil { // return ext.ProfessionalContact{}, fmt.Errorf("get contact: %w", err) // } // c := professionalContactFromModel(model, tags) // return c, nil // } // func (db *DB) LogInteraction(ctx context.Context, interaction ext.ContactInteraction) (ext.ContactInteraction, error) { // occurredAt := interaction.OccurredAt // if occurredAt.IsZero() { // occurredAt = time.Now() // } // row := db.pool.QueryRow(ctx, ` // insert into contact_interactions (contact_id, interaction_type, occurred_at, summary, follow_up_needed, follow_up_notes) // values ($1, $2, $3, $4, $5, $6) // returning id, created_at // `, interaction.ContactID, interaction.InteractionType, occurredAt, interaction.Summary, // interaction.FollowUpNeeded, nullStr(interaction.FollowUpNotes)) // created := interaction // created.OccurredAt = occurredAt // var model generatedmodels.ModelPublicContactInteractions // if err := row.Scan(&model.ID, &model.CreatedAt); err != nil { // return ext.ContactInteraction{}, fmt.Errorf("insert interaction: %w", err) // } // created.ID = model.ID.UUID() // created.CreatedAt = model.CreatedAt.Time() // return created, nil // } // func (db *DB) GetContactHistory(ctx context.Context, contactID uuid.UUID) (ext.ContactHistory, error) { // contact, err := db.GetContact(ctx, contactID) // if err != nil { // return ext.ContactHistory{}, err // } // rows, err := db.pool.Query(ctx, ` // select id, contact_id, interaction_type, occurred_at, summary, follow_up_needed, follow_up_notes, created_at // from contact_interactions where contact_id = $1 order by occurred_at desc // `, contactID) // if err != nil { // return ext.ContactHistory{}, fmt.Errorf("get interactions: %w", err) // } // defer rows.Close() // var interactions []ext.ContactInteraction // for rows.Next() { // var model generatedmodels.ModelPublicContactInteractions // if err := rows.Scan(&model.ID, &model.ContactID, &model.InteractionType, &model.OccurredAt, &model.Summary, // &model.FollowUpNeeded, &model.FollowUpNotes, &model.CreatedAt); err != nil { // return ext.ContactHistory{}, fmt.Errorf("scan interaction: %w", err) // } // interactions = append(interactions, contactInteractionFromModel(model)) // } // if err := rows.Err(); err != nil { // return ext.ContactHistory{}, err // } // oppRows, err := db.pool.Query(ctx, ` // select id, contact_id, title, description, stage, value, expected_close_date, notes, created_at, updated_at // from opportunities where contact_id = $1 order by created_at desc // `, contactID) // if err != nil { // return ext.ContactHistory{}, fmt.Errorf("get opportunities: %w", err) // } // defer oppRows.Close() // var opportunities []ext.Opportunity // for oppRows.Next() { // var model generatedmodels.ModelPublicOpportunities // if err := oppRows.Scan(&model.ID, &model.ContactID, &model.Title, &model.Description, &model.Stage, &model.Value, // &model.ExpectedCloseDate, &model.Notes, &model.CreatedAt, &model.UpdatedAt); err != nil { // return ext.ContactHistory{}, fmt.Errorf("scan opportunity: %w", err) // } // opportunities = append(opportunities, opportunityFromModel(model)) // } // if err := oppRows.Err(); err != nil { // return ext.ContactHistory{}, err // } // return ext.ContactHistory{ // Contact: contact, // Interactions: interactions, // Opportunities: opportunities, // }, nil // } // func (db *DB) CreateOpportunity(ctx context.Context, o ext.Opportunity) (ext.Opportunity, error) { // row := db.pool.QueryRow(ctx, ` // insert into opportunities (contact_id, title, description, stage, value, expected_close_date, notes) // values ($1, $2, $3, $4, $5, $6, $7) // returning id, created_at, updated_at // `, o.ContactID, o.Title, nullStr(o.Description), o.Stage, o.Value, o.ExpectedCloseDate, nullStr(o.Notes)) // created := o // var model generatedmodels.ModelPublicOpportunities // if err := row.Scan(&model.ID, &model.CreatedAt, &model.UpdatedAt); err != nil { // return ext.Opportunity{}, fmt.Errorf("insert opportunity: %w", err) // } // created.ID = model.ID.UUID() // created.CreatedAt = model.CreatedAt.Time() // created.UpdatedAt = model.UpdatedAt.Time() // return created, nil // } // func (db *DB) GetFollowUpsDue(ctx context.Context, daysAhead int) ([]ext.ProfessionalContact, error) { // if daysAhead <= 0 { // daysAhead = 7 // } // cutoff := time.Now().AddDate(0, 0, daysAhead) // rows, err := db.pool.Query(ctx, ` // select id, name, company, title, email, phone, linkedin_url, how_we_met, tags::text[], notes, last_contacted, follow_up_date, created_at, updated_at // from professional_contacts // where follow_up_date <= $1 // order by follow_up_date asc // `, cutoff) // if err != nil { // return nil, fmt.Errorf("get follow-ups: %w", err) // } // defer rows.Close() // return scanContacts(rows) // } // func (db *DB) AppendThoughtToContactNotes(ctx context.Context, contactID uuid.UUID, thoughtContent string) error { // _, err := db.pool.Exec(ctx, ` // update professional_contacts // set notes = coalesce(notes, '') || $2 // where id = $1 // `, contactID, thoughtContent) // if err != nil { // return fmt.Errorf("append thought to contact: %w", err) // } // return nil // } // func scanContacts(rows interface { // Next() bool // Scan(...any) error // Err() error // Close() // }) ([]ext.ProfessionalContact, error) { // defer rows.Close() // var contacts []ext.ProfessionalContact // for rows.Next() { // var model generatedmodels.ModelPublicProfessionalContacts // var tags []string // if err := rows.Scan(&model.ID, &model.Name, &model.Company, &model.Title, &model.Email, &model.Phone, // &model.LinkedinURL, &model.HowWeMet, &tags, &model.Notes, &model.LastContacted, &model.FollowUpDate, // &model.CreatedAt, &model.UpdatedAt); err != nil { // return nil, fmt.Errorf("scan contact: %w", err) // } // contacts = append(contacts, professionalContactFromModel(model, tags)) // } // return contacts, rows.Err() // }