package models import ( "context" "database/sql" "errors" "log" "screenmark/screenmark/.gen/haystack/haystack/model" . "screenmark/screenmark/.gen/haystack/haystack/table" . "github.com/go-jet/jet/v2/postgres" "github.com/go-jet/jet/v2/qrm" "github.com/google/uuid" ) type UserModel struct { dbPool *sql.DB } type ImageWithProperties struct { ID uuid.UUID Image model.Image Locations []model.Locations Events []model.Events Notes []model.Notes Contacts []model.Contacts } type PropertiesWithImage struct { Locations []struct { model.Locations Images uuid.UUIDs } Contacts []struct { model.Contacts Images uuid.UUIDs } Events []struct { model.Events Images uuid.UUIDs } Notes []struct { model.Notes Images uuid.UUIDs } } func transpose(imageProperties []ImageWithProperties) PropertiesWithImage { // EntityID -> []ImageIDs dependencies := make(map[uuid.UUID]uuid.UUIDs) addDependency := func(entityId uuid.UUID, imageId uuid.UUID) { deps, exists := dependencies[entityId] if !exists { dep := uuid.UUIDs{imageId} dependencies[entityId] = dep return } deps = append(deps, imageId) } contactMap := make(map[uuid.UUID]model.Contacts) locationMap := make(map[uuid.UUID]model.Locations) noteMap := make(map[uuid.UUID]model.Notes) eventMap := make(map[uuid.UUID]model.Events) for _, image := range imageProperties { for _, contact := range image.Contacts { contactMap[contact.ID] = contact addDependency(contact.ID, image.Image.ID) } for _, location := range image.Locations { locationMap[location.ID] = location addDependency(location.ID, image.Image.ID) } for _, note := range image.Notes { noteMap[note.ID] = note addDependency(note.ID, image.Image.ID) } for _, event := range image.Events { eventMap[event.ID] = event addDependency(event.ID, image.Image.ID) } } properties := PropertiesWithImage{ Contacts: make([]struct { model.Contacts Images uuid.UUIDs }, 0), Locations: make([]struct { model.Locations Images uuid.UUIDs }, 0), Notes: make([]struct { model.Notes Images uuid.UUIDs }, 0), Events: make([]struct { model.Events Images uuid.UUIDs }, 0), } for contactId, contact := range contactMap { properties.Contacts = append(properties.Contacts, struct { model.Contacts Images uuid.UUIDs }{ Contacts: contact, Images: dependencies[contactId], }) } for locationId, location := range locationMap { properties.Locations = append(properties.Locations, struct { model.Locations Images uuid.UUIDs }{ Locations: location, Images: dependencies[locationId], }) } for noteId, note := range noteMap { properties.Notes = append(properties.Notes, struct { model.Notes Images uuid.UUIDs }{ Notes: note, Images: dependencies[noteId], }) } for eventId, event := range eventMap { properties.Events = append(properties.Events, struct { model.Events Images uuid.UUIDs }{ Events: event, Images: dependencies[eventId], }) } return properties } type TypedProperties struct { Type string `json:"type"` Data any `json:"data"` } func propertiesToTypeArray(properties PropertiesWithImage) []TypedProperties { typedProperties := make([]TypedProperties, 0) for _, location := range properties.Locations { typedProperties = append(typedProperties, TypedProperties{ Type: "location", Data: location, }) } for _, contact := range properties.Contacts { typedProperties = append(typedProperties, TypedProperties{ Type: "contact", Data: contact, }) } for _, note := range properties.Notes { typedProperties = append(typedProperties, TypedProperties{ Type: "note", Data: note, }) } for _, event := range properties.Events { typedProperties = append(typedProperties, TypedProperties{ Type: "event", Data: event, }) } return typedProperties } func GetTypedImageProperties(imageProperties []ImageWithProperties) []TypedProperties { return propertiesToTypeArray(transpose(imageProperties)) } func getUserIdFromImage(ctx context.Context, dbPool *sql.DB, imageId uuid.UUID) (uuid.UUID, error) { getUserIdStmt := UserImages.SELECT(UserImages.UserID).WHERE(UserImages.ImageID.EQ(UUID(imageId))) log.Println(getUserIdStmt.DebugSql()) userImages := []model.UserImages{} err := getUserIdStmt.QueryContext(ctx, dbPool, &userImages) if err != nil { return uuid.Nil, err } if len(userImages) != 1 { return uuid.Nil, errors.New("Expected exactly one choice.") } return userImages[0].UserID, nil } func getListImagesStmt() SelectStatement { return SELECT( UserImages.ID.AS("ImageWithProperties.ID"), Image.ID, Image.ImageName, ImageTags.AllColumns, UserTags.AllColumns, ImageText.AllColumns, ImageLinks.AllColumns, ImageLocations.AllColumns, Locations.AllColumns, ImageEvents.AllColumns, Events.AllColumns, ImageContacts.AllColumns, Contacts.AllColumns, ImageNotes.AllColumns, Notes.AllColumns, ). FROM( UserImages.INNER_JOIN(Image, Image.ID.EQ(UserImages.ImageID)). LEFT_JOIN(ImageTags, ImageTags.ImageID.EQ(Image.ID)). LEFT_JOIN(UserTags, UserTags.ID.EQ(ImageTags.TagID)). LEFT_JOIN(ImageText, ImageText.ImageID.EQ(Image.ID)). LEFT_JOIN(ImageLinks, ImageLinks.ImageID.EQ(Image.ID)). LEFT_JOIN(ImageLocations, ImageLocations.ImageID.EQ(UserImages.ImageID)). LEFT_JOIN(Locations, Locations.ID.EQ(ImageLocations.LocationID)). LEFT_JOIN(ImageEvents, ImageEvents.ImageID.EQ(UserImages.ImageID)). LEFT_JOIN(Events, Events.ID.EQ(ImageEvents.EventID)). LEFT_JOIN(ImageContacts, ImageContacts.ImageID.EQ(UserImages.ImageID)). LEFT_JOIN(Contacts, Contacts.ID.EQ(ImageContacts.ContactID)). LEFT_JOIN(ImageNotes, ImageNotes.ImageID.EQ(UserImages.ImageID)). LEFT_JOIN(Notes, Notes.ID.EQ(ImageNotes.NoteID))) } func (m UserModel) ListImageWithProperties(ctx context.Context, userId uuid.UUID, imageId uuid.UUID) (ImageWithProperties, error) { listImagePropertiesStmt := getListImagesStmt(). WHERE(UserImages.ImageID.EQ(UUID(imageId))) image := ImageWithProperties{} err := listImagePropertiesStmt.QueryContext(ctx, m.dbPool, &image) return image, err } func (m UserModel) ListWithProperties(ctx context.Context, userId uuid.UUID) ([]ImageWithProperties, error) { listWithPropertiesStmt := getListImagesStmt(). WHERE(UserImages.UserID.EQ(UUID(userId))) images := []ImageWithProperties{} err := listWithPropertiesStmt.QueryContext(ctx, m.dbPool, &images) return images, err } func (m UserModel) GetUserIdFromEmail(ctx context.Context, email string) (uuid.UUID, error) { getUserIdStmt := Users.SELECT(Users.ID).WHERE(Users.Email.EQ(String(email))) user := model.Users{} err := getUserIdStmt.QueryContext(ctx, m.dbPool, &user) return user.ID, err } func (m UserModel) DoesUserExist(ctx context.Context, email string) bool { getUserIdStmt := Users.SELECT(Users.ID).WHERE(Users.Email.EQ(String(email))) user := model.Users{} err := getUserIdStmt.QueryContext(ctx, m.dbPool, &user) return err != qrm.ErrNoRows } func (m UserModel) Save(ctx context.Context, user model.Users) (model.Users, error) { insertUserStmt := Users.INSERT(Users.Email).VALUES(user.Email).RETURNING(Users.AllColumns) insertedUser := model.Users{} err := insertUserStmt.QueryContext(ctx, m.dbPool, &insertedUser) return insertedUser, err } func NewUserModel(db *sql.DB) UserModel { return UserModel{dbPool: db} }