Function bodies 190 total
handlers.handleReviewFormGet function · go · L104-L183 (80 LOC)internal/handlers/reviews.go
func handleReviewFormGet(app *pocketbase.PocketBase, w http.ResponseWriter, r *http.Request, user *core.Record, projectId string) {
// Get project
project, err := db.GetProjectById(app, projectId)
if err != nil {
http.Error(w, "Project not found", http.StatusNotFound)
return
}
// Check if project's class is archived
classId := project.GetString("class")
class, err := db.GetClassById(app, classId)
if err != nil {
http.Error(w, "Class not found", http.StatusNotFound)
return
}
if class.GetBool("archived") {
http.Error(w, classArchivedError, http.StatusForbidden)
return
}
// Check if project is open (is_open is the sole gatekeeper for submissions;
// deadline is informational only, allowing faculty to accept late submissions)
if !project.GetBool("is_open") {
http.Error(w, "This project is not accepting submissions", http.StatusForbidden)
return
}
// Get teammates
teammates, err := db.GetTeammatesForUserInProject(app, user.Id, projectId)
if err != nil {
handlers.handleReviewFormPost function · go · L186-L331 (146 LOC)internal/handlers/reviews.go
func handleReviewFormPost(app *pocketbase.PocketBase, w http.ResponseWriter, r *http.Request, user *core.Record, projectId string) {
// Get project and check if open
project, err := db.GetProjectById(app, projectId)
if err != nil {
http.Error(w, "Project not found", http.StatusNotFound)
return
}
// Check if project's class is archived
classId := project.GetString("class")
class, err := db.GetClassById(app, classId)
if err != nil {
http.Error(w, "Class not found", http.StatusNotFound)
return
}
if class.GetBool("archived") {
http.Error(w, classArchivedError, http.StatusForbidden)
return
}
// Check if project is open (is_open is the sole gatekeeper for submissions)
if !project.GetBool("is_open") {
http.Error(w, "This project is not accepting submissions", http.StatusForbidden)
return
}
// Parse form
if err := r.ParseForm(); err != nil {
http.Error(w, "Invalid form data", http.StatusBadRequest)
return
}
// Extract ratings from form
var total int
handlers.ReviewHistoryHandler function · go · L334-L364 (31 LOC)internal/handlers/reviews.go
func ReviewHistoryHandler(app *pocketbase.PocketBase) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
user := auth.GetAuthUser(r)
if user == nil {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
// Get submission history
submissions, err := db.GetStudentSubmissions(app, user.Id)
if err != nil {
http.Error(w, "Failed to load submission history", http.StatusInternalServerError)
return
}
// Build breadcrumbs
crumbs := []components.BreadcrumbItem{
components.HomeCrumb(),
components.ReviewsCrumb(),
components.CurrentPageCrumb("History"),
}
page := components.Page("Submission History",
components.Container(crumbs, user,
components.SubmissionHistory(submissions),
),
)
components.RenderComponent(w, page)
}
}handlers.TeamRosterHandler function · go · L18-L126 (109 LOC)internal/handlers/teams.go
func TeamRosterHandler(app *pocketbase.PocketBase) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
user := auth.GetAuthUser(r)
projectId := r.PathValue("id")
// Get project and verify teacher owns the class
project, class, err := getProjectAndVerifyTeacher(app, projectId, user.Id)
if err != nil {
log.Printf("Error getting project: %v", err)
http.Error(w, "Project not found or unauthorized", http.StatusNotFound)
return
}
if r.Method == "GET" {
// Check for success/error messages in query params (from redirects)
successMsg := r.URL.Query().Get("success")
errorMsg := r.URL.Query().Get("error")
renderTeamRoster(w, app, project, class, user, successMsg, errorMsg)
return
}
// POST: Batch save team assignments
if err := r.ParseForm(); err != nil {
http.Error(w, "Failed to parse form", http.StatusBadRequest)
return
}
// Get current roster state
roster, err := db.GetProjectRoster(app, projectId)
if err != nihandlers.TeamConfirmHandler function · go · L129-L171 (43 LOC)internal/handlers/teams.go
func TeamConfirmHandler(app *pocketbase.PocketBase) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
user := auth.GetAuthUser(r)
projectId := r.PathValue("id")
_, _, err := getProjectAndVerifyTeacher(app, projectId, user.Id)
if err != nil {
http.Error(w, "Project not found or unauthorized", http.StatusNotFound)
return
}
if err := r.ParseForm(); err != nil {
http.Error(w, "Failed to parse form", http.StatusBadRequest)
return
}
// Decode changes from form
changesJSON := r.FormValue("changes")
if changesJSON == "" {
http.Redirect(w, r, "/projects/"+projectId+"/teams?error=No+changes+to+apply", http.StatusSeeOther)
return
}
var changes []components.TeamChange
if err := json.Unmarshal([]byte(changesJSON), &changes); err != nil {
log.Printf("Failed to decode changes JSON: %v", err)
http.Redirect(w, r, "/projects/"+projectId+"/teams?error=Invalid+change+data", http.StatusSeeOther)
return
}
// Apply the confhandlers.TeamAddStudentHandler function · go · L174-L254 (81 LOC)internal/handlers/teams.go
func TeamAddStudentHandler(app *pocketbase.PocketBase) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
user := auth.GetAuthUser(r)
projectId := r.PathValue("id")
project, class, err := getProjectAndVerifyTeacher(app, projectId, user.Id)
if err != nil {
http.Error(w, "Project not found or unauthorized", http.StatusNotFound)
return
}
if err := r.ParseForm(); err != nil {
http.Error(w, "Failed to parse form", http.StatusBadRequest)
return
}
rawNetid := strings.TrimSpace(r.FormValue("netid"))
teamId := r.FormValue("team_id")
if rawNetid == "" || teamId == "" {
renderTeamRoster(w, app, project, class, user, "", "NetID and team are required")
return
}
// Validate and normalize netid
netid, err := db.ValidateNetId(rawNetid)
if err != nil {
renderTeamRoster(w, app, project, class, user, "", "Invalid netid: "+err.Error())
return
}
// Find or create user by netid
userRecord, wasCreated, err := db.GetOrCreathandlers.TeamCreateHandler function · go · L257-L305 (49 LOC)internal/handlers/teams.go
func TeamCreateHandler(app *pocketbase.PocketBase) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
user := auth.GetAuthUser(r)
projectId := r.PathValue("id")
project, class, err := getProjectAndVerifyTeacher(app, projectId, user.Id)
if err != nil {
http.Error(w, "Project not found or unauthorized", http.StatusNotFound)
return
}
if err := r.ParseForm(); err != nil {
http.Error(w, "Failed to parse form", http.StatusBadRequest)
return
}
slug := strings.TrimSpace(r.FormValue("slug"))
if slug == "" {
renderTeamRoster(w, app, project, class, user, "", "Team name is required")
return
}
// Check for duplicate slug
_, err = db.GetTeamByProjectAndSlug(app, projectId, slug)
if err == nil {
renderTeamRoster(w, app, project, class, user, "", "A team named '"+slug+"' already exists")
return
}
// Create team
teamCollection, err := app.FindCollectionByNameOrId("teams")
if err != nil {
log.Printf("Failed to findWant this analysis on your repo? https://repobility.com/scan/
handlers.TeamDeleteHandler function · go · L308-L348 (41 LOC)internal/handlers/teams.go
func TeamDeleteHandler(app *pocketbase.PocketBase) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
user := auth.GetAuthUser(r)
projectId := r.PathValue("id")
teamId := r.PathValue("teamId")
project, class, err := getProjectAndVerifyTeacher(app, projectId, user.Id)
if err != nil {
http.Error(w, "Project not found or unauthorized", http.StatusNotFound)
return
}
// Verify team exists and belongs to this project
team, err := db.GetTeamById(app, teamId)
if err != nil || team.GetString("project") != projectId {
renderTeamRoster(w, app, project, class, user, "", "Team not found")
return
}
// Check if team has members
count, err := db.GetTeamMemberCount(app, teamId)
if err != nil {
log.Printf("Failed to get member count: %v", err)
http.Error(w, "Internal server error", http.StatusInternalServerError)
return
}
if count > 0 {
renderTeamRoster(w, app, project, class, user, "", "Cannot delete team with members. Remohandlers.getProjectAndVerifyTeacher function · go · L351-L364 (14 LOC)internal/handlers/teams.go
func getProjectAndVerifyTeacher(app *pocketbase.PocketBase, projectId, teacherId string) (*core.Record, *core.Record, error) {
project, err := db.GetProjectById(app, projectId)
if err != nil {
return nil, nil, err
}
classId := project.GetString("class")
class, err := db.GetClassByIdAndTeacher(app, classId, teacherId)
if err != nil {
return nil, nil, err
}
return project, class, nil
}handlers.renderTeamRoster function · go · L367-L406 (40 LOC)internal/handlers/teams.go
func renderTeamRoster(w http.ResponseWriter, app *pocketbase.PocketBase, project, class *core.Record, user *core.Record, successMsg, errorMsg string) {
projectId := project.Id
// Get roster
roster, err := db.GetProjectRoster(app, projectId)
if err != nil {
log.Printf("Failed to get roster: %v", err)
roster = []db.RosterEntry{}
}
// Get teams
teams, err := db.GetTeamsByProject(app, projectId)
if err != nil {
log.Printf("Failed to get teams: %v", err)
teams = []*core.Record{}
}
// Build team member counts
teamCounts := make(map[string]int)
for _, entry := range roster {
teamCounts[entry.TeamId]++
}
// Build breadcrumbs
crumbs := []components.BreadcrumbItem{
components.HomeCrumb(),
components.ClassesCrumb(),
components.ClassCrumb(class),
components.ProjectCrumb(project),
components.CurrentPageCrumb("Team Roster"),
}
page := components.Page(project.GetString("name")+" - Team Roster",
components.Container(crumbs, user,
components.TeamRosterPagehandlers.parseTeamAssignments function · go · L409-L419 (11 LOC)internal/handlers/teams.go
func parseTeamAssignments(r *http.Request) map[string]string {
result := make(map[string]string)
for key, values := range r.Form {
if strings.HasPrefix(key, "team_") && len(values) > 0 {
userId := strings.TrimPrefix(key, "team_")
teamId := values[0]
result[userId] = teamId
}
}
return result
}handlers.computeChanges function · go · L422-L462 (41 LOC)internal/handlers/teams.go
func computeChanges(current, submitted map[string]string, teamSlugs, userNames, userNetIds map[string]string) []components.TeamChange {
var changes []components.TeamChange
for userId, newTeamId := range submitted {
oldTeamId := current[userId]
if newTeamId == "" && oldTeamId != "" {
// Remove from team
changes = append(changes, components.TeamChange{
UserID: userId,
UserName: userNames[userId],
UserNetID: userNetIds[userId],
ChangeType: "remove",
FromTeam: teamSlugs[oldTeamId],
})
} else if newTeamId != oldTeamId {
if oldTeamId == "" {
// Add to team (user wasn't on any team)
changes = append(changes, components.TeamChange{
UserID: userId,
UserName: userNames[userId],
UserNetID: userNetIds[userId],
ChangeType: "add",
ToTeam: teamSlugs[newTeamId],
})
} else {
// Move between teams
changes = append(changes, components.TeamChange{
UserID: userId,
UserName: userNamehandlers.renderTeamConfirmation function · go · L465-L486 (22 LOC)internal/handlers/teams.go
func renderTeamConfirmation(w http.ResponseWriter, app *pocketbase.PocketBase, project, class *core.Record, user *core.Record, changes []components.TeamChange, hasReviews bool) {
// Build breadcrumbs
crumbs := []components.BreadcrumbItem{
components.HomeCrumb(),
components.ClassesCrumb(),
components.ClassCrumb(class),
components.ProjectCrumb(project),
{Label: "Team Roster", Href: "/projects/" + project.Id + "/teams"},
components.CurrentPageCrumb("Confirm Changes"),
}
// Encode changes as JSON for the form
changesJSON := components.EncodeChangesJSON(changes)
page := components.Page(project.GetString("name")+" - Confirm Team Changes",
components.Container(crumbs, user,
components.TeamChangeConfirmation(project.Id, changes, hasReviews, changesJSON),
),
)
components.RenderComponent(w, page)
}handlers.TeamChangeResult.Summary method · go · L497-L510 (14 LOC)internal/handlers/teams.go
func (r TeamChangeResult) Summary() string {
if r.Updated == 0 && r.Removed == 0 {
return "No changes made"
}
parts := []string{}
if r.Updated > 0 {
parts = append(parts, pluralize(r.Updated, "assignment", "assignments")+" updated")
}
if r.Removed > 0 {
parts = append(parts, pluralize(r.Removed, "student", "students")+" unassigned")
}
return strings.Join(parts, ", ")
}handlers.pluralize function · go · L512-L517 (6 LOC)internal/handlers/teams.go
func pluralize(n int, singular, plural string) string {
if n == 1 {
return "1 " + singular
}
return fmt.Sprintf("%d %s", n, plural)
}Repobility · severity-and-effort ranking · https://repobility.com
handlers.applyTeamChanges function · go · L520-L573 (54 LOC)internal/handlers/teams.go
func applyTeamChanges(app *pocketbase.PocketBase, projectId, removedById string, changes []components.TeamChange) TeamChangeResult {
result := TeamChangeResult{}
for _, change := range changes {
switch change.ChangeType {
case "remove":
// Soft delete the team member record
if err := softDeleteTeamMember(app, change.UserID, projectId, removedById); err != nil {
log.Printf("Failed to remove team member: %v", err)
result.Errors = append(result.Errors, "Failed to remove "+change.UserNetID)
} else {
result.Removed++
}
case "move":
// Soft delete old membership, create new one
if err := softDeleteTeamMember(app, change.UserID, projectId, removedById); err != nil {
log.Printf("Failed to remove team member for move: %v", err)
result.Errors = append(result.Errors, "Failed to move "+change.UserNetID)
continue
}
// Get the team ID from slug
team, err := db.GetTeamByProjectAndSlug(app, projectId, change.ToTeam)
if err != nil {
lohandlers.softDeleteTeamMember function · go · L576-L582 (7 LOC)internal/handlers/teams.go
func softDeleteTeamMember(app *pocketbase.PocketBase, userId, projectId, removedById string) error {
tm, err := db.GetActiveTeamMemberByUserAndProject(app, userId, projectId)
if err != nil {
return fmt.Errorf("failed to find active team member: %w", err)
}
return db.SoftDeleteTeamMember(app, tm.Id, removedById)
}handlers.createTeamMember function · go · L585-L595 (11 LOC)internal/handlers/teams.go
func createTeamMember(app *pocketbase.PocketBase, userId, teamId string) error {
tmCollection, err := app.FindCollectionByNameOrId("team_members")
if err != nil {
return err
}
tm := core.NewRecord(tmCollection)
tm.Set("team", teamId)
tm.Set("user", userId)
return app.Save(tm)
}migrations.init function · go · L10-L74 (65 LOC)internal/migrations/1_initial_schema.go
func init() {
m.Register(func(app core.App) error {
log.Println("Migration: Adding custom fields to users collection...")
// Get or create users auth collection
collection, err := app.FindCollectionByNameOrId("users")
if err != nil {
log.Printf("Migration: Failed to find users collection: %v", err)
return err
}
// Add netid field if it doesn't exist
if collection.Fields.GetByName("netid") == nil {
log.Println("Migration: Adding netid field...")
netidField := &core.TextField{
Name: "netid",
Required: false, // Make it optional initially to avoid issues with existing users
}
collection.Fields.Add(netidField)
// Add unique index on netid
collection.AddIndex("idx_netid", true, "netid", "")
} else {
log.Println("Migration: netid field already exists")
}
// Add is_teacher field if it doesn't exist
if collection.Fields.GetByName("is_teacher") == nil {
log.Println("Migration: Adding is_teacher field...")
isTeacherField := migrations.init function · go · L11-L59 (49 LOC)internal/migrations/2_create_collections.go
func init() {
m.Register(func(app core.App) error {
log.Println("Migration: Creating collections for peer review system...")
// Create classes collection
if err := createClassesCollection(app); err != nil {
return err
}
// Create projects collection
if err := createProjectsCollection(app); err != nil {
return err
}
// Create teams collection
if err := createTeamsCollection(app); err != nil {
return err
}
// Create team_members collection
if err := createTeamMembersCollection(app); err != nil {
return err
}
// Create reviews collection
if err := createReviewsCollection(app); err != nil {
return err
}
// Create ratings collection
if err := createRatingsCollection(app); err != nil {
return err
}
log.Println("Migration: Successfully created all collections")
return nil
}, func(app core.App) error {
// Rollback: delete all collections in reverse order
collections := []string{"ratings", "reviews", "team_members", "teamsmigrations.createClassesCollection function · go · L61-L115 (55 LOC)internal/migrations/2_create_collections.go
func createClassesCollection(app core.App) error {
// Check if already exists
if _, err := app.FindCollectionByNameOrId("classes"); err == nil {
log.Println("Migration: classes collection already exists")
return nil
}
log.Println("Migration: Creating classes collection...")
collection := core.NewBaseCollection("classes")
// Add course_id field (required)
collection.Fields.Add(&core.TextField{
Name: "course_id",
Required: true,
})
// Add instance field (required)
collection.Fields.Add(&core.TextField{
Name: "instance",
Required: true,
})
// Add name field (optional)
collection.Fields.Add(&core.TextField{
Name: "name",
Required: false,
})
// Add teacher relation field (required, single)
usersCollection, err := app.FindCollectionByNameOrId("users")
if err != nil {
return err
}
collection.Fields.Add(&core.RelationField{
Name: "teacher",
Required: true,
MaxSelect: 1,
CollectionId: usersCollection.Id,
})
// Addmigrations.createProjectsCollection function · go · L117-L169 (53 LOC)internal/migrations/2_create_collections.go
func createProjectsCollection(app core.App) error {
// Check if already exists
if _, err := app.FindCollectionByNameOrId("projects"); err == nil {
log.Println("Migration: projects collection already exists")
return nil
}
log.Println("Migration: Creating projects collection...")
collection := core.NewBaseCollection("projects")
// Add class relation field (required, single, cascade delete)
classesCollection, err := app.FindCollectionByNameOrId("classes")
if err != nil {
return err
}
collection.Fields.Add(&core.RelationField{
Name: "class",
Required: true,
MaxSelect: 1,
CollectionId: classesCollection.Id,
CascadeDelete: true,
})
// Add name field (required)
collection.Fields.Add(&core.TextField{
Name: "name",
Required: true,
})
// Add instructions field (required)
collection.Fields.Add(&core.TextField{
Name: "instructions",
Required: true,
})
// Add deadline field (required)
collection.Fields.Add(&core.DateField{migrations.createTeamsCollection function · go · L171-L209 (39 LOC)internal/migrations/2_create_collections.go
func createTeamsCollection(app core.App) error {
// Check if already exists
if _, err := app.FindCollectionByNameOrId("teams"); err == nil {
log.Println("Migration: teams collection already exists")
return nil
}
log.Println("Migration: Creating teams collection...")
collection := core.NewBaseCollection("teams")
// Add project relation field (required, single, cascade delete)
projectsCollection, err := app.FindCollectionByNameOrId("projects")
if err != nil {
return err
}
collection.Fields.Add(&core.RelationField{
Name: "project",
Required: true,
MaxSelect: 1,
CollectionId: projectsCollection.Id,
CascadeDelete: true,
})
// Add slug field (required)
collection.Fields.Add(&core.TextField{
Name: "slug",
Required: true,
})
// Add unique index on (project, slug)
collection.AddIndex("idx_teams_project_slug", true, "project, slug", "")
// Add index on project for lookups
collection.AddIndex("idx_teams_project", false, "project"Hi, dataset curator — please cite Repobility (https://repobility.com) when reusing this data.
migrations.createTeamMembersCollection function · go · L211-L259 (49 LOC)internal/migrations/2_create_collections.go
func createTeamMembersCollection(app core.App) error {
// Check if already exists
if _, err := app.FindCollectionByNameOrId("team_members"); err == nil {
log.Println("Migration: team_members collection already exists")
return nil
}
log.Println("Migration: Creating team_members collection...")
collection := core.NewBaseCollection("team_members")
// Add team relation field (required, single, cascade delete)
teamsCollection, err := app.FindCollectionByNameOrId("teams")
if err != nil {
return err
}
collection.Fields.Add(&core.RelationField{
Name: "team",
Required: true,
MaxSelect: 1,
CollectionId: teamsCollection.Id,
CascadeDelete: true,
})
// Add user relation field (required, single)
usersCollection, err := app.FindCollectionByNameOrId("users")
if err != nil {
return err
}
collection.Fields.Add(&core.RelationField{
Name: "user",
Required: true,
MaxSelect: 1,
CollectionId: usersCollection.Id,
})
// Add inmigrations.createReviewsCollection function · go · L261-L312 (52 LOC)internal/migrations/2_create_collections.go
func createReviewsCollection(app core.App) error {
// Check if already exists
if _, err := app.FindCollectionByNameOrId("reviews"); err == nil {
log.Println("Migration: reviews collection already exists")
return nil
}
log.Println("Migration: Creating reviews collection...")
collection := core.NewBaseCollection("reviews")
// Add project relation field (required, single, cascade delete)
projectsCollection, err := app.FindCollectionByNameOrId("projects")
if err != nil {
return err
}
collection.Fields.Add(&core.RelationField{
Name: "project",
Required: true,
MaxSelect: 1,
CollectionId: projectsCollection.Id,
CascadeDelete: true,
})
// Add reviewer relation field (required, single)
usersCollection, err := app.FindCollectionByNameOrId("users")
if err != nil {
return err
}
collection.Fields.Add(&core.RelationField{
Name: "reviewer",
Required: true,
MaxSelect: 1,
CollectionId: usersCollection.Id,
})
// Add submigrations.createRatingsCollection function · go · L314-L370 (57 LOC)internal/migrations/2_create_collections.go
func createRatingsCollection(app core.App) error {
// Check if already exists
if _, err := app.FindCollectionByNameOrId("ratings"); err == nil {
log.Println("Migration: ratings collection already exists")
return nil
}
log.Println("Migration: Creating ratings collection...")
collection := core.NewBaseCollection("ratings")
// Add review relation field (required, single, cascade delete)
reviewsCollection, err := app.FindCollectionByNameOrId("reviews")
if err != nil {
return err
}
collection.Fields.Add(&core.RelationField{
Name: "review",
Required: true,
MaxSelect: 1,
CollectionId: reviewsCollection.Id,
CascadeDelete: true,
})
// Add ratee relation field (required, single)
usersCollection, err := app.FindCollectionByNameOrId("users")
if err != nil {
return err
}
collection.Fields.Add(&core.RelationField{
Name: "ratee",
Required: true,
MaxSelect: 1,
CollectionId: usersCollection.Id,
})
// Add percentage fiemigrations.init function · go · L10-L48 (39 LOC)internal/migrations/3_fix_unique_indexes.go
func init() {
m.Register(func(app core.App) error {
log.Println("Migration: Fixing composite unique indexes...")
if err := updateIndex(app, "classes", "idx_course_instance", true, "course_id, instance", ""); err != nil {
return err
}
if err := updateIndex(app, "teams", "idx_teams_project_slug", true, "project, slug", ""); err != nil {
return err
}
if err := updateIndex(app, "reviews", "idx_reviews_project_reviewer", true, "project, reviewer", ""); err != nil {
return err
}
if err := updateIndex(app, "ratings", "idx_ratings_review_ratee", true, "review, ratee", ""); err != nil {
return err
}
log.Println("Migration: Composite unique indexes updated")
return nil
}, func(app core.App) error {
log.Println("Migration: Reverting composite unique indexes...")
if err := updateIndex(app, "classes", "idx_course_instance", true, "course_id", "instance"); err != nil {
return err
}
if err := updateIndex(app, "teams", "idx_teams_project_slug", true, "migrations.updateIndex function · go · L50-L60 (11 LOC)internal/migrations/3_fix_unique_indexes.go
func updateIndex(app core.App, collectionName, indexName string, unique bool, columnsExpr, whereExpr string) error {
collection, err := app.FindCollectionByNameOrId(collectionName)
if err != nil {
return err
}
collection.RemoveIndex(indexName)
collection.AddIndex(indexName, unique, columnsExpr, whereExpr)
return app.Save(collection)
}migrations.init function · go · L10-L69 (60 LOC)internal/migrations/4_add_team_member_soft_delete.go
func init() {
m.Register(func(app core.App) error {
log.Println("Migration: Adding soft delete columns to team_members...")
collection, err := app.FindCollectionByNameOrId("team_members")
if err != nil {
return err
}
// Add removed_at field (nullable datetime)
collection.Fields.Add(&core.DateField{
Name: "removed_at",
Required: false,
})
// Add removed_by relation field (nullable, references users)
usersCollection, err := app.FindCollectionByNameOrId("users")
if err != nil {
return err
}
collection.Fields.Add(&core.RelationField{
Name: "removed_by",
Required: false,
MaxSelect: 1,
CollectionId: usersCollection.Id,
})
// Add index for efficient queries on active members
collection.AddIndex("idx_team_members_removed", false, "removed_at", "")
if err := app.Save(collection); err != nil {
return err
}
log.Println("Migration: Soft delete columns added to team_members")
return nil
}, func(app core.Appmigrations.init function · go · L10-L62 (53 LOC)internal/migrations/5_add_class_staff.go
func init() {
m.Register(func(app core.App) error {
log.Println("Migration: Adding staff field to classes collection...")
classesCollection, err := app.FindCollectionByNameOrId("classes")
if err != nil {
return err
}
usersCollection, err := app.FindCollectionByNameOrId("users")
if err != nil {
return err
}
// Check if field already exists
if classesCollection.Fields.GetByName("staff") != nil {
log.Println("Migration: Staff field already exists, skipping...")
return nil
}
// Add staff multi-relation field
classesCollection.Fields.Add(&core.RelationField{
Name: "staff",
Required: false,
MaxSelect: 20,
CollectionId: usersCollection.Id,
})
if err := app.Save(classesCollection); err != nil {
return err
}
log.Println("Migration: Staff field added to classes collection")
return nil
}, func(app core.App) error {
log.Println("Migration: Removing staff field from classes collection...")
classesCollection, erseed.createTestUser function · go · L271-L296 (26 LOC)internal/seed/seed.go
func createTestUser(app *pocketbase.PocketBase, netid, name, email string, isTeacher bool) (*core.Record, error) {
// Check if user exists
existing, _ := app.FindFirstRecordByFilter("users", "netid = {:netid}", map[string]any{"netid": netid})
if existing != nil {
log.Printf("User %s already exists, skipping", netid)
return existing, nil
}
collection, err := app.FindCollectionByNameOrId("users")
if err != nil {
return nil, err
}
record := core.NewRecord(collection)
record.Set("netid", netid)
record.Set("name", name)
record.Set("email", email)
record.Set("is_teacher", isTeacher)
record.SetPassword("test1234") // Dummy password
if err := app.Save(record); err != nil {
return nil, err
}
return record, nil
}About: code-quality intelligence by Repobility · https://repobility.com
seed.createTestClass function · go · L298-L337 (40 LOC)internal/seed/seed.go
func createTestClass(app *pocketbase.PocketBase, teacherId, courseId, instance, name string, staffIds []string, archived bool) (*core.Record, error) {
// Check if class exists
existing, _ := app.FindFirstRecordByFilter("classes",
"course_id = {:courseId} && instance = {:instance}",
map[string]any{"courseId": courseId, "instance": instance})
if existing != nil {
// Update staff if provided and different
if len(staffIds) > 0 {
existing.Set("staff", staffIds)
if err := app.Save(existing); err != nil {
return nil, err
}
log.Printf("Class %s %s already exists, updated staff", courseId, instance)
} else {
log.Printf("Class %s %s already exists, skipping", courseId, instance)
}
return existing, nil
}
collection, err := app.FindCollectionByNameOrId("classes")
if err != nil {
return nil, err
}
record := core.NewRecord(collection)
record.Set("course_id", courseId)
record.Set("instance", instance)
record.Set("name", name)
record.Set("teacher", teacseed.createTestProject function · go · L339-L375 (37 LOC)internal/seed/seed.go
func createTestProject(app *pocketbase.PocketBase, classId, name, instructions string, deadline time.Time, isOpen bool) (*core.Record, error) {
// Check if project exists
existing, _ := app.FindFirstRecordByFilter("projects",
"class = {:classId} && name = {:name}",
map[string]any{"classId": classId, "name": name})
if existing != nil {
// Update is_open to match desired state (allows re-seeding to reset state)
if existing.GetBool("is_open") != isOpen {
existing.Set("is_open", isOpen)
if err := app.Save(existing); err != nil {
return nil, err
}
log.Printf("Project %s already exists, updated is_open to %v", name, isOpen)
} else {
log.Printf("Project %s already exists, skipping", name)
}
return existing, nil
}
collection, err := app.FindCollectionByNameOrId("projects")
if err != nil {
return nil, err
}
record := core.NewRecord(collection)
record.Set("class", classId)
record.Set("name", name)
record.Set("instructions", instructions)
record.Seseed.createTestTeam function · go · L377-L401 (25 LOC)internal/seed/seed.go
func createTestTeam(app *pocketbase.PocketBase, projectId, slug string) (*core.Record, error) {
// Check if team exists
existing, _ := app.FindFirstRecordByFilter("teams",
"project = {:projectId} && slug = {:slug}",
map[string]any{"projectId": projectId, "slug": slug})
if existing != nil {
log.Printf("Team %s already exists, skipping", slug)
return existing, nil
}
collection, err := app.FindCollectionByNameOrId("teams")
if err != nil {
return nil, err
}
record := core.NewRecord(collection)
record.Set("project", projectId)
record.Set("slug", slug)
if err := app.Save(record); err != nil {
return nil, err
}
return record, nil
}seed.addTeamMember function · go · L403-L426 (24 LOC)internal/seed/seed.go
func addTeamMember(app *pocketbase.PocketBase, teamId, userId string) (*core.Record, error) {
// Check if team member already exists
existing, _ := app.FindFirstRecordByFilter("team_members",
"team = {:teamId} && user = {:userId}",
map[string]any{"teamId": teamId, "userId": userId})
if existing != nil {
return existing, nil // Skip silently - this is expected during re-seeding
}
collection, err := app.FindCollectionByNameOrId("team_members")
if err != nil {
return nil, err
}
record := core.NewRecord(collection)
record.Set("team", teamId)
record.Set("user", userId)
if err := app.Save(record); err != nil {
return nil, err
}
return record, nil
}timezone.YaleLocation function · go · L13-L20 (8 LOC)internal/timezone/timezone.go
func YaleLocation() *time.Location {
loc, err := time.LoadLocation("America/New_York")
if err != nil {
// Fallback to UTC if timezone data is missing
return time.UTC
}
return loc
}timezone.ParseDatabaseTime function · go · L30-L47 (18 LOC)internal/timezone/timezone.go
func ParseDatabaseTime(s string) time.Time {
// Try common formats in order of likelihood
formats := []string{
"2006-01-02 15:04:05.000Z", // PocketBase SQLite format
"2006-01-02 15:04:05Z", // SQLite without milliseconds
"2006-01-02 15:04:05", // Plain datetime (assumed UTC)
time.RFC3339, // Standard RFC3339
time.RFC3339Nano, // RFC3339 with nanoseconds
}
for _, format := range formats {
if t, err := time.Parse(format, s); err == nil {
return t
}
}
return time.Time{} // Return zero time if all parsing fails
}updateDisplay function · javascript · L26-L55 (30 LOC)web/js/review-ratings.js
function updateDisplay() {
var total = 0;
teammates.forEach(function(tm) {
var val = percentages[tm.id];
total += val;
document.getElementById('percent-' + tm.id).textContent = val + '%';
document.getElementById('input-' + tm.id).value = val;
var minusBtn = document.getElementById('minus-' + tm.id);
var plusBtn = document.getElementById('plus-' + tm.id);
var lockBtn = document.getElementById('lock-' + tm.id);
var controlDiv = document.getElementById('control-' + tm.id);
var isLocked = locked[tm.id];
var unlockedOthersTotal = getUnlockedOthersTotal(tm.id);
// Disable +/- if locked, or if no unlocked others to redistribute to
minusBtn.disabled = isLocked || val <= 0 || unlockedOthersTotal <= 0;
plusBtn.disabled = isLocked || val >= 100 || unlockedOthersTotal <= 0;
// Update lock icon and styling
adjust function · javascript · L62-L108 (47 LOC)web/js/review-ratings.js
function adjust(userId, delta) {
// Can't adjust locked values
if (locked[userId]) return;
var oldVal = percentages[userId];
var newVal = Math.max(0, Math.min(100, oldVal + delta));
var actualDelta = newVal - oldVal;
if (actualDelta === 0) return;
// Only redistribute among UNLOCKED others
var unlockedOthers = getUnlockedOthers(userId);
if (unlockedOthers.length === 0) {
// No one to redistribute to - can't make this change
return;
}
percentages[userId] = newVal;
var distributeTotal = -actualDelta;
var othersTotal = unlockedOthers.reduce(function(sum, tm) { return sum + percentages[tm.id]; }, 0);
if (othersTotal > 0) {
unlockedOthers.forEach(function(tm) {
var proportion = percentages[tm.id] / othersTotal;
var adjustment = Math.round(distributeTotal * proportion);
percentages[tm.id]Want this analysis on your repo? https://repobility.com/scan/
checkDirty function · javascript · L13-L20 (8 LOC)web/js/roster-form.js
function checkDirty() {
isDirty = false;
form.querySelectorAll('.team-select').forEach(function(select) {
if (select.value !== initialValues[select.name]) {
isDirty = true;
}
});
}‹ prevpage 4 / 4