class WIP

pull/5923/head
Thibault Duplessis 2020-01-16 11:52:20 -06:00
parent 79b39b6366
commit 4e5d337c7d
23 changed files with 318 additions and 94 deletions

View File

@ -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
}

View File

@ -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)
)
)
}
}

View File

@ -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())
)
)
}

View File

@ -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")
)
)
}
}

View File

@ -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)

View File

@ -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]
}

View File

@ -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

View File

@ -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
)
}
}

View File

@ -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
)
}

View File

@ -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
)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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
}
}
}

View File

@ -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
}
}
}

View File

@ -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, _) } }
} >>

View File

@ -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")

View File

@ -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;
}
}
}

View File

@ -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;
}
}

View File

@ -0,0 +1,3 @@
@import '../../../common/css/plugin';
@import '../../../common/css/form/form3';
@import '../clas';

View File

@ -1,2 +0,0 @@
@import '../../../common/css/plugin';
@import '../swag';

View File

@ -0,0 +1,2 @@
@import '../../../common/css/theme/dark';
@import 'clas';

View File

@ -0,0 +1,2 @@
@import '../../../common/css/theme/light';
@import 'clas';

View File

@ -0,0 +1,2 @@
@import '../../../common/css/theme/transp';
@import 'clas';