import starshipfights.data.auth.User
import starshipfights.data.auth.UserSession
import starshipfights.data.createNonce
-import starshipfights.game.Faction
-import starshipfights.game.ShipType
-import starshipfights.game.buyPrice
-import starshipfights.game.toUrlSlug
+import starshipfights.game.*
import starshipfights.info.*
import java.time.Instant
import java.time.temporal.ChronoUnit
+const val PROFILE_NAME_MAX_LENGTH = 32
+const val PROFILE_BIO_MAX_LENGTH = 240
+const val ADMIRAL_NAME_MAX_LENGTH = 48
+const val SHIP_NAME_MAX_LENGTH = 48
+
interface AuthProvider {
fun installApplication(app: Application) = Unit
fun installAuth(conf: Authentication.Configuration)
val form = call.receiveParameters()
val newUser = currentUser.copy(
- profileName = form["name"]?.takeIf { it.isNotBlank() && it.length <= 32 } ?: redirect("/me/manage?" + parametersOf("error", "Invalid name - must not be blank, must be at most 32 characters").formUrlEncode()),
- profileBio = form["bio"]?.takeIf { it.isNotBlank() && it.length <= 240 } ?: redirect("/me/manage?" + parametersOf("error", "Invalid bio - must not be blank, must be at most 240 characters").formUrlEncode())
+ profileName = form["name"]?.takeIf { it.isNotBlank() && it.length <= PROFILE_NAME_MAX_LENGTH } ?: redirect("/me/manage?" + parametersOf("error", "Invalid name - must not be blank, must be at most $PROFILE_NAME_MAX_LENGTH characters").formUrlEncode()),
+ profileBio = form["bio"]?.takeIf { it.isNotBlank() && it.length <= PROFILE_BIO_MAX_LENGTH } ?: redirect("/me/manage?" + parametersOf("error", "Invalid bio - must not be blank, must be at most $PROFILE_BIO_MAX_LENGTH characters").formUrlEncode())
)
User.put(newUser)
redirect("/user/${newUser.id}")
val newAdmiral = Admiral(
owningUser = currentUser,
- name = form["name"]?.takeIf { it.isNotBlank() } ?: throw MissingRequestParameterException("name"),
+ name = form["name"]?.takeIf { it.isNotBlank() && it.length < ADMIRAL_NAME_MAX_LENGTH } ?: throw MissingRequestParameterException("name"),
isFemale = form.getOrFail("sex") == "female",
faction = Faction.valueOf(form.getOrFail("faction")),
acumen = 0,
redirect("/admiral/$admiralId")
}
+ get("/admiral/{id}/rename/{ship}") {
+ call.respondHtml(HttpStatusCode.OK, call.renameShipPage())
+ }
+
+ post("/admiral/{id}/rename/{ship}") {
+ val currentUser = call.getUserSession()?.user
+
+ val admiralId = call.parameters["id"]?.let { Id<Admiral>(it) }!!
+ val shipId = call.parameters["ship"]?.let { Id<ShipInDrydock>(it) }!!
+
+ val (admiral, ship) = coroutineScope {
+ Admiral.get(admiralId)!! to ShipInDrydock.get(shipId)!!
+ }
+
+ if (admiral.owningUser != currentUser) throw ForbiddenException()
+ if (ship.owningAdmiral != admiralId) throw ForbiddenException()
+
+ val newName = call.receiveParameters()["name"]?.takeIf { it.isNotBlank() && it.length <= SHIP_NAME_MAX_LENGTH } ?: redirect("/admiral/${admiralId}/manage")
+ ShipInDrydock.set(shipId, setValue(ShipInDrydock::name, newName))
+ redirect("/admiral/${admiralId}/manage")
+ }
+
+ get("/admiral/{id}/sell/{ship}") {
+ call.respondHtml(HttpStatusCode.OK, call.sellShipConfirmPage())
+ }
+
+ post("/admiral/{id}/sell/{ship}") {
+ val currentUser = call.getUserSession()?.user
+
+ val admiralId = call.parameters["id"]?.let { Id<Admiral>(it) }!!
+ val shipId = call.parameters["ship"]?.let { Id<ShipInDrydock>(it) }!!
+
+ val (admiral, ship) = coroutineScope {
+ Admiral.get(admiralId)!! to ShipInDrydock.get(shipId)!!
+ }
+
+ if (admiral.owningUser != currentUser) throw ForbiddenException()
+ if (ship.owningAdmiral != admiralId) throw ForbiddenException()
+
+ if (ship.status != DrydockStatus.Ready) redirect("/admiral/${admiralId}/manage")
+ if (ship.shipType.weightClass.isUnique) redirect("/admiral/${admiralId}/manage")
+
+ launch { ShipInDrydock.del(shipId) }
+ launch {
+ Admiral.set(admiralId, inc(Admiral::money, ship.shipType.weightClass.sellPrice))
+ }
+
+ redirect("/admiral/${admiralId}/manage")
+ }
+
get("/admiral/{id}/buy/{ship}") {
call.respondHtml(HttpStatusCode.OK, call.buyShipConfirmPage())
}
import org.litote.kmongo.or
import starshipfights.CurrentConfiguration
import starshipfights.ForbiddenException
-import starshipfights.auth.getUser
-import starshipfights.auth.getUserSession
+import starshipfights.auth.*
import starshipfights.data.Id
import starshipfights.data.admiralty.*
import starshipfights.data.auth.User
}
textInput(name = "name") {
required = true
- maxLength = "32"
+ maxLength = "$PROFILE_NAME_MAX_LENGTH"
value = currentUser.profileName
autoComplete = false
}
p {
style = "font-style:italic;font-size:0.8em;color:#555"
- +"Max length 32 characters"
+ +"Max length $PROFILE_NAME_MAX_LENGTH characters"
}
h3 {
label {
style = "width: 100%;height:5em"
required = true
- maxLength = "240"
+ maxLength = "$PROFILE_BIO_MAX_LENGTH"
+currentUser.profileBio
}
autoComplete = false
required = true
+ maxLength = "$ADMIRAL_NAME_MAX_LENGTH"
}
p {
label {
val ownedShips = ShipInDrydock.select(ShipInDrydock::owningAdmiral eq admiralId).toList()
- //val sellableShips = ownedShips.filter { it.status == DrydockStatus.Ready }.sortedBy { it.name }.sortedBy { it.shipType.weightClass.rank }
-
val buyableShips = ShipType.values().filter { type ->
type.faction == admiral.faction && type.weightClass.rank <= admiral.rank.maxShipWeightClass.rank && type.weightClass.buyPrice <= admiral.money && (if (type.weightClass.isUnique) ownedShips.none { it.shipType.weightClass == type.weightClass } else true)
}.sortedBy { it.name }.sortedBy { it.weightClass.rank }
required = true
value = admiral.name
+ maxLength = "$ADMIRAL_NAME_MAX_LENGTH"
}
p {
label {
}
section {
h2 { +"Manage Fleet" }
+ table {
+ tr {
+ th { +"Ship Name" }
+ th { +"Ship Class" }
+ th { +"Ship Status" }
+ th { +"Ship Value" }
+ }
+ ownedShips.sortedBy { it.name }.sortedBy { it.shipType.weightClass.rank }.forEach { ship ->
+ tr {
+ td {
+ +ship.shipData.fullName
+ br
+ a(href = "/admiral/${admiralId}/rename/${ship.id}") { +"Rename" }
+ }
+ td {
+ a(href = "/info/${ship.shipData.shipType.toUrlSlug()}") {
+ +ship.shipData.shipType.fullDisplayName
+ }
+ }
+ td {
+ when (ship.status) {
+ DrydockStatus.Ready -> +"Ready"
+ is DrydockStatus.InRepair -> {
+ +"Repairing"
+ br
+ +"Will be ready at "
+ span(classes = "moment") {
+ style = "display:none"
+ +ship.status.until.toEpochMilli().toString()
+ }
+ }
+ }
+ }
+ td {
+ +ship.shipType.weightClass.sellPrice.toString()
+ +" Electro-Ducats"
+ if (ship.status == DrydockStatus.Ready && !ship.shipType.weightClass.isUnique) {
+ br
+ a(href = "/admiral/${admiralId}/sell/${ship.id}") { +"Sell" }
+ }
+ }
+ }
+ }
+ }
h3 { +"Buy New Ship" }
table {
tr {
+" Electro-Ducats"
}
td {
- form(action = "/admiral/${admiralId}/buy/${st.toUrlSlug()}", method = FormMethod.get) {
- submitInput {
- value = "Buy"
- }
+ a(href = "/admiral/${admiralId}/buy/${st.toUrlSlug()}") {
+ +"Buy"
}
}
}
}
}
+suspend fun ApplicationCall.renameShipPage(): HTML.() -> Unit {
+ val currentUser = getUserSession()?.user
+
+ val admiralId = parameters["id"]?.let { Id<Admiral>(it) }!!
+ val shipId = parameters["ship"]?.let { Id<ShipInDrydock>(it) }!!
+
+ val (admiral, ship) = coroutineScope {
+ Admiral.get(admiralId)!! to ShipInDrydock.get(shipId)!!
+ }
+
+ if (admiral.owningUser != currentUser) throw ForbiddenException()
+ if (ship.owningAdmiral != admiralId) throw ForbiddenException()
+
+ return page("Renaming Ship", null, null) {
+ section {
+ h1 { +"Renaming Ship" }
+ p {
+ +"${admiral.fullName} is about to rename the ${ship.shipData.fullName}. Choose a name here:"
+ }
+ form(method = FormMethod.post, action = "/admiral/${admiral.id}/rename/${ship.id}") {
+ textInput(name = "name") {
+ id = "name"
+
+ autoComplete = false
+ required = true
+
+ maxLength = "$SHIP_NAME_MAX_LENGTH"
+ }
+ p {
+ style = "font-style:italic;font-size:0.8em;color:#555"
+ +"Max length $SHIP_NAME_MAX_LENGTH characters"
+ }
+ submitInput {
+ value = "Rename"
+ }
+ }
+ form(method = FormMethod.get, action = "/admiral/${admiral.id}/manage") {
+ submitInput {
+ value = "Cancel"
+ }
+ }
+ }
+ }
+}
+
+suspend fun ApplicationCall.sellShipConfirmPage(): HTML.() -> Unit {
+ val currentUser = getUserSession()?.user
+
+ val admiralId = parameters["id"]?.let { Id<Admiral>(it) }!!
+ val shipId = parameters["ship"]?.let { Id<ShipInDrydock>(it) }!!
+
+ val (admiral, ship) = coroutineScope {
+ Admiral.get(admiralId)!! to ShipInDrydock.get(shipId)!!
+ }
+
+ if (admiral.owningUser != currentUser) throw ForbiddenException()
+ if (ship.owningAdmiral != admiralId) throw ForbiddenException()
+
+ if (ship.status != DrydockStatus.Ready) redirect("/admiral/${admiralId}/manage")
+ if (ship.shipType.weightClass.isUnique) redirect("/admiral/${admiralId}/manage")
+
+ return page(
+ "Are You Sure?", null, null
+ ) {
+ section {
+ h1 { +"Are You Sure?" }
+ p {
+ +"${admiral.fullName} is about to sell the ${ship.shipData.fullName} for ${ship.shipType.weightClass.sellPrice} Electro-Ducats."
+ }
+ form(method = FormMethod.get, action = "/admiral/${admiral.id}/manage") {
+ submitInput {
+ value = "Cancel"
+ }
+ }
+ form(method = FormMethod.post, action = "/admiral/${admiral.id}/sell/${ship.id}") {
+ submitInput {
+ value = "Sell"
+ }
+ }
+ }
+ }
+}
+
suspend fun ApplicationCall.buyShipConfirmPage(): HTML.() -> Unit {
val currentUser = getUserSession()?.user
val admiralId = parameters["id"]?.let { Id<Admiral>(it) }!!