From 973a33135735f95c7cd260a68a7a696fee3327c4 Mon Sep 17 00:00:00 2001 From: Luca <30503695+tameTNT@users.noreply.github.com> Date: Fri, 25 Oct 2024 10:03:57 +0100 Subject: [PATCH 1/6] =?UTF-8?q?=F0=9F=8F=B7=EF=B8=8F=20Change=20project=20?= =?UTF-8?q?location=20to=20a=20string=20(from=20int64)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The CSV import now supports a 15th column (index 14, column O in Excel) with the location of a team (e.g. 14.3). String is flexible for any format that logistics chooses. --- client/src/enums.ts | 2 +- server/funcs/csv.go | 80 ++++++++++++++-------------------------- server/models/flag.go | 2 +- server/models/judge.go | 2 +- server/models/project.go | 4 +- server/router/project.go | 27 +++----------- 6 files changed, 38 insertions(+), 79 deletions(-) diff --git a/client/src/enums.ts b/client/src/enums.ts index 9a025ae..bac6b55 100644 --- a/client/src/enums.ts +++ b/client/src/enums.ts @@ -9,7 +9,7 @@ export enum JudgeSortField { export enum ProjectSortField { Name, - TableNumber, + Location, Score, Seen, Updated, diff --git a/server/funcs/csv.go b/server/funcs/csv.go index a29b2b3..7fc036d 100644 --- a/server/funcs/csv.go +++ b/server/funcs/csv.go @@ -3,12 +3,10 @@ package funcs import ( "archive/zip" "bytes" - "context" "encoding/csv" "fmt" "io" "net/http" - "server/database" "server/models" "server/util" "strings" @@ -26,12 +24,6 @@ func ParseProjectCsv(content string, hasHeader bool, db *mongo.Database) ([]*mod return []*models.Project{}, nil } - // Get the options from the database - options, err := database.GetOptions(db) - if err != nil { - return nil, err - } - // If the CSV file has a header, skip the first line if hasHeader { r.Read() @@ -48,15 +40,15 @@ func ParseProjectCsv(content string, hasHeader bool, db *mongo.Database) ([]*mod return nil, err } - // Make sure the record has at least 3 elements (name, description, URL) - if len(record) < 3 { - return nil, fmt.Errorf("record contains less than 3 elements: '%s'", strings.Join(record, ",")) + // Make sure the record has at least 4 elements (name, location, description, URL) + if len(record) < 4 { + return nil, fmt.Errorf("record contains less than 4 elements: '%s'", strings.Join(record, ",")) } // Get the challenge list - challengeList := []string{} - if len(record) > 5 && record[5] != "" { - challengeList = strings.Split(record[5], ",") + var challengeList []string + if len(record) > 5 && record[6] != "" { + challengeList = strings.Split(record[6], ",") } for i := range challengeList { challengeList[i] = strings.TrimSpace(challengeList[i]) @@ -64,25 +56,16 @@ func ParseProjectCsv(content string, hasHeader bool, db *mongo.Database) ([]*mod // Optional fields var tryLink string - if len(record) > 3 && record[3] != "" { + if len(record) > 3 && record[4] != "" { tryLink = record[3] } var videoLink string - if len(record) > 4 && record[4] != "" { + if len(record) > 4 && record[5] != "" { videoLink = record[4] } - // Increment the table number - database.GetNextTableNum(options) - // Add project to slice - projects = append(projects, models.NewProject(record[0], options.CurrTableNum, record[1], record[2], tryLink, videoLink, challengeList)) - } - - // Update the options table number in the database - err = database.UpdateCurrTableNum(db, context.Background(), options.CurrTableNum) - if err != nil { - return nil, err + projects = append(projects, models.NewProject(record[0], record[1], record[2], record[3], tryLink, videoLink, challengeList)) } return projects, nil @@ -105,7 +88,8 @@ func ParseProjectCsv(content string, hasHeader bool, db *mongo.Database) ([]*mod // 11. Notes - ignore // 12. Team Colleges/Universities - ignore // 13. Additional Team Member Count - ignore -// 14. (and remiaining rows) Custom questions - custom_questions (ignore for now) +// 14. !!Table number - location +// 15. (and remiaining columns) Custom questions - custom_questions (ignore for now) func ParseDevpostCSV(content string, db *mongo.Database) ([]*models.Project, error) { r := csv.NewReader(strings.NewReader(content)) @@ -115,10 +99,7 @@ func ParseDevpostCSV(content string, db *mongo.Database) ([]*models.Project, err } // Skip the first line - r.Read() - - // Get the options from the database - options, err := database.GetOptions(db) + _, err := r.Read() if err != nil { return nil, err } @@ -135,8 +116,8 @@ func ParseDevpostCSV(content string, db *mongo.Database) ([]*models.Project, err } // Make sure the record has 14 or more elements (see above) - if len(record) < 13 { - return nil, fmt.Errorf("record does not contain 14 or more elements (invalid devpost csv): '%s'", strings.Join(record, ",")) + if len(record) < 14 { + return nil, fmt.Errorf("record does not contain 15 or more elements (invalid devpost csv): '%s'", strings.Join(record, ",")) } // If the project is a Draft, skip it @@ -153,13 +134,18 @@ func ParseDevpostCSV(content string, db *mongo.Database) ([]*models.Project, err challengeList[i] = strings.TrimSpace(challengeList[i]) } - // Increment table number - database.GetNextTableNum(options) + // Interpret location string + location := "" + if record[14] == "" { + location = "No location given." + } else { + location = record[14] + } // Add project to slice projects = append(projects, models.NewProject( record[0], - options.CurrTableNum, + location, record[6], record[1], record[7], @@ -168,12 +154,6 @@ func ParseDevpostCSV(content string, db *mongo.Database) ([]*models.Project, err )) } - // Update the options table number in the database - err = database.UpdateCurrTableNum(db, context.Background(), options.CurrTableNum) - if err != nil { - return nil, err - } - return projects, nil } @@ -210,7 +190,7 @@ func CreateJudgeRankingCSV(judges []*models.Judge) []byte { } // Create a list of all ranked projects (just their location) - ranked := make([]int64, 0, len(judge.CurrentRankings)) + ranked := make([]string, 0, len(judge.CurrentRankings)) for _, projId := range judge.CurrentRankings { idx := util.IndexFunc(judge.SeenProjects, func(p models.JudgedProject) bool { return p.ProjectId == projId @@ -223,20 +203,16 @@ func CreateJudgeRankingCSV(judges []*models.Judge) []byte { } // Create a list of all unranked projects (filter using ranked projects) - unranked := make([]int64, 0, len(judge.SeenProjects)-len(judge.CurrentRankings)) + unranked := make([]string, 0, len(judge.SeenProjects)-len(judge.CurrentRankings)) for _, proj := range judge.SeenProjects { - if util.ContainsFunc(ranked, func(table int64) bool { return table == proj.Location }) { + if util.ContainsFunc(ranked, func(location string) bool { return location == proj.Location }) { unranked = append(unranked, proj.Location) } } - // Convert arrays to strings - rankedStr := util.IntToString(ranked) - unrankedStr := util.IntToString(unranked) - // Write line to CSV // lucatodo: get judge info (from gocloak using admin api) to write to csv - w.Write([]string{judge.KeycloakUserId, strings.Join(rankedStr, ","), strings.Join(unrankedStr, ",")}) + w.Write([]string{judge.KeycloakUserId, strings.Join(ranked, ","), strings.Join(unranked, ",")}) } // Flush the writer @@ -253,11 +229,11 @@ func CreateProjectCSV(projects []*models.Project) []byte { w := csv.NewWriter(csvBuffer) // Write the header - w.Write([]string{"Name", "Table", "Description", "URL", "TryLink", "VideoLink", "ChallengeList", "Seen", "Active", "LastActivity"}) + w.Write([]string{"Name", "Location", "Description", "URL", "TryLink", "VideoLink", "ChallengeList", "Seen", "Active", "LastActivity"}) // Write each project for _, project := range projects { - w.Write([]string{project.Name, fmt.Sprintf("Table %d", project.Location), project.Description, project.Url, project.TryLink, project.VideoLink, strings.Join(project.ChallengeList, ","), fmt.Sprintf("%d", project.Seen), fmt.Sprintf("%t", project.Active), fmt.Sprintf("%d", project.LastActivity)}) + w.Write([]string{project.Name, project.Location, project.Description, project.Url, project.TryLink, project.VideoLink, strings.Join(project.ChallengeList, ","), fmt.Sprintf("%d", project.Seen), fmt.Sprintf("%t", project.Active), fmt.Sprintf("%d", project.LastActivity)}) } // Flush the writer diff --git a/server/models/flag.go b/server/models/flag.go index f87034e..3b83f5f 100644 --- a/server/models/flag.go +++ b/server/models/flag.go @@ -28,7 +28,7 @@ type Flag struct { JudgeId *primitive.ObjectID `json:"judge_id" bson:"judge_id"` Time primitive.DateTime `json:"time" bson:"time"` ProjectName string `json:"project_name" bson:"project_name"` - ProjectLocation int64 `json:"project_location" bson:"project_location"` + ProjectLocation string `json:"project_location" bson:"project_location"` JudgeName string `json:"judge_name" bson:"judge_name"` Reason string `json:"reason" bson:"reason"` } diff --git a/server/models/judge.go b/server/models/judge.go index d86ec7e..cf2522f 100644 --- a/server/models/judge.go +++ b/server/models/judge.go @@ -24,7 +24,7 @@ type JudgedProject struct { Categories map[string]int `bson:"categories" json:"categories"` Notes string `bson:"notes" json:"notes"` Name string `bson:"name" json:"name"` - Location int64 `bson:"location" json:"location"` + Location string `bson:"location" json:"location"` Description string `bson:"description" json:"description"` } diff --git a/server/models/project.go b/server/models/project.go index f9da72a..99bc8a4 100644 --- a/server/models/project.go +++ b/server/models/project.go @@ -9,7 +9,7 @@ import ( type Project struct { Id primitive.ObjectID `bson:"_id,omitempty" json:"id"` Name string `bson:"name" json:"name"` - Location int64 `bson:"location" json:"location"` + Location string `bson:"location" json:"location"` Description string `bson:"description" json:"description"` Url string `bson:"url" json:"url"` TryLink string `bson:"try_link" json:"try_link"` @@ -20,7 +20,7 @@ type Project struct { LastActivity primitive.DateTime `bson:"last_activity" json:"last_activity"` } -func NewProject(name string, location int64, description string, url string, tryLink string, videoLink string, challengeList []string) *Project { +func NewProject(name string, location string, description string, url string, tryLink string, videoLink string, challengeList []string) *Project { return &Project{ Name: name, Location: location, diff --git a/server/router/project.go b/server/router/project.go index c9950c3..3943801 100644 --- a/server/router/project.go +++ b/server/router/project.go @@ -1,12 +1,10 @@ package router import ( - "context" "net/http" "server/database" "server/funcs" "server/models" - "sort" "strings" "github.com/gin-gonic/gin" @@ -61,6 +59,7 @@ func AddDevpostCsv(ctx *gin.Context) { type AddProjectRequest struct { Name string `json:"name"` + Location string `json:"location"` Description string `json:"description"` Url string `json:"url"` TryLink string `json:"tryLink"` @@ -82,15 +81,8 @@ func AddProject(ctx *gin.Context) { } // Make sure name, description, and url are defined - if projectReq.Name == "" || projectReq.Description == "" || projectReq.Url == "" { - ctx.JSON(http.StatusBadRequest, gin.H{"error": "name, description, and url are required"}) - return - } - - // Get the options from the database - options, err := database.GetOptions(db) - if err != nil { - ctx.JSON(http.StatusInternalServerError, gin.H{"error": "error getting options from database: " + err.Error()}) + if projectReq.Name == "" || projectReq.Description == "" || projectReq.Url == "" || projectReq.Location == "" { + ctx.JSON(http.StatusBadRequest, gin.H{"error": "name, description, url and a location are required"}) return } @@ -103,22 +95,13 @@ func AddProject(ctx *gin.Context) { challengeList[i] = strings.TrimSpace(challengeList[i]) } - // Increment table num - database.GetNextTableNum(options) - // Create the project - project := models.NewProject(projectReq.Name, options.CurrTableNum, projectReq.Description, projectReq.Url, projectReq.TryLink, projectReq.VideoLink, challengeList) + project := models.NewProject(projectReq.Name, projectReq.Location, projectReq.Description, projectReq.Url, projectReq.TryLink, projectReq.VideoLink, challengeList) // Insert project and update the next table num field in options err = database.WithTransaction(db, func(ctx mongo.SessionContext) (interface{}, error) { // Insert project err := database.InsertProject(db, ctx, project) - if err != nil { - return nil, err - } - - // Update next table num in options doc - err = database.UpdateCurrTableNum(db, ctx, options.CurrTableNum) return nil, err }) if err != nil { @@ -148,7 +131,7 @@ func ListProjects(ctx *gin.Context) { type PublicProject struct { Name string `json:"name"` - Location int64 `json:"location"` + Location string `json:"location"` Description string `json:"description"` Url string `json:"url"` TryLink string `json:"tryLink"` From 0d1b2bde01c2b8a3c24494677da5aac204a85fbd Mon Sep 17 00:00:00 2001 From: Luca <30503695+tameTNT@users.noreply.github.com> Date: Fri, 25 Oct 2024 10:06:32 +0100 Subject: [PATCH 2/6] =?UTF-8?q?=E2=9A=B0=20Remove=20old=20current=20table?= =?UTF-8?q?=20number=20functionality?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This was used when table numbers were assigned automatically based on import order and is no longer needed now that location has to be specified. --- server/database/options.go | 6 ------ server/database/util.go | 10 ---------- server/models/options.go | 2 -- server/models/project.go | 4 ---- 4 files changed, 22 deletions(-) diff --git a/server/database/options.go b/server/database/options.go index f2cf043..1ae03cc 100644 --- a/server/database/options.go +++ b/server/database/options.go @@ -23,12 +23,6 @@ func GetOptions(db *mongo.Database) (*models.Options, error) { return &options, err } -// UpdateOptions updates the current table number in the database -func UpdateCurrTableNum(db *mongo.Database, ctx context.Context, currTableNum int64) error { - _, err := db.Collection("options").UpdateOne(ctx, gin.H{}, gin.H{"$set": gin.H{"curr_table_num": currTableNum}}) - return err -} - // UpdateOptions updates the clock in the database func UpdateClock(db *mongo.Database, clock *models.ClockState) error { _, err := db.Collection("options").UpdateOne(context.Background(), gin.H{}, gin.H{"$set": gin.H{"clock": clock}}) diff --git a/server/database/util.go b/server/database/util.go index 4b72bcf..1a78630 100644 --- a/server/database/util.go +++ b/server/database/util.go @@ -2,8 +2,6 @@ package database import ( "context" - "server/models" - "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" "go.mongodb.org/mongo-driver/mongo/writeconcern" @@ -22,11 +20,3 @@ func WithTransaction(db *mongo.Database, fn func(mongo.SessionContext) (interfac _, err = session.WithTransaction(context.Background(), fn, txnOptions) return err } - -// GetNextTableNum increments the table number and returns the new table number -// TODO: Are we updating the current table number in the database? -func GetNextTableNum(o *models.Options) int64 { - // Increment table number - o.CurrTableNum++ - return o.CurrTableNum -} diff --git a/server/models/options.go b/server/models/options.go index 80ce53b..fa0e6b9 100644 --- a/server/models/options.go +++ b/server/models/options.go @@ -5,7 +5,6 @@ import "go.mongodb.org/mongo-driver/bson/primitive" type Options struct { Id primitive.ObjectID `bson:"_id,omitempty" json:"id"` Ref int64 `bson:"ref" json:"ref"` - CurrTableNum int64 `bson:"curr_table_num" json:"curr_table_num"` Clock ClockState `bson:"clock" json:"clock"` JudgingTimer int64 `bson:"judging_timer" json:"judging_timer"` MinViews int64 `bson:"min_views" json:"min_views"` @@ -16,7 +15,6 @@ type Options struct { func NewOptions() *Options { return &Options{ Ref: 0, - CurrTableNum: 0, JudgingTimer: 300, MinViews: 3, Clock: *NewClockState(), diff --git a/server/models/project.go b/server/models/project.go index 99bc8a4..b388d19 100644 --- a/server/models/project.go +++ b/server/models/project.go @@ -35,10 +35,6 @@ func NewProject(name string, location string, description string, url string, tr } } -func DefaultProject() *Project { - return NewProject("", 0, "", "", "", "", []string{}) -} - // Create custom marshal function to change the format of the primitive.DateTime to a unix timestamp func (p *Project) MarshalJSON() ([]byte, error) { type Alias Project From 9bf29bc366143d0c25b48ea1e2f4478fdfd762e0 Mon Sep 17 00:00:00 2001 From: Luca <30503695+tameTNT@users.noreply.github.com> Date: Fri, 25 Oct 2024 10:12:33 +0100 Subject: [PATCH 3/6] =?UTF-8?q?=F0=9F=92=84=20Rename=20table=20to=20locati?= =?UTF-8?q?on=20on=20frontend=20and=20handle=20strings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Change previous number type to string in line with backend. --- .../admin/add-projects/NewProjectForm.tsx | 2 ++ .../components/admin/tables/ProjectRow.tsx | 2 +- .../components/admin/tables/ProjectsTable.tsx | 8 ++--- .../src/components/judge/ProjectDisplay.tsx | 2 +- client/src/components/judge/ProjectEntry.tsx | 5 ++- client/src/pages/admin/settings.tsx | 36 ------------------- client/src/pages/judge/project.tsx | 2 +- client/src/types.d.ts | 10 +++--- 8 files changed, 16 insertions(+), 51 deletions(-) diff --git a/client/src/components/admin/add-projects/NewProjectForm.tsx b/client/src/components/admin/add-projects/NewProjectForm.tsx index 9dc04e3..b0c8d33 100644 --- a/client/src/components/admin/add-projects/NewProjectForm.tsx +++ b/client/src/components/admin/add-projects/NewProjectForm.tsx @@ -7,6 +7,7 @@ import { errorAlert } from '../../../util'; interface NewProjectData { name: string; + location: string; description: string; url: string; link: string; @@ -45,6 +46,7 @@ const NewProjectForm = () => {

Add Project

+