Skip to content
This repository has been archived by the owner on Apr 27, 2023. It is now read-only.

Commit

Permalink
Add support for teams
Browse files Browse the repository at this point in the history
  • Loading branch information
gerits committed Jan 28, 2016
1 parent e535955 commit 4b08ea8
Show file tree
Hide file tree
Showing 37 changed files with 878 additions and 18 deletions.
15 changes: 15 additions & 0 deletions codebrag-dao/src/main/resources/db/migration/V19__add_teams.sql
@@ -0,0 +1,15 @@
CREATE TABLE IF NOT EXISTS "teams"(
"id" VARCHAR NOT NULL,
"name" VARCHAR NOT NULL,
);
ALTER TABLE "teams" ADD CONSTRAINT IF NOT EXISTS "pk" PRIMARY KEY("id");

CREATE TABLE IF NOT EXISTS "team_members"(
"team_id" VARCHAR NOT NULL,
"user_id" VARCHAR NOT NULL,
"contributor" BOOLEAN NOT NULL
);
ALTER TABLE "team_members" ADD CONSTRAINT IF NOT EXISTS "pk" PRIMARY KEY("team_id", "user_id");

ALTER TABLE "team_members" ADD CONSTRAINT IF NOT EXISTS "team_id" FOREIGN KEY ("team_id") REFERENCES "teams" ("id") ON DELETE CASCADE ON UPDATE CASCADE NOCHECK;
ALTER TABLE "team_members" ADD CONSTRAINT IF NOT EXISTS "user_id" FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE CASCADE ON UPDATE CASCADE NOCHECK;
Expand Up @@ -22,6 +22,7 @@ import com.softwaremill.codebrag.dao.branch.SQLWatchedBranchesDao

trait Daos {
lazy val userDao = new SQLUserDAO(sqlDatabase)
lazy val teamDao = new SQLTeamDAO(sqlDatabase)
lazy val internalUserDao = new SQLInternalUserDAO(sqlDatabase)
lazy val commitInfoDao = new SQLCommitInfoDAO(sqlDatabase)
lazy val followupDao = new SQLFollowupDAO(sqlDatabase)
Expand Down
@@ -0,0 +1,70 @@
package com.softwaremill.codebrag.dao.user

import com.softwaremill.codebrag.dao.sql.SQLDatabase
import com.softwaremill.codebrag.domain._
import org.bson.types.ObjectId
import org.joda.time.{ DateTime, DateTimeZone }

class SQLTeamDAO(val database: SQLDatabase) extends TeamDAO with SQLTeamSchema {
import database._
import database.driver.simple._

def add(team: Team) {
db.withTransaction { implicit session =>
teams += new TeamTuple(team.id, team.name)
}
}

def countAll() = {
db.withTransaction { implicit session =>
Query(teams.length).first().toLong
}
}

def findAll() = db.withTransaction { implicit session =>
teams.list().map(queryTeamMembers).map(untuple)
}

def findById(teamId: ObjectId) = db.withTransaction { implicit session =>
val q = for {
t <- teams if (t.id === teamId)
} yield (t)

q.firstOption.map(queryTeamMembers).map(untuple)
}

def findByName(name: String) = db.withTransaction { implicit session =>
val q = for {
t <- teams if (t.name === name)
} yield (t)

q.firstOption.map(queryTeamMembers).map(untuple)
}

def findByUser(userId: ObjectId) = db.withTransaction { implicit session =>
teamMembers.where(_.userId === userId).list().map(t => findById(t.team_id).get);
}

def modifyTeam(team: Team) = db.withTransaction { implicit session =>
teams.where(_.id === team.id).update(tuple(team))

// Remove members from database.
teamMembers.where(_.teamId === team.id).delete

// Insert new ones
teamMembers.insertAll(team.teamMembers.map(t => SQLTeamMember(team.id, t.user_id, t.contributor)).toSeq: _*)
}

def delete(teamId: ObjectId) = db.withTransaction { implicit session =>
teams.where(_.id === teamId).delete

teamMembers.where(_.teamId === teamId).delete
}

private def queryTeamMembers(tuple: TeamTuple)(implicit session: Session) = {
val teamId = tuple._1
val members = teamMembers.where(_.teamId === teamId).list()
(tuple, members)
}

}
@@ -0,0 +1,52 @@
package com.softwaremill.codebrag.dao.user

import com.softwaremill.codebrag.dao.sql.SQLDatabase
import com.softwaremill.codebrag.domain._
import org.bson.types.ObjectId
import org.joda.time.DateTime
import scala.slick.model.ForeignKeyAction

trait SQLTeamSchema {
val database: SQLDatabase

import database._
import database.driver.simple._

protected case class SQLTeamMember(team_id: ObjectId, user_id: ObjectId, contributor: Boolean = true) {
def toTeamMember = TeamMember(team_id, user_id, contributor)
}

protected val teamMembers = TableQuery[TeamMembers]

protected class Teams(tag: Tag) extends Table[TeamTuple](tag, "teams") {
def id = column[ObjectId]("id", O.PrimaryKey)
def name = column[String]("name")

def teammembers = foreignKey("team_members_fk", id, teamMembers)(_.teamId, ForeignKeyAction.Cascade, ForeignKeyAction.Cascade)

def * = (id, name)
}

protected class TeamMembers(tag: Tag) extends Table[SQLTeamMember](tag, "team_members") {
def teamId = column[ObjectId]("team_id")
def userId = column[ObjectId]("user_id")
def contributor = column[Boolean]("contributor")

def * = (teamId, userId, contributor) <> (SQLTeamMember.tupled, SQLTeamMember.unapply)
}

protected val teams = TableQuery[Teams]

protected type TeamTuple = (ObjectId, String)

protected def tuple(team: Team): TeamTuple = (team.id, team.name)

protected val untuple: ((TeamTuple, List[SQLTeamMember])) => Team = {
case (tuple, sqlTeamMembers) =>
Team(
tuple._1,
tuple._2,
sqlTeamMembers.map(_.toTeamMember).toList)
}

}
@@ -0,0 +1,23 @@
package com.softwaremill.codebrag.dao.user

import com.softwaremill.codebrag.domain._
import org.joda.time.DateTime
import org.bson.types.ObjectId

trait TeamDAO {
def add(team: Team)

def findAll(): List[Team]

def findById(teamId: ObjectId): Option[Team]

def findByName(name: String): Option[Team]

def findByUser(userId: ObjectId): List[Team]

def modifyTeam(team: Team)

def delete(teamId: ObjectId)

def countAll(): Long
}
@@ -0,0 +1,10 @@
package com.softwaremill.codebrag.domain

import com.softwaremill.codebrag.common.Utils
import org.bson.types.ObjectId

case class Team(id: ObjectId, name: String, teamMembers: List[TeamMember] = null)

case class TeamMember(team_id: ObjectId, user_id: ObjectId, contributor: Boolean = true) {

}
Expand Up @@ -79,7 +79,6 @@ object UserSettings {

}


case class UserAlias(id: ObjectId, userId: ObjectId, alias: String)

object UserAlias {
Expand Down
3 changes: 2 additions & 1 deletion codebrag-rest/src/main/scala/ScalatraBootstrap.scala
Expand Up @@ -82,7 +82,8 @@ class ScalatraBootstrap extends LifeCycle with Logging {
context.mount(new RepoStatusServlet(authenticator, repositories.head, repoStatusDao), Prefix + RepoStatusServlet.Mapping)
context.mount(new RepositoryBranchesServlet(authenticator, toReviewCommitsFinder, listRepoBranches, addBranchToObserved, removeBranchFromObserved), Prefix + RepositoryBranchesServlet.MountPath)
context.mount(new BrowsingContextServlet(authenticator, userBrowsingContextFinder, updateUserBrowsingContextUseCase), Prefix + BrowsingContextServlet.MappingPath)

context.mount(new TeamsServlet(authenticator, teamFinder, addTeamUseCase, deleteTeamUseCase, addTeamMemberUseCase, deleteTeamMemberUseCase, modifyTeamMemberUseCase, config), Prefix + "teams")

context.mount(new TimingFilter, "/*")

InstanceContext.put(context, beans)
Expand Down
Expand Up @@ -29,6 +29,8 @@ import com.softwaremill.codebrag.usecases.notifications.FindUserNotifications
import com.softwaremill.codebrag.usecases.reactions._
import com.softwaremill.codebrag.usecases.registration.{ListRepoBranchesAfterRegistration, ListRepositoriesAfterRegistration, UnwatchBranchAfterRegistration, WatchBranchAfterRegistration}
import com.softwaremill.codebrag.usecases.user._
import com.softwaremill.codebrag.usecases.team.{AddTeamUseCase, DeleteTeamUseCase, AddTeamMemberUseCase, DeleteTeamMemberUseCase, ModifyTeamMemberUseCase}
import com.softwaremill.codebrag.finders.team.TeamFinder

trait Beans extends ActorSystemSupport with Daos {

Expand Down Expand Up @@ -101,6 +103,7 @@ trait Beans extends ActorSystemSupport with Daos {
lazy val toReviewCommitsFinder = new ToReviewCommitsFinder(
repositoriesCache,
userDao,
teamDao,
userBrowsingContextFinder,
new ToReviewBranchCommitsFilter(reviewedCommitsCache, config),
new ToReviewCommitsViewBuilder(userDao, commitInfoDao)
Expand All @@ -119,4 +122,10 @@ trait Beans extends ActorSystemSupport with Daos {
lazy val commitImportService = new CommitImportService(repoStatusDao, branchStateDao, repositoriesCache, config, eventBus)
lazy val diffLoader = new JgitDiffLoader()

lazy val teamFinder = new TeamFinder(userDao, teamDao)
lazy val addTeamUseCase = new AddTeamUseCase(teamDao)
lazy val deleteTeamUseCase = new DeleteTeamUseCase(teamDao)
lazy val addTeamMemberUseCase = new AddTeamMemberUseCase(teamDao)
lazy val deleteTeamMemberUseCase = new DeleteTeamMemberUseCase(teamDao)
lazy val modifyTeamMemberUseCase = new ModifyTeamMemberUseCase(teamDao)
}
@@ -0,0 +1,89 @@
package com.softwaremill.codebrag.rest

import com.softwaremill.codebrag.service.user.{ RegisterService, Authenticator }
import com.softwaremill.codebrag.service.config.CodebragConfig
import org.bson.types.ObjectId
import org.scalatra
import com.softwaremill.codebrag.usecases.team.{ AddTeamUseCase, DeleteTeamUseCase, AddTeamMemberUseCase, DeleteTeamMemberUseCase, ModifyTeamMemberUseCase }
import com.softwaremill.codebrag.finders.team.TeamFinder
import com.softwaremill.codebrag.finders.team.ManagedTeamMembersListView

class TeamsServlet(
val authenticator: Authenticator,
teamFinder: TeamFinder,
addTeamUseCase: AddTeamUseCase,
deleteTeamUseCase: DeleteTeamUseCase,
addTeamMemberUseCase: AddTeamMemberUseCase,
deleteTeamMemberUseCase: DeleteTeamMemberUseCase,
modifyTeamMemberUseCase: ModifyTeamMemberUseCase,
config: CodebragConfig) extends JsonServletWithAuthentication {

get("/") {
haltIfNotAuthenticated()
teamFinder.findAllTeams()
}

post("/") {
haltIfNotAuthenticated()
val teamName = extractReq[String]("name")
addTeamUseCase.execute(new ObjectId, teamName) match {
case Left(errors) => scalatra.BadRequest(errors)
case Right(teamCreated) => scalatra.Ok(teamCreated)
}
}

delete("/:teamId") {
haltIfNotAuthenticated()
val teamIdToRemove = new ObjectId(params("teamId"))
deleteTeamUseCase.execute(teamIdToRemove) match {
case Left(errors) => scalatra.BadRequest(errors)
case _ => scalatra.Ok()
}
}

get("/:teamId/members") {
haltIfNotAuthenticated()
if (!config.demo) {
var team = teamFinder.findTeam(new ObjectId(params("teamId")));
teamFinder.findAllAsManagedTeamMembers(team)
} else {
ManagedTeamMembersListView(List.empty)
}
}

post("/:teamId/members") {
haltIfNotAuthenticated()
val targetTeamId = new ObjectId(extractOpt[String]("teamId").get)
val targetUserId = new ObjectId(extractOpt[String]("userId").get)
addTeamMemberUseCase.execute(targetTeamId, targetUserId) match {
case Left(errors) => scalatra.BadRequest(errors)
case Right(team) => scalatra.Ok(team)
}
}

delete("/:teamId/members/:userId") {
haltIfNotAuthenticated()
val targetTeamId = new ObjectId(params("teamId"))
val targetUserId = new ObjectId(params("userId"))
deleteTeamMemberUseCase.execute(targetTeamId, targetUserId) match {
case Left(errors) => scalatra.BadRequest(errors)
case Right(team) => scalatra.Ok(team)
}
}

put("/:teamId/members") {
haltIfNotAuthenticated()
val targetTeamId = new ObjectId(extractOpt[String]("teamId").get)
val targetUserId = new ObjectId(extractOpt[String]("userId").get)
val contributorOpt = extractOpt[Boolean]("contributor")
modifyTeamMemberUseCase.execute(targetTeamId, targetUserId, contributorOpt) match {
case Left(errors) => scalatra.BadRequest(errors)
case Right(team) => scalatra.Ok(team)
}
}

object TeamsServlet {
val MappingPath = "teams"
}

}
Expand Up @@ -6,7 +6,7 @@ import org.bson.types.ObjectId
import com.typesafe.scalalogging.slf4j.Logging
import com.softwaremill.codebrag.domain.UserRepoDetails

case class UserBrowsingContext(userId: ObjectId, repoName: String, branchName: String)
case class UserBrowsingContext(userId: ObjectId, repoName: String, branchName: String, visibleUsers : Set[ObjectId] = Set.empty[ObjectId])

object UserBrowsingContext {
def apply(d: UserRepoDetails) = new UserBrowsingContext(d.userId, d.repoName, d.branchName)
Expand Down Expand Up @@ -63,4 +63,5 @@ class UserBrowsingContextFinder(val userRepoDetailsDao: UserRepoDetailsDAO, val
logger.debug(s"Building system default context from $repoName and $branchName")
UserBrowsingContext(userId, repoName, branchName)
}

}
@@ -0,0 +1,17 @@
package com.softwaremill.codebrag.finders.commits

import org.bson.types.ObjectId
import com.softwaremill.codebrag.dao.user.{UserDAO, TeamDAO}
import com.typesafe.scalalogging.slf4j.Logging

protected[finders] trait TeamMemberLoader extends Logging {

protected def userDao: UserDAO

protected def teamDao: TeamDAO

protected def loadTeamMembersWithDetails(userId: ObjectId) = userDao.findPartialUserDetails(loadTeamMembers(userId)).toList

protected def loadTeamMembers(userId: ObjectId) = teamDao.findByUser(userId).flatMap(_.teamMembers).filter(_.contributor).map(_.user_id).distinct.toList

}
Expand Up @@ -9,5 +9,5 @@ protected[finders] trait UserLoader extends Logging {
protected def userDao: UserDAO

protected def loadUser(userId: ObjectId) = userDao.findById(userId).getOrElse(throw new IllegalArgumentException("Invalid userId provided"))

}
Expand Up @@ -4,7 +4,7 @@ import org.bson.types.ObjectId
import com.softwaremill.codebrag.common.paging.PagingCriteria
import com.softwaremill.codebrag.dao.commitinfo.CommitInfoDAO
import com.softwaremill.codebrag.dao.finders.views.CommitListView
import com.softwaremill.codebrag.dao.user.UserDAO
import com.softwaremill.codebrag.dao.user.{UserDAO, TeamDAO}
import com.typesafe.scalalogging.slf4j.Logging
import com.softwaremill.codebrag.cache.RepositoriesCache
import com.softwaremill.codebrag.finders.commits.UserLoader
Expand All @@ -29,6 +29,6 @@ class AllCommitsFinder(
case None => Left("Commit not found")
}
}

}

0 comments on commit 4b08ea8

Please sign in to comment.