Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add classroom name check #185

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
33 changes: 33 additions & 0 deletions backend/internal/handlers/classrooms/classrooms.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ package classrooms

import (
"context"
"errors"
"fmt"
"log"
"net/http"
"net/url"
"strconv"
"strings"
"time"
Expand Down Expand Up @@ -48,6 +50,28 @@ func (s *ClassroomService) getClassroom() fiber.Handler {
}
}

func (s *ClassroomService) checkClassroomExists() fiber.Handler {
return func(c *fiber.Ctx) error {
classroomName := c.Params("classroom_name")
if classroomName == "" {
return errs.BadRequest(errors.New("classroom name is required"))
}

// Decode the URL-encoded classroom name
decodedName, err := url.QueryUnescape(classroomName)
if err != nil {
return errs.BadRequest(errors.New("invalid classroom name encoding"))
}

_, err = s.store.GetClassroomByName(c.Context(), decodedName)
exists := err == nil // If no error, classroom exists

return c.Status(http.StatusOK).JSON(fiber.Map{
"exists": exists,
})
}
}

// Creates a new classroom.
func (s *ClassroomService) createClassroom() fiber.Handler {
return func(c *fiber.Ctx) error {
Expand All @@ -62,6 +86,15 @@ func (s *ClassroomService) createClassroom() fiber.Handler {
return errs.InvalidRequestBody(models.Classroom{})
}

// check if classroom exists already
_, err = s.store.GetClassroomByName(c.Context(), classroomData.Name)
exists := err == nil // If no error, classroom exists
if exists {
return c.Status(http.StatusConflict).JSON(fiber.Map{
"exists": true,
})
}

membership, err := client.GetUserOrgMembership(c.Context(), classroomData.OrgName, githubUser.Login)
if err != nil || *membership.Role != "admin" {
return errs.InsufficientPermissionsError()
Expand Down
3 changes: 3 additions & 0 deletions backend/internal/handlers/classrooms/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ func classroomRoutes(router fiber.Router, service *ClassroomService) fiber.Route
// Create a classroom
classroomRouter.Post("/", service.createClassroom())

// Check if a classroom exists
classroomRouter.Get("/check-classroom/:classroom_name", service.checkClassroomExists())

// Update a classroom
classroomRouter.Put("/classroom/:classroom_id", service.updateClassroom())

Expand Down
21 changes: 21 additions & 0 deletions backend/internal/storage/postgres/classrooms.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,27 @@ func (db *DB) GetClassroomByID(ctx context.Context, classroomID int64) (models.C
return classroomData, nil
}

func (db *DB) GetClassroomByName(ctx context.Context, classroomName string) (models.Classroom, error) {
var classroomData models.Classroom
err := db.connPool.QueryRow(ctx, `
SELECT id, name, org_id, org_name, created_at, student_team_name
FROM classrooms
WHERE name = $1`, classroomName).Scan(
&classroomData.ID,
&classroomData.Name,
&classroomData.OrgID,
&classroomData.OrgName,
&classroomData.CreatedAt,
&classroomData.StudentTeamName,
)

if err != nil {
return models.Classroom{}, errs.NewDBError(err)
}

return classroomData, nil
}

func (db *DB) AddUserToClassroom(ctx context.Context, classroomID int64, classroomRole string, classroomStatus models.UserStatus, userID int64) (models.ClassroomUser, error) {
var classroomUser models.ClassroomUser

Expand Down
1 change: 1 addition & 0 deletions backend/internal/storage/storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ type Classroom interface {
CreateClassroom(ctx context.Context, classroomData models.Classroom) (models.Classroom, error)
UpdateClassroom(ctx context.Context, classroomData models.Classroom) (models.Classroom, error)
GetClassroomByID(ctx context.Context, classroomID int64) (models.Classroom, error)
GetClassroomByName(ctx context.Context, classroomName string) (models.Classroom, error)
AddUserToClassroom(ctx context.Context, classroomID int64, classroomRole string, classroomStatus models.UserStatus, userID int64) (models.ClassroomUser, error)
RemoveUserFromClassroom(ctx context.Context, classroomID int64, userID int64) error
ModifyUserRole(ctx context.Context, classroomID int64, classroomRole string, userID int64) (models.ClassroomUser, error)
Expand Down
16 changes: 16 additions & 0 deletions frontend/src/api/classrooms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -245,3 +245,19 @@ export async function getClassroomNames(): Promise<string[]> {
const resp: { semester_names: string[] } = await response.json();
return resp.semester_names;
}

export async function checkClassroomExists(classroomName: string): Promise<boolean> {
const encodedName = encodeURIComponent(classroomName);
const response = await fetch(`${base_url}/classrooms/check-classroom/${encodedName}`, {
method: "GET",
credentials: "include",
headers: {
"Content-Type": "application/json",
},
});
if (!response.ok) {
throw new Error(response.statusText);
}
const data = await response.json();
return data.exists;
}
7 changes: 7 additions & 0 deletions frontend/src/global.css
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,13 @@ a {
font-size: var(--body-font-size);
}

.error {
color: var(--red);
font-size: var(--font-size-xs);
margin-top: 5px;
text-align: center;
}

/* Custom scroll bar flex properties and styling */
.simplebar-wrapper {
flex-grow: 1;
Expand Down
21 changes: 19 additions & 2 deletions frontend/src/pages/Classrooms/Create/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useContext, useEffect, useState } from "react";
import { useNavigate, Link } from "react-router-dom";
import { getClassroomNames, postClassroom } from "@/api/classrooms";
import { checkClassroomExists, getClassroomNames, postClassroom } from "@/api/classrooms";
import { SelectedClassroomContext } from "@/contexts/selectedClassroom";
import { getOrganizationDetails } from "@/api/organizations";
import useUrlParameter from "@/hooks/useUrlParameter";
Expand All @@ -22,6 +22,23 @@ const ClassroomCreation: React.FC = () => {
const { setSelectedClassroom } = useContext(SelectedClassroomContext);
const navigate = useNavigate();

const [classroomExists, setClassroomExists] = useState(false);

useEffect(() => {
const checkExists = async () => {
if (name) {
const exists = await checkClassroomExists(name);
setClassroomExists(exists);
if (exists) {
setError("A classroom with this name already exists.");
} else {
setError(null);
}
}
};
checkExists();
}, [name]);

useEffect(() => {
const fetchClassroomNames = async () => {
try {
Expand Down Expand Up @@ -137,7 +154,7 @@ const ClassroomCreation: React.FC = () => {
</p>
)}
<div className="ClassroomCreation__buttonWrapper">
<Button type="submit">Create Classroom</Button>
<Button variant={classroomExists ? "disabled" : "primary"} type="submit" disabled={classroomExists}>Create Classroom</Button>
<Button variant="secondary" href="/app/organization/select">
Select a different organization
</Button>
Expand Down
Loading