class WIP
parent
79b39b6366
commit
4e5d337c7d
|
@ -1,9 +1,6 @@
|
|||
package controllers
|
||||
|
||||
import com.github.ghik.silencer.silent
|
||||
import play.api.mvc._
|
||||
import play.api.data.Form
|
||||
import play.api.libs.json._
|
||||
|
||||
import lila.api.Context
|
||||
import lila.app._
|
||||
|
@ -15,12 +12,42 @@ final class Clas(
|
|||
|
||||
def index = Secure(_.Teacher) { implicit ctx => me =>
|
||||
WithTeacher(me) { t =>
|
||||
Ok(views.html.clas.index(t)).fuccess
|
||||
env.clas.api.clas.of(t.teacher) map { classes =>
|
||||
Ok(views.html.clas.clas.index(classes, t))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def form = Secure(_.Teacher) { implicit ctx => _ =>
|
||||
Ok(html.clas.form.create(env.clas.forms.create)).fuccess
|
||||
}
|
||||
|
||||
def create = SecureBody(_.Teacher) { implicit ctx => me =>
|
||||
WithTeacher(me) { t =>
|
||||
env.clas.forms.create
|
||||
.bindFromRequest()(ctx.body)
|
||||
.fold(
|
||||
err => BadRequest(html.clas.form.create(err)).fuccess,
|
||||
setup =>
|
||||
env.clas.api.clas.create(setup, t.teacher) map { clas =>
|
||||
Redirect(routes.Clas.show(clas.id.value))
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
def show(id: String) = Secure(_.Teacher) { implicit ctx => me =>
|
||||
WithTeacher(me) { t =>
|
||||
env.clas.api.clas.getAndView(lila.clas.Clas.Id(id), t.teacher) map {
|
||||
_ ?? { clas =>
|
||||
views.html.clas.clas.show(clas, t)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def WithTeacher(me: lila.user.User)(
|
||||
f: lila.clas.Teacher.WithUser => Fu[Result]
|
||||
): Fu[Result] =
|
||||
env.clas.api withTeacherOrCreate me flatMap f
|
||||
env.clas.api.teacher withOrCreate me flatMap f
|
||||
}
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
package views.html.clas
|
||||
|
||||
import lila.api.Context
|
||||
import lila.app.templating.Environment._
|
||||
import lila.app.ui.ScalatagsTemplate._
|
||||
import lila.common.paginator.Paginator
|
||||
|
||||
import controllers.routes
|
||||
|
||||
object clas {
|
||||
|
||||
def index(
|
||||
classes: List[lila.clas.Clas],
|
||||
teacher: lila.clas.Teacher.WithUser
|
||||
)(implicit ctx: Context) =
|
||||
views.html.base.layout(
|
||||
title = "Lichess Classes",
|
||||
moreCss = cssTag("clas")
|
||||
) {
|
||||
main(cls := "page box clas-index")(
|
||||
div(cls := "box__top")(
|
||||
h1("Lichess Classes"),
|
||||
a(
|
||||
href := routes.Clas.form,
|
||||
cls := "new button button-empty",
|
||||
title := "New Class",
|
||||
dataIcon := "O"
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
def show(
|
||||
clas: lila.clas.Clas,
|
||||
teacher: lila.clas.Teacher.WithUser
|
||||
)(implicit ctx: Context) =
|
||||
views.html.base.layout(
|
||||
title = clas.name,
|
||||
moreCss = cssTag("clas")
|
||||
) {
|
||||
main(cls := "page box clas-show")(
|
||||
div(cls := "box__top")(
|
||||
h1(clas.name)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
package views.html.clas
|
||||
|
||||
import play.api.data.Form
|
||||
|
||||
import lila.api.Context
|
||||
import lila.app.templating.Environment._
|
||||
import lila.app.ui.ScalatagsTemplate._
|
||||
import lila.clas.ClasForm.Data
|
||||
|
||||
import controllers.routes
|
||||
|
||||
object form {
|
||||
|
||||
def create(form: Form[Data])(implicit ctx: Context) =
|
||||
layout("New class")(
|
||||
h1("New class"),
|
||||
inner(form, routes.Clas.create)
|
||||
)
|
||||
|
||||
// def edit(r: lila.relay.Relay, form: Form[Data])(implicit ctx: Context) =
|
||||
// layout(r.name)(
|
||||
// h1("Edit ", r.name),
|
||||
// inner(form, routes.Relay.update(r.slug, r.id.value)),
|
||||
// hr,
|
||||
// postForm(action := routes.Relay.cloneRelay(r.slug, r.id.value))(
|
||||
// submitButton(
|
||||
// cls := "button button-empty confirm",
|
||||
// title := "Create an new identical broadcast, for another round or a similar tournament"
|
||||
// )("Clone the broadcast")
|
||||
// ),
|
||||
// hr,
|
||||
// postForm(action := routes.Relay.reset(r.slug, r.id.value))(
|
||||
// submitButton(
|
||||
// cls := "button button-red button-empty confirm",
|
||||
// title := "The source will need to be active in order to re-create the chapters!"
|
||||
// )("Reset the broadcast")
|
||||
// )
|
||||
// )
|
||||
|
||||
private def layout(title: String)(body: Modifier*)(implicit ctx: Context) =
|
||||
views.html.base.layout(
|
||||
title = title,
|
||||
moreCss = cssTag("clas")
|
||||
)(
|
||||
main(cls := "page-small box box-pad")(body)
|
||||
)
|
||||
|
||||
private def inner(form: Form[Data], url: play.api.mvc.Call)(implicit ctx: Context) =
|
||||
postForm(cls := "form3", action := url)(
|
||||
form3.globalError(form),
|
||||
form3.group(form("name"), frag("Class name"))(form3.input(_)(autofocus)),
|
||||
form3.group(form("desc"), raw("Class description"))(form3.textarea(_)(rows := 5)),
|
||||
form3.actions(
|
||||
a(href := routes.Clas.index)(trans.cancel()),
|
||||
form3.submit(trans.apply())
|
||||
)
|
||||
)
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
package views.html.clas
|
||||
|
||||
import lila.api.Context
|
||||
import lila.app.templating.Environment._
|
||||
import lila.app.ui.ScalatagsTemplate._
|
||||
import lila.common.paginator.Paginator
|
||||
|
||||
import controllers.routes
|
||||
|
||||
object index {
|
||||
|
||||
def apply(
|
||||
teacher: lila.clas.Teacher.WithUser
|
||||
)(implicit ctx: Context) =
|
||||
views.html.base.layout(
|
||||
title = "Lichess Classes",
|
||||
moreCss = cssTag("clas")
|
||||
) {
|
||||
main(cls := "page box")(
|
||||
div(cls := "box__top")(
|
||||
h1("Lichess Classes")
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
|
@ -439,6 +439,11 @@ POST /coach/:username/review controllers.Coach.review(username: String
|
|||
|
||||
# Clas
|
||||
GET /class controllers.Clas.index
|
||||
GET /class/new controllers.Clas.form
|
||||
POST /class/new controllers.Clas.create
|
||||
GET /class/$id<\w{8}> controllers.Clas.show(id: String)
|
||||
#3 GET /class/:slug/$id<\w{8}>/edit controllers.Clas.edit(slug: String, id: String)
|
||||
#3 POST /class/:slug/$id<\w{8}>/edit controllers.Clas.update(slug: String, id: String)
|
||||
|
||||
# DB image
|
||||
GET /image/:id/:hash/:name controllers.Main.image(id: String, hash: String, name: String)
|
||||
|
|
|
@ -10,4 +10,7 @@ private[clas] object BsonHandlers {
|
|||
|
||||
implicit val clasIdBSONHandler = stringAnyValHandler[Clas.Id](_.value, Clas.Id.apply)
|
||||
implicit val clasBSONHandler = Macros.handler[Clas]
|
||||
|
||||
implicit val studentIdBSONHandler = stringAnyValHandler[Student.Id](_.value, Student.Id.apply)
|
||||
implicit val studentBSONHandler = Macros.handler[Student]
|
||||
}
|
||||
|
|
|
@ -1,18 +1,35 @@
|
|||
package lila.clas
|
||||
|
||||
import scalaz.NonEmptyList
|
||||
import org.joda.time.DateTime
|
||||
|
||||
case class Clas(
|
||||
_id: Clas.Id,
|
||||
name: String,
|
||||
desc: String,
|
||||
ownerId: Teacher.Id,
|
||||
teachers: NonEmptyList[Teacher.Id], // first is owner
|
||||
nbStudents: Int,
|
||||
createdAt: DateTime,
|
||||
updatedAt: DateTime
|
||||
) {}
|
||||
updatedAt: DateTime,
|
||||
viewedAt: DateTime
|
||||
) {
|
||||
|
||||
def id = _id
|
||||
}
|
||||
|
||||
object Clas {
|
||||
|
||||
def make(teacher: Teacher, name: String, desc: String) = Clas(
|
||||
_id = Id(scala.util.Random.alphanumeric take 8 mkString),
|
||||
name = name,
|
||||
desc = desc,
|
||||
teachers = NonEmptyList(teacher.id),
|
||||
nbStudents = 0,
|
||||
createdAt = DateTime.now,
|
||||
updatedAt = DateTime.now,
|
||||
viewedAt = DateTime.now
|
||||
)
|
||||
|
||||
case class WithOwner(clas: Clas, teacher: Teacher)
|
||||
|
||||
case class Id(value: String) extends AnyVal with StringValue
|
||||
|
|
|
@ -1,21 +1,51 @@
|
|||
package lila.clas
|
||||
|
||||
import org.joda.time.DateTime
|
||||
|
||||
import lila.db.dsl._
|
||||
import lila.user.{ User, UserRepo }
|
||||
import lila.user.User
|
||||
|
||||
final class ClasApi(
|
||||
teacherColl: Coll,
|
||||
clasColl: Coll,
|
||||
userRepo: UserRepo
|
||||
clasColl: Coll
|
||||
)(implicit ec: scala.concurrent.ExecutionContext) {
|
||||
|
||||
import BsonHandlers._
|
||||
|
||||
def withTeacherOrCreate(user: User): Fu[Teacher.WithUser] =
|
||||
teacherColl.byId[Teacher](user.id) flatMap {
|
||||
case Some(teacher) => fuccess(Teacher.WithUser(teacher, user))
|
||||
case None =>
|
||||
val teacher = Teacher make user
|
||||
teacherColl.insert(teacher) inject Teacher.WithUser(teacher, user)
|
||||
object teacher {
|
||||
|
||||
val coll = teacherColl
|
||||
|
||||
def withOrCreate(user: User): Fu[Teacher.WithUser] =
|
||||
coll.byId[Teacher](user.id) flatMap {
|
||||
case Some(teacher) => fuccess(Teacher.WithUser(teacher, user))
|
||||
case None =>
|
||||
val teacher = Teacher make user
|
||||
coll.insert.one(teacher) inject Teacher.WithUser(teacher, user)
|
||||
}
|
||||
}
|
||||
|
||||
object clas {
|
||||
|
||||
val coll = clasColl
|
||||
|
||||
def of(teacher: Teacher): Fu[List[Clas]] =
|
||||
coll.ext
|
||||
.find($doc("ownerId" -> teacher.id))
|
||||
.sort($sort desc "viewedAt")
|
||||
.list[Clas]()
|
||||
|
||||
def create(data: ClasForm.Data, teacher: Teacher): Fu[Clas] = {
|
||||
val clas = Clas.make(teacher, data.name, data.desc)
|
||||
coll.insert.one(clas) inject clas
|
||||
}
|
||||
|
||||
def getAndView(id: Clas.Id, teacher: Teacher): Fu[Option[Clas]] =
|
||||
coll.ext
|
||||
.findAndUpdate[Clas](
|
||||
selector = $id(id) ++ $doc("teachers" -> teacher.id),
|
||||
update = $set("viewedAt" -> DateTime.now),
|
||||
fetchNewObject = true
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
package lila.clas
|
||||
|
||||
import play.api.data._
|
||||
import play.api.data.Forms._
|
||||
|
||||
final class ClasForm {
|
||||
|
||||
import ClasForm._
|
||||
|
||||
val form = Form(
|
||||
mapping(
|
||||
"name" -> text(minLength = 3, maxLength = 100),
|
||||
"desc" -> text(minLength = 0, maxLength = 2000)
|
||||
)(Data.apply)(Data.unapply)
|
||||
)
|
||||
|
||||
def create = form
|
||||
|
||||
def edit(c: Clas) = form fill Data(
|
||||
name = c.name,
|
||||
desc = c.desc
|
||||
)
|
||||
}
|
||||
|
||||
object ClasForm {
|
||||
|
||||
case class Data(
|
||||
name: String,
|
||||
desc: String
|
||||
)
|
||||
}
|
|
@ -1,23 +1,21 @@
|
|||
package lila.clas
|
||||
|
||||
import com.softwaremill.macwire._
|
||||
import play.api.Configuration
|
||||
|
||||
import lila.common.config._
|
||||
import lila.security.Permission
|
||||
|
||||
@Module
|
||||
final class Env(
|
||||
userRepo: lila.user.UserRepo,
|
||||
db: lila.db.Db
|
||||
)(implicit ec: scala.concurrent.ExecutionContext) {
|
||||
|
||||
private lazy val teacherColl = db(CollName("clas_teacher"))
|
||||
private lazy val clasColl = db(CollName("clas_clas"))
|
||||
|
||||
lazy val forms = wire[ClasForm]
|
||||
|
||||
lazy val api = new ClasApi(
|
||||
teacherColl = teacherColl,
|
||||
clasColl = clasColl,
|
||||
userRepo = userRepo
|
||||
clasColl = clasColl
|
||||
)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
package lila.clas
|
||||
|
||||
import lila.user.User
|
||||
|
||||
import org.joda.time.DateTime
|
||||
|
||||
case class Student(
|
||||
_id: Student.Id, // userId:clasId
|
||||
userId: User.ID,
|
||||
clasId: Clas.Id,
|
||||
createdAt: DateTime,
|
||||
updatedAt: DateTime
|
||||
) {
|
||||
|
||||
def id = _id
|
||||
|
||||
def is(user: User) = userId == user.id
|
||||
}
|
||||
|
||||
object Student {
|
||||
|
||||
def id(userId: User.ID, clasId: Clas.Id) = Id(s"${userId}:${clasId}")
|
||||
|
||||
def make(user: User, clas: Clas) = Student(
|
||||
_id = id(user.id, clas.id),
|
||||
userId = user.id,
|
||||
clasId = clas.id,
|
||||
createdAt = DateTime.now,
|
||||
updatedAt = DateTime.now
|
||||
)
|
||||
|
||||
case class Id(value: String) extends AnyVal with StringValue
|
||||
|
||||
case class WithUser(student: Student, user: User)
|
||||
}
|
|
@ -1,5 +1,7 @@
|
|||
package lila.clas
|
||||
|
||||
import lila.user.User
|
||||
|
||||
import org.joda.time.DateTime
|
||||
|
||||
case class Teacher(
|
||||
|
@ -10,12 +12,12 @@ case class Teacher(
|
|||
|
||||
def id = _id
|
||||
|
||||
def is(user: lila.user.User) = id.value == user.id
|
||||
def is(user: User) = id.value == user.id
|
||||
}
|
||||
|
||||
object Teacher {
|
||||
|
||||
def make(user: lila.user.User) = Teacher(
|
||||
def make(user: User) = Teacher(
|
||||
_id = Id(user.id),
|
||||
createdAt = DateTime.now,
|
||||
updatedAt = DateTime.now
|
||||
|
@ -23,5 +25,5 @@ object Teacher {
|
|||
|
||||
case class Id(value: String) extends AnyVal with StringValue
|
||||
|
||||
case class WithUser(teacher: Teacher, user: lila.user.User)
|
||||
case class WithUser(teacher: Teacher, user: User)
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ import scala.collection.Factory
|
|||
import com.github.ghik.silencer.silent
|
||||
import reactivemongo.api._
|
||||
import reactivemongo.api.bson._
|
||||
import reactivemongo.api.commands.{ WriteConcern => CWC, FindAndModifyCommand => FNM }
|
||||
import reactivemongo.api.commands.{ WriteConcern => CWC }
|
||||
|
||||
trait CollExt { self: dsl with QueryBuilderExt =>
|
||||
|
||||
|
@ -209,7 +209,7 @@ trait CollExt { self: dsl with QueryBuilderExt =>
|
|||
): Fu[M[T]] =
|
||||
coll.distinct(key, selector.some, ReadConcern.Local, None)
|
||||
|
||||
def findAndUpdate[S, T](
|
||||
def findAndUpdate[D: BSONDocumentReader](
|
||||
selector: coll.pack.Document,
|
||||
update: coll.pack.Document,
|
||||
fetchNewObject: Boolean = false,
|
||||
|
@ -217,7 +217,7 @@ trait CollExt { self: dsl with QueryBuilderExt =>
|
|||
sort: Option[coll.pack.Document] = None,
|
||||
fields: Option[coll.pack.Document] = None,
|
||||
@silent writeConcern: CWC = CWC.Acknowledged
|
||||
): Fu[FNM.Result[coll.pack.type]] =
|
||||
): Fu[Option[D]] =
|
||||
coll.findAndUpdate(
|
||||
selector = selector,
|
||||
update = update,
|
||||
|
@ -230,6 +230,8 @@ trait CollExt { self: dsl with QueryBuilderExt =>
|
|||
maxTime = none,
|
||||
collation = none,
|
||||
arrayFilters = Seq.empty
|
||||
)
|
||||
) map {
|
||||
_.value flatMap implicitly[BSONDocumentReader[D]].readOpt
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -57,14 +57,11 @@ final class OAuthServer(
|
|||
private def fetchAccessToken(tokenId: AccessToken.Id): Fu[Option[AccessToken.ForAuth]] =
|
||||
tokenColl {
|
||||
_.ext
|
||||
.findAndUpdate(
|
||||
.findAndUpdate[AccessToken.ForAuth](
|
||||
selector = $doc(F.id -> tokenId),
|
||||
update = $set(F.usedAt -> DateTime.now),
|
||||
fields = AccessToken.forAuthProjection.some
|
||||
)
|
||||
.map(_.value) dmap {
|
||||
_ ?? AccessToken.ForAuthBSONReader.readOpt
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -213,7 +213,7 @@ final class PlaybanApi(
|
|||
private def save(outcome: Outcome, userId: User.ID, rsUpdate: RageSit.Update): Funit = {
|
||||
lila.mon.playban.outcome(outcome.key).increment()
|
||||
coll.ext
|
||||
.findAndUpdate(
|
||||
.findAndUpdate[UserRecord](
|
||||
selector = $id(userId),
|
||||
update = $doc(
|
||||
$push("o" -> $doc("$each" -> List(outcome), "$slice" -> -30)) ++ {
|
||||
|
@ -226,9 +226,7 @@ final class PlaybanApi(
|
|||
),
|
||||
fetchNewObject = true,
|
||||
upsert = true
|
||||
)
|
||||
.dmap(_.value flatMap UserRecordBSONHandler.readOpt) orFail
|
||||
s"can't find newly created record for user $userId" flatMap { record =>
|
||||
) orFail s"can't find newly created record for user $userId" flatMap { record =>
|
||||
(outcome != Outcome.Good) ?? {
|
||||
userRepo.createdAtById(userId).flatMap { _ ?? { legiferate(record, _) } }
|
||||
} >>
|
||||
|
|
|
@ -71,13 +71,12 @@ final class ShutupApi(
|
|||
)
|
||||
) ++ pushPublicLine
|
||||
coll.ext
|
||||
.findAndUpdate(
|
||||
.findAndUpdate[UserRecord](
|
||||
selector = $id(userId),
|
||||
update = $push(push),
|
||||
fetchNewObject = true,
|
||||
upsert = true
|
||||
)
|
||||
.dmap(_.value flatMap UserRecordBSONHandler.readOpt) flatMap {
|
||||
) flatMap {
|
||||
case None => fufail(s"can't find user record for $userId")
|
||||
case Some(userRecord) => legiferate(userRecord, major)
|
||||
} logFailure lila.log("shutup")
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
.clas-index {
|
||||
.box__top {
|
||||
flex-flow: row nowrap;
|
||||
}
|
||||
.new::before {
|
||||
font-size: 3em;
|
||||
}
|
||||
section {
|
||||
margin-bottom: 2em;
|
||||
h2 {
|
||||
@extend %roboto;
|
||||
color: $c-font-dimmer;
|
||||
text-transform: uppercase;
|
||||
text-align: center;
|
||||
line-height: 2em;
|
||||
letter-spacing: .1em;
|
||||
border-bottom: $border;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
.swag {
|
||||
grid-gap: $block-gap;
|
||||
&__side {
|
||||
max-width: 350px;
|
||||
.box {
|
||||
padding: #{2 * $block-gap};
|
||||
}
|
||||
ul li {
|
||||
list-style: disc inside;
|
||||
margin: .5em 0;
|
||||
}
|
||||
}
|
||||
.page-menu__content {
|
||||
padding-top: 1em;
|
||||
overflow: hidden;
|
||||
}
|
||||
.spinner {
|
||||
margin: 100px auto;
|
||||
}
|
||||
.loading {
|
||||
margin: 100px auto;
|
||||
text-align: center;
|
||||
}
|
||||
#myShop #sprd-main {
|
||||
z-index: auto;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
@import '../../../common/css/plugin';
|
||||
@import '../../../common/css/form/form3';
|
||||
@import '../clas';
|
|
@ -1,2 +0,0 @@
|
|||
@import '../../../common/css/plugin';
|
||||
@import '../swag';
|
|
@ -0,0 +1,2 @@
|
|||
@import '../../../common/css/theme/dark';
|
||||
@import 'clas';
|
|
@ -0,0 +1,2 @@
|
|||
@import '../../../common/css/theme/light';
|
||||
@import 'clas';
|
|
@ -0,0 +1,2 @@
|
|||
@import '../../../common/css/theme/transp';
|
||||
@import 'clas';
|
Loading…
Reference in New Issue