package orm import ( "fmt" "log" "net/http" "strconv" "strings" "git.andreafazzi.eu/andrea/oef/errors" "git.andreafazzi.eu/andrea/oef/renderer" "github.com/jinzhu/gorm" ) type ContestIDs []uint type Participant struct { gorm.Model UserModifier UserID uint Firstname string Lastname string Password string `gorm:"-"` FiscalCode string CategoryID uint `schema:"category_id"` SchoolID uint `schema:"school_id"` User *User School *School Category *Category Responses []*Response ContestIDs ContestIDs `schema:"contest_ids" gorm:"-"` Contests []*Contest `gorm:"many2many:subscriptions"` SelectedCategory map[uint]string `gorm:"-"` AllCategories []*Category `gorm:"-"` SelectedContest map[uint]string `gorm:"-"` AllContests []*Contest `gorm:"-"` SelectedSchool map[uint]string `gorm:"-"` AllSchools []*School `gorm:"-"` } func (ids *ContestIDs) UnmarshalCSV(csv string) error { splits := strings.Split(csv, ",") for _, s := range splits { id, err := strconv.Atoi(s) if err != nil { return err } *ids = append(*ids, uint(id)) } return nil } func (model *Participant) sanitize(s string) string { lower := strings.ToLower(s) r := strings.NewReplacer("'", "", "-", "", " ", "", "ò", "o", "ì", "i") return r.Replace(lower) } func (model *Participant) username() string { return strings.ToUpper(model.FiscalCode) } func (model *Participant) GetID() uint { return model.ID } func (model *Participant) String() string { return fmt.Sprintf("%s %s", strings.ToUpper(model.Lastname), strings.Title(strings.ToLower(model.Firstname))) } func (model *Participant) SetCreatorID(id uint) { model.CreatorID = id } func (model *Participant) SetCreatorRole(role string) { model.CreatorRole = role } func (model *Participant) SetCreatorIP(addr string) { model.CreatorIP = addr } func (model *Participant) SetUpdaterID(id uint) { model.UpdaterID = id } func (model *Participant) SetUpdaterRole(role string) { model.UpdaterRole = role } func (model *Participant) SetUpdaterIP(addr string) { model.UpdaterIP = addr } func (model *Participant) exists(db *Database) (*User, error) { var user User if err := db._db.First(&user, &User{Username: model.username()}).Error; err != nil && err != gorm.ErrRecordNotFound { return nil, err } else if err == gorm.ErrRecordNotFound { log.Printf("User %s doesn't exist", user) return nil, nil } log.Printf("User %s exists", user) return &user, nil } func (model *Participant) BeforeSave(tx *gorm.DB) error { var user User if err := tx.FirstOrCreate(&user, &User{ Username: model.username(), Password: model.Password, Role: "participant", }).Error; err != nil { return err } model.UserID = user.ID return nil } func (model *Participant) AfterSave(tx *gorm.DB) error { for _, contest := range model.Contests { var response Response if err := tx.FirstOrCreate( &response, &Response{ Name: fmt.Sprintf("%s (%s)", contest.Name, model.String()), ContestID: contest.ID, ParticipantID: model.ID, }).Error; err != nil { return err } order, err := contest.generateQuestionsOrder(tx) if err != nil { return err } if err := tx.Model(&response).Update("QuestionsOrder", order).Error; err != nil { return err } } return nil } func (model *Participant) AfterDelete(tx *gorm.DB) error { if err := tx.Unscoped().Delete(model.User).Error; err != nil { return err } return nil } func (model *Participant) Create(db *Database, args map[string]string, w http.ResponseWriter, r *http.Request) (interface{}, error) { if r.Method == "GET" { participant := new(Participant) if isSchool(r) { if err := db._db.Find(&participant.AllCategories).Error; err != nil { return nil, err } } else { if err := db._db.Find(&participant.AllCategories).Error; err != nil { return nil, err } if err := db._db.Find(&participant.AllContests).Error; err != nil { return nil, err } if err := db._db.Find(&participant.AllSchools).Error; err != nil { return nil, err } } return participant, nil } else { participant := new(Participant) err := renderer.Decode(participant, r) if err != nil { return nil, err } // Check if participant exists log.Printf("Check if participant %s already exists", participant) if user, err := participant.exists(db); err == nil && user != nil { // if err := db._db.Where("user_id = ?", user.ID).Find(&participant).Error; err != nil && err != gorm.ErrRecordNotFound { // return nil, err // } return nil, errors.ParticipantExists } else if err != nil { return nil, err } // If user has "school" role get school id from token if isSchool(r) { schoolID, err := strconv.Atoi(getModelIDFromToken(r)) if err != nil { return nil, err } participant.SchoolID = uint(schoolID) } // Check if a participant of the same category exists var school School if participant.SchoolID > 0 { if err := db._db.First(&school, participant.SchoolID).Error; err != nil { return nil, err } log.Printf("Check if a participant of the same category exists for school %s", school) hasCategory, err := school.HasCategory(db, participant) if err != nil { return nil, err } if hasCategory { return nil, errors.CategoryExists } } WriteCreator(r, participant) log.Printf("Create participant %s", participant) participant, err = CreateParticipant(db, participant) if err != nil { return nil, err } // FIXME: Should be managed by API if isAdministrator(r) { var response Response err := db._db.First(&response, &Response{ParticipantID: participant.ID}).Error if err != nil && err != gorm.ErrRecordNotFound { return nil, err } if err != gorm.ErrRecordNotFound { response.CreatorID = getUserIDFromTokenAsUint(r) if err := db._db.Save(&response).Error; err != nil { return nil, err } } } return participant, nil } } func (model *Participant) Read(db *Database, args map[string]string, w http.ResponseWriter, r *http.Request) (interface{}, error) { var participant Participant id := args["id"] if isParticipant(r) { if id != getModelIDFromToken(r) { return nil, errors.NotAuthorized } } // School user can access to its participants only! if isSchool(r) { if err := db._db.Preload("School").First(&participant, id).Error; err != nil { return nil, err } if strconv.Itoa(int(participant.SchoolID)) != getModelIDFromToken(r) { return nil, errors.NotAuthorized } if err := db._db. Preload("User"). Preload("School"). Preload("School.Participants"). Preload("Category"). First(&participant, id).Error; err != nil { return nil, err } } else { if err := db._db. Preload("User"). Preload("Creator"). Preload("Updater"). Preload("School"). Preload("School.Participants"). Preload("Responses"). Preload("Responses.Contest"). Preload("Contests"). Preload("Category"). First(&participant, id).Error; err != nil { return nil, err } } return &participant, nil } func (model *Participant) ReadAll(db *Database, args map[string]string, w http.ResponseWriter, r *http.Request) (interface{}, error) { var participants []*Participant // School user can access to its participants only! if isSchool(r) { schoolId, err := strconv.Atoi(getModelIDFromToken(r)) if err != nil { return nil, err } if err := db._db. Preload("Category"). Preload("School"). Preload("Contests"). Order("lastname"). Find(&participants, &Participant{SchoolID: uint(schoolId)}).Error; err != nil { return nil, err } } else { if err := db._db. Preload("Category"). Preload("School"). Preload("Contests"). Preload("Responses"). Order("created_at").Find(&participants).Error; err != nil { return nil, err } } return participants, nil } func (model *Participant) Update(db *Database, args map[string]string, w http.ResponseWriter, r *http.Request) (interface{}, error) { if r.Method == "GET" { result, err := model.Read(db, args, w, r) if err != nil { return nil, err } participant := result.(*Participant) if isSchool(r) { if err := db._db.Find(&participant.AllCategories).Error; err != nil { return nil, err } participant.SelectedCategory = make(map[uint]string) participant.SelectedCategory[participant.CategoryID] = "selected" } else { if err := db._db.Find(&participant.AllCategories).Error; err != nil { return nil, err } participant.SelectedCategory = make(map[uint]string) participant.SelectedCategory[participant.CategoryID] = "selected" if err := db._db.Find(&participant.AllContests).Error; err != nil { return nil, err } participant.SelectedContest = make(map[uint]string) for _, c := range participant.Contests { participant.SelectedContest[c.ID] = "selected" } if err := db._db.Find(&participant.AllSchools).Error; err != nil { return nil, err } participant.SelectedSchool = make(map[uint]string) participant.SelectedSchool[participant.SchoolID] = "selected" } return participant, nil } else { participant, err := model.Read(db, args, w, r) if err != nil { return nil, err } err = renderer.Decode(participant, r) if err != nil { return nil, err } if user, err := participant.(*Participant).exists(db); err == nil && user != nil { if user.ID != participant.(*Participant).UserID { return nil, errors.ParticipantExists } } else if err != nil { return nil, err } // Check if a participant of the same category exists var school School if participant.(*Participant).SchoolID > 0 { if err := db._db.First(&school, participant.(*Participant).SchoolID).Error; err != nil { return nil, err } hasCategory, err := school.HasCategory(db, participant.(*Participant)) if err != nil { return nil, err } if hasCategory { return nil, errors.CategoryExists } } if err := db._db. Where([]uint(participant.(*Participant).ContestIDs)). Find(&participant.(*Participant).Contests).Error; err != nil { return nil, err } WriteUpdater(r, participant.(*Participant)) _, err = SaveParticipant(db, participant) if err != nil { return nil, err } if err := db._db. Model(participant). Association("Contests"). Replace(participant.(*Participant).Contests).Error; err != nil { return nil, err } participant, err = model.Read(db, args, w, r) if err != nil { return nil, err } // FIXME: Should be managed by API if isAdministrator(r) { var response Response err := db._db.First( &response, &Response{ParticipantID: participant.(*Participant).ID}, ).Error if err != nil && err != gorm.ErrRecordNotFound { return nil, err } if err != gorm.ErrRecordNotFound { response.UpdaterID = getUserIDFromTokenAsUint(r) if err := db._db.Save(&response).Error; err != nil { return nil, err } } } // Delete responses for unsubscribed participants var found bool toBeDeletedResponses := make([]*Response, 0) for _, response := range participant.(*Participant).Responses { for _, contest := range participant.(*Participant).Contests { if response.ContestID == contest.ID { found = true break } } if !found { toBeDeletedResponses = append(toBeDeletedResponses, response) } found = false } if len(toBeDeletedResponses) > 0 { for _, response := range toBeDeletedResponses { if err := db._db.Unscoped().Delete(response).Error; err != nil { return nil, err } } } return participant.(*Participant), nil } } func (model *Participant) Delete(db *Database, args map[string]string, w http.ResponseWriter, r *http.Request) (interface{}, error) { participant, err := model.Read(db, args, w, r) if err != nil { return nil, err } if err := db._db.Unscoped().Delete(participant.(*Participant)).Error; err != nil { return nil, err } return participant.(*Participant), nil } func CreateParticipant(db *Database, participant *Participant) (*Participant, error) { if err := db._db.Where([]uint(participant.ContestIDs)).Find(&participant.Contests).Error; err != nil { return nil, err } if err := db._db.Create(participant).Error; err != nil { return nil, err } return participant, nil } func SaveParticipant(db *Database, participant interface{}) (interface{}, error) { participant.(*Participant).FiscalCode = strings.ToUpper(participant.(*Participant).FiscalCode) if err := db._db.Omit("Category", "School", "Creator", "Updater").Save(participant).Error; err != nil { return nil, err } return participant, nil }