diff --git a/app/controllers/AdminController.scala b/app/controllers/AdminController.scala
index afc8a9cddd..ca277af839 100644
--- a/app/controllers/AdminController.scala
+++ b/app/controllers/AdminController.scala
@@ -752,7 +752,7 @@ class AdminController @Inject() (implicit val env: Environment[User, SessionAuth
if (isAdmin(request.identity)) {
val data = Json.obj(
"user_stats" -> Json.toJson(UserDAOSlick.getUserStatsForAdminPage),
- "organizations" -> Json.toJson(OrganizationTable.getAllOrganizations)
+ "organizations" -> Json.toJson(OrganizationTable.getAllTeams)
)
Future.successful(Ok(data))
} else {
diff --git a/app/controllers/LabelController.scala b/app/controllers/LabelController.scala
index 78c9f625b6..12d2282376 100644
--- a/app/controllers/LabelController.scala
+++ b/app/controllers/LabelController.scala
@@ -19,6 +19,108 @@ import play.api.Logger
class LabelController @Inject() (implicit val env: Environment[User, SessionAuthenticator])
extends Silhouette[User, SessionAuthenticator] with ProvidesHeader {
+ /**
+ * Fetches a single label by its ID.
+ *
+ * @param labelId The ID of the label to find.
+ * @return The label data as JSON if found, otherwise a 404 response.
+ */
+ def getLabelById(labelId: Int) = UserAwareAction.async { implicit request =>
+ Logger.info(s"Attempting to fetch label with ID: $labelId")
+
+ // Check if the user is authenticated
+ request.identity match {
+ case Some(user) =>
+ LabelTable.getLabelById(labelId) match {
+ case Some(label) =>
+ // Map the Label object to a JSON response
+ val jsLabel = Json.obj(
+ "labelId" -> label.labelId,
+ "description" -> label.description,
+ "severity" -> label.severity,
+ "tags" -> label.tags
+ // Add other fields as necessary
+ )
+ // Return the label data as JSON
+ Future.successful(Ok(jsLabel))
+ case None =>
+ // If label is not found, return a 404 Not Found response
+ Future.successful(NotFound(Json.obj("error" -> "Label not found")))
+ }
+ case None =>
+ // If user is not authenticated, redirect to login
+ Future.successful(Redirect("/login"))
+ }
+ }
+
+ /**
+ * Delete a label by its ID.
+ */
+ def deleteLabelById(labelId: Int) = UserAwareAction.async { implicit request =>
+ Logger.info(s"Attempting to delete label with ID: $labelId")
+
+ // Check if the user is authenticated
+ request.identity match {
+ case Some(user) =>
+ // Try to delete the label
+ LabelTable.deleteLabelById(labelId) match {
+ case affectedRows if affectedRows > 0 =>
+ // If at least one row was affected, label is deleted
+ Future.successful(Ok(Json.obj("message" -> s"Label with ID $labelId has been deleted.")))
+ case _ =>
+ // If no rows were affected, return a 404 response
+ Future.successful(NotFound(Json.obj("error" -> "Label not found")))
+ }
+ case None =>
+ // If the user is not authenticated, redirect to login
+ Future.successful(Redirect("/login"))
+ }
+ }
+
+ /**
+ * Updates a label's properties (severity, description, tags).
+ *
+ * @param labelId The ID of the label to update.
+ * @return A JSON response indicating success or failure.
+ */
+ def updateFromUserDashboard(labelId: Int) = UserAwareAction.async(parse.json) { implicit request =>
+ // Log the labelId for debugging purposes
+ Logger.info(s"Attempting to update label with ID: $labelId")
+
+ // Extract the JSON data from the request body
+ val updatedData = request.body.as[JsObject]
+
+ // Extract severity, description, and tags from the request body
+ val severity = (updatedData \ "severity").asOpt[Int]
+ val description = (updatedData \ "description").asOpt[String]
+ val tags = (updatedData \ "tags").asOpt[List[String]].getOrElse(List())
+
+ // Check if the user is authenticated
+ request.identity match {
+ case Some(user) =>
+ // Check if the label exists
+ LabelTable.getLabelById(labelId) match {
+ case Some(labelToUpdate) =>
+ // Proceed with updating the label
+ val updateResult = LabelTable.updateFromUserDashboard(labelId, severity, description, tags)
+
+ // If update was successful, return the updated label details
+ if (updateResult > 0) {
+ Future.successful(Ok(Json.obj("message" -> s"Label with ID $labelId updated successfully.")))
+ } else {
+ // If no rows were affected, return Not Found
+ Future.successful(NotFound(Json.obj("error" -> "Label update failed or no changes made.")))
+ }
+ case None =>
+ // If the label was not found, return Not Found
+ Future.successful(NotFound(Json.obj("error" -> "Label not found")))
+ }
+ case None =>
+ // If the user is not authenticated, redirect to login
+ Future.successful(Redirect("/login"))
+ }
+ }
+
/**
* Fetches the labels that a user has added in the current region they are working in.
*
diff --git a/app/controllers/UserProfileController.scala b/app/controllers/UserProfileController.scala
index 5fc788b510..03c76417af 100644
--- a/app/controllers/UserProfileController.scala
+++ b/app/controllers/UserProfileController.scala
@@ -21,6 +21,7 @@ import play.api.i18n.Messages
import scala.concurrent.Future
import play.api.mvc._
import models.user.OrganizationTable
+import models.user.Organization
/**
* Holds the HTTP requests associated with the user dashboard.
@@ -205,17 +206,43 @@ class UserProfileController @Inject() (implicit val env: Environment[User, Sessi
* Creates a team and puts them in the organization table.
*/
def createTeam() = Action(parse.json) { request =>
- val orgName: String = (request.body \ "name").as[String]
- val orgDescription: String = (request.body \ "description").as[String]
+ val orgName: String = (request.body \ "name").as[String]
+ val orgDescription: String = (request.body \ "description").as[String]
- // Inserting into the database and capturing the generated orgId.
- val orgId: Int = OrganizationTable.insert(orgName, orgDescription)
+ // Inserting into the database and capturing the generated orgId.
+ val orgId: Int = OrganizationTable.insert(orgName, orgDescription)
- Ok(Json.obj(
- "message" -> "Organization created successfully!",
- "org_id" -> orgId
- ))
-}
+ Ok(Json.obj(
+ "message" -> "Organization created successfully!",
+ "org_id" -> orgId
+ ))
+ }
+
+ /**
+ * Grabs a list of all the teams in the tables,
+ * regardless of open or closed status.
+ */
+ def getTeams() = UserAwareAction.async { implicit request =>
+ val teams: List[Organization] = OrganizationTable.getAllTeams()
+
+ // Convert the list of organizations to JSON
+ val teamJson = Json.toJson(teams)
+
+ // Return the JSON response
+ Future.successful(Ok(teamJson))
+ }
+
+ /**
+ * Grabs a list of all "open" teams in the tables.
+ */
+ def getAllOpenTeams() = UserAwareAction.async { implicit request =>
+
+ val OpenTeams: List[Organization] = OrganizationTable.getAllOpenTeams()
+
+ val teamJson = Json.toJson(OpenTeams)
+
+ Future.successful(Ok(teamJson))
+ }
/**
@@ -239,4 +266,30 @@ class UserProfileController @Inject() (implicit val env: Environment[User, Sessi
Future.successful(Ok(Json.obj("error" -> "0", "message" -> "Your user id could not be found.")))
}
}
+
+ /**
+ * Updates the open status of the specified organization.
+ *
+ * @param orgId The ID of the organization to update.
+ */
+ def updateStatus(orgId: Int) = Action(parse.json) { request =>
+ val isOpen = (request.body \ "isOpen").as[Boolean]
+
+ OrganizationTable.updateStatus(orgId, isOpen)
+
+ Ok(Json.obj("status" -> "success", "org_id" -> orgId, "isOpen" -> isOpen))
+ }
+
+ /**
+ * Updates the visibility status of the specified organization.
+ *
+ * @param orgId The ID of the organization to update.
+ */
+ def updateVisibility(orgId: Int) = Action(parse.json) { request =>
+ val isVisible = (request.body \ "isVisible").as[Boolean]
+
+ OrganizationTable.updateVisibility(orgId, isVisible)
+
+ Ok(Json.obj("status" -> "success", "org_id" -> orgId, "isVisible" -> isVisible))
+ }
}
diff --git a/app/formats/json/OrganizationFormats.scala b/app/formats/json/OrganizationFormats.scala
index 6e10120f25..c21c7bf3f5 100644
--- a/app/formats/json/OrganizationFormats.scala
+++ b/app/formats/json/OrganizationFormats.scala
@@ -9,6 +9,8 @@ object OrganizationFormats {
implicit val organizationWrites: Writes[Organization] = (
(JsPath \ "orgId").write[Int] and
(JsPath \ "orgName").write[String] and
- (JsPath \ "orgDescription").write[String]
+ (JsPath \ "orgDescription").write[String] and
+ (JsPath \ "isOpen").write[Boolean] and
+ (JsPath \ "isVisible").write[Boolean]
)(unlift(Organization.unapply _))
}
diff --git a/app/models/label/LabelTable.scala b/app/models/label/LabelTable.scala
index 86f2326612..b473c585fe 100644
--- a/app/models/label/LabelTable.scala
+++ b/app/models/label/LabelTable.scala
@@ -372,6 +372,51 @@ object LabelTable {
labelsWithExcludedUsers.filter(_.userId === userId.toString).size.run
}
+ /**
+ * Find a Label by its ID.
+ *
+ * @param labelId The ID of the label to find.
+ * @return An Option[Label] - Some(Label) if found, None if not found.
+ */
+ def getLabelById(labelId: Int): Option[Label] = db.withSession { implicit session =>
+ val query = labelsUnfiltered.filter(_.labelId === labelId).take(1)
+
+ query.firstOption
+ }
+
+ /**
+ * Marks a label as deleted by setting its `deleted` field to true.
+ *
+ * @param labelId The ID of the label to delete.
+ * @return The number of affected rows (should be 1 if the label exists and is successfully updated).
+ */
+ def deleteLabelById(labelId: Int): Int = db.withTransaction { implicit session =>
+ val labelToDeleteQuery = labelsUnfiltered.filter(_.labelId === labelId)
+
+ labelToDeleteQuery.map(_.deleted).update(true)
+ }
+
+ def updateFromUserDashboard(labelId: Int, severity: Option[Int], description: Option[String], tags: List[String]): Int = db.withTransaction { implicit session =>
+ val labelToUpdateQuery = labelsUnfiltered.filter(_.labelId === labelId)
+ val labelToUpdate: Label = labelToUpdateQuery.first
+ val cleanedTags: List[String] = TagTable.cleanTagList(tags, labelToUpdate.labelTypeId)
+ // If the severity or tags have been changed, we need to update the label_history table as well.
+ if (labelToUpdate.severity != severity || labelToUpdate.tags.toSet != cleanedTags.toSet) {
+ // If there are multiple entries in the label_history table, then the label has been edited before and we need to
+ // add an entirely new entry to the table. Otherwise we can just update the existing entry.
+ val labelHistoryCount: Int = LabelHistoryTable.labelHistory.filter(_.labelId === labelId).length.run
+ if (labelHistoryCount > 1) {
+ LabelHistoryTable.save(LabelHistory(0, labelId, severity, cleanedTags, labelToUpdate.userId, new Timestamp(Instant.now.toEpochMilli), "User Dashboard Edit", None))
+ } else {
+ LabelHistoryTable.labelHistory.filter(_.labelId === labelId).map(l => (l.severity, l.tags)).update((severity, cleanedTags))
+ }
+ }
+ // Update the label table here.
+ labelToUpdateQuery
+ .map(l => (l.severity, l.description, l.tags))
+ .update((severity, description, tags.distinct))
+ }
+
/**
* Update the metadata that users might change on the Explore page after initially placing the label.
*
diff --git a/app/models/user/OrganizationTable.scala b/app/models/user/OrganizationTable.scala
index 551fe91335..43436ce7f2 100644
--- a/app/models/user/OrganizationTable.scala
+++ b/app/models/user/OrganizationTable.scala
@@ -2,15 +2,23 @@ package models.user
import models.utils.MyPostgresDriver.simple._
import play.api.Play.current
+import play.api.libs.json.{Json, OFormat}
-case class Organization(orgId: Int, orgName: String, orgDescription: String)
+
+case class Organization(orgId: Int, orgName: String, orgDescription: String, isOpen: Boolean, isVisible: Boolean)
+
+object Organization {
+ implicit val format: OFormat[Organization] = Json.format[Organization]
+}
class OrganizationTable(tag: slick.lifted.Tag) extends Table[Organization](tag, "organization") {
def orgId = column[Int]("org_id", O.PrimaryKey, O.AutoInc)
def orgName = column[String]("org_name", O.NotNull)
def orgDescription = column[String]("org_description", O.NotNull)
+ def isOpen = column[Boolean]("is_open", O.NotNull, O.Default(true))
+ def isVisible = column[Boolean]("is_visible", O.NotNull, O.Default(true))
- def * = (orgId, orgName, orgDescription) <> ((Organization.apply _).tupled, Organization.unapply)
+ def * = (orgId, orgName, orgDescription, isOpen, isVisible) <> ((Organization.apply _).tupled, Organization.unapply)
}
/**
@@ -20,15 +28,6 @@ object OrganizationTable {
val db = play.api.db.slick.DB
val organizations = TableQuery[OrganizationTable]
- /**
- * Gets a list of all organizations.
- *
- * @return A list of all organizations.
- */
- def getAllOrganizations: List[Organization] = db.withSession { implicit session =>
- organizations.list
- }
-
/**
* Checks if the organization with the given id exists.
*
@@ -67,7 +66,61 @@ object OrganizationTable {
* @return The auto-generated ID of the newly created organization.
*/
def insert(orgName: String, orgDescription: String): Int = db.withSession { implicit session =>
- val newOrganization = Organization(0, orgName, orgDescription) // orgId is auto-generated.
+ val newOrganization = Organization(0, orgName, orgDescription, true, true) // orgId is auto-generated.
(organizations returning organizations.map(_.orgId)) += newOrganization
}
+
+ /**
+ * Gets a list of all teams, regardless of status.
+ *
+ * @return A list of all teams.
+ */
+ def getAllTeams(): List[Organization] = db.withSession { implicit session =>
+ organizations.list
+ }
+
+ /**
+ * Gets a list of all "open" teams.
+ *
+ * @return A list of all open teams.
+ */
+ def getAllOpenTeams(): List[Organization] = db.withSession { implicit session =>
+ organizations.filter(_.isOpen === true).list
+ }
+
+ /**
+ * Updates the visibility of an organization.
+ *
+ * @param orgId: The ID of the organization to update.
+ * @param isVisible: The new visibility status.
+ */
+ def updateVisibility(orgId: Int, isVisible: Boolean): Int = db.withSession { implicit session =>
+ val query = for {
+ org <- organizations if org.orgId === orgId
+ } yield (org.isVisible)
+ query.update((isVisible))
+ }
+
+ /**
+ * Updates the status of an organization.
+ *
+ * @param orgId: The ID of the organization to update.
+ * @param isOpen: The new status of the organization.
+ */
+ def updateStatus(orgId: Int, isOpen: Boolean): Int = db.withSession { implicit session =>
+ val query = for {
+ org <- organizations if org.orgId === orgId
+ } yield (org.isOpen)
+ query.update((isOpen))
+ }
+
+ /**
+ * Gets the organization by the given organization id.
+ *
+ * @param orgId The id of the organization.
+ * @return An Option containing the organization, or None if not found.
+ */
+ def getOrganization(orgId: Int): Option[Organization] = db.withTransaction { implicit session =>
+ organizations.filter(_.orgId === orgId).firstOption
+ }
}
diff --git a/app/models/user/UserStatTable.scala b/app/models/user/UserStatTable.scala
index 846d3aab59..0726431b6c 100644
--- a/app/models/user/UserStatTable.scala
+++ b/app/models/user/UserStatTable.scala
@@ -306,11 +306,15 @@ object UserStatTable {
""
}
val orgFilter: String = orgId match {
- case Some(id) => "AND user_org.org_id = " + id
- case None =>
- // Temporarily filtering out previous course sections from the leaderboard. Need to remove soon.
- if (byOrg) "AND organization.org_name NOT LIKE 'DHD206 % 2021' AND organization.org_name NOT LIKE 'DHD206 % 2022'"
- else ""
+ case Some(id) =>
+ // If orgId is provided, filter by orgId and also ensure the organization is visible
+ s"AND user_org.org_id = $id AND organization.is_visible = TRUE"
+ case None =>
+ if (byOrg) {
+ "AND organization.is_visible = TRUE"
+ } else {
+ ""
+ }
}
// There are quite a few changes to make to the query when grouping by team/org instead of user. All of those below.
val groupingCol: String = if (byOrg) "user_org.org_id" else "sidewalk_user.user_id"
diff --git a/app/views/admin/index.scala.html b/app/views/admin/index.scala.html
index d12ad26609..16ae47b947 100644
--- a/app/views/admin/index.scala.html
+++ b/app/views/admin/index.scala.html
@@ -4,6 +4,7 @@
@import models.label._
@import models.audit.AuditTaskCommentTable
@import models.utils.DataFormatter
+@import models.user.OrganizationTable
@(title: String, user: Option[User] = None)(implicit lang: Lang)
@main(title) {
@@ -24,8 +25,9 @@