~jan0sch/smederee
Showing details for patch 3843e368454c33eb0d675400fa626fe73449d105.
diff -rN -u old-smederee/CODINGSTYLE.md new-smederee/CODINGSTYLE.md --- old-smederee/CODINGSTYLE.md 2025-01-11 12:08:45.249424754 +0000 +++ new-smederee/CODINGSTYLE.md 2025-01-11 12:08:45.265424782 +0000 @@ -28,7 +28,7 @@ ``` 4. The maximum line length in the source code SHOULD not be longer than 120 characters. There MAY be rare cases where exceptions are allowed. 5. Imports SHOULD be written after the following guidelines and automatically applied by running Scalafix: - 1. Imports from Java core (e.g. `import java.time._`) MUST be written (grouped) first, followed by a blank line. + 1. Imports from Java core (e.g. `import java.time.*`) MUST be written (grouped) first, followed by a blank line. 2. Imports from the Scala core (e.g. `import scala.util.Failure`) MUST be written (grouped) last, prefixed by a blank line. 3. Other imports SHOULD be grouped in between the Java and Scala core imports. 4. Within the other imports test framework imports (e.g. `munit.` or `org.scalacheck.`) SHOULD be put last and grouped. @@ -40,6 +40,26 @@ **TODO**: Describe `danglingParentheses.preset = true` +### 2.2 Twirl Templates + +We are using the Twirl templating engine to render templates which reside under `src/main/twirl` folders. The most common use case is to render HTML output which is indicated by the `.scala.html` file extension. However others are possible for example `.scala.txt` for rendering plain text emails. + +1. Each level of indentation MUST be 2 space characters. +2. There is no maximum line length constraint for Twirl templates. +3. Template tags (e.g. HTML tags) following within a control structure (e.g. `@for` or `@defining`) MAY not be indented to avoid too deeply nested code. +4. Imports SHOULD be sorted by their ASCII names. +5. Template parameters MUST be broken up upon multiple lines with the parentheses on separate lines if there are 3 or more parameters or they are curried. This rule MAY be broken if this generates undesired whitespace characters in the rendered output. + ```txt + @( + a: A, + b: Option[B], + c: Either[A, B] + )( + d: D, + e: Option[E] + ) + ``` + ## 3. Build configuration file style (sbt) 1. Build definition and settings SHOULD be formatted to have one setting per line if possible. diff -rN -u old-smederee/modules/hub/src/main/resources/messages.properties new-smederee/modules/hub/src/main/resources/messages.properties --- old-smederee/modules/hub/src/main/resources/messages.properties 2025-01-11 12:08:45.249424754 +0000 +++ new-smederee/modules/hub/src/main/resources/messages.properties 2025-01-11 12:08:45.265424782 +0000 @@ -40,6 +40,11 @@ form.change-password.username.help=Please enter the username for your account. form.change-password.username.placeholder=Please enter your username. form.change-password.username=Username +form.organisation.admins.add.name=Add administrator +form.organisation.admins.add.name.help=You may add an administrator to this organisation by specifying the name of a validated user account. +form.organisation.admins.add.name.placeholder= +form.organisation.admins.button.edit.submit=Save changes +form.organisation.admins.delete.notice=Please select all administrators that you would like to remove. form.organisation.button.create.submit=Create organisation form.organisation.button.delete.submit=Delete this organisation! form.organisation.button.edit.submit=Save changes diff -rN -u old-smederee/modules/hub/src/main/scala/de/smederee/hub/AccountManagementRoutes.scala new-smederee/modules/hub/src/main/scala/de/smederee/hub/AccountManagementRoutes.scala --- old-smederee/modules/hub/src/main/scala/de/smederee/hub/AccountManagementRoutes.scala 2025-01-11 12:08:45.253424761 +0000 +++ new-smederee/modules/hub/src/main/scala/de/smederee/hub/AccountManagementRoutes.scala 2025-01-11 12:08:45.265424782 +0000 @@ -136,17 +136,9 @@ case ar @ POST -> Root / "user" / "settings" / "ssh" / "add" as user => ar.req.decodeStrict[F, UrlForm] { urlForm => for { - csrf <- Sync[F].delay(ar.req.getCsrfToken) - language <- Sync[F].delay(user.language.getOrElse(LanguageCode("en"))) - formData <- Sync[F].delay { - urlForm.values.map { t => - val (key, values) = t - ( - key, - values.headOption.getOrElse("") - ) // Pick the first value (a field might get submitted multiple times)! - } - } + csrf <- Sync[F].delay(ar.req.getCsrfToken) + language <- Sync[F].delay(user.language.getOrElse(LanguageCode("en"))) + formData <- Sync[F].delay(urlForm.values) form <- Sync[F].delay(AddPublicSshKeyForm.validate(formData)) actionBaseUri <- Sync[F].delay(configuration.external.createFullUri(uri"user/settings")) addAction <- Sync[F].delay(configuration.external.createFullUri(uri"user/settings/ssh/add")) @@ -165,7 +157,7 @@ addAction, deleteAction, keys - )(formData, FormErrors.fromNec(errors)) + )(formData.withDefaultValue(Chain.empty), FormErrors.fromNec(errors)) ) case Validated.Valid(validSshKeyForm) => for { @@ -197,7 +189,7 @@ deleteAction, keys )( - formData, + formData.withDefaultValue(Chain.empty), Map( AddPublicSshKeyForm.fieldGlobal -> List( FormFieldError("The key could not be properly converted!") @@ -223,7 +215,7 @@ deleteAction, keys )( - formData, + formData.withDefaultValue(Chain.empty), Map( AddPublicSshKeyForm.fieldGlobal -> List( FormFieldError( diff -rN -u old-smederee/modules/hub/src/main/scala/de/smederee/hub/AddPublicSshKeyForm.scala new-smederee/modules/hub/src/main/scala/de/smederee/hub/AddPublicSshKeyForm.scala --- old-smederee/modules/hub/src/main/scala/de/smederee/hub/AddPublicSshKeyForm.scala 2025-01-11 12:08:45.253424761 +0000 +++ new-smederee/modules/hub/src/main/scala/de/smederee/hub/AddPublicSshKeyForm.scala 2025-01-11 12:08:45.265424782 +0000 @@ -36,13 +36,14 @@ val fieldName: FormField = FormField("name") val fieldKey: FormField = FormField("key") - override def validate(data: Map[String, String]): ValidatedNec[FormErrors, AddPublicSshKeyForm] = { - val name = data.get(fieldName).fold(None.validNec)(name => KeyComment.from(name).validNec) + override def validate(data: Map[String, Chain[String]]): ValidatedNec[FormErrors, AddPublicSshKeyForm] = { + val name = data.get(fieldName).fold(None.validNec)(name => name.headOption.flatMap(KeyComment.from).validNec) val key = data .get(fieldKey) .fold(FormFieldError("No ssh public key given!").invalidNec)(string => - SshPublicKeyString - .from(string.trim) + string.headOption + .map(_.trim) + .flatMap(SshPublicKeyString.from) .fold(FormFieldError("Invalid ssh public key!").invalidNec)(_.validNec) ) .leftMap(errors => NonEmptyChain.of(Map(fieldKey -> errors.toList))) diff -rN -u old-smederee/modules/hub/src/main/scala/de/smederee/hub/AuthenticationRoutes.scala new-smederee/modules/hub/src/main/scala/de/smederee/hub/AuthenticationRoutes.scala --- old-smederee/modules/hub/src/main/scala/de/smederee/hub/AuthenticationRoutes.scala 2025-01-11 12:08:45.253424761 +0000 +++ new-smederee/modules/hub/src/main/scala/de/smederee/hub/AuthenticationRoutes.scala 2025-01-11 12:08:45.265424782 +0000 @@ -88,23 +88,15 @@ private val parseLoginForm: HttpRoutes[F] = HttpRoutes.of[F] { case request @ POST -> Root / "login" => request.decodeStrict[F, UrlForm] { urlForm => for { - csrf <- Sync[F].delay(request.getCsrfToken) - formData <- Sync[F].delay { - urlForm.values.map { t => - val (key, values) = t - ( - key, - values.headOption.getOrElse("") - ) // Pick the first value (a field might get submitted multiple times)! - } - } - form <- Sync[F].delay(LoginForm.validate(formData)) + csrf <- Sync[F].delay(request.getCsrfToken) + formData <- Sync[F].delay(urlForm.values) + form <- Sync[F].delay(LoginForm.validate(formData)) response <- form match { case Validated.Invalid(es) => BadRequest( views.html .login()(loginPath, csrf, resetPath, title = "Smederee - Login to your account".some)( - formData, + formData.withDefaultValue(Chain.empty), FormErrors.fromNec(es) ) ) @@ -171,7 +163,7 @@ resetPath, title = "Smederee - Login to your account".some )( - formData, + formData.withDefaultValue(Chain.empty), Map(LoginForm.fieldGlobal -> List(FormFieldError("Invalid credentials!"))) ) ) diff -rN -u old-smederee/modules/hub/src/main/scala/de/smederee/hub/ChangePasswordForm.scala new-smederee/modules/hub/src/main/scala/de/smederee/hub/ChangePasswordForm.scala --- old-smederee/modules/hub/src/main/scala/de/smederee/hub/ChangePasswordForm.scala 2025-01-11 12:08:45.253424761 +0000 +++ new-smederee/modules/hub/src/main/scala/de/smederee/hub/ChangePasswordForm.scala 2025-01-11 12:08:45.265424782 +0000 @@ -47,20 +47,24 @@ val fieldPasswordConfirmation: FormField = FormField("password_confirmation") val fieldResetToken: FormField = FormField("token") - override def validate(data: Map[String, String]): ValidatedNec[FormErrors, ChangePasswordForm] = { + override def validate(data: Map[String, Chain[String]]): ValidatedNec[FormErrors, ChangePasswordForm] = { val name: ValidatedNec[FormErrors, Username] = data .get(fieldName) - .fold(FormFieldError("No username given!").invalidNec)(s => - Username.from(s).fold(FormFieldError("Invalid username!").invalidNec)(_.validNec) + .fold(FormFieldError("No username given!").invalidNec)( + _.headOption.flatMap(Username.from).fold(FormFieldError("Invalid username!").invalidNec)(_.validNec) ) .leftMap(es => NonEmptyChain.of(Map(fieldName -> es.toList))) val password: ValidatedNec[FormErrors, Password] = data .get(fieldPassword) - .fold("No password given!".invalidNec)(Password.validate) + .fold("No password given!".invalidNec)( + _.headOption.fold("No password given!".invalidNec)(Password.validate) + ) .leftMap(es => NonEmptyChain.of(Map(fieldPassword -> es.toList.map(FormFieldError.apply)))) val passwordConfirmation: ValidatedNec[FormErrors, Password] = data .get(fieldPasswordConfirmation) - .fold("No password given!".invalidNec)(Password.validate) + .fold("No password given!".invalidNec)( + _.headOption.fold("No password given!".invalidNec)(Password.validate) + ) .leftMap(es => NonEmptyChain.of(Map(fieldPasswordConfirmation -> es.toList.map(FormFieldError.apply)))) val passwordsMatching: ValidatedNec[FormErrors, Password] = (password.toOption, passwordConfirmation.toOption) .mapN { case (pw, pwc) => @@ -73,8 +77,10 @@ } val token: ValidatedNec[FormErrors, ResetToken] = data .get(fieldResetToken) - .fold(FormFieldError("No reset token!").invalidNec)(s => - ResetToken.from(s).fold(FormFieldError("Invalid reset token!").invalidNec)(_.validNec) + .fold(FormFieldError("No reset token!").invalidNec)( + _.headOption + .flatMap(ResetToken.from) + .fold(FormFieldError("Invalid reset token!").invalidNec)(_.validNec) ) .leftMap(es => NonEmptyChain.of(Map(fieldResetToken -> es.toList))) (name, password, passwordConfirmation, passwordsMatching, token).mapN { diff -rN -u old-smederee/modules/hub/src/main/scala/de/smederee/hub/DoobieOrganisationRepository.scala new-smederee/modules/hub/src/main/scala/de/smederee/hub/DoobieOrganisationRepository.scala --- old-smederee/modules/hub/src/main/scala/de/smederee/hub/DoobieOrganisationRepository.scala 2025-01-11 12:08:45.253424761 +0000 +++ new-smederee/modules/hub/src/main/scala/de/smederee/hub/DoobieOrganisationRepository.scala 2025-01-11 12:08:45.269424789 +0000 @@ -100,6 +100,22 @@ sqlQuery.query[Organisation].option.transact(tx) } + override def findNewAdminByName(name: Username): F[Option[Account]] = { + val nameFilter = fr"""name = $name""" + val notLockedFilter = fr"""locked_at IS NULL""" + val validatedEmailFilter = fr"""validated_email IS TRUE""" + val query = + fr"""SELECT + uid, + name, + email, + full_name, + validated_email, + language + FROM hub.accounts""" ++ whereAnd(notLockedFilter, validatedEmailFilter, nameFilter) ++ fr"""LIMIT 1""" + query.query[Account].option.transact(tx) + } + override def findOwner(organisationId: OrganisationId): F[Option[Account]] = { val organisationFilter = fr"""organisations.id = $organisationId""" val sqlQuery = diff -rN -u old-smederee/modules/hub/src/main/scala/de/smederee/hub/EditVcsRepositoryForm.scala new-smederee/modules/hub/src/main/scala/de/smederee/hub/EditVcsRepositoryForm.scala --- old-smederee/modules/hub/src/main/scala/de/smederee/hub/EditVcsRepositoryForm.scala 2025-01-11 12:08:45.253424761 +0000 +++ new-smederee/modules/hub/src/main/scala/de/smederee/hub/EditVcsRepositoryForm.scala 2025-01-11 12:08:45.269424789 +0000 @@ -63,18 +63,22 @@ def fromVcsRepository(repo: VcsRepository): EditVcsRepositoryForm = EditVcsRepositoryForm(repo.name, repo.isPrivate, repo.description, repo.ticketsEnabled, repo.website) - override def validate(data: Map[String, String]): ValidatedNec[FormErrors, EditVcsRepositoryForm] = { + override def validate(data: Map[String, Chain[String]]): ValidatedNec[FormErrors, EditVcsRepositoryForm] = { val name = data .get(fieldName) - .fold(FormFieldError("No repository name given!").invalidNec)(s => - VcsRepositoryName.from(s).fold(FormFieldError("Invalid repository name!").invalidNec)(_.validNec) + .fold(FormFieldError("No repository name given!").invalidNec)( + _.headOption + .flatMap(VcsRepositoryName.from) + .fold(FormFieldError("Invalid repository name!").invalidNec)(_.validNec) ) .leftMap(es => NonEmptyChain.of(Map(fieldName -> es.toList))) val privateFlag: ValidatedNec[FormErrors, Boolean] = - data.get(fieldIsPrivate).fold(false.validNec)(s => s.matches("true").validNec) + data.get(fieldIsPrivate).fold(false.validNec)(_.headOption.getOrElse("false").matches("true").validNec) val description = data .get(fieldDescription) - .fold(Option.empty[VcsRepositoryDescription].validNec) { s => + .filter(_.nonEmpty) + .fold(None.validNec) { values => + val s = values.headOption.getOrElse("") if (s.trim.isEmpty) Option.empty[VcsRepositoryDescription].validNec // Sometimes "empty" strings are sent. else @@ -86,10 +90,12 @@ } .leftMap(es => NonEmptyChain.of(Map(fieldDescription -> es.toList))) val ticketsEnabledFlag: ValidatedNec[FormErrors, Boolean] = - data.get(fieldTicketsEnabled).fold(false.validNec)(s => s.matches("true").validNec) + data.get(fieldTicketsEnabled).fold(false.validNec)(_.headOption.getOrElse("false").matches("true").validNec) val website = data .get(fieldWebsite) - .fold(Option.empty[Uri].validNec) { s => + .filter(_.nonEmpty) + .fold(None.validNec) { values => + val s = values.headOption.getOrElse("") if (s.trim.isEmpty) Option.empty[Uri].validNec // Sometimes "empty" strings are sent. else @@ -118,7 +124,7 @@ * @return * A stringified map containing the data of the form. */ - def toMap: Map[String, String] = { + def toMap: Map[String, Chain[String]] = { val isPrivate = if (form.isPrivate) "true" @@ -130,16 +136,16 @@ else "false" val formData = Map( - EditVcsRepositoryForm.fieldName.toString -> form.name.toString, - EditVcsRepositoryForm.fieldIsPrivate.toString -> isPrivate, - EditVcsRepositoryForm.fieldTicketsEnabled.toString -> ticketsEnabled + EditVcsRepositoryForm.fieldName.toString -> Chain(form.name.toString), + EditVcsRepositoryForm.fieldIsPrivate.toString -> Chain(isPrivate), + EditVcsRepositoryForm.fieldTicketsEnabled.toString -> Chain(ticketsEnabled) ) val description = form.description.fold(Map.empty)(description => - Map(EditVcsRepositoryForm.fieldDescription.toString -> description.toString) + Map(EditVcsRepositoryForm.fieldDescription.toString -> Chain(description.toString)) ) val website = form.website.fold(Map.empty)(website => - Map(EditVcsRepositoryForm.fieldWebsite.toString -> website.toString) + Map(EditVcsRepositoryForm.fieldWebsite.toString -> Chain(website.toString)) ) formData ++ description ++ website } diff -rN -u old-smederee/modules/hub/src/main/scala/de/smederee/hub/forms/FormValidator.scala new-smederee/modules/hub/src/main/scala/de/smederee/hub/forms/FormValidator.scala --- old-smederee/modules/hub/src/main/scala/de/smederee/hub/forms/FormValidator.scala 2025-01-11 12:08:45.253424761 +0000 +++ new-smederee/modules/hub/src/main/scala/de/smederee/hub/forms/FormValidator.scala 2025-01-11 12:08:45.269424789 +0000 @@ -44,7 +44,7 @@ * @return * Either the validated form as concrete type T or a list of form errors. */ - def validate(data: Map[String, String]): ValidatedNec[FormErrors, T] + def validate(data: Map[String, Chain[String]]): ValidatedNec[FormErrors, T] } diff -rN -u old-smederee/modules/hub/src/main/scala/de/smederee/hub/LoginForm.scala new-smederee/modules/hub/src/main/scala/de/smederee/hub/LoginForm.scala --- old-smederee/modules/hub/src/main/scala/de/smederee/hub/LoginForm.scala 2025-01-11 12:08:45.253424761 +0000 +++ new-smederee/modules/hub/src/main/scala/de/smederee/hub/LoginForm.scala 2025-01-11 12:08:45.269424789 +0000 @@ -36,15 +36,19 @@ val fieldName: FormField = FormField("name") val fieldPassword: FormField = FormField("password") - override def validate(data: Map[String, String]): ValidatedNec[FormErrors, LoginForm] = { + override def validate(data: Map[String, Chain[String]]): ValidatedNec[FormErrors, LoginForm] = { val genericError = FormFieldError("Invalid credentials!") // We just return a generic error. val name: ValidatedNec[FormErrors, Username] = data .get(fieldName) - .fold(genericError.invalidNec)(s => Username.from(s).fold(genericError.invalidNec)(_.validNec)) + .fold(genericError.invalidNec)( + _.headOption.flatMap(Username.from).fold(genericError.invalidNec)(_.validNec) + ) .leftMap(es => NonEmptyChain.of(Map(fieldName -> es.toList))) val password: ValidatedNec[FormErrors, Password] = data .get(fieldPassword) - .fold(genericError.invalidNec)(s => Password.from(s).fold(genericError.invalidNec)(_.validNec)) + .fold(genericError.invalidNec)( + _.headOption.flatMap(Password.from).fold(genericError.invalidNec)(_.validNec) + ) .leftMap(es => NonEmptyChain.of(Map(fieldPassword -> es.toList))) (name, password).mapN { case (n, pw) => LoginForm(n, pw) diff -rN -u old-smederee/modules/hub/src/main/scala/de/smederee/hub/NewVcsRepositoryForm.scala new-smederee/modules/hub/src/main/scala/de/smederee/hub/NewVcsRepositoryForm.scala --- old-smederee/modules/hub/src/main/scala/de/smederee/hub/NewVcsRepositoryForm.scala 2025-01-11 12:08:45.253424761 +0000 +++ new-smederee/modules/hub/src/main/scala/de/smederee/hub/NewVcsRepositoryForm.scala 2025-01-11 12:08:45.269424789 +0000 @@ -53,18 +53,22 @@ val fieldTicketsEnabled: FormField = FormField("tickets_enabled") val fieldWebsite: FormField = FormField("website") - override def validate(data: Map[String, String]): ValidatedNec[FormErrors, NewVcsRepositoryForm] = { + override def validate(data: Map[String, Chain[String]]): ValidatedNec[FormErrors, NewVcsRepositoryForm] = { val name = data .get(fieldName) - .fold(FormFieldError("No repository name given!").invalidNec)(s => - VcsRepositoryName.from(s).fold(FormFieldError("Invalid repository name!").invalidNec)(_.validNec) + .fold(FormFieldError("No repository name given!").invalidNec)( + _.headOption + .flatMap(VcsRepositoryName.from) + .fold(FormFieldError("Invalid repository name!").invalidNec)(_.validNec) ) .leftMap(es => NonEmptyChain.of(Map(fieldName -> es.toList))) val privateFlag: ValidatedNec[FormErrors, Boolean] = - data.get(fieldIsPrivate).fold(false.validNec)(s => s.matches("true").validNec) + data.get(fieldIsPrivate).fold(false.validNec)(_.headOption.getOrElse("false").matches("true").validNec) val description = data .get(fieldDescription) - .fold(Option.empty[VcsRepositoryDescription].validNec) { s => + .filter(_.nonEmpty) + .fold(None.validNec) { values => + val s = values.headOption.getOrElse("") if (s.trim.isEmpty) Option.empty[VcsRepositoryDescription].validNec // Sometimes "empty" strings are sent. else @@ -76,10 +80,12 @@ } .leftMap(es => NonEmptyChain.of(Map(fieldDescription -> es.toList))) val ticketsEnabledFlag: ValidatedNec[FormErrors, Boolean] = - data.get(fieldTicketsEnabled).fold(false.validNec)(s => s.matches("true").validNec) + data.get(fieldTicketsEnabled).fold(false.validNec)(_.headOption.getOrElse("false").matches("true").validNec) val website = data .get(fieldWebsite) - .fold(Option.empty[Uri].validNec) { s => + .filter(_.nonEmpty) + .fold(None.validNec) { values => + val s = values.headOption.getOrElse("") if (s.trim.isEmpty) Option.empty[Uri].validNec // Sometimes "empty" strings are sent. else diff -rN -u old-smederee/modules/hub/src/main/scala/de/smederee/hub/OrganisationAdminsForm.scala new-smederee/modules/hub/src/main/scala/de/smederee/hub/OrganisationAdminsForm.scala --- old-smederee/modules/hub/src/main/scala/de/smederee/hub/OrganisationAdminsForm.scala 1970-01-01 00:00:00.000000000 +0000 +++ new-smederee/modules/hub/src/main/scala/de/smederee/hub/OrganisationAdminsForm.scala 2025-01-11 12:08:45.269424789 +0000 @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2022 Contributors as noted in the AUTHORS.md file + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package de.smederee.hub + +import cats.data.* +import cats.syntax.all.* +import de.smederee.hub.forms.* +import de.smederee.hub.forms.types.* +import de.smederee.security.* + +final case class OrganisationAdminsForm( + adminsToDelete: List[Username], + newAdminName: Option[Username] +) + +object OrganisationAdminsForm extends FormValidator[OrganisationAdminsForm] { + val fieldAdminsToDelete: FormField = FormField("adminsToDelete") + val fieldNewAdminName: FormField = FormField("newAdminName") + + override def validate(data: Map[String, Chain[String]]): ValidatedNec[FormErrors, OrganisationAdminsForm] = { + val newAdminName = data + .get(fieldNewAdminName) + .filter(_.nonEmpty) + .fold(None.validNec)( + _.headOption + .filter(_.nonEmpty) + .fold(None.validNec)(string => + Username.from(string).fold(FormFieldError("Invalid name!").invalidNec)(_.some.validNec) + ) + ) + .leftMap(es => NonEmptyChain.of(Map(fieldNewAdminName -> es.toList))) + // TODO: Currently we silently drop invalid names from the list, should we send feedback to the user? + val adminsToDelete = data + .get(fieldAdminsToDelete) + .filter(_.nonEmpty) + .fold(Nil.validNec)(_.toList.map(Username.from).flatten.validNec) + (adminsToDelete, newAdminName).mapN { case (admins, name) => + OrganisationAdminsForm(adminsToDelete = admins, newAdminName = name) + } + } +} diff -rN -u old-smederee/modules/hub/src/main/scala/de/smederee/hub/OrganisationForm.scala new-smederee/modules/hub/src/main/scala/de/smederee/hub/OrganisationForm.scala --- old-smederee/modules/hub/src/main/scala/de/smederee/hub/OrganisationForm.scala 2025-01-11 12:08:45.253424761 +0000 +++ new-smederee/modules/hub/src/main/scala/de/smederee/hub/OrganisationForm.scala 2025-01-11 12:08:45.269424789 +0000 @@ -74,43 +74,49 @@ website = organisation.website ) - override def validate(data: Map[String, String]): ValidatedNec[FormErrors, OrganisationForm] = { + override def validate(data: Map[String, Chain[String]]): ValidatedNec[FormErrors, OrganisationForm] = { val name = data .get(fieldName) - .fold(FormFieldError("No name given!").invalidNec)(s => - Username.from(s).fold(FormFieldError("Invalid name!").invalidNec)(_.validNec) + .fold(FormFieldError("No name given!").invalidNec)( + _.headOption.flatMap(Username.from).fold(FormFieldError("Invalid name!").invalidNec)(_.validNec) ) .leftMap(es => NonEmptyChain.of(Map(fieldName -> es.toList))) val owner = data .get(fieldOwner) - .fold(None.validNec)(s => + .filter(_.nonEmpty) + .fold(None.validNec) { values => + val s = values.headOption.getOrElse("") UserId.fromString(s) match { case Left(error) => FormFieldError(error).invalidNec case Right(id) => id.some.validNec } - ) + } .leftMap(es => NonEmptyChain.of(Map(fieldName -> es.toList))) val fullName = data .get(fieldFullName) .filter(_.nonEmpty) - .fold(None.validNec)(s => - FullName.from(s).fold(FormFieldError("Invalid full organisation name!").invalidNec)(_.some.validNec) + .fold(None.validNec)( + _.headOption + .flatMap(FullName.from) + .fold(FormFieldError("Invalid full organisation name!").invalidNec)(_.some.validNec) ) - .leftMap(es => NonEmptyChain.of(Map(fieldName -> es.toList))) + .leftMap(es => NonEmptyChain.of(Map(fieldFullName -> es.toList))) val description = data .get(fieldDescription) .filter(_.nonEmpty) - .fold(None.validNec)(s => - OrganisationDescription - .from(s) + .fold(None.validNec)( + _.headOption + .flatMap(OrganisationDescription.from) .fold(FormFieldError("Invalid organisation description!").invalidNec)(_.some.validNec) ) .leftMap(es => NonEmptyChain.of(Map(fieldDescription -> es.toList))) val privateFlag: ValidatedNec[FormErrors, Boolean] = - data.get(fieldIsPrivate).fold(false.validNec)(s => s.matches("true").validNec) + data.get(fieldIsPrivate).fold(false.validNec)(_.headOption.getOrElse("false").matches("true").validNec) val website = data .get(fieldWebsite) - .fold(Option.empty[Uri].validNec) { s => + .filter(_.nonEmpty) + .fold(Option.empty[Uri].validNec) { values => + val s = values.headOption.getOrElse("") if (s.trim.isEmpty) Option.empty[Uri].validNec // Sometimes "empty" strings are sent. else @@ -146,23 +152,26 @@ * @return * A stringified map containing the data of the form. */ - def toMap: Map[String, String] = { - val name = Map(OrganisationForm.fieldName.toString -> form.name.toString) - val owner = form.owner.fold(Map.empty)(owner => Map(OrganisationForm.fieldOwner.toString -> owner.toString)) + def toMap: Map[String, Chain[String]] = { + val name = Map(OrganisationForm.fieldName.toString -> Chain(form.name.toString)) + val owner = + form.owner.fold(Map.empty)(owner => Map(OrganisationForm.fieldOwner.toString -> Chain(owner.toString))) val fullName = form.fullName.fold(Map.empty)(fullName => - Map(OrganisationForm.fieldFullName.toString -> fullName.toString) + Map(OrganisationForm.fieldFullName.toString -> Chain(fullName.toString)) ) val description = form.description.fold(Map.empty)(description => - Map(OrganisationForm.fieldDescription.toString -> description.toString) + Map(OrganisationForm.fieldDescription.toString -> Chain(description.toString)) ) val isPrivate = if (form.isPrivate) { - Map(OrganisationForm.fieldIsPrivate.toString -> "true") + Map(OrganisationForm.fieldIsPrivate.toString -> Chain("true")) } else { - Map(OrganisationForm.fieldIsPrivate.toString -> "false") + Map(OrganisationForm.fieldIsPrivate.toString -> Chain("false")) } val website = - form.website.fold(Map.empty)(website => Map(OrganisationForm.fieldWebsite.toString -> website.toString)) + form.website.fold(Map.empty)(website => + Map(OrganisationForm.fieldWebsite.toString -> Chain(website.toString)) + ) name ++ owner ++ fullName ++ description ++ isPrivate ++ website } } diff -rN -u old-smederee/modules/hub/src/main/scala/de/smederee/hub/OrganisationRepository.scala new-smederee/modules/hub/src/main/scala/de/smederee/hub/OrganisationRepository.scala --- old-smederee/modules/hub/src/main/scala/de/smederee/hub/OrganisationRepository.scala 2025-01-11 12:08:45.253424761 +0000 +++ new-smederee/modules/hub/src/main/scala/de/smederee/hub/OrganisationRepository.scala 2025-01-11 12:08:45.269424789 +0000 @@ -84,6 +84,16 @@ */ def findByName(name: Username): F[Option[Organisation]] + /** Find the user account that is supposed to be an new administrator for an organisation by the user name. The user + * account must not be locked and must also have validated their email address to be returned. + * + * @param name + * A user name. + * @return + * An option to the found user account. + */ + def findNewAdminByName(name: Username): F[Option[Account]] + /** Find the account of an organisation owner. * * @param organisationId diff -rN -u old-smederee/modules/hub/src/main/scala/de/smederee/hub/OrganisationRoutes.scala new-smederee/modules/hub/src/main/scala/de/smederee/hub/OrganisationRoutes.scala --- old-smederee/modules/hub/src/main/scala/de/smederee/hub/OrganisationRoutes.scala 2025-01-11 12:08:45.253424761 +0000 +++ new-smederee/modules/hub/src/main/scala/de/smederee/hub/OrganisationRoutes.scala 2025-01-11 12:08:45.269424789 +0000 @@ -28,6 +28,7 @@ import cats.syntax.all.* import de.smederee.html.LinkTools.* import de.smederee.hub.RequestHelpers.instances.given +import de.smederee.hub.UrlFormHelpers.instances.* import de.smederee.hub.config.* import de.smederee.hub.forms.types.* import de.smederee.i18n.LanguageCode @@ -153,16 +154,8 @@ "An unvalidated account is not allowed to create an organisation!" ) // FIXME: Proper error handling! ) - formData <- Sync[F].delay { - urlForm.values.map { t => - val (key, values) = t - ( - key, - values.headOption.getOrElse("") - ) // Pick the first value (a field might get submitted multiple times)! - } - } - form <- Sync[F].delay(OrganisationForm.validate(formData)) + formData <- Sync[F].delay(urlForm.valuesWithoutEmptyStrings) + form <- Sync[F].delay(OrganisationForm.validate(formData)) // TODO: Cleanup the checking into something like `form.andThen(...)` or so. orgExists <- form.traverse(validForm => orgRepo.findByName(validForm.name)) checkedForm = orgExists match { @@ -185,7 +178,7 @@ "Smederee - Create a new organisation".some, user )( - formData, + formData.withDefaultValue(Chain.empty), FormErrors.fromNec(es) ) ) @@ -227,16 +220,10 @@ case None => NotFound() case Some((organisation, _, owner)) => for { - formData <- Sync[F].delay { - urlForm.values.map { t => - val (key, values) = t - ( - key, - values.headOption.getOrElse("") - ) // Pick the first value (a field might get submitted multiple times)! - } - } - userIsSure <- Sync[F].delay(formData.get("i-am-sure").exists(_ === "yes")) + formData <- Sync[F].delay(urlForm.valuesWithoutEmptyStrings) + userIsSure <- Sync[F].delay( + formData.get("i-am-sure").exists(_.headOption.exists(_ === "yes")) + ) response <- if (owner === user && userIsSure) { for { @@ -266,6 +253,81 @@ } } + /** Handle the submission of the form to edit organisation administrators. + */ + private val editOrganisationAdmins: AuthedRoutes[Account, F] = AuthedRoutes.of { + case ar @ POST -> Root / "user" / "settings" / "organisations" / UsernamePathParameter( + organisationName + ) / "admins" as user => + ar.req.decodeStrict[F, UrlForm] { urlForm => + for { + csrf <- Sync[F].delay(ar.req.getCsrfToken) + language <- Sync[F].delay(user.language.getOrElse(LanguageCode("en"))) + _ <- Sync[F].raiseUnless(user.validatedEmail)( + new Error( + "An unvalidated account is not allowed to edit an organisation!" + ) // FIXME: Proper error handling! + ) + orgAndAdmins <- loadOrganisation(user.some)(organisationName) + orgaData = orgAndAdmins.filter(tuple => tuple._1.owner === user.uid || tuple._2.exists(_ === user)) + resp <- orgaData match { + case None => NotFound() + case Some((organisation, admins, _)) => + for { + formData <- Sync[F].delay(urlForm.valuesWithoutEmptyStrings) + form <- Sync[F].delay(OrganisationAdminsForm.validate(formData)) + resp <- form match { + case Validated.Invalid(es) => + val actionBaseUri = + uri"user/settings/organisations".addSegment( + s"~${organisation.name.toString}" + ) + val editAction = linkConfig.createFullUri(actionBaseUri.addSegment("admins")) + BadRequest( + views.html + .editOrganisationAdmins(lang = language)( + editAction, + csrf, + admins, + Option(s"~$organisationName - admins"), + user + )( + formData.withDefaultValue(Chain.empty), + FormErrors.fromNec(es) + ) + ) + case Validated.Valid(organisationAdminsForm) => + val logic = for { + newAdmin <- organisationAdminsForm.newAdminName match { + case None => None.pure + case Some(name) => orgRepo.findNewAdminByName(name) + } + foundAdminsToRemove <- organisationAdminsForm.adminsToDelete.traverse( + orgRepo.findNewAdminByName + ) + adminsToRemove = foundAdminsToRemove.flatten + addedAdmin <- newAdmin + .map(_.uid) + .traverse(orgRepo.addAdministrator(organisation.oid)) + removedAdmins <- adminsToRemove + .map(_.uid) + .traverse(orgRepo.removeAdministrator(organisation.oid)) + } yield (addedAdmin.getOrElse(0), removedAdmins.sum) + val targetUri = linkConfig.createFullUri( + Uri(path = + Uri.Path( + Vector(Uri.Path.Segment(s"~${organisation.name.toString}")) + ) + ) + ) + logic *> SeeOther(Location(targetUri)) + } + } yield resp + } + } yield resp + } + } + private val editOrganisation: AuthedRoutes[Account, F] = AuthedRoutes.of { case ar @ POST -> Root / "user" / "settings" / "organisations" / UsernamePathParameter( organisationName @@ -284,17 +346,10 @@ resp <- orgaData match { case None => NotFound() case Some((organisation, admins, owner)) => + val possibleOwners = (List(owner, user) ::: admins).distinct for { - formData <- Sync[F].delay { - urlForm.values.map { t => - val (key, values) = t - ( - key, - values.headOption.getOrElse("") - ) // Pick the first value (a field might get submitted multiple times)! - } - } - form <- Sync[F].delay(OrganisationForm.validate(formData)) + formData <- Sync[F].delay(urlForm.valuesWithoutEmptyStrings) + form <- Sync[F].delay(OrganisationForm.validate(formData)) resp <- form match { case Validated.Invalid(es) => val actionBaseUri = uri"user/settings/organisations".addSegment( @@ -306,7 +361,6 @@ .filter(_ => owner === user) val editOrgPath = linkConfig.createFullUri(actionBaseUri.addSegment("edit")) - val possibleOwners = (List(owner, user) ::: admins).distinct BadRequest( views.html .editOrganisation(lang = language)( @@ -317,13 +371,18 @@ Option(s"~$organisationName - edit"), user )( - formData, + formData.withDefaultValue(Chain.empty), FormErrors.fromNec(es) ) ) case Validated.Valid(newOrganisationForm) => + val newOwner = + newOrganisationForm.owner + .flatMap(uid => possibleOwners.find(_.uid === uid)) + .map(_.uid) + .getOrElse(user.uid) val updatedOrganisation = organisation.copy( - owner = user.uid, + owner = newOwner, fullName = newOrganisationForm.fullName, description = newOrganisationForm.description, isPrivate = newOrganisationForm.isPrivate, @@ -372,6 +431,48 @@ } yield resp } + private val showEditOrganisationAdminsForm: AuthedRoutes[Account, F] = AuthedRoutes.of { + case ar @ GET -> Root / "user" / "settings" / "organisations" / UsernamePathParameter( + organisationName + ) / "admins" as user => + for { + csrf <- Sync[F].delay(ar.req.getCsrfToken) + language <- Sync[F].delay(user.language.getOrElse(LanguageCode("en"))) + orgAndAdmins <- loadOrganisation(user.some)(organisationName) + orgaData = orgAndAdmins.filter(tuple => tuple._1.owner === user.uid || tuple._2.exists(_ === user)) + resp <- orgaData match { + case None => NotFound("Organisation not found!") + case Some((organisation, admins, _)) => + user.validatedEmail match { + case false => + Forbidden( + views.html.errors + .unvalidatedAccount(lang = language)( + csrf, + "Smederee - Account not validated!".some, + user + ) + ) + case true => + val actionBaseUri = + uri"user/settings/organisations".addSegment(s"~${organisation.name.toString}") + val editAction = + linkConfig.createFullUri(actionBaseUri.addSegment("admins")) + Ok( + views.html + .editOrganisationAdmins(lang = language)( + editAction, + csrf, + admins, + Option(s"~$organisationName - admins"), + user + )() + ) + } + } + } yield resp + } + private val showEditOrganisationForm: AuthedRoutes[Account, F] = AuthedRoutes.of { case ar @ GET -> Root / "user" / "settings" / "organisations" / UsernamePathParameter( organisationName @@ -414,7 +515,7 @@ possibleOwners, Option(s"~$organisationName - edit"), user - )(formData) + )(formData.withDefaultValue(Chain.empty)) ) } } @@ -422,6 +523,6 @@ } val protectedRoutes = - showCreateOrganisationForm <+> createOrganisation <+> showEditOrganisationForm <+> deleteOrganisation <+> editOrganisation + showCreateOrganisationForm <+> createOrganisation <+> showEditOrganisationAdminsForm <+> showEditOrganisationForm <+> deleteOrganisation <+> editOrganisationAdmins <+> editOrganisation } diff -rN -u old-smederee/modules/hub/src/main/scala/de/smederee/hub/ResetPasswordForm.scala new-smederee/modules/hub/src/main/scala/de/smederee/hub/ResetPasswordForm.scala --- old-smederee/modules/hub/src/main/scala/de/smederee/hub/ResetPasswordForm.scala 2025-01-11 12:08:45.253424761 +0000 +++ new-smederee/modules/hub/src/main/scala/de/smederee/hub/ResetPasswordForm.scala 2025-01-11 12:08:45.269424789 +0000 @@ -33,11 +33,13 @@ object ResetPasswordForm extends FormValidator[ResetPasswordForm] { val fieldEmail: FormField = FormField("email") - override def validate(data: Map[String, String]): ValidatedNec[FormErrors, ResetPasswordForm] = { + override def validate(data: Map[String, Chain[String]]): ValidatedNec[FormErrors, ResetPasswordForm] = { val email: ValidatedNec[FormErrors, EmailAddress] = data .get(fieldEmail) - .fold(FormFieldError("No email address given!").invalidNec)(s => - EmailAddress.from(s).fold(FormFieldError("Invalid email address!").invalidNec)(_.validNec) + .fold(FormFieldError("No email address given!").invalidNec)( + _.headOption + .flatMap(EmailAddress.from) + .fold(FormFieldError("Invalid email address!").invalidNec)(_.validNec) ) .leftMap(es => NonEmptyChain.of(Map(fieldEmail -> es.toList))) email.map(ResetPasswordForm.apply) diff -rN -u old-smederee/modules/hub/src/main/scala/de/smederee/hub/ResetPasswordRoutes.scala new-smederee/modules/hub/src/main/scala/de/smederee/hub/ResetPasswordRoutes.scala --- old-smederee/modules/hub/src/main/scala/de/smederee/hub/ResetPasswordRoutes.scala 2025-01-11 12:08:45.253424761 +0000 +++ new-smederee/modules/hub/src/main/scala/de/smederee/hub/ResetPasswordRoutes.scala 2025-01-11 12:08:45.269424789 +0000 @@ -65,23 +65,15 @@ case req @ POST -> Root / "forgot-password" / "request-email" => req.decodeStrict[F, UrlForm] { urlForm => for { - csrf <- Sync[F].delay(req.getCsrfToken) - formData <- Sync[F].delay { - urlForm.values.map { t => - val (key, values) = t - ( - key, - values.headOption.getOrElse("") - ) // Pick the first value (a field might get submitted multiple times)! - } - } - form <- Sync[F].delay(ResetPasswordForm.validate(formData)) + csrf <- Sync[F].delay(req.getCsrfToken) + formData <- Sync[F].delay(urlForm.values) + form <- Sync[F].delay(ResetPasswordForm.validate(formData)) response <- form match { case Validated.Invalid(es) => BadRequest( views.html .reset()(resetPath, csrf, title = "Smederee - Reset your account password".some)( - formData, + formData.withDefaultValue(Chain.empty), FormErrors.fromNec(es) ) ) @@ -157,16 +149,8 @@ case req @ POST -> Root / "forgot-password" / "change-password" / ResetTokenPathParameter(token) => req.decodeStrict[F, UrlForm] { urlForm => for { - csrf <- Sync[F].delay(req.getCsrfToken) - formData <- Sync[F].delay { - urlForm.values.map { t => - val (key, values) = t - ( - key, - values.headOption.getOrElse("") - ) // Pick the first value (a field might get submitted multiple times)! - } - } + csrf <- Sync[F].delay(req.getCsrfToken) + formData <- Sync[F].delay(urlForm.values) form <- Sync[F].delay( ChangePasswordForm .validate(formData) @@ -191,7 +175,7 @@ title = "Smederee - Change your password.".some, token )( - formData, + formData.withDefaultValue(Chain.empty), FormErrors.fromNec(es) ) ) diff -rN -u old-smederee/modules/hub/src/main/scala/de/smederee/hub/SignupForm.scala new-smederee/modules/hub/src/main/scala/de/smederee/hub/SignupForm.scala --- old-smederee/modules/hub/src/main/scala/de/smederee/hub/SignupForm.scala 2025-01-11 12:08:45.253424761 +0000 +++ new-smederee/modules/hub/src/main/scala/de/smederee/hub/SignupForm.scala 2025-01-11 12:08:45.269424789 +0000 @@ -41,22 +41,27 @@ val fieldName: FormField = FormField("name") val fieldEmail: FormField = FormField("email") val fieldPassword: FormField = FormField("password") - override def validate(data: Map[String, String]): ValidatedNec[FormErrors, SignupForm] = { + + override def validate(data: Map[String, Chain[String]]): ValidatedNec[FormErrors, SignupForm] = { val email: ValidatedNec[FormErrors, EmailAddress] = data .get(fieldEmail) - .fold(FormFieldError("No email address given!").invalidNec)(s => - EmailAddress.from(s).fold(FormFieldError("Invalid email address!").invalidNec)(_.validNec) + .fold(FormFieldError("No email address given!").invalidNec)( + _.headOption + .flatMap(EmailAddress.from) + .fold(FormFieldError("Invalid email address!").invalidNec)(_.validNec) ) .leftMap(es => NonEmptyChain.of(Map(fieldEmail -> es.toList))) val name: ValidatedNec[FormErrors, Username] = data .get(fieldName) - .fold(FormFieldError("No username given!").invalidNec)(s => - Username.from(s).fold(FormFieldError("Invalid username!").invalidNec)(_.validNec) + .fold(FormFieldError("No username given!").invalidNec)( + _.headOption.flatMap(Username.from).fold(FormFieldError("Invalid username!").invalidNec)(_.validNec) ) .leftMap(es => NonEmptyChain.of(Map(fieldName -> es.toList))) val password: ValidatedNec[FormErrors, Password] = data .get(fieldPassword) - .fold("No password given!".invalidNec)(Password.validate) + .fold("No password given!".invalidNec)( + _.headOption.fold("No password given!".invalidNec)(Password.validate) + ) .leftMap(es => NonEmptyChain.of(Map(fieldPassword -> es.toList.map(FormFieldError.apply)))) (email, name, password).mapN { case (validEmail, validName, validPassword) => SignupForm(validName, validEmail, validPassword) diff -rN -u old-smederee/modules/hub/src/main/scala/de/smederee/hub/SignupRoutes.scala new-smederee/modules/hub/src/main/scala/de/smederee/hub/SignupRoutes.scala --- old-smederee/modules/hub/src/main/scala/de/smederee/hub/SignupRoutes.scala 2025-01-11 12:08:45.253424761 +0000 +++ new-smederee/modules/hub/src/main/scala/de/smederee/hub/SignupRoutes.scala 2025-01-11 12:08:45.269424789 +0000 @@ -55,17 +55,9 @@ private val parseSignUpForm = HttpRoutes.of[F] { case request @ POST -> Root / "signup" => request.decodeStrict[F, UrlForm] { urlForm => for { - csrf <- Sync[F].delay(request.getCsrfToken) - formData <- Sync[F].delay { - urlForm.values.map { t => - val (key, values) = t - ( - key, - values.headOption.getOrElse("") - ) // Pick the first value (a field might get submitted multiple times)! - } - } - form <- Sync[F].delay(SignupForm.validate(formData)) + csrf <- Sync[F].delay(request.getCsrfToken) + formData <- Sync[F].delay(urlForm.values) + form <- Sync[F].delay(SignupForm.validate(formData)) // Only check for existing email or username if the first validation was successful. // TODO: Find a way to remove the nesting of `Validated[Validated[...]]` to also remove the nested pattern match later on. checkExisting <- form.traverse { signupForm => @@ -92,7 +84,7 @@ BadRequest( views.html .signup()(signupUri, csrf, "Smederee - Sign up for an account".some)( - formData, + formData.withDefaultValue(Chain.empty), FormErrors.fromNec(es) ) ) @@ -102,7 +94,7 @@ BadRequest( views.html .signup()(signupUri, csrf, "Smederee - Sign up for an account".some)( - formData, + formData.withDefaultValue(Chain.empty), FormErrors.fromNec(es) ) ) diff -rN -u old-smederee/modules/hub/src/main/scala/de/smederee/hub/UrlFormHelpers.scala new-smederee/modules/hub/src/main/scala/de/smederee/hub/UrlFormHelpers.scala --- old-smederee/modules/hub/src/main/scala/de/smederee/hub/UrlFormHelpers.scala 1970-01-01 00:00:00.000000000 +0000 +++ new-smederee/modules/hub/src/main/scala/de/smederee/hub/UrlFormHelpers.scala 2025-01-11 12:08:45.269424789 +0000 @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2022 Contributors as noted in the AUTHORS.md file + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package de.smederee.hub + +import cats.data.Chain +import org.http4s.UrlForm + +object UrlFormHelpers { + object instances { + extension (urlForm: UrlForm) { + + /** Return the values of the given [[org.http4s.UrlForm]] without empty strings. + * + * This function is useful if HTML forms are submitted and values may contain empty strings which are not + * desired. To avoid filtering these out upon every use this function can be used. + * + * ```scala + * Map( + * "a" -> Chain("an a"), + * "b" -> Chain("a b", "another b"), + * "empty-chain" -> Chain(), + * "empty-chain-value" -> Chain(""), + * "some-empty-chain-values" -> Chain("not-empty", "", "also not empty"), + * "all-empty" -> Chain("", "") + * ) // will be transformed to: + * Map( + * "a" -> Chain("an a"), + * "b" -> Chain("a b", "another b"), + * "some-empty-chain-values" -> Chain("not-empty", "also not empty") + * ) + * ``` + * + * @return + * The stringified map of the form values with every key removed that doesn't contain other values than + * an empty string and also every empty string inside a chain of values is removed too. + */ + def valuesWithoutEmptyStrings: Map[String, Chain[String]] = + urlForm.values + .map((key, values) => (key, values.filter(_.nonEmpty))) + .filter((_, values) => values.nonEmpty) + } + } +} diff -rN -u old-smederee/modules/hub/src/main/scala/de/smederee/hub/VcsRepositoryRoutes.scala new-smederee/modules/hub/src/main/scala/de/smederee/hub/VcsRepositoryRoutes.scala --- old-smederee/modules/hub/src/main/scala/de/smederee/hub/VcsRepositoryRoutes.scala 2025-01-11 12:08:45.253424761 +0000 +++ new-smederee/modules/hub/src/main/scala/de/smederee/hub/VcsRepositoryRoutes.scala 2025-01-11 12:08:45.269424789 +0000 @@ -320,6 +320,8 @@ uri"user/settings/organisations".addSegment(s"~${org.name.toString}") ) ) + possibleOrgaAdmins <- organisation.map(_.oid).traverse(id => orgRepo.getAdministrators(id).compile.toList) + organisationAdmins = user.map(u => List(u)).getOrElse(Nil) ::: possibleOrgaAdmins.getOrElse(Nil) resp <- (owner, organisation) match { case (Some(owner), organisation) => loadRepos(owner).compile.toList.flatMap { repos => @@ -329,7 +331,7 @@ csrf, s"Smederee/~$repositoriesOwnerName".some, user - )(repos, repositoriesOwnerName, organisation, organisationActionBaseUri) + )(repos, repositoriesOwnerName, organisation, organisationActionBaseUri, organisationAdmins) ) } case (None, Some(organisation)) => @@ -339,7 +341,7 @@ csrf, s"Smederee/~$repositoriesOwnerName".some, user - )(Nil, repositoriesOwnerName, organisation.some, organisationActionBaseUri) + )(Nil, repositoriesOwnerName, organisation.some, organisationActionBaseUri, organisationAdmins) ) case _ => NotFound( @@ -1192,16 +1194,8 @@ "An unvalidated account is not allowed to create a repository!" ) // FIXME: Proper error handling! ) - formData <- Sync[F].delay { - urlForm.values.map { t => - val (key, values) = t - ( - key, - values.headOption.getOrElse("") - ) // Pick the first value (a field might get submitted multiple times)! - } - } - form <- Sync[F].delay(NewVcsRepositoryForm.validate(formData)) + formData <- Sync[F].delay(urlForm.values) + form <- Sync[F].delay(NewVcsRepositoryForm.validate(formData)) resp <- form match { case Validated.Invalid(es) => BadRequest( @@ -1212,7 +1206,7 @@ "Smederee - Create a new repository".some, user )( - formData, + formData.withDefaultValue(Chain.empty), FormErrors.fromNec(es) ) ) @@ -1392,16 +1386,8 @@ ) ) ) - formData <- Sync[F].delay { - urlForm.values.map { t => - val (key, values) = t - ( - key, - values.headOption.getOrElse("") - ) // Pick the first value (a field might get submitted multiple times)! - } - } - form <- Sync[F].delay(EditVcsRepositoryForm.validate(formData)) + formData <- Sync[F].delay(urlForm.values) + form <- Sync[F].delay(EditVcsRepositoryForm.validate(formData)) resp <- form match { case Validated.Invalid(errors) => BadRequest( @@ -1413,7 +1399,7 @@ user, repo, branches - )(formData, FormErrors.fromNec(errors)) + )(formData.withDefaultValue(Chain.empty), FormErrors.fromNec(errors)) ) case Validated.Valid(updatedVcsRepositoryForm) => val updatedRepo = repo.copy( @@ -1604,7 +1590,7 @@ user, repo, branches - )(formData) + )(formData.withDefaultValue(Chain.empty)) ) case _ => NotFound() } diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/account/settingsOrganisations.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/account/settingsOrganisations.scala.html --- old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/account/settingsOrganisations.scala.html 2025-01-11 12:08:45.257424768 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/account/settingsOrganisations.scala.html 2025-01-11 12:08:45.273424796 +0000 @@ -1,7 +1,18 @@ @import de.smederee.hub.* @import de.smederee.hub.views.html.* -@(baseUri: Uri = Uri(path = Uri.Path.Root), lang: LanguageCode = LanguageCode("en"))(csrf: Option[CsrfToken] = None, title: Option[String] = None, user: Account)(actionBaseUri: Uri, organisations: List[Organisation], organisationActionBaseUri: Uri) +@( + baseUri: Uri = Uri(path = Uri.Path.Root), + lang: LanguageCode = LanguageCode("en") +)( + csrf: Option[CsrfToken] = None, + title: Option[String] = None, + user: Account +)( + actionBaseUri: Uri, + organisations: List[Organisation], + organisationActionBaseUri: Uri +) @main(baseUri, lang)()(csrf, title, user.some) { @defining(lang.toLocale) { implicit locale => <div class="content"> diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/account/settings.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/account/settings.scala.html --- old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/account/settings.scala.html 2025-01-11 12:08:45.257424768 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/account/settings.scala.html 2025-01-11 12:08:45.273424796 +0000 @@ -1,8 +1,20 @@ @import java.util.Locale -@import de.smederee.hub._ -@import de.smederee.hub.views.html._ +@import de.smederee.hub.* +@import de.smederee.hub.views.html.* -@(baseUri: Uri = Uri(path = Uri.Path.Root), lang: LanguageCode = LanguageCode("en"))(csrf: Option[CsrfToken] = None, title: Option[String] = None, user: Account)(actionBaseUri: Uri, deleteAction: Uri, languageAction: Uri, validateAction: Uri) +@( + baseUri: Uri = Uri(path = Uri.Path.Root), + lang: LanguageCode = LanguageCode("en") +)( + csrf: Option[CsrfToken] = None, + title: Option[String] = None, + user: Account +)( + actionBaseUri: Uri, + deleteAction: Uri, + languageAction: Uri, + validateAction: Uri +) @main(baseUri, lang)()(csrf, title, user.some) { @defining(lang.toLocale) { implicit locale => <div class="content"> diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/account/sshSettings.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/account/sshSettings.scala.html --- old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/account/sshSettings.scala.html 2025-01-11 12:08:45.257424768 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/account/sshSettings.scala.html 2025-01-11 12:08:45.273424796 +0000 @@ -1,11 +1,26 @@ -@import de.smederee.hub.AddPublicSshKeyForm._ -@import de.smederee.hub._ -@import de.smederee.hub.forms.types._ +@import de.smederee.hub.AddPublicSshKeyForm.* +@import de.smederee.hub.* +@import de.smederee.hub.forms.types.* @import de.smederee.ssh.PublicSshKey -@import de.smederee.hub.views.html._ +@import de.smederee.hub.views.html.* @import de.smederee.hub.views.html.forms.renderFormErrors -@(baseUri: Uri = Uri(path = Uri.Path.Root), lang: LanguageCode = LanguageCode("en"))(csrf: Option[CsrfToken] = None, title: Option[String] = None, user: Account)(actionBaseUri: Uri, addAction: Uri, deleteAction: Uri, keys: List[PublicSshKey])(formData: Map[String, String] = Map.empty, formErrors: FormErrors = FormErrors.empty) +@( + baseUri: Uri = Uri(path = Uri.Path.Root), + lang: LanguageCode = LanguageCode("en") +)( + csrf: Option[CsrfToken] = None, + title: Option[String] = None, + user: Account +)( + actionBaseUri: Uri, + addAction: Uri, + deleteAction: Uri, + keys: List[PublicSshKey] +)( + formData: Map[String, Chain[String]] = Map.empty.withDefaultValue(Chain.empty), + formErrors: FormErrors = FormErrors.empty +) @main(baseUri, lang)()(csrf, title, user.some) { @defining(lang.toLocale) { implicit locale => <div class="content"> @@ -46,13 +61,13 @@ <fieldset class="pure-group"> <div class="pure-control-group"> <label for="@{fieldName}">@Messages("form.ssh.add.name")</label> - <input class="pure-input-1" id="@{fieldName}" name="@{fieldName}" maxlength="256" type="text" value="@{formData.get(fieldName)}"> + <input class="pure-input-1" id="@{fieldName}" name="@{fieldName}" maxlength="256" type="text" value="@{formData(fieldName).headOption}"> <span class="pure-form-message" id="@{fieldName}.help">@Messages("form.ssh.add.name.help")</span> @renderFormErrors(fieldName, formErrors) </div> <div class="pure-control-group"> <label for="@{fieldKey}">@Messages("form.ssh.add.key")</label> - <textarea class="pure-input-1" id="@{fieldKey}" name="@{fieldKey}" placeholder="@Messages("form.ssh.add.key.placeholder")" maxlength="4096" rows="8" required="">@{formData.get(fieldKey)}</textarea> + <textarea class="pure-input-1" id="@{fieldKey}" name="@{fieldKey}" placeholder="@Messages("form.ssh.add.key.placeholder")" maxlength="4096" rows="8" required="">@{formData(fieldKey).headOption}</textarea> <span class="pure-form-message" id="@{fieldKey}.help"><strong>@Messages("form.ssh.add.key.help")</strong></span> @renderFormErrors(fieldKey, formErrors) </div> diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/changePassword.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/changePassword.scala.html --- old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/changePassword.scala.html 2025-01-11 12:08:45.253424761 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/changePassword.scala.html 2025-01-11 12:08:45.269424789 +0000 @@ -1,9 +1,21 @@ -@import de.smederee.hub._ -@import de.smederee.hub.forms.types._ +@import de.smederee.hub.* +@import de.smederee.hub.forms.types.* @import de.smederee.hub.views.html.forms.renderFormErrors -@import ChangePasswordForm._ +@import ChangePasswordForm.* -@(baseUri: Uri = Uri(path = Uri.Path.Root), lang: LanguageCode = LanguageCode("en"), tags: MetaTags = MetaTags.default)(action: Uri, csrf: Option[CsrfToken] = None, title: Option[String] = None, token: ResetToken)(formData: Map[String, String] = Map.empty, formErrors: FormErrors = FormErrors.empty) +@( + baseUri: Uri = Uri(path = Uri.Path.Root), + lang: LanguageCode = LanguageCode("en"), + tags: MetaTags = MetaTags.default +)( + action: Uri, + csrf: Option[CsrfToken] = None, + title: Option[String] = None, + token: ResetToken +)( + formData: Map[String, Chain[String]] = Map.empty.withDefaultValue(Chain.empty), + formErrors: FormErrors = FormErrors.empty +) @main(baseUri, lang, tags)()(csrf, title, user = None) { @defining(lang.toLocale) { implicit locale => <div class="content"> @@ -26,7 +38,7 @@ <fieldset id="change-password-data"> <div class="pure-control-group"> <label for="@{fieldName}">@Messages("form.change-password.username")</label> - <input class="pure-input-1-2" id="@{fieldName}" name="@{fieldName}" placeholder="@Messages("form.change-password.username.placeholder")" maxlength="31" required="" type="text" value="@{formData.get(fieldName)}" autocomplete="username" autofocus> + <input class="pure-input-1-2" id="@{fieldName}" name="@{fieldName}" placeholder="@Messages("form.change-password.username.placeholder")" maxlength="31" required="" type="text" value="@{formData(fieldName).headOption}" autocomplete="username" autofocus> <small class="pure-form-message" id="@{fieldName}-help">@Messages("form.change-password.username.help")</small> @renderFormErrors(fieldName, formErrors) </div> diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/contact.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/contact.scala.html --- old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/contact.scala.html 2025-01-11 12:08:45.253424761 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/contact.scala.html 2025-01-11 12:08:45.269424789 +0000 @@ -1,8 +1,10 @@ -@import de.smederee.hub._ +@import de.smederee.hub.* -@(baseUri: Uri = Uri(path = Uri.Path.Root), +@( + baseUri: Uri = Uri(path = Uri.Path.Root), lang: LanguageCode = LanguageCode("en") -)(csrf: Option[CsrfToken] = None, +)( + csrf: Option[CsrfToken] = None, title: Option[String] = None, user: Option[Account] = None ) diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/createOrganisation.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/createOrganisation.scala.html --- old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/createOrganisation.scala.html 2025-01-11 12:08:45.253424761 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/createOrganisation.scala.html 2025-01-11 12:08:45.269424789 +0000 @@ -3,7 +3,18 @@ @import de.smederee.hub.forms.types.* @import de.smederee.hub.views.html.forms.* -@(baseUri: Uri = Uri(path = Uri.Path.Root), lang: LanguageCode = LanguageCode("en"))(action: Uri, csrf: Option[CsrfToken] = None, possibleOwners: List[Account], title: Option[String] = None, user: Account)(formData: Map[String, String] = Map.empty, formErrors: FormErrors = FormErrors.empty) +@( + baseUri: Uri = Uri(path = Uri.Path.Root), + lang: LanguageCode = LanguageCode("en") +)( + action: Uri, csrf: Option[CsrfToken] = None, + possibleOwners: List[Account], + title: Option[String] = None, + user: Account +)( + formData: Map[String, Chain[String]] = Map.empty.withDefaultValue(Chain.empty), + formErrors: FormErrors = FormErrors.empty +) @main(baseUri, lang)()(csrf, title, user.some) { @defining(lang.toLocale) { implicit locale => <div class="content"> diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/createRepository.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/createRepository.scala.html --- old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/createRepository.scala.html 2025-01-11 12:08:45.253424761 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/createRepository.scala.html 2025-01-11 12:08:45.269424789 +0000 @@ -1,9 +1,20 @@ -@import de.smederee.hub._ -@import de.smederee.hub.forms.types._ -@import NewVcsRepositoryForm._ -@import de.smederee.hub.views.html.forms.renderFormErrors +@import de.smederee.hub.* +@import de.smederee.hub.forms.types.* +@import de.smederee.hub.views.html.forms.* +@import NewVcsRepositoryForm.* -@(baseUri: Uri = Uri(path = Uri.Path.Root), lang: LanguageCode = LanguageCode("en"))(action: Uri, csrf: Option[CsrfToken] = None, title: Option[String] = None, user: Account)(formData: Map[String, String] = Map.empty, formErrors: FormErrors = FormErrors.empty) +@( + baseUri: Uri = Uri(path = Uri.Path.Root), + lang: LanguageCode = LanguageCode("en") +)( + action: Uri, + csrf: Option[CsrfToken] = None, + title: Option[String] = None, + user: Account +)( + formData: Map[String, Chain[String]] = Map.empty.withDefaultValue(Chain.empty), + formErrors: FormErrors = FormErrors.empty +) @main(baseUri, lang)()(csrf, title, user.some) { @defining(lang.toLocale) { implicit locale => <div class="content"> @@ -26,31 +37,29 @@ <fieldset id="repository-data"> <div class="pure-control-group"> <label for="@{fieldName}">@Messages("form.create-repo.name")</label> - <input class="pure-input-1-2" id="@{fieldName}" name="@{fieldName}" placeholder="@Messages("form.create-repo.name.placeholder")" maxlength="64" required="" type="text" value="@{formData.get(fieldName)}" autofocus> + <input class="pure-input-1-2" id="@{fieldName}" name="@{fieldName}" placeholder="@Messages("form.create-repo.name.placeholder")" maxlength="64" required="" type="text" value="@{formData(fieldName).headOption}" autofocus> <small class="pure-form-message" id="@{fieldName}.help">@Messages("form.create-repo.name.help")</small> @renderFormErrors(fieldName, formErrors) </div> <div class="pure-control-group"> - <label for="@{fieldIsPrivate}">@Messages("form.create-repo.is-private")</label> - <input id="@{fieldIsPrivate}" name="@{fieldIsPrivate}" type="checkbox" value="true" @if(formData.get(fieldIsPrivate).map(_ === "true").getOrElse(false)){ checked="" } else { }> + @renderBooleanCheckbox(Messages("form.create-repo.is-private"), fieldIsPrivate, formData) <span class="pure-form-message-inline" id="@{fieldIsPrivate}.help">@Messages("form.create-repo.is-private.help")</span> @renderFormErrors(fieldIsPrivate, formErrors) </div> <div class="pure-control-group"> - <label for="@{fieldTicketsEnabled}">@Messages("form.create-repo.tickets-enabled")</label> - <input id="@{fieldTicketsEnabled}" name="@{fieldTicketsEnabled}" type="checkbox" value="true" @if(formData.get(fieldTicketsEnabled).map(_ === "true").getOrElse(false)){ checked="" } else { }> + @renderBooleanCheckbox(Messages("form.create-repo.tickets-enabled"), fieldTicketsEnabled, formData) <span class="pure-form-message-inline" id="@{fieldTicketsEnabled}.help">@Messages("form.create-repo.tickets-enabled.help")</span> @renderFormErrors(fieldTicketsEnabled, formErrors) </div> <div class="pure-control-group"> <label for="@{fieldDescription}">@Messages("form.create-repo.description")</label> - <textarea class="pure-input-1-2" id="@{fieldDescription}" name="@{fieldDescription}" placeholder="@Messages("form.create-repo.description.placeholder")" maxlength="254" rows="3">@{formData.get(fieldDescription)}</textarea> + <textarea class="pure-input-1-2" id="@{fieldDescription}" name="@{fieldDescription}" placeholder="@Messages("form.create-repo.description.placeholder")" maxlength="254" rows="3">@{formData(fieldDescription).headOption}</textarea> <span class="pure-form-message" id="@{fieldDescription}.help">@Messages("form.create-repo.description.help")</span> @renderFormErrors(fieldDescription, formErrors) </div> <div class="pure-control-group"> <label for="@{fieldWebsite}">@Messages("form.create-repo.website")</label> - <input id="@{fieldWebsite}" name="@{fieldWebsite}" maxlength="128" placeholder="https://example.com" type="text" value="@{formData.get(fieldWebsite)}"> + <input id="@{fieldWebsite}" name="@{fieldWebsite}" maxlength="128" placeholder="https://example.com" type="text" value="@{formData(fieldWebsite).headOption}"> <span class="pure-form-message" id="@{fieldWebsite}.help">@Messages("form.create-repo.website.help")</span> @renderFormErrors(fieldWebsite, formErrors) </div> diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/deleteRepository.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/deleteRepository.scala.html --- old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/deleteRepository.scala.html 2025-01-11 12:08:45.253424761 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/deleteRepository.scala.html 2025-01-11 12:08:45.269424789 +0000 @@ -1,8 +1,10 @@ -@import de.smederee.hub._ +@import de.smederee.hub.* -@(baseUri: Uri = Uri(path = Uri.Path.Root), +@( + baseUri: Uri = Uri(path = Uri.Path.Root), lang: LanguageCode = LanguageCode("en") -)(deleteAction: Uri, +)( + deleteAction: Uri, repositoryBaseUri: Uri, csrf: Option[CsrfToken] = None, title: Option[String] = None, diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/editOrganisationAdmins.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/editOrganisationAdmins.scala.html --- old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/editOrganisationAdmins.scala.html 1970-01-01 00:00:00.000000000 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/editOrganisationAdmins.scala.html 2025-01-11 12:08:45.269424789 +0000 @@ -0,0 +1,65 @@ +@import de.smederee.hub.* +@import de.smederee.hub.OrganisationAdminsForm.* +@import de.smederee.hub.forms.types.* +@import de.smederee.hub.views.html.forms.* + +@( + baseUri: Uri = Uri(path = Uri.Path.Root), + lang: LanguageCode = LanguageCode("en") +)( + editAction: Uri, + csrf: Option[CsrfToken] = None, + admins: List[Account], + title: Option[String] = None, + user: Account +)( + formData: Map[String, Chain[String]] = Map.empty.withDefaultValue(Chain.empty), + formErrors: FormErrors = FormErrors.empty +) +@main(baseUri, lang)()(csrf, title, user.some) { +@defining(lang.toLocale) { implicit locale => + <div class="content"> + <div class="pure-g"> + <div class="pure-u-1-1 pure-u-md-1-1"> + <div class="l-box"> + <div class="form-errors"> + @formErrors.get(fieldGlobal).map { es => + @for(error <- es) { + <p class="alert alert-error"> + <span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span> + <span class="sr-only">Fehler:</span> + @error + </p> + } + } + </div> + <div class="organisation-form"> + <form action="@editAction" method="POST" accept-charset="UTF-8" class="pure-form pure-form-aligned"> + <fieldset id="organisation-add-admin"> + <div class="pure-control-group"> + <label for="@fieldNewAdminName">@Messages("form.organisation.admins.add.name")</label> + <input class="pure-input-1-2" id="@fieldNewAdminName" name="@fieldNewAdminName" placeholder="@Messages("form.organisation.admins.add.name.placeholder")" maxlength="31" type="text" value="@{formData(fieldNewAdminName).headOption}" autofocus> + <small class="pure-form-message" id="@{fieldNewAdminName}.help">@Messages("form.organisation.admins.add.name.help")</small> + @renderFormErrors(fieldNewAdminName, formErrors) + </div> + </fieldset> + <fieldset id="organisation-admins"> + <p class="alert alert-warning">@Messages("form.organisation.admins.delete.notice")</p> + @for(admin <- admins) { + <label for="remove-admin-@{admin.name}" class="pure-checkbox"> + <input type="checkbox" id="remove-admin-@{admin.name}" name="@fieldAdminsToDelete" value="@{admin.name}" /> @{admin.name} @{admin.fullName.map(fn => "(" + fn + ")")} + </label> + } + </fieldset> + @csrfToken(csrf) + <div class="pure-controls"> + <button type="submit" class="pure-button">@Messages("form.organisation.admins.button.edit.submit")</button> + </div> + </form> + </div> + </div> + </div> + </div> + </div> +} +} diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/editOrganisation.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/editOrganisation.scala.html --- old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/editOrganisation.scala.html 2025-01-11 12:08:45.253424761 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/editOrganisation.scala.html 2025-01-11 12:08:45.269424789 +0000 @@ -3,7 +3,20 @@ @import de.smederee.hub.forms.types.* @import de.smederee.hub.views.html.forms.* -@(baseUri: Uri = Uri(path = Uri.Path.Root), lang: LanguageCode = LanguageCode("en"))(editAction: Uri, deleteAction: Option[Uri], csrf: Option[CsrfToken] = None, possibleOwners: List[Account], title: Option[String] = None, user: Account)(formData: Map[String, String] = Map.empty, formErrors: FormErrors = FormErrors.empty) +@( + baseUri: Uri = Uri(path = Uri.Path.Root), + lang: LanguageCode = LanguageCode("en") +)( + editAction: Uri, + deleteAction: Option[Uri], + csrf: Option[CsrfToken] = None, + possibleOwners: List[Account], + title: Option[String] = None, + user: Account +)( + formData: Map[String, Chain[String]] = Map.empty.withDefaultValue(Chain.empty), + formErrors: FormErrors = FormErrors.empty +) @main(baseUri, lang)()(csrf, title, user.some) { @defining(lang.toLocale) { implicit locale => <div class="content"> @@ -41,7 +54,7 @@ <div class="l-box"> <div class="organisation-delete-form"> <h4>@Messages("form.organisation.delete.title")</h4> - @defining(formData.get(fieldName)) { organisationName => + @for(organisationName <- formData(fieldName).headOption) { <form action="@deleteAction" class="pure-form pure-form-aligned" method="POST" accept-charset="UTF-8"> <fieldset> <p class="alert alert-error"> diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/editRepository.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/editRepository.scala.html --- old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/editRepository.scala.html 2025-01-11 12:08:45.253424761 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/editRepository.scala.html 2025-01-11 12:08:45.269424789 +0000 @@ -1,18 +1,21 @@ -@import de.smederee.hub._ -@import de.smederee.hub.forms.types._ -@import EditVcsRepositoryForm._ -@import de.smederee.hub.views.html.forms.renderFormErrors +@import de.smederee.hub.* +@import de.smederee.hub.forms.types.* +@import de.smederee.hub.views.html.forms.* +@import EditVcsRepositoryForm.* -@(baseUri: Uri = Uri(path = Uri.Path.Root), +@( + baseUri: Uri = Uri(path = Uri.Path.Root), lang: LanguageCode = LanguageCode("en") -)(action: Uri, +)( + action: Uri, csrf: Option[CsrfToken] = None, repositoryBaseUri: Uri, title: Option[String] = None, user: Account, vcsRepository: VcsRepository, vcsRepositoryBranches: List[(Username, VcsRepositoryName)] -)(formData: Map[String, String] = Map.empty, +)( + formData: Map[String, Chain[String]] = Map.empty.withDefaultValue(Chain.empty), formErrors: FormErrors = FormErrors.empty ) @main(baseUri, lang)()(csrf, title, user.some) { @@ -48,31 +51,29 @@ <fieldset id="repository-data"> <div class="pure-control-group"> <label for="@{fieldName}">@Messages("form.edit-repo.name")</label> - <input class="pure-input-1-2" id="@{fieldName}" name="@{fieldName}" maxlength="64" readonly="" required="" type="text" value="@{formData.get(fieldName)}"> + <input class="pure-input-1-2" id="@{fieldName}" name="@{fieldName}" maxlength="64" readonly="" required="" type="text" value="@{formData(fieldName).headOption}"> <small class="pure-form-message" id="@{fieldName}.help">@Messages("form.edit-repo.name.help")</small> @renderFormErrors(fieldName, formErrors) </div> <div class="pure-control-group"> - <label for="@{fieldIsPrivate}">@Messages("form.edit-repo.is-private")</label> - <input id="@{fieldIsPrivate}" name="@{fieldIsPrivate}" type="checkbox" value="true" @if(formData.get(fieldIsPrivate).map(_ === "true").getOrElse(false)){ checked="" } else { }> + @renderBooleanCheckbox(Messages("form.edit-repo.is-private"), fieldIsPrivate, formData) <span class="pure-form-message-inline" id="@{fieldIsPrivate}.help">@Messages("form.edit-repo.is-private.help")</span> @renderFormErrors(fieldIsPrivate, formErrors) </div> <div class="pure-control-group"> - <label for="@{fieldTicketsEnabled}">@Messages("form.create-repo.tickets-enabled")</label> - <input id="@{fieldTicketsEnabled}" name="@{fieldTicketsEnabled}" type="checkbox" value="true" @if(formData.get(fieldTicketsEnabled).map(_ === "true").getOrElse(false)){ checked="" } else { }> + @renderBooleanCheckbox(Messages("form.create-repo.tickets-enabled"), fieldTicketsEnabled, formData) <span class="pure-form-message-inline" id="@{fieldTicketsEnabled}.help">@Messages("form.create-repo.tickets-enabled.help")</span> @renderFormErrors(fieldTicketsEnabled, formErrors) </div> <div class="pure-control-group"> <label for="@{fieldDescription}">@Messages("form.edit-repo.description")</label> - <textarea class="pure-input-1-2" id="@{fieldDescription}" name="@{fieldDescription}" maxlength="254" rows="3">@{formData.get(fieldDescription)}</textarea> + <textarea class="pure-input-1-2" id="@{fieldDescription}" name="@{fieldDescription}" maxlength="254" rows="3">@{formData(fieldDescription).headOption}</textarea> <span class="pure-form-message" id="@{fieldDescription}.help">@Messages("form.edit-repo.description.help")</span> @renderFormErrors(fieldDescription, formErrors) </div> <div class="pure-control-group"> <label for="@{fieldWebsite}">@Messages("form.edit-repo.website")</label> - <input id="@{fieldWebsite}" name="@{fieldWebsite}" maxlength="128" placeholder="https://example.com" type="text" value="@{formData.get(fieldWebsite)}"> + <input id="@{fieldWebsite}" name="@{fieldWebsite}" maxlength="128" placeholder="https://example.com" type="text" value="@{formData(fieldWebsite).headOption}"> <span class="pure-form-message" id="@{fieldWebsite}.help">@Messages("form.edit-repo.website.help")</span> @renderFormErrors(fieldWebsite, formErrors) </div> diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/emails/reset.scala.txt new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/emails/reset.scala.txt --- old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/emails/reset.scala.txt 2025-01-11 12:08:45.257424768 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/emails/reset.scala.txt 2025-01-11 12:08:45.273424796 +0000 @@ -1,4 +1,4 @@ -@import de.smederee.hub._ +@import de.smederee.hub.* @(user: Account, resetUri: Uri) Hello, diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/emails/validate.scala.txt new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/emails/validate.scala.txt --- old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/emails/validate.scala.txt 2025-01-11 12:08:45.257424768 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/emails/validate.scala.txt 2025-01-11 12:08:45.273424796 +0000 @@ -1,5 +1,9 @@ -@import de.smederee.hub._ -@(user: Account, validationToken: ValidationToken, validationBaseUri: Uri) +@import de.smederee.hub.* +@( + user: Account, + validationToken: ValidationToken, + validationBaseUri: Uri +) Hello @{user.name}, you have registered an account at the Smederee (https://smeder.ee) and to diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/errors/csrfFailed.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/errors/csrfFailed.scala.html --- old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/errors/csrfFailed.scala.html 2025-01-11 12:08:45.257424768 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/errors/csrfFailed.scala.html 2025-01-11 12:08:45.273424796 +0000 @@ -1,7 +1,11 @@ -@import de.smederee.hub._ -@import de.smederee.hub.views.html._ +@import de.smederee.hub.* +@import de.smederee.hub.views.html.* -@(baseUri: Uri = Uri(path = Uri.Path.Root), lang: LanguageCode = LanguageCode("en"), tags: MetaTags = MetaTags.default) +@( + baseUri: Uri = Uri(path = Uri.Path.Root), + lang: LanguageCode = LanguageCode("en"), + tags: MetaTags = MetaTags.default +) @defining(lang.toLocale) { implicit locale => <!DOCTYPE html> <html lang="@lang"> diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/errors/internalServerError.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/errors/internalServerError.scala.html --- old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/errors/internalServerError.scala.html 2025-01-11 12:08:45.257424768 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/errors/internalServerError.scala.html 2025-01-11 12:08:45.273424796 +0000 @@ -1,7 +1,13 @@ -@import de.smederee.hub._ -@import de.smederee.hub.views.html._ +@import de.smederee.hub.* +@import de.smederee.hub.views.html.* -@(baseUri: Uri = Uri(path = Uri.Path.Root), lang: LanguageCode = LanguageCode("en"))(csrf: Option[CsrfToken] = None, user: Option[Account]) +@( + baseUri: Uri = Uri(path = Uri.Path.Root), + lang: LanguageCode = LanguageCode("en") +)( + csrf: Option[CsrfToken] = None, + user: Option[Account] +) @defining(lang.toLocale) { implicit locale => @main(baseUri, lang)()(csrf, Messages("errors.internal-server-error.title").some, user) { <div class="content"> diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/errors/unvalidatedAccount.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/errors/unvalidatedAccount.scala.html --- old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/errors/unvalidatedAccount.scala.html 2025-01-11 12:08:45.261424775 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/errors/unvalidatedAccount.scala.html 2025-01-11 12:08:45.273424796 +0000 @@ -1,7 +1,14 @@ -@import de.smederee.hub._ -@import de.smederee.hub.views.html._ +@import de.smederee.hub.* +@import de.smederee.hub.views.html.* -@(baseUri: Uri = Uri(path = Uri.Path.Root), lang: LanguageCode = LanguageCode("en"))(csrf: Option[CsrfToken] = None, title: Option[String] = None, user: Account) +@( + baseUri: Uri = Uri(path = Uri.Path.Root), + lang: LanguageCode = LanguageCode("en") +)( + csrf: Option[CsrfToken] = None, + title: Option[String] = None, + user: Account +) @main(baseUri, lang)()(csrf, title, user.some) { @defining(lang.toLocale) { implicit locale => <div class="content"> diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/errors/userOrOrganisationNotFound.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/errors/userOrOrganisationNotFound.scala.html --- old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/errors/userOrOrganisationNotFound.scala.html 2025-01-11 12:08:45.261424775 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/errors/userOrOrganisationNotFound.scala.html 2025-01-11 12:08:45.273424796 +0000 @@ -1,7 +1,16 @@ @import de.smederee.hub.* @import de.smederee.hub.views.html.* -@(baseUri: Uri = Uri(path = Uri.Path.Root), lang: LanguageCode = LanguageCode("en"))(csrf: Option[CsrfToken] = None, title: Option[String] = None, user: Option[Account])(repositoriesOwner: Username) +@( + baseUri: Uri = Uri(path = Uri.Path.Root), + lang: LanguageCode = LanguageCode("en") +)( + csrf: Option[CsrfToken] = None, + title: Option[String] = None, + user: Option[Account] +)( + repositoriesOwner: Username +) @main(baseUri, lang)()(csrf, title, user) { @defining(lang.toLocale) { implicit locale => <div class="content"> diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/format/formatDate.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/format/formatDate.scala.html --- old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/format/formatDate.scala.html 2025-01-11 12:08:45.261424775 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/format/formatDate.scala.html 2025-01-11 12:08:45.273424796 +0000 @@ -1,6 +1,11 @@ -@import java.time._ -@import java.time.format._ +@import java.time.* +@import java.time.format.* @import java.util.Locale -@(date: LocalDate, style: FormatStyle = FormatStyle.MEDIUM)(implicit locale: Locale) +@( + date: LocalDate, + style: FormatStyle = FormatStyle.MEDIUM +)( + implicit locale: Locale +) (@DateTimeFormatter.ofLocalizedDate(style).withLocale(locale).format(date)) diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/forms/organisationFormFields.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/forms/organisationFormFields.scala.html --- old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/forms/organisationFormFields.scala.html 2025-01-11 12:08:45.261424775 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/forms/organisationFormFields.scala.html 2025-01-11 12:08:45.273424796 +0000 @@ -2,20 +2,27 @@ @import de.smederee.hub.OrganisationForm.* @import de.smederee.hub.forms.types.* -@(possibleOwners: List[Account])(formData: Map[String, String] = Map.empty, formErrors: FormErrors = FormErrors.empty)(implicit locale: java.util.Locale) +@( + possibleOwners: List[Account] +)( + formData: Map[String, Chain[String]] = Map.empty.withDefaultValue(Chain.empty), + formErrors: FormErrors = FormErrors.empty +)( + implicit locale: java.util.Locale +) <div class="pure-control-group"> <label for="@{fieldName}">@Messages("form.organisation.name")</label> - <input class="pure-input-1-2" id="@{fieldName}" name="@{fieldName}" placeholder="@Messages("form.organisation.name.placeholder")" maxlength="31" required="" type="text" value="@{formData.get(fieldName)}" autofocus> + <input class="pure-input-1-2" id="@{fieldName}" name="@{fieldName}" placeholder="@Messages("form.organisation.name.placeholder")" maxlength="31" required="" type="text" value="@{formData(fieldName).headOption}" autofocus> <small class="pure-form-message" id="@{fieldName}.help">@Messages("form.organisation.name.help")</small> @renderFormErrors(fieldName, formErrors) </div> -@if(formData.get(fieldOwner).nonEmpty && possibleOwners.nonEmpty) { +@if(formData(fieldOwner).headOption.nonEmpty && possibleOwners.nonEmpty) { <div class="pure-control-group"> <label for="@{fieldOwner}">@Messages("form.organisation.owner")</label> <select class="pure-input-1-2" id="@fieldOwner" name="@fieldOwner"> @for(owner <- possibleOwners) { @defining(owner.fullName.map(fullName => s"($fullName)")) { fullName => - <option value="@owner.uid" @if(formData.get(fieldOwner).exists(_ === owner.uid.toString)){selected}else{}>@owner.name @fullName</option> + <option value="@owner.uid" @if(formData(fieldOwner).headOption.exists(_ === owner.uid.toString)){selected}else{}>@owner.name @fullName</option> } } </select> @@ -25,25 +32,24 @@ }else{} <div class="pure-control-group"> <label for="@{fieldFullName}">@Messages("form.organisation.full-name")</label> - <input class="pure-input-1-2" id="@{fieldFullName}" name="@{fieldFullName}" maxlength="128" placeholder="@Messages("form.organisation.full-name.placeholder")" type="text" value="@{formData.get(fieldFullName)}"> + <input class="pure-input-1-2" id="@{fieldFullName}" name="@{fieldFullName}" maxlength="128" placeholder="@Messages("form.organisation.full-name.placeholder")" type="text" value="@{formData(fieldFullName).headOption}"> <span class="pure-form-message" id="@{fieldFullName}.help">@Messages("form.organisation.full-name.help")</span> @renderFormErrors(fieldFullName, formErrors) </div> <div class="pure-control-group"> - <label for="@{fieldIsPrivate}">@Messages("form.organisation.is-private")</label> - <input id="@{fieldIsPrivate}" name="@{fieldIsPrivate}" type="checkbox" value="true" @if(formData.get(fieldIsPrivate).map(_ === "true").getOrElse(false)){ checked="" } else { }> + @renderBooleanCheckbox(Messages("form.organisation.is-private"), fieldIsPrivate, formData) <span class="pure-form-message-inline" id="@{fieldIsPrivate}.help">@Messages("form.organisation.is-private.help")</span> @renderFormErrors(fieldIsPrivate, formErrors) </div> <div class="pure-control-group"> <label for="@{fieldDescription}">@Messages("form.organisation.description")</label> - <textarea class="pure-input-1-2" id="@{fieldDescription}" name="@{fieldDescription}" placeholder="@Messages("form.organisation.description.placeholder")" maxlength="254" rows="3">@{formData.get(fieldDescription)}</textarea> + <textarea class="pure-input-1-2" id="@{fieldDescription}" name="@{fieldDescription}" placeholder="@Messages("form.organisation.description.placeholder")" maxlength="254" rows="3">@{formData(fieldDescription).headOption}</textarea> <span class="pure-form-message" id="@{fieldDescription}.help">@Messages("form.organisation.description.help")</span> @renderFormErrors(fieldDescription, formErrors) </div> <div class="pure-control-group"> <label for="@{fieldWebsite}">@Messages("form.organisation.website")</label> - <input class="pure-input-1-3" id="@{fieldWebsite}" name="@{fieldWebsite}" maxlength="128" placeholder="https://example.com" type="text" value="@{formData.get(fieldWebsite)}"> + <input class="pure-input-1-3" id="@{fieldWebsite}" name="@{fieldWebsite}" maxlength="128" placeholder="https://example.com" type="text" value="@{formData(fieldWebsite).headOption}"> <span class="pure-form-message" id="@{fieldWebsite}.help">@Messages("form.organisation.website.help")</span> @renderFormErrors(fieldWebsite, formErrors) </div> diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/forms/renderBooleanCheckbox.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/forms/renderBooleanCheckbox.scala.html --- old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/forms/renderBooleanCheckbox.scala.html 1970-01-01 00:00:00.000000000 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/forms/renderBooleanCheckbox.scala.html 2025-01-11 12:08:45.273424796 +0000 @@ -0,0 +1,9 @@ +@import de.smederee.hub.forms.types.* + +@( + fieldLabel: String, + fieldName: FormField, + formData: Map[String, Chain[String]] = Map.empty.withDefaultValue(Chain.empty) +) +<label for="@fieldName">@fieldLabel</label> +<input id="@fieldName" name="@fieldName" type="checkbox" value="true" @if(formData(fieldName).headOption.exists(_ === "true")){ checked="" } else { }> diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/forms/renderFormErrors.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/forms/renderFormErrors.scala.html --- old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/forms/renderFormErrors.scala.html 2025-01-11 12:08:45.261424775 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/forms/renderFormErrors.scala.html 2025-01-11 12:08:45.273424796 +0000 @@ -1,4 +1,4 @@ -@import de.smederee.hub.forms.types._ +@import de.smederee.hub.forms.types.* @(field: FormField, errors: FormErrors) @errors.get(field).map { fieldErrors => diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/icon.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/icon.scala.html --- old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/icon.scala.html 2025-01-11 12:08:45.253424761 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/icon.scala.html 2025-01-11 12:08:45.269424789 +0000 @@ -1,4 +1,9 @@ -@(baseUri: Uri = Uri(path = Uri.Path.Root))(icon: String, overrideSize: Option[Int] = None) +@( + baseUri: Uri = Uri(path = Uri.Path.Root) +)( + icon: String, + overrideSize: Option[Int] = None +) @defining(overrideSize.map(size => s"""style="height: ${size}px; width: ${size}px;"""").getOrElse("")) { sizeOverride => <span class="feather-icon" aria-hidden="true"><svg class="feather-svg" @Html(sizeOverride)><use href="@{baseUri.addPath("assets/feather/4.29.0/feather-sprite.svg").withFragment(icon)}"/></svg></span> } diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/imprint.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/imprint.scala.html --- old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/imprint.scala.html 2025-01-11 12:08:45.253424761 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/imprint.scala.html 2025-01-11 12:08:45.269424789 +0000 @@ -1,8 +1,10 @@ -@import de.smederee.hub._ +@import de.smederee.hub.* -@(baseUri: Uri = Uri(path = Uri.Path.Root), +@( + baseUri: Uri = Uri(path = Uri.Path.Root), lang: LanguageCode = LanguageCode("en") -)(csrf: Option[CsrfToken] = None, +)( + csrf: Option[CsrfToken] = None, title: Option[String] = None, user: Option[Account] = None ) diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/index.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/index.scala.html --- old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/index.scala.html 2025-01-11 12:08:45.253424761 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/index.scala.html 2025-01-11 12:08:45.269424789 +0000 @@ -1,12 +1,15 @@ -@import de.smederee.hub._ -@import SignupForm._ +@import de.smederee.hub.* +@import SignupForm.* -@(baseUri: Uri = Uri(path = Uri.Path.Root), +@( + baseUri: Uri = Uri(path = Uri.Path.Root), lang: LanguageCode = LanguageCode("en"), tags: MetaTags = MetaTags.empty -)(customFooters: Html = Html(""), +)( + customFooters: Html = Html(""), customHeaders: Html = Html("") -)(actionSignup: Uri, +)( + actionSignup: Uri, csrf: Option[CsrfToken] = None, title: Option[String] = None, user: Option[Account] = None diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/login.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/login.scala.html --- old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/login.scala.html 2025-01-11 12:08:45.257424768 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/login.scala.html 2025-01-11 12:08:45.273424796 +0000 @@ -1,9 +1,20 @@ -@import de.smederee.hub._ -@import de.smederee.hub.forms.types._ -@import LoginForm._ +@import de.smederee.hub.* +@import de.smederee.hub.forms.types.* +@import LoginForm.* @import de.smederee.hub.views.html.forms.renderFormErrors -@(baseUri: Uri = Uri(path = Uri.Path.Root), lang: LanguageCode = LanguageCode("en"))(action: Uri, csrf: Option[CsrfToken] = None, resetPasswordUri: Uri, title: Option[String] = None)(formData: Map[String, String] = Map.empty, formErrors: FormErrors = FormErrors.empty) +@( + baseUri: Uri = Uri(path = Uri.Path.Root), + lang: LanguageCode = LanguageCode("en") +)( + action: Uri, + csrf: Option[CsrfToken] = None, + resetPasswordUri: Uri, + title: Option[String] = None +)( + formData: Map[String, Chain[String]] = Map.empty.withDefaultValue(Chain.empty), + formErrors: FormErrors = FormErrors.empty +) @main(baseUri, lang)()(csrf, title, user = None) { @defining(lang.toLocale) { implicit locale => <div class="content"> @@ -26,7 +37,7 @@ <fieldset id="login-data"> <div class="pure-control-group"> <label for="@{fieldName}">@Messages("form.login.username")</label> - <input class="pure-input-1-2" id="@{fieldName}" name="@{fieldName}" placeholder="@Messages("form.login.username.placeholder")" maxlength="31" required="" type="text" value="@{formData.get(fieldName)}" autocomplete="username" autofocus> + <input class="pure-input-1-2" id="@{fieldName}" name="@{fieldName}" placeholder="@Messages("form.login.username.placeholder")" maxlength="31" required="" type="text" value="@{formData(fieldName).headOption}" autocomplete="username" autofocus> @renderFormErrors(fieldName, formErrors) </div> <div class="pure-control-group"> diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/main.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/main.scala.html --- old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/main.scala.html 2025-01-11 12:08:45.257424768 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/main.scala.html 2025-01-11 12:08:45.273424796 +0000 @@ -1,14 +1,19 @@ @import de.smederee.hub.Account -@(baseUri: Uri = Uri(path = Uri.Path.Root), +@( + baseUri: Uri = Uri(path = Uri.Path.Root), lang: LanguageCode = LanguageCode("en"), tags: MetaTags = MetaTags.default -)(customFooters: Html = Html(""), +)( + customFooters: Html = Html(""), customHeaders: Html = Html("") -)(csrf: Option[CsrfToken], +)( + csrf: Option[CsrfToken], title: Option[String], user: Option[Account] -)(content: Html) +)( + content: Html +) @defining(lang.toLocale) { implicit locale => <!DOCTYPE html> <html lang="@lang"> diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/navbar.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/navbar.scala.html --- old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/navbar.scala.html 2025-01-11 12:08:45.257424768 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/navbar.scala.html 2025-01-11 12:08:45.273424796 +0000 @@ -1,6 +1,13 @@ -@import de.smederee.hub._ +@import de.smederee.hub.* -@(baseUri: Uri, lang: LanguageCode)(csrf: Option[CsrfToken] = None, extraCss: Option[String] = None, user: Option[Account] = None) +@( + baseUri: Uri, + lang: LanguageCode +)( + csrf: Option[CsrfToken] = None, + extraCss: Option[String] = None, + user: Option[Account] = None +) @defining(lang.toLocale) { implicit locale => <nav class="home-menu pure-menu pure-menu-horizontal pure-menu-scrollable @extraCss"> <a class="logo pure-menu-heading" href="@baseUri">@Messages("global.navbar.top.logo")</a> diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/privacyPolicy.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/privacyPolicy.scala.html --- old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/privacyPolicy.scala.html 2025-01-11 12:08:45.257424768 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/privacyPolicy.scala.html 2025-01-11 12:08:45.273424796 +0000 @@ -1,8 +1,10 @@ -@import de.smederee.hub._ +@import de.smederee.hub.* -@(baseUri: Uri = Uri(path = Uri.Path.Root), +@( + baseUri: Uri = Uri(path = Uri.Path.Root), lang: LanguageCode = LanguageCode("en") -)(csrf: Option[CsrfToken] = None, +)( + csrf: Option[CsrfToken] = None, title: Option[String] = None, user: Option[Account] = None ) diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/publicAlpha.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/publicAlpha.scala.html --- old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/publicAlpha.scala.html 2025-01-11 12:08:45.257424768 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/publicAlpha.scala.html 2025-01-11 12:08:45.273424796 +0000 @@ -1,8 +1,10 @@ -@import de.smederee.hub._ +@import de.smederee.hub.* -@(baseUri: Uri = Uri(path = Uri.Path.Root), +@( + baseUri: Uri = Uri(path = Uri.Path.Root), lang: LanguageCode = LanguageCode("en") -)(csrf: Option[CsrfToken] = None, +)( + csrf: Option[CsrfToken] = None, title: Option[String] = None, user: Option[Account] = None ) diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/repositoryPatchMetadata.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/repositoryPatchMetadata.scala.html --- old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/repositoryPatchMetadata.scala.html 2025-01-11 12:08:45.257424768 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/repositoryPatchMetadata.scala.html 2025-01-11 12:08:45.273424796 +0000 @@ -1,6 +1,12 @@ -@import de.smederee.hub._ +@import de.smederee.hub.* -@(actionBaseUri: Option[Uri], patch: VcsRepositoryPatchMetadata, linkTitle: Boolean = true)(implicit locale: java.util.Locale) +@( + actionBaseUri: Option[Uri], + patch: VcsRepositoryPatchMetadata, + linkTitle: Boolean = true +)( + implicit locale: java.util.Locale +) <div class="patch"> <div class="patch-details"> <span class="timestamp">@Messages("repository.overview.latest-changes.timestamp", java.util.Date.from(patch.timestamp.toInstant))</span> - <span class="author">@patch.author.withoutEmail</span> - <span class="patch-hash">@patch.hash</span> diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/reset.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/reset.scala.html --- old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/reset.scala.html 2025-01-11 12:08:45.257424768 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/reset.scala.html 2025-01-11 12:08:45.273424796 +0000 @@ -1,9 +1,19 @@ -@import de.smederee.hub._ -@import de.smederee.hub.forms.types._ +@import de.smederee.hub.* +@import de.smederee.hub.forms.types.* @import de.smederee.hub.views.html.forms.renderFormErrors -@import ResetPasswordForm._ +@import ResetPasswordForm.* -@(baseUri: Uri = Uri(path = Uri.Path.Root), lang: LanguageCode = LanguageCode("en"))(action: Uri, csrf: Option[CsrfToken] = None, title: Option[String] = None)(formData: Map[String, String] = Map.empty, formErrors: FormErrors = FormErrors.empty) +@( + baseUri: Uri = Uri(path = Uri.Path.Root), + lang: LanguageCode = LanguageCode("en") +)( + action: Uri, + csrf: Option[CsrfToken] = None, + title: Option[String] = None +)( + formData: Map[String, Chain[String]] = Map.empty.withDefaultValue(Chain.empty), + formErrors: FormErrors = FormErrors.empty +) @main(baseUri, lang)()(csrf, title, user = None) { @defining(lang.toLocale) { implicit locale => <div class="content"> @@ -26,7 +36,7 @@ <fieldset id="reset-password-data"> <div class="pure-control-group"> <label for="@{fieldEmail}">Email address</label> - <input class="pure-input-1-2" id="@{fieldEmail}" name="@{fieldEmail}" placeholder="some@@somewhere.org" maxlength="128" required="" type="email" value="@{formData.get(fieldEmail)}" autocomplete="email"> + <input class="pure-input-1-2" id="@{fieldEmail}" name="@{fieldEmail}" placeholder="some@@somewhere.org" maxlength="128" required="" type="email" value="@{formData(fieldEmail).headOption}" autocomplete="email"> <small class="pure-form-message" id="reset-password-help">@Messages("form.reset-password.help")</small> @renderFormErrors(fieldEmail, formErrors) </div> diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/resetSent.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/resetSent.scala.html --- old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/resetSent.scala.html 2025-01-11 12:08:45.257424768 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/resetSent.scala.html 2025-01-11 12:08:45.273424796 +0000 @@ -1,9 +1,15 @@ -@import de.smederee.hub._ -@import de.smederee.hub.forms.types._ +@import de.smederee.hub.* +@import de.smederee.hub.forms.types.* @import de.smederee.hub.views.html.forms.renderFormErrors -@import ResetPasswordForm._ +@import ResetPasswordForm.* -@(baseUri: Uri = Uri(path = Uri.Path.Root), lang: LanguageCode = LanguageCode("en"))(csrf: Option[CsrfToken] = None, title: Option[String] = None) +@( + baseUri: Uri = Uri(path = Uri.Path.Root), + lang: LanguageCode = LanguageCode("en") +)( + csrf: Option[CsrfToken] = None, + title: Option[String] = None +) @main(baseUri, lang)()(csrf, title, user = None) { @defining(lang.toLocale) { implicit locale => <div class="content"> diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showAllRepositories.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showAllRepositories.scala.html --- old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showAllRepositories.scala.html 2025-01-11 12:08:45.257424768 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showAllRepositories.scala.html 2025-01-11 12:08:45.273424796 +0000 @@ -1,6 +1,16 @@ -@import de.smederee.hub._ +@import de.smederee.hub.* -@(baseUri: Uri = Uri(path = Uri.Path.Root), lang: LanguageCode = LanguageCode("en"))(actionBaseUri: Uri, csrf: Option[CsrfToken] = None, title: Option[String] = None, user: Option[Account])(listing: List[VcsRepository]) +@( + baseUri: Uri = Uri(path = Uri.Path.Root), + lang: LanguageCode = LanguageCode("en") +)( + actionBaseUri: Uri, + csrf: Option[CsrfToken] = None, + title: Option[String] = None, + user: Option[Account] +)( + listing: List[VcsRepository] +) @main(baseUri, lang)()(csrf, title, user) { @defining(lang.toLocale) { implicit locale => <div class="content"> diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showRepositories.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showRepositories.scala.html --- old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showRepositories.scala.html 2025-01-11 12:08:45.257424768 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showRepositories.scala.html 2025-01-11 12:08:45.273424796 +0000 @@ -1,7 +1,21 @@ @import de.smederee.hub.* @import de.smederee.security.Username -@(baseUri: Uri = Uri(path = Uri.Path.Root), lang: LanguageCode = LanguageCode("en"))(actionBaseUri: Uri, csrf: Option[CsrfToken] = None, title: Option[String] = None, user: Option[Account])(listing: List[VcsRepository], repositoriesOwner: Username, organisation: Option[Organisation], organisationActionBaseUri: Option[Uri]) +@( + baseUri: Uri = Uri(path = Uri.Path.Root), + lang: LanguageCode = LanguageCode("en") +)( + actionBaseUri: Uri, + csrf: Option[CsrfToken] = None, + title: Option[String] = None, + user: Option[Account] +)( + listing: List[VcsRepository], + repositoriesOwner: Username, + organisation: Option[Organisation], + organisationActionBaseUri: Option[Uri], + organisationAdmins: List[Account] +) @main(baseUri, lang)()(csrf, title, user) { @defining(lang.toLocale) { implicit locale => <div class="content"> @@ -12,7 +26,7 @@ </div> </div> <div class="pure-u-1-5 pure-u-md-1-5"> - @if(user.exists(user => organisation.exists(_.owner === user.uid))) { + @if(user.exists(user => organisationAdmins.exists(_.uid === user.uid))) { @defining(organisationActionBaseUri.map(_.addSegment("edit"))) { orgEditUri => <div class="l-box"> <a href="@orgEditUri">@Messages("organisation.menu.edit")</a> diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showRepositoryBranches.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showRepositoryBranches.scala.html --- old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showRepositoryBranches.scala.html 2025-01-11 12:08:45.257424768 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showRepositoryBranches.scala.html 2025-01-11 12:08:45.273424796 +0000 @@ -1,13 +1,16 @@ -@import de.smederee.hub._ +@import de.smederee.hub.* -@(baseUri: Uri, +@( + baseUri: Uri, lang: LanguageCode = LanguageCode("en") -)(actionBaseUri: Uri, +)( + actionBaseUri: Uri, csrf: Option[CsrfToken] = None, linkToTicketService: Option[Uri] = None, title: Option[String] = None, user: Option[Account] -)(vcsRepository: VcsRepository, +)( + vcsRepository: VcsRepository, vcsRepositoryBranches: List[(Username, VcsRepositoryName)] ) @main(baseUri, lang)()(csrf, title, user) { diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showRepositoryFiles.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showRepositoryFiles.scala.html --- old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showRepositoryFiles.scala.html 2025-01-11 12:08:45.257424768 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showRepositoryFiles.scala.html 2025-01-11 12:08:45.273424796 +0000 @@ -3,15 +3,18 @@ @import de.smederee.html.ToDoTextCssMapping.* @import de.smederee.hub.* -@(baseUri: Uri, +@( + baseUri: Uri, lang: LanguageCode = LanguageCode("en") -)(actionBaseUri: Uri, +)( + actionBaseUri: Uri, csrf: Option[CsrfToken] = None, goBackUri: Option[Uri] = None, linkToTicketService: Option[Uri] = None, title: Option[String] = None, user: Option[Account] -)(fileContent: List[String], +)( + fileContent: List[String], renderableContent: Option[RenderableContent], listing: IndexedSeq[(os.RelPath, os.StatInfo)], repositoryBaseUri: Uri, diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showRepositoryHistory.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showRepositoryHistory.scala.html --- old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showRepositoryHistory.scala.html 2025-01-11 12:08:45.257424768 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showRepositoryHistory.scala.html 2025-01-11 12:08:45.273424796 +0000 @@ -1,14 +1,17 @@ -@import de.smederee.hub._ +@import de.smederee.hub.* -@(baseUri: Uri, +@( + baseUri: Uri, lang: LanguageCode = LanguageCode("en") -)(actionBaseUri: Uri, +)( + actionBaseUri: Uri, csrf: Option[CsrfToken] = None, goBackUri: Option[Uri] = None, linkToTicketService: Option[Uri] = None, title: Option[String] = None, user: Option[Account] -)(history: List[VcsRepositoryPatchMetadata], +)( + history: List[VcsRepositoryPatchMetadata], nextEntry: Option[Int], repositoryBaseUri: Uri, vcsRepository: VcsRepository, diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showRepositoryMenu.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showRepositoryMenu.scala.html --- old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showRepositoryMenu.scala.html 2025-01-11 12:08:45.257424768 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showRepositoryMenu.scala.html 2025-01-11 12:08:45.273424796 +0000 @@ -1,13 +1,17 @@ -@import de.smederee.hub._ +@import de.smederee.hub.* -@(baseUri: Uri, +@( + baseUri: Uri, linkToTicketService: Option[Uri] = None, -)(activeUri: Option[Uri], +)( + activeUri: Option[Uri], branches: Int, repositoryBaseUri: Uri, user: Option[Account] = None, vcsRepository: VcsRepository -)(implicit locale: java.util.Locale) +)( + implicit locale: java.util.Locale +) <nav class="pure-menu pure-menu-horizontal"> <ul class="pure-menu-list"> @defining(repositoryBaseUri) { uri => diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showRepositoryOverview.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showRepositoryOverview.scala.html --- old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showRepositoryOverview.scala.html 2025-01-11 12:08:45.257424768 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showRepositoryOverview.scala.html 2025-01-11 12:08:45.273424796 +0000 @@ -1,13 +1,16 @@ @import de.smederee.hub.* -@(baseUri: Uri, +@( + baseUri: Uri, lang: LanguageCode = LanguageCode("en") -)(actionBaseUri: Uri, +)( + actionBaseUri: Uri, csrf: Option[CsrfToken] = None, linkToTicketService: Option[Uri] = None, title: Option[String] = None, user: Option[Account] -)(vcsRepository: VcsRepository, +)( + vcsRepository: VcsRepository, vcsRepositoryBranches: List[(Username, VcsRepositoryName)], vcsRepositoryHistory: List[VcsRepositoryPatchMetadata], vcsRepositoryLastTag: Option[VcsRepositoryPatchMetadata], diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showRepositoryPatch.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showRepositoryPatch.scala.html --- old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showRepositoryPatch.scala.html 2025-01-11 12:08:45.257424768 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showRepositoryPatch.scala.html 2025-01-11 12:08:45.273424796 +0000 @@ -1,13 +1,16 @@ -@import de.smederee.hub._ +@import de.smederee.hub.* -@(baseUri: Uri, +@( + baseUri: Uri, lang: LanguageCode = LanguageCode("en") -)(actionBaseUri: Uri, +)( + actionBaseUri: Uri, csrf: Option[CsrfToken] = None, linkToTicketService: Option[Uri] = None, title: Option[String] = None, user: Option[Account] -)(patch: Option[VcsRepositoryPatchMetadata], +)( + patch: Option[VcsRepositoryPatchMetadata], patchDiff: String, vcsRepository: VcsRepository, vcsRepositoryBranches: List[(Username, VcsRepositoryName)], diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/signup.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/signup.scala.html --- old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/signup.scala.html 2025-01-11 12:08:45.257424768 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/signup.scala.html 2025-01-11 12:08:45.273424796 +0000 @@ -1,9 +1,19 @@ -@import de.smederee.hub._ -@import de.smederee.hub.forms.types._ -@import SignupForm._ +@import de.smederee.hub.* +@import de.smederee.hub.forms.types.* +@import SignupForm.* @import de.smederee.hub.views.html.forms.renderFormErrors -@(baseUri: Uri = Uri(path = Uri.Path.Root), lang: LanguageCode = LanguageCode("en"))(action: Uri, csrf: Option[CsrfToken] = None, title: Option[String] = None)(formData: Map[String, String] = Map.empty, formErrors: FormErrors = FormErrors.empty) +@( + baseUri: Uri = Uri(path = Uri.Path.Root), + lang: LanguageCode = LanguageCode("en") +)( + action: Uri, + csrf: Option[CsrfToken] = None, + title: Option[String] = None +)( + formData: Map[String, Chain[String]] = Map.empty.withDefaultValue(Chain.empty), + formErrors: FormErrors = FormErrors.empty +) @main(baseUri, lang)()(csrf, title, user = None) { @defining(lang.toLocale) { implicit locale => <div class="content"> @@ -26,13 +36,13 @@ <fieldset id="signup-data"> <div class="pure-control-group"> <label for="@{fieldName}">@Messages("form.signup.username")</label> - <input class="pure-input-1-2" id="@{fieldName}" name="@{fieldName}" placeholder="@Messages("form.signup.username.placeholder")" maxlength="31" required="" type="text" value="@{formData.get(fieldName)}" autocomplete="username" autofocus> + <input class="pure-input-1-2" id="@{fieldName}" name="@{fieldName}" placeholder="@Messages("form.signup.username.placeholder")" maxlength="31" required="" type="text" value="@{formData(fieldName).headOption}" autocomplete="username" autofocus> <small class="pure-form-message" id="@{fieldName}.help">@Messages("form.signup.username.help")</small> @renderFormErrors(fieldName, formErrors) </div> <div class="pure-control-group"> <label for="@{fieldEmail}">Email address</label> - <input class="pure-input-1-2" id="@{fieldEmail}" name="@{fieldEmail}" placeholder="some@@somewhere.org" maxlength="128" required="" type="email" value="@{formData.get(fieldEmail)}" autocomplete="email"> + <input class="pure-input-1-2" id="@{fieldEmail}" name="@{fieldEmail}" placeholder="some@@somewhere.org" maxlength="128" required="" type="email" value="@{formData(fieldEmail).headOption}" autocomplete="email"> <small class="pure-form-message" id="@{fieldEmail}.help">Please enter your email address.</small> @renderFormErrors(fieldEmail, formErrors) </div> diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/termsOfUse.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/termsOfUse.scala.html --- old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/termsOfUse.scala.html 2025-01-11 12:08:45.257424768 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/termsOfUse.scala.html 2025-01-11 12:08:45.273424796 +0000 @@ -1,8 +1,10 @@ -@import de.smederee.hub._ +@import de.smederee.hub.* -@(baseUri: Uri = Uri(path = Uri.Path.Root), +@( + baseUri: Uri = Uri(path = Uri.Path.Root), lang: LanguageCode = LanguageCode("en") -)(csrf: Option[CsrfToken] = None, +)( + csrf: Option[CsrfToken] = None, title: Option[String] = None, user: Option[Account] = None ) diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/welcome.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/welcome.scala.html --- old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/welcome.scala.html 2025-01-11 12:08:45.257424768 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/welcome.scala.html 2025-01-11 12:08:45.273424796 +0000 @@ -1,6 +1,12 @@ -@import de.smederee.hub._ +@import de.smederee.hub.* -@(baseUri: Uri, lang: LanguageCode = LanguageCode("en"))(csrf: Option[CsrfToken] = None, title: Option[String] = None) +@( + baseUri: Uri, + lang: LanguageCode = LanguageCode("en") +)( + csrf: Option[CsrfToken] = None, + title: Option[String] = None +) @main(baseUri, lang)()(csrf, title, user = None) { @defining(lang.toLocale) { implicit locale => <div class="content"> diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/createTicket.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/createTicket.scala.html --- old-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/createTicket.scala.html 2025-01-11 12:08:45.261424775 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/createTicket.scala.html 2025-01-11 12:08:45.277424803 +0000 @@ -1,13 +1,15 @@ @import de.smederee.hub.Account -@import de.smederee.tickets.TicketForm._ -@import de.smederee.tickets._ -@import de.smederee.tickets.forms._ -@import de.smederee.tickets.forms.types._ +@import de.smederee.tickets.TicketForm.* +@import de.smederee.tickets.* +@import de.smederee.tickets.forms.* +@import de.smederee.tickets.forms.types.* @import de.smederee.tickets.views.html.forms.renderFormErrors -@(baseUri: Uri = Uri(path = Uri.Path.Root), +@( + baseUri: Uri = Uri(path = Uri.Path.Root), lang: LanguageCode = LanguageCode("en") -)(action: Uri, +)( + action: Uri, csrf: Option[CsrfToken] = None, linkToHubService: Uri, labels: List[Label] = Nil, @@ -16,7 +18,8 @@ title: Option[String] = None, user: Option[Account], project: Project -)(formData: Map[String, Chain[String]] = Map.empty.withDefaultValue(Chain.empty), +)( + formData: Map[String, Chain[String]] = Map.empty.withDefaultValue(Chain.empty), formErrors: FormErrors = FormErrors.empty ) @footer = { diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/editLabel.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/editLabel.scala.html --- old-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/editLabel.scala.html 2025-01-11 12:08:45.261424775 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/editLabel.scala.html 2025-01-11 12:08:45.277424803 +0000 @@ -1,14 +1,16 @@ @import de.smederee.hub.Account -@import de.smederee.tickets.LabelForm._ -@import de.smederee.tickets._ -@import de.smederee.tickets.forms._ -@import de.smederee.tickets.forms.types._ +@import de.smederee.tickets.LabelForm.* +@import de.smederee.tickets.* +@import de.smederee.tickets.forms.* +@import de.smederee.tickets.forms.types.* @import de.smederee.tickets.views.html.forms.renderFormErrors @import de.smederee.tickets.views.html.showProjectMenu -@(baseUri: Uri = Uri(path = Uri.Path.Root), +@( + baseUri: Uri = Uri(path = Uri.Path.Root), lang: LanguageCode = LanguageCode("en") -)(action: Uri, +)( + action: Uri, csrf: Option[CsrfToken] = None, linkToHubService: Uri, label: Label, @@ -16,7 +18,8 @@ title: Option[String] = None, user: Account, project: Project -)(formData: Map[String, Chain[String]] = Map.empty.withDefaultValue(Chain.empty), +)( + formData: Map[String, Chain[String]] = Map.empty.withDefaultValue(Chain.empty), formErrors: FormErrors = FormErrors.empty ) @main(linkToHubService, lang)()(csrf, title, user.some) { diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/editLabels.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/editLabels.scala.html --- old-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/editLabels.scala.html 2025-01-11 12:08:45.261424775 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/editLabels.scala.html 2025-01-11 12:08:45.277424803 +0000 @@ -1,14 +1,16 @@ @import de.smederee.hub.Account -@import de.smederee.tickets.LabelForm._ -@import de.smederee.tickets._ -@import de.smederee.tickets.forms._ -@import de.smederee.tickets.forms.types._ +@import de.smederee.tickets.LabelForm.* +@import de.smederee.tickets.* +@import de.smederee.tickets.forms.* +@import de.smederee.tickets.forms.types.* @import de.smederee.tickets.views.html.forms.renderFormErrors @import de.smederee.tickets.views.html.showProjectMenu -@(baseUri: Uri = Uri(path = Uri.Path.Root), +@( + baseUri: Uri = Uri(path = Uri.Path.Root), lang: LanguageCode = LanguageCode("en") -)(action: Uri, +)( + action: Uri, csrf: Option[CsrfToken] = None, linkToHubService: Uri, labels: List[Label], @@ -16,7 +18,8 @@ title: Option[String] = None, user: Option[Account], project: Project -)(formData: Map[String, Chain[String]] = Map.empty.withDefaultValue(Chain.empty), +)( + formData: Map[String, Chain[String]] = Map.empty.withDefaultValue(Chain.empty), formErrors: FormErrors = FormErrors.empty ) @main(linkToHubService, lang)()(csrf, title, user) { diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/editMilestone.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/editMilestone.scala.html --- old-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/editMilestone.scala.html 2025-01-11 12:08:45.261424775 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/editMilestone.scala.html 2025-01-11 12:08:45.277424803 +0000 @@ -1,14 +1,16 @@ @import de.smederee.hub.Account -@import de.smederee.tickets.MilestoneForm._ -@import de.smederee.tickets._ -@import de.smederee.tickets.forms._ -@import de.smederee.tickets.forms.types._ +@import de.smederee.tickets.MilestoneForm.* +@import de.smederee.tickets.* +@import de.smederee.tickets.forms.* +@import de.smederee.tickets.forms.types.* @import de.smederee.tickets.views.html.forms.renderFormErrors @import de.smederee.tickets.views.html.showProjectMenu -@(baseUri: Uri = Uri(path = Uri.Path.Root), +@( + baseUri: Uri = Uri(path = Uri.Path.Root), lang: LanguageCode = LanguageCode("en") -)(action: Uri, +)( + action: Uri, csrf: Option[CsrfToken] = None, linkToHubService: Uri, milestone: Milestone, @@ -16,7 +18,8 @@ title: Option[String] = None, user: Account, project: Project -)(formData: Map[String, Chain[String]] = Map.empty.withDefaultValue(Chain.empty), +)( + formData: Map[String, Chain[String]] = Map.empty.withDefaultValue(Chain.empty), formErrors: FormErrors = FormErrors.empty ) @main(linkToHubService, lang)()(csrf, title, user.some) { diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/editMilestones.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/editMilestones.scala.html --- old-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/editMilestones.scala.html 2025-01-11 12:08:45.261424775 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/editMilestones.scala.html 2025-01-11 12:08:45.277424803 +0000 @@ -1,16 +1,18 @@ -@import java.time._ +@import java.time.* @import de.smederee.hub.Account -@import de.smederee.tickets.MilestoneForm._ -@import de.smederee.tickets._ -@import de.smederee.tickets.forms._ -@import de.smederee.tickets.forms.types._ +@import de.smederee.tickets.MilestoneForm.* +@import de.smederee.tickets.* +@import de.smederee.tickets.forms.* +@import de.smederee.tickets.forms.types.* @import de.smederee.tickets.views.html.format.formatDate @import de.smederee.tickets.views.html.forms.renderFormErrors @import de.smederee.tickets.views.html.showProjectMenu -@(baseUri: Uri = Uri(path = Uri.Path.Root), +@( + baseUri: Uri = Uri(path = Uri.Path.Root), lang: LanguageCode = LanguageCode("en") -)(action: Uri, +)( + action: Uri, csrf: Option[CsrfToken] = None, linkToHubService: Uri, milestones: List[Milestone], @@ -18,7 +20,8 @@ title: Option[String] = None, user: Option[Account], project: Project -)(formData: Map[String, Chain[String]] = Map.empty.withDefaultValue(Chain.empty), +)( + formData: Map[String, Chain[String]] = Map.empty.withDefaultValue(Chain.empty), formErrors: FormErrors = FormErrors.empty ) @main(linkToHubService, lang)()(csrf, title, user) { diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/editTicket.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/editTicket.scala.html --- old-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/editTicket.scala.html 2025-01-11 12:08:45.261424775 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/editTicket.scala.html 2025-01-11 12:08:45.277424803 +0000 @@ -1,13 +1,15 @@ @import de.smederee.hub.Account -@import de.smederee.tickets.TicketForm._ -@import de.smederee.tickets._ -@import de.smederee.tickets.forms._ -@import de.smederee.tickets.forms.types._ +@import de.smederee.tickets.TicketForm.* +@import de.smederee.tickets.* +@import de.smederee.tickets.forms.* +@import de.smederee.tickets.forms.types.* @import de.smederee.tickets.views.html.forms.renderFormErrors -@(baseUri: Uri = Uri(path = Uri.Path.Root), +@( + baseUri: Uri = Uri(path = Uri.Path.Root), lang: LanguageCode = LanguageCode("en") -)(action: Uri, +)( + action: Uri, csrf: Option[CsrfToken] = None, linkToHubService: Uri, labels: List[Label] = Nil, @@ -17,7 +19,8 @@ title: Option[String] = None, user: Option[Account], project: Project -)(formData: Map[String, Chain[String]], +)( + formData: Map[String, Chain[String]], formErrors: FormErrors = FormErrors.empty ) @footer = { diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/errors/internalServerError.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/errors/internalServerError.scala.html --- old-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/errors/internalServerError.scala.html 2025-01-11 12:08:45.261424775 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/errors/internalServerError.scala.html 2025-01-11 12:08:45.277424803 +0000 @@ -1,7 +1,13 @@ -@import de.smederee.hub._ -@import de.smederee.hub.views.html._ +@import de.smederee.hub.* +@import de.smederee.hub.views.html.* -@(baseUri: Uri = Uri(path = Uri.Path.Root), lang: LanguageCode = LanguageCode("en"))(csrf: Option[CsrfToken] = None, user: Option[Account]) +@( + baseUri: Uri = Uri(path = Uri.Path.Root), + lang: LanguageCode = LanguageCode("en") +)( + csrf: Option[CsrfToken] = None, + user: Option[Account] +) @defining(lang.toLocale) { implicit locale => @main(baseUri, lang)()(csrf, Messages("errors.internal-server-error.title").some, user) { <div class="content"> diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/errors/unvalidatedAccount.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/errors/unvalidatedAccount.scala.html --- old-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/errors/unvalidatedAccount.scala.html 2025-01-11 12:08:45.261424775 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/errors/unvalidatedAccount.scala.html 2025-01-11 12:08:45.277424803 +0000 @@ -1,7 +1,14 @@ -@import de.smederee.hub._ -@import de.smederee.hub.views.html._ +@import de.smederee.hub.* +@import de.smederee.hub.views.html.* -@(baseUri: Uri = Uri(path = Uri.Path.Root), lang: LanguageCode = LanguageCode("en"))(csrf: Option[CsrfToken] = None, title: Option[String] = None, user: Account) +@( + baseUri: Uri = Uri(path = Uri.Path.Root), + lang: LanguageCode = LanguageCode("en") +)( + csrf: Option[CsrfToken] = None, + title: Option[String] = None, + user: Account +) @main(baseUri, lang)()(csrf, title, user.some) { @defining(lang.toLocale) { implicit locale => <div class="content"> diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/format/formatDate.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/format/formatDate.scala.html --- old-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/format/formatDate.scala.html 2025-01-11 12:08:45.261424775 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/format/formatDate.scala.html 2025-01-11 12:08:45.277424803 +0000 @@ -1,6 +1,11 @@ -@import java.time._ -@import java.time.format._ +@import java.time.* +@import java.time.format.* @import java.util.Locale -@(date: LocalDate, style: FormatStyle = FormatStyle.MEDIUM)(implicit locale: Locale) +@( + date: LocalDate, + style: FormatStyle = FormatStyle.MEDIUM +)( + implicit locale: Locale +) @{DateTimeFormatter.ofLocalizedDate(style).withLocale(locale).format(date)} diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/format/formatDateTime.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/format/formatDateTime.scala.html --- old-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/format/formatDateTime.scala.html 2025-01-11 12:08:45.261424775 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/format/formatDateTime.scala.html 2025-01-11 12:08:45.277424803 +0000 @@ -1,6 +1,11 @@ -@import java.time._ -@import java.time.format._ +@import java.time.* +@import java.time.format.* @import java.util.Locale -@(timestamp: OffsetDateTime, style: FormatStyle = FormatStyle.MEDIUM)(implicit locale: Locale) +@( + timestamp: OffsetDateTime, + style: FormatStyle = FormatStyle.MEDIUM +)( + implicit locale: Locale +) @DateTimeFormatter.ofLocalizedDateTime(style).withLocale(locale).format(timestamp) diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/format/formatTicketStatus.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/format/formatTicketStatus.scala.html --- old-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/format/formatTicketStatus.scala.html 2025-01-11 12:08:45.261424775 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/format/formatTicketStatus.scala.html 2025-01-11 12:08:45.277424803 +0000 @@ -1,7 +1,11 @@ @import java.util.Locale @import de.smederee.tickets.Ticket -@(ticket: Ticket)(implicit locale: Locale) +@( + ticket: Ticket +)( + implicit locale: Locale +) @defining(ticket.status.toString.toLowerCase(Locale.ROOT)) { status => @if(ticket.resolution.nonEmpty) { @for(resolution <- ticket.resolution) { diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/format/formatTicketSubmitter.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/format/formatTicketSubmitter.scala.html --- old-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/format/formatTicketSubmitter.scala.html 2025-01-11 12:08:45.261424775 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/format/formatTicketSubmitter.scala.html 2025-01-11 12:08:45.277424803 +0000 @@ -1,7 +1,13 @@ @import java.util.Locale @import de.smederee.tickets.Ticket -@(baseUri: Uri)(ticket: Ticket)(implicit locale: Locale) +@( + baseUri: Uri +)( + ticket: Ticket +)( + implicit locale: Locale +) @for(submitter <- ticket.submitter) { <a href="@baseUri.addSegment(s"~${submitter.name}")">@submitter.name</a> } diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/forms/renderFormErrors.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/forms/renderFormErrors.scala.html --- old-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/forms/renderFormErrors.scala.html 2025-01-11 12:08:45.261424775 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/forms/renderFormErrors.scala.html 2025-01-11 12:08:45.277424803 +0000 @@ -1,4 +1,4 @@ -@import de.smederee.tickets.forms.types._ +@import de.smederee.tickets.forms.types.* @(field: FormField, errors: FormErrors) @errors.get(field).map { fieldErrors => diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/icon.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/icon.scala.html --- old-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/icon.scala.html 2025-01-11 12:08:45.261424775 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/icon.scala.html 2025-01-11 12:08:45.277424803 +0000 @@ -1,4 +1,9 @@ -@(baseUri: Uri = Uri(path = Uri.Path.Root))(icon: String, overrideSize: Option[Int] = None) +@( + baseUri: Uri = Uri(path = Uri.Path.Root) +)( + icon: String, + overrideSize: Option[Int] = None +) @defining(overrideSize.map(size => s"""style="height: ${size}px; width: ${size}px;"""").getOrElse("")) { sizeOverride => <span class="feather-icon" aria-hidden="true"><svg class="feather-svg" @Html(sizeOverride)><use href="@{baseUri.addPath("assets/feather/4.29.0/feather-sprite.svg").withFragment(icon)}"/></svg></span> } diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/main.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/main.scala.html --- old-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/main.scala.html 2025-01-11 12:08:45.261424775 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/main.scala.html 2025-01-11 12:08:45.277424803 +0000 @@ -1,14 +1,19 @@ @import de.smederee.hub.Account -@(baseUri: Uri = Uri(path = Uri.Path.Root), +@( + baseUri: Uri = Uri(path = Uri.Path.Root), lang: LanguageCode = LanguageCode("en"), tags: MetaTags = MetaTags.empty -)(customFooters: Html = Html(""), +)( + customFooters: Html = Html(""), customHeaders: Html = Html("") -)(csrf: Option[CsrfToken], +)( + csrf: Option[CsrfToken], title: Option[String], user: Option[Account] -)(content: Html) +)( + content: Html +) @defining(lang.toLocale) { implicit locale => <!DOCTYPE html> <html lang="@lang"> diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/navbar.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/navbar.scala.html --- old-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/navbar.scala.html 2025-01-11 12:08:45.261424775 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/navbar.scala.html 2025-01-11 12:08:45.277424803 +0000 @@ -1,6 +1,13 @@ @import de.smederee.hub.Account -@(baseUri: Uri, lang: LanguageCode)(csrf: Option[CsrfToken] = None, extraCss: Option[String] = None, user: Option[Account] = None) +@( + baseUri: Uri, + lang: LanguageCode +)( + csrf: Option[CsrfToken] = None, + extraCss: Option[String] = None, + user: Option[Account] = None +) @defining(lang.toLocale) { implicit locale => <nav class="home-menu pure-menu pure-menu-horizontal pure-menu-scrollable @extraCss"> <a class="logo pure-menu-heading" href="@baseUri">@Messages("global.navbar.top.logo")</a> diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/showMilestone.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/showMilestone.scala.html --- old-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/showMilestone.scala.html 2025-01-11 12:08:45.261424775 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/showMilestone.scala.html 2025-01-11 12:08:45.277424803 +0000 @@ -1,15 +1,17 @@ @import de.smederee.hub.Account -@import de.smederee.tickets.MilestoneForm._ -@import de.smederee.tickets._ -@import de.smederee.tickets.forms._ -@import de.smederee.tickets.forms.types._ +@import de.smederee.tickets.MilestoneForm.* +@import de.smederee.tickets.* +@import de.smederee.tickets.forms.* +@import de.smederee.tickets.forms.types.* @import de.smederee.tickets.views.html.forms.renderFormErrors -@import de.smederee.tickets.views.html.format._ +@import de.smederee.tickets.views.html.format.* @import de.smederee.tickets.views.html.showProjectMenu -@(baseUri: Uri = Uri(path = Uri.Path.Root), +@( + baseUri: Uri = Uri(path = Uri.Path.Root), lang: LanguageCode = LanguageCode("en") -)(action: Uri, +)( + action: Uri, csrf: Option[CsrfToken] = None, linkToHubService: Uri, milestone: Milestone, diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/showProjectMenu.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/showProjectMenu.scala.html --- old-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/showProjectMenu.scala.html 2025-01-11 12:08:45.261424775 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/showProjectMenu.scala.html 2025-01-11 12:08:45.277424803 +0000 @@ -1,13 +1,17 @@ @import de.smederee.hub.Account @import de.smederee.tickets.{ Project, ProjectOwnerId } -@(baseUri: Uri, +@( + baseUri: Uri, linkToHubService: Uri -)(activeUri: Option[Uri], +)( + activeUri: Option[Uri], projectBaseUri: Uri, user: Option[Account] = None, project: Project -)(implicit locale: java.util.Locale) +)( + implicit locale: java.util.Locale +) <nav class="pure-menu pure-menu-horizontal"> <ul class="pure-menu-list"> @defining(linkToHubService.addPath(projectBaseUri.path.toString)) { uri => diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/showTicket.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/showTicket.scala.html --- old-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/showTicket.scala.html 2025-01-11 12:08:45.261424775 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/showTicket.scala.html 2025-01-11 12:08:45.277424803 +0000 @@ -1,10 +1,12 @@ @import de.smederee.hub.Account -@import de.smederee.tickets._ -@import de.smederee.tickets.views.html.format._ +@import de.smederee.tickets.* +@import de.smederee.tickets.views.html.format.* -@(baseUri: Uri = Uri(path = Uri.Path.Root), +@( + baseUri: Uri = Uri(path = Uri.Path.Root), lang: LanguageCode = LanguageCode("en") -)(action: Uri, +)( + action: Uri, csrf: Option[CsrfToken] = None, linkToHubService: Uri, labels: List[Label], diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/showTickets.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/showTickets.scala.html --- old-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/showTickets.scala.html 2025-01-11 12:08:45.261424775 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/showTickets.scala.html 2025-01-11 12:08:45.277424803 +0000 @@ -1,10 +1,12 @@ @import de.smederee.hub.Account -@import de.smederee.tickets._ +@import de.smederee.tickets.* @import de.smederee.tickets.views.html.format.formatDateTime -@(baseUri: Uri = Uri(path = Uri.Path.Root), +@( + baseUri: Uri = Uri(path = Uri.Path.Root), lang: LanguageCode = LanguageCode("en") -)(action: Uri, +)( + action: Uri, csrf: Option[CsrfToken] = None, linkToHubService: Uri, tickets: List[Ticket], diff -rN -u old-smederee/modules/hub/src/test/scala/de/smederee/hub/AuthenticationRoutesTest.scala new-smederee/modules/hub/src/test/scala/de/smederee/hub/AuthenticationRoutesTest.scala --- old-smederee/modules/hub/src/test/scala/de/smederee/hub/AuthenticationRoutesTest.scala 2025-01-11 12:08:45.261424775 +0000 +++ new-smederee/modules/hub/src/test/scala/de/smederee/hub/AuthenticationRoutesTest.scala 2025-01-11 12:08:45.277424803 +0000 @@ -19,6 +19,7 @@ import java.nio.charset.StandardCharsets +import cats.data.* import cats.effect.* import cats.syntax.all.* import com.typesafe.config.ConfigFactory @@ -189,7 +190,7 @@ val expectedHtml = views.html.login()(loginPath, None, resetPath, title = "Smederee - Login to your account".some)( - formData = Map(LoginForm.fieldName.toString -> account.name.toString), + formData = Map(LoginForm.fieldName.toString -> Chain(account.name.toString)), Map(LoginForm.fieldGlobal -> List(FormFieldError("Invalid credentials!"))) ) @@ -242,7 +243,7 @@ val expectedHtml = views.html.login()(loginPath, None, resetPath, title = "Smederee - Login to your account".some)( - formData = Map(LoginForm.fieldName.toString -> account.name.toString), + formData = Map(LoginForm.fieldName.toString -> Chain(account.name.toString)), Map(LoginForm.fieldGlobal -> List(FormFieldError("Invalid credentials!"))) ) @@ -292,7 +293,7 @@ val expectedHtml = views.html.login()(loginPath, None, resetPath, title = "Smederee - Login to your account".some)( - formData = Map(LoginForm.fieldName.toString -> account.name.toString), + formData = Map(LoginForm.fieldName.toString -> Chain(account.name.toString)), Map(LoginForm.fieldGlobal -> List(FormFieldError("Invalid credentials!"))) ) diff -rN -u old-smederee/modules/hub/src/test/scala/de/smederee/hub/DoobieOrganisationRepositoryTest.scala new-smederee/modules/hub/src/test/scala/de/smederee/hub/DoobieOrganisationRepositoryTest.scala --- old-smederee/modules/hub/src/test/scala/de/smederee/hub/DoobieOrganisationRepositoryTest.scala 2025-01-11 12:08:45.261424775 +0000 +++ new-smederee/modules/hub/src/test/scala/de/smederee/hub/DoobieOrganisationRepositoryTest.scala 2025-01-11 12:08:45.277424803 +0000 @@ -188,6 +188,80 @@ } } + test("findNewAdminByName must return an unlocked and validated account".tag(NeedsDatabase)) { + genValidAccount.sample match { + case Some(genAccount) => + val account = genAccount.copy(language = None, validatedEmail = true) + val dbConfig = configuration.database + val tx = Transactor.fromDriverManager[IO]( + driver = dbConfig.driver, + url = dbConfig.url, + user = dbConfig.user, + password = dbConfig.pass, + logHandler = None + ) + val repo = new DoobieOrganisationRepository[IO](tx) + val test = for { + _ <- createAccount(account, PasswordHash("I am not a password hash!")) + found <- repo.findNewAdminByName(account.name) + } yield found + test.map { found => + assertEquals(found, account.some) + } + case _ => fail("Could not generate data samples!") + } + } + + test("findNewAdminByName must not return a locked account".tag(NeedsDatabase)) { + genValidAccount.sample match { + case Some(genAccount) => + val account = genAccount.copy(validatedEmail = true) + val token = UnlockToken.generate + val dbConfig = configuration.database + val tx = Transactor.fromDriverManager[IO]( + driver = dbConfig.driver, + url = dbConfig.url, + user = dbConfig.user, + password = dbConfig.pass, + logHandler = None + ) + val repo = new DoobieOrganisationRepository[IO](tx) + val test = for { + _ <- createAccount(account, PasswordHash("I am not a password hash!"), unlockToken = token.some) + found <- repo.findNewAdminByName(account.name) + } yield found + test.map { found => + assertEquals(found, None) + } + case _ => fail("Could not generate data samples!") + } + } + + test("findNewAdminByName must not return an unvalidated account".tag(NeedsDatabase)) { + genValidAccount.sample match { + case Some(genAccount) => + val account = genAccount.copy(validatedEmail = false) + val token = ValidationToken.generate + val dbConfig = configuration.database + val tx = Transactor.fromDriverManager[IO]( + driver = dbConfig.driver, + url = dbConfig.url, + user = dbConfig.user, + password = dbConfig.pass, + logHandler = None + ) + val repo = new DoobieOrganisationRepository[IO](tx) + val test = for { + _ <- createAccount(account, PasswordHash("I am not a password hash!"), validationToken = token.some) + found <- repo.findNewAdminByName(account.name) + } yield found + test.map { found => + assertEquals(found, None) + } + case _ => fail("Could not generate data samples!") + } + } + test("findOwner must return the user account of the owner".tag(NeedsDatabase)) { (genValidAccount.sample, genOrganisation.sample) match { case (Some(account), Some(org)) => diff -rN -u old-smederee/modules/hub/src/test/scala/de/smederee/hub/UrlFormHelpersTest.scala new-smederee/modules/hub/src/test/scala/de/smederee/hub/UrlFormHelpersTest.scala --- old-smederee/modules/hub/src/test/scala/de/smederee/hub/UrlFormHelpersTest.scala 1970-01-01 00:00:00.000000000 +0000 +++ new-smederee/modules/hub/src/test/scala/de/smederee/hub/UrlFormHelpersTest.scala 2025-01-11 12:08:45.277424803 +0000 @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2022 Contributors as noted in the AUTHORS.md file + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package de.smederee.hub + +import cats.data.* +import de.smederee.hub.UrlFormHelpers.instances.* +import org.http4s.UrlForm + +import munit.* + +import org.scalacheck.* +import org.scalacheck.Prop.* + +final class UrlFormHelpersTest extends ScalaCheckSuite { + private val genUrlForm: Gen[UrlForm] = for { + keys <- Gen.listOf(Gen.nonEmptyStringOf(Gen.alphaChar)) + values <- Gen.listOfN(keys.size, Gen.listOf(Gen.alphaNumStr).map(list => Chain.fromSeq(list))) + tuples = keys.zip(values) + } yield UrlForm(tuples.toMap) + + given Arbitrary[UrlForm] = Arbitrary(genUrlForm) + + property("valuesWithoutEmptyStrings must remove all empty strings") { + forAll { (urlForm: UrlForm) => + val expected = urlForm.values + .map((key, values) => (key, values.filter(_.nonEmpty))) + .filter((_, values) => values.nonEmpty) + val obtained = urlForm.valuesWithoutEmptyStrings + assertEquals(obtained, expected) + } + } + + property("valuesWithoutEmptyStrings must be idempotent") { + forAll { (urlForm: UrlForm) => + val firstRun = urlForm.valuesWithoutEmptyStrings + val secondRun = UrlForm(firstRun).valuesWithoutEmptyStrings + assertEquals(firstRun, secondRun) + } + } +}