Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions backend/src/models/company.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ type CompanyParticipation struct {
// Some random notes about this participation.
Notes string `json:"notes" bson:"notes"`

// GmailThreadIds is an array of Gmail thread IDs linked to this participation.
// These are used to sync communications with Gmail.
GmailThreadIds []string `json:"gmailThreadIds,omitempty" bson:"gmailThreadIds,omitempty"`

// Stand details
StandDetails StandDetails `json:"standDetails,omitempty" bson:"standDetails,omitempty"`

Expand Down Expand Up @@ -104,6 +108,8 @@ type Company struct {

Site string `json:"site" bson:"site"`

LinkedIn string `json:"linkedin,omitempty" bson:"linkedin,omitempty"`

// Company's contacts is an array of CompanyRep _id (see models.CompanyRep).
Employers []primitive.ObjectID `json:"employers,omitempty" bson:"employers,omitempty"`

Expand Down Expand Up @@ -140,8 +146,6 @@ type CompanyPublic struct {

Name string `json:"name"`

Description string `json:"description"`

// Company's image (public).
Image string `json:"img,omitempty"`

Expand Down
4 changes: 4 additions & 0 deletions backend/src/models/speaker.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ type SpeakerParticipation struct {

// Hotel information regarding this speaker.
Room SpeakerParticipationRoom `json:"room" bson:"room"`

// GmailThreadIds is an array of Gmail thread IDs linked to this participation.
// These are used to sync communications with Gmail.
GmailThreadIds []string `json:"gmailThreadIds,omitempty" bson:"gmailThreadIds,omitempty"`
}

type SpeakerImages struct {
Expand Down
4 changes: 4 additions & 0 deletions backend/src/models/thread.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ type Thread struct {
// REVIEWED => thread is posted, but some changed must be made before it's ready to be approved.
// PENDING => thread is posted and is waiting for the coordination's approval/review.
Status ThreadStatus `json:"status" bson:"status"`

// GmailMessageId is the ID of the Gmail message this thread was synced from.
// This is used to prevent duplicate syncs.
GmailMessageId string `json:"gmailMessageId,omitempty" bson:"gmailMessageId,omitempty"`
}

type ThreadWithEntry struct {
Expand Down
76 changes: 67 additions & 9 deletions backend/src/mongodb/company.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"fmt"
"io"
"log"
"strings"
"time"

"go.mongodb.org/mongo-driver/bson/primitive"
Expand All @@ -31,11 +32,26 @@ func ResetCurrentPublicCompanies() {
currentPublicCompanies = nil
}

func formatLinkedInURL(url *string) *string {
if url == nil || *url == "" {
return url
}

trimmed := strings.TrimSpace(*url)
if strings.HasPrefix(strings.ToLower(trimmed), "http://") || strings.HasPrefix(strings.ToLower(trimmed), "https://") {
return &trimmed
}

formatted := "https://linkedin.com/company/" + strings.TrimPrefix(trimmed, "@")
return &formatted
}

// CreateCompanyData holds data needed to create a company
type CreateCompanyData struct {
Name *string `json:"name"`
Description *string `json:"description"`
Site *string `json:"site"`
LinkedIn *string `json:"linkedin"`
}

// ParseBody fills the CreateCompanyData from a body
Expand Down Expand Up @@ -80,10 +96,17 @@ func (c *CompaniesType) CreateCompany(data CreateCompanyData) (*models.Company,
return nil, err
}

var linkedinValue interface{} = nil
formattedLinkedin := formatLinkedInURL(data.LinkedIn)
if formattedLinkedin != nil {
linkedinValue = *formattedLinkedin
}

insertResult, err := c.Collection.InsertOne(ctx, bson.M{
"name": data.Name,
"description": data.Description,
"site": data.Site,
"linkedin": linkedinValue,
"employers": []primitive.ObjectID{},
"participations": []models.CompanyParticipation{},
"contact": createdContact.InsertedID.(primitive.ObjectID),
Expand Down Expand Up @@ -325,11 +348,10 @@ func (c *CompaniesType) GetCompaniesByMembers(memberIDs []primitive.ObjectID, ev
func companyToPublic(company models.Company, eventID *int) (*models.CompanyPublic, error) {

public := models.CompanyPublic{
ID: company.ID,
Name: company.Name,
Image: company.Images.Public,
Site: company.Site,
Description: company.Description,
ID: company.ID,
Name: company.Name,
Image: company.Images.Public,
Site: company.Site,
}

var participation *models.CompanyParticipation
Expand Down Expand Up @@ -824,6 +846,35 @@ func (c *CompaniesType) DeleteCompanyThread(id, threadID primitive.ObjectID) (*m
return &updatedCompany, nil
}

// UpdateCompanyGmailThreadIds updates the gmail thread IDs for a company's current participation
func (c *CompaniesType) UpdateCompanyGmailThreadIds(companyID primitive.ObjectID, gmailThreadIds []string) (*models.Company, error) {
ctx := context.Background()
currentEvent, err := Events.GetCurrentEvent()
if err != nil {
return nil, err
}

var updatedCompany models.Company

var updateQuery = bson.M{
"$set": bson.M{
"participations.$.gmailThreadIds": gmailThreadIds,
},
}

var filterQuery = bson.M{"_id": companyID, "participations.event": currentEvent.ID}

var optionsQuery = options.FindOneAndUpdate()
optionsQuery.SetReturnDocument(options.After)

if err := c.Collection.FindOneAndUpdate(ctx, filterQuery, updateQuery, optionsQuery).Decode(&updatedCompany); err != nil {
log.Println("Error updating company gmail thread IDs:", err)
return nil, err
}

return &updatedCompany, nil
}

// UpdateCompanyParticipationStatus updates a company's participation status
// related to the current event. This is the method used when one does not want necessarily to follow
// the state machine described on models.ParticipationStatus.
Expand Down Expand Up @@ -860,10 +911,11 @@ func (c *CompaniesType) UpdateCompanyParticipationStatus(companyID primitive.Obj

// UpdateCompanyData is the data used to update a company, using the method UpdateCompany.
type UpdateCompanyData struct {
Name *string
Description *string
Site *string
BillingInfo *models.CompanyBillingInfo
Name *string `json:"name"`
Description *string `json:"description"`
Site *string `json:"site"`
LinkedIn *string `json:"linkedin"`
BillingInfo *models.CompanyBillingInfo `json:"billingInfo"`
}

// ParseBody fills the UpdateCompanyData from a body
Expand Down Expand Up @@ -893,6 +945,12 @@ func (c *CompaniesType) UpdateCompany(companyID primitive.ObjectID, data UpdateC
if data.Site != nil {
updateFields["site"] = *data.Site
}
if data.LinkedIn != nil {
formattedLinkedin := formatLinkedInURL(data.LinkedIn)
if formattedLinkedin != nil {
updateFields["linkedin"] = *formattedLinkedin
}
}
if data.BillingInfo != nil {
billingInfo := *data.BillingInfo
updateFields["billingInfo.name"] = billingInfo.Name
Expand Down
33 changes: 16 additions & 17 deletions backend/src/mongodb/companyReps.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,23 +105,18 @@ func (c *CompanyRepsType) CreateCompanyRep(data CreateCompanyRepData) (*models.C

dataQuery["contact"] = contact.ID
} else {
contact, err := Contacts.Collection.InsertOne(ctx, bson.M{
"phones": []models.ContactPhone{},
"socials": bson.M{
"facebook": "",
"skype": "",
"github": "",
"twitter": "",
"linkedin": "",
},
"mails": []models.ContactMail{},
})

// Create empty contact using CreateContact to ensure proper initialization
emptyContact := CreateContactData{
Phones: []models.ContactPhone{},
Socials: models.ContactSocials{},
Mails: []models.ContactMail{},
}
contact, err := Contacts.CreateContact(emptyContact)
if err != nil {
return nil, err
}

dataQuery["contact"] = contact.InsertedID.(primitive.ObjectID)
dataQuery["contact"] = contact.ID
}

insertResult, err := c.Collection.InsertOne(ctx, dataQuery)
Expand All @@ -140,7 +135,7 @@ func (c *CompanyRepsType) CreateCompanyRep(data CreateCompanyRepData) (*models.C
return newRep, nil
}

// UpdateCompanyRep creates a contact and adds it to a companyRep
// UpdateCompanyRep updates a company rep and its contact
func (c *CompanyRepsType) UpdateCompanyRep(id primitive.ObjectID, data CreateCompanyRepData) (*models.CompanyRep, error) {
ctx := context.Background()

Expand All @@ -149,13 +144,17 @@ func (c *CompanyRepsType) UpdateCompanyRep(id primitive.ObjectID, data CreateCom
}

if data.Contact != nil {
contact, err := Contacts.CreateContact(*data.Contact)
// Get the existing rep to find its contact ID
existingRep, err := c.GetCompanyRep(id)
if err != nil {
return nil, err
}

// add contact id to the $set document
setDoc["contact"] = contact.ID
// Update the existing contact instead of creating a new one
_, err = Contacts.UpdateContact(existingRep.Contact, *data.Contact)
if err != nil {
return nil, err
}
}

var updateQuery = bson.M{"$set": setDoc}
Expand Down
45 changes: 40 additions & 5 deletions backend/src/mongodb/contact.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,41 @@ import (
"go.mongodb.org/mongo-driver/bson"
)

func formatSocialURL(platform, url string) string {
if url == "" {
return ""
}

trimmed := strings.TrimSpace(url)
if strings.HasPrefix(strings.ToLower(trimmed), "http://") || strings.HasPrefix(strings.ToLower(trimmed), "https://") {
return trimmed
}

switch strings.ToLower(platform) {
case "linkedin":
return "https://linkedin.com/in/" + strings.TrimPrefix(trimmed, "@")
case "twitter":
return "https://twitter.com/" + strings.TrimPrefix(trimmed, "@")
case "facebook":
return "https://facebook.com/" + strings.TrimPrefix(trimmed, "@")
case "github":
return "https://github.com/" + strings.TrimPrefix(trimmed, "@")
default:
return trimmed
}
}

// formatContactSocials formats all social media links in a contact
func formatContactSocials(socials models.ContactSocials) models.ContactSocials {
return models.ContactSocials{
LinkedIn: formatSocialURL("linkedin", socials.LinkedIn),
Twitter: formatSocialURL("twitter", socials.Twitter),
Facebook: formatSocialURL("facebook", socials.Facebook),
Github: formatSocialURL("github", socials.Github),
Skype: socials.Skype,
}
}

// ContactsType stores importat db information on contacts
type ContactsType struct {
Collection *mongo.Collection
Expand Down Expand Up @@ -127,7 +162,7 @@ func (c *ContactsType) CreateContact(data CreateContactData) (*models.Contact, e
ctx := context.Background()
var insertData = bson.M{}
insertData["phones"] = data.Phones
insertData["socials"] = data.Socials
insertData["socials"] = formatContactSocials(data.Socials)
insertData["mails"] = data.Mails
insertData["gender"] = data.Gender
insertData["language"] = data.Language
Expand All @@ -150,10 +185,10 @@ func (c *ContactsType) UpdateContact(contactID primitive.ObjectID, data CreateCo
ctx := context.Background()
var updateQuery = bson.M{
"$set": bson.M{
"phones": data.Phones,
"socials": data.Socials,
"mails": data.Mails,
"gender": data.Gender,
"phones": data.Phones,
"socials": formatContactSocials(data.Socials),
"mails": data.Mails,
"gender": data.Gender,
"language": data.Language,
},
}
Expand Down
31 changes: 30 additions & 1 deletion backend/src/mongodb/speaker.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ func (s *SpeakersType) CreateSpeaker(data CreateSpeakerData) (*models.Speaker, e
if data.Contact != nil {
contact["phones"] = data.Contact.Phones
contact["mails"] = data.Contact.Mails
contact["socials"] = data.Contact.Socials
contact["socials"] = formatContactSocials(data.Contact.Socials)
}

createdContact, err := Contacts.Collection.InsertOne(ctx, contact)
Expand Down Expand Up @@ -862,6 +862,35 @@ func (s *SpeakersType) GetSpeakerParticipationStatusValidSteps(speakerID primiti
return nil, errors.New("No participation found")
}

// UpdateSpeakerGmailThreadIds updates the gmail thread IDs for a speaker's current participation
func (s *SpeakersType) UpdateSpeakerGmailThreadIds(speakerID primitive.ObjectID, gmailThreadIds []string) (*models.Speaker, error) {
ctx := context.Background()
currentEvent, err := Events.GetCurrentEvent()
if err != nil {
return nil, err
}

var updatedSpeaker models.Speaker

var updateQuery = bson.M{
"$set": bson.M{
"participations.$.gmailThreadIds": gmailThreadIds,
},
}

var filterQuery = bson.M{"_id": speakerID, "participations.event": currentEvent.ID}

var optionsQuery = options.FindOneAndUpdate()
optionsQuery.SetReturnDocument(options.After)

if err := s.Collection.FindOneAndUpdate(ctx, filterQuery, updateQuery, optionsQuery).Decode(&updatedSpeaker); err != nil {
log.Println("Error updating speaker gmail thread IDs:", err)
return nil, err
}

return &updatedSpeaker, nil
}

// UpdateSpeakerParticipationStatus updates a speaker's participation status
// related to the current event. This is the method used when one does not want necessarily to follow
// the state machine described on models.ParticipationStatus.
Expand Down
Loading
Loading