~jan0sch/smederee
Showing details for patch bb0956671017d8179c52bcb57e64b6902ab947b7.
diff -rN -u old-smederee/modules/hub/src/main/resources/assets/css/main.css new-smederee/modules/hub/src/main/resources/assets/css/main.css --- old-smederee/modules/hub/src/main/resources/assets/css/main.css 2025-01-12 10:03:38.507669896 +0000 +++ new-smederee/modules/hub/src/main/resources/assets/css/main.css 2025-01-12 10:03:38.507669896 +0000 @@ -250,6 +250,11 @@ vertical-align: middle; } +.organisation-delete-form { + border: 1px solid black; + padding: 0px 10px; +} + .overview-latest-changes { font-size: 85%; } 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-12 10:03:38.507669896 +0000 +++ new-smederee/modules/hub/src/main/resources/messages.properties 2025-01-12 10:03:38.507669896 +0000 @@ -41,23 +41,27 @@ form.change-password.username.placeholder=Please enter your username. form.change-password.username=Username form.organisation.button.create.submit=Create organisation +form.organisation.button.delete.submit=Delete this organisation! form.organisation.button.edit.submit=Save changes -form.organisation.name=Name -form.organisation.name.placeholder=Please enter a organisation name. -form.organisation.name.help=It must start with a letter, contain only alphanumeric characters, be at least 2 and at most 31 characters long and be all lowercase. -form.organisation.owner=Owner -form.organisation.owner.help=You may change the primary owner of the organisation here. -form.organisation.full-name=Full Name -form.organisation.full-name.placeholder= +form.organisation.delete.i-am-sure=Yes, I am sure that I want to delete this organisation and all related data! +form.organisation.delete.notice=If you delete an organisation then all related data will be permanently removed. This action CANNOT be undone! +form.organisation.delete.title=Delete this organisation! +form.organisation.description.help=An optional short description of your organisation. +form.organisation.description.placeholder= +form.organisation.description=Description form.organisation.full-name.help=The full name of an organisation is allowed to be more verbose and less restricitive but it must not exceed 128 characters. -form.organisation.is-private=Private Organisation +form.organisation.full-name.placeholder= +form.organisation.full-name=Full Name form.organisation.is-private.help=A private organisation can only be accessed by the owner and accounts which have been given permissions to do so. -form.organisation.description=Description -form.organisation.description.placeholder= -form.organisation.description.help=An optional short description of your organisation. -form.organisation.website=Website -form.organisation.website.placeholder=https://example.com +form.organisation.is-private=Private Organisation +form.organisation.name.help=It must start with a letter, contain only alphanumeric characters, be at least 2 and at most 31 characters long and be all lowercase. +form.organisation.name.placeholder=Please enter a organisation name. +form.organisation.name=Name +form.organisation.owner.help=You may change the primary owner of the organisation here. +form.organisation.owner=Owner form.organisation.website.help=An optional URI pointing to a website. +form.organisation.website.placeholder=https://example.com +form.organisation.website=Website form.create-repo.button.submit=Create repository form.create-repo.name=Name form.create-repo.name.placeholder=Please enter a repository name. @@ -280,6 +284,10 @@ user.settings.account.title=Account user.settings.account.validate-email.title=Validate your email address user.settings.language.title=You preferred language. +user.settings.organisation.admins=Administrators +user.settings.organisation.edit=Edit +user.settings.organisations.description=Here you find all organisations that you''re the owner of. +user.settings.organisations.title=Organisations user.settings.ssh.add.title=Add a new public ssh key. user.settings.ssh.description=Here you can manage your SSH keys. user.settings.ssh.key.created=Uploaded on {0,date,yyyy-MM-dd (E)} 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-12 10:03:38.507669896 +0000 +++ new-smederee/modules/hub/src/main/scala/de/smederee/hub/AccountManagementRoutes.scala 2025-01-12 10:03:38.511669908 +0000 @@ -57,6 +57,8 @@ * The hub service configuration. * @param emailMiddleware * Middleware layer needed to send emails. + * @param organisationsRepo + * The repository providing functionality to handle organisations. * @param signAndValidate * A class providing functions to handle session token signing and validation. * @param ticketServiceApi @@ -68,6 +70,7 @@ accountManagementRepo: AccountManagementRepository[F], configuration: ServiceConfig, emailMiddleware: EmailMiddleware[F], + organisationsRepo: OrganisationRepository[F], signAndValidate: SignAndValidate, ticketServiceApi: TicketServiceApi[F] ) extends Http4sDsl[F] { @@ -420,6 +423,27 @@ } yield resp } + private val showAccountOrganisations: AuthedRoutes[Account, F] = AuthedRoutes.of { + case ar @ GET -> Root / "user" / "settings" / "organisations" as user => + for { + csrf <- Sync[F].delay(ar.req.getCsrfToken) + language <- Sync[F].delay(user.language.getOrElse(LanguageCode("en"))) + actionBaseUri <- Sync[F].delay(configuration.external.createFullUri(uri"user/settings")) + orgs <- organisationsRepo.allByOwner(user.uid).compile.toList + organisationActionBaseUri <- Sync[F].delay( + configuration.external.createFullUri(uri"user/settings/organisations") + ) + resp <- Ok( + views.html.account + .settingsOrganisations(lang = language)( + csrf, + Option(s"Smederee/~${user.name} - Organisations"), + user + )(actionBaseUri, orgs, organisationActionBaseUri) + ) + } yield resp + } + private val showAccountSshSettings: AuthedRoutes[Account, F] = AuthedRoutes.of { case ar @ GET -> Root / "user" / "settings" / "ssh" as user => for { @@ -454,7 +478,7 @@ } val protectedRoutes = - addSshKey <+> deleteAccount <+> deleteSshKey <+> sendValidationEmail <+> setLanguage <+> showAccountSettings <+> showAccountSshSettings + addSshKey <+> deleteAccount <+> deleteSshKey <+> sendValidationEmail <+> setLanguage <+> showAccountSettings <+> showAccountOrganisations <+> showAccountSshSettings val routes = validateEmailAddress diff -rN -u old-smederee/modules/hub/src/main/scala/de/smederee/hub/HubServer.scala new-smederee/modules/hub/src/main/scala/de/smederee/hub/HubServer.scala --- old-smederee/modules/hub/src/main/scala/de/smederee/hub/HubServer.scala 2025-01-12 10:03:38.507669896 +0000 +++ new-smederee/modules/hub/src/main/scala/de/smederee/hub/HubServer.scala 2025-01-12 10:03:38.511669908 +0000 @@ -472,11 +472,13 @@ ) darcsWrapper = new DarcsCommands[IO](hubConfiguration.service.darcs.executable) emailMiddleware = new SimpleJavaMailMiddleware(hubConfiguration.service.email) + orgRepo = new DoobieOrganisationRepository[IO](hubTransactor) accountManagementRepo = new DoobieAccountManagementRepository[IO](hubTransactor) accountManagementRoutes = new AccountManagementRoutes[IO]( accountManagementRepo, hubConfiguration.service, emailMiddleware, + orgRepo, signAndValidate, ticketServiceApi ) @@ -498,7 +500,6 @@ signUpRepo = new DoobieSignupRepository[IO](hubTransactor) signUpRoutes = new SignupRoutes[IO](hubConfiguration.service, signUpRepo) landingPages = new LandingPageRoutes[IO](hubConfiguration.service) - orgRepo = new DoobieOrganisationRepository[IO](hubTransactor) orgRoutes = new OrganisationRoutes[IO](hubConfiguration.service, orgRepo) vcsMetadataRepo = new DoobieVcsMetadataRepository[IO](hubTransactor) vcsRepoRoutes = new VcsRepositoryRoutes[IO]( 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-12 10:03:38.507669896 +0000 +++ new-smederee/modules/hub/src/main/scala/de/smederee/hub/OrganisationRoutes.scala 2025-01-12 10:03:38.511669908 +0000 @@ -17,6 +17,11 @@ package de.smederee.hub +import java.io.IOException +import java.nio.file.FileVisitResult +import java.nio.file.FileVisitor +import java.nio.file.Files + import cats.* import cats.data.* import cats.effect.* @@ -48,9 +53,65 @@ private val log = LoggerFactory.getLogger(getClass) given org.typelevel.log4cats.LoggerFactory[F] = org.typelevel.log4cats.slf4j.Slf4jFactory.create[F] - private val createOrgPath = uri"/org/create" + private val createOrgPath = uri"/user/settings/organisations/create" private val linkConfig = configuration.external + /** Delete the given directory recursively. It is checked if the given directory is a direct sub directory of the + * `repositoriesDirectory` and only if this is the case is the directory removed. + * + * @param organisationDirectory + * The path on the filesystem to the directory that shall be deleted. + * @return + * `true` if the directory was deleted. + */ + protected def deleteOrganisationDirectory(organisationDirectory: java.nio.file.Path): F[Boolean] = + for { + _ <- Sync[F].delay(log.debug(s"Request to delete organisation dir: $organisationDirectory")) + reposDirPath <- Sync[F].delay(configuration.darcs.repositoriesDirectory.toPath) + isSubDir <- Sync[F].delay(reposDirPath.equals(organisationDirectory.getParent())) + deleted <- + Sync[F].delay { + if (isSubDir) { + Files.walkFileTree( + organisationDirectory, + new FileVisitor[java.nio.file.Path] { + override def visitFileFailed( + file: java.nio.file.Path, + exc: IOException + ): FileVisitResult = FileVisitResult.CONTINUE + + override def visitFile( + file: java.nio.file.Path, + attrs: java.nio.file.attribute.BasicFileAttributes + ): FileVisitResult = { + Files.delete(file) + FileVisitResult.CONTINUE + } + + override def preVisitDirectory( + dir: java.nio.file.Path, + attrs: java.nio.file.attribute.BasicFileAttributes + ): FileVisitResult = FileVisitResult.CONTINUE + + override def postVisitDirectory( + dir: java.nio.file.Path, + exc: IOException + ): FileVisitResult = { + Files.delete(dir) + FileVisitResult.CONTINUE + } + } + ) + Files.deleteIfExists(organisationDirectory) + } else { + log.warn( + s"Refused requested removal of directory $organisationDirectory which is not a direct sub directory of the configured repositories directory!" + ) + false + } + } + } yield deleted + /** Load an organisation and related metadata from the database if the permissions allow it. * * @param currentUser @@ -82,7 +143,7 @@ } yield result private val createOrganisation: AuthedRoutes[Account, F] = AuthedRoutes.of { - case ar @ POST -> Root / "org" / "create" as user => + case ar @ POST -> Root / "user" / "settings" / "organisations" / "create" as user => ar.req.decodeStrict[F, UrlForm] { urlForm => for { csrf <- Sync[F].delay(ar.req.getCsrfToken) @@ -147,8 +208,68 @@ } } + private val deleteOrganisation: AuthedRoutes[Account, F] = AuthedRoutes.of { + case ar @ POST -> Root / "user" / "settings" / "organisations" / UsernamePathParameter( + organisationName + ) / "delete" 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)) + response <- orgaData match { + 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")) + response <- + if (owner === user && userIsSure) { + for { + _ <- Sync[F].delay( + log.info( + s"Going to delete organisation ${organisation.name} as requested by the owner ${user.uid}." + ) + ) + organisationDir <- Sync[F].delay( + java.nio.file.Paths + .get( + configuration.darcs.repositoriesDirectory.toPath.toString, + organisation.name.toString + ) + ) + _ <- deleteOrganisationDirectory(organisationDir) + _ <- orgRepo.delete(organisation.oid) + response <- SeeOther( + Location(linkConfig.createFullUri(Uri(path = Uri.Path.Root))) + ) + } yield response + } else + SeeOther(Location(linkConfig.createFullUri(uri"user/settings/organisations"))) + } yield response + } + } yield response + } + } + private val editOrganisation: AuthedRoutes[Account, F] = AuthedRoutes.of { - case ar @ POST -> Root / UsernamePathParameter(organisationName) / "edit" as user => + case ar @ POST -> Root / "user" / "settings" / "organisations" / UsernamePathParameter( + organisationName + ) / "edit" as user => ar.req.decodeStrict[F, UrlForm] { urlForm => for { csrf <- Sync[F].delay(ar.req.getCsrfToken) @@ -176,21 +297,21 @@ form <- Sync[F].delay(OrganisationForm.validate(formData)) resp <- form match { case Validated.Invalid(es) => - val editOrgPath = linkConfig.createFullUri( - Uri(path = - Uri.Path( - Vector( - Uri.Path.Segment(s"~${organisation.name.toString}"), - Uri.Path.Segment("edit") - ) - ) - ) + val actionBaseUri = uri"user/settings/organisations".addSegment( + s"~${organisation.name.toString}" ) + val deleteOrgPath = linkConfig + .createFullUri(actionBaseUri.addSegment("delete")) + .some + .filter(_ => owner === user) + val editOrgPath = + linkConfig.createFullUri(actionBaseUri.addSegment("edit")) val possibleOwners = (List(owner, user) ::: admins).distinct BadRequest( views.html .editOrganisation(lang = language)( editOrgPath, + deleteOrgPath, csrf, possibleOwners, Option(s"~$organisationName - edit"), @@ -222,7 +343,7 @@ } private val showCreateOrganisationForm: AuthedRoutes[Account, F] = AuthedRoutes.of { - case ar @ GET -> Root / "org" / "create" as user => + case ar @ GET -> Root / "user" / "settings" / "organisations" / "create" as user => for { csrf <- Sync[F].delay(ar.req.getCsrfToken) language <- Sync[F].delay(user.language.getOrElse(LanguageCode("en"))) @@ -252,7 +373,9 @@ } private val showEditOrganisationForm: AuthedRoutes[Account, F] = AuthedRoutes.of { - case ar @ GET -> Root / UsernamePathParameter(organisationName) / "edit" as user => + case ar @ GET -> Root / "user" / "settings" / "organisations" / UsernamePathParameter( + organisationName + ) / "edit" as user => for { csrf <- Sync[F].delay(ar.req.getCsrfToken) language <- Sync[F].delay(user.language.getOrElse(LanguageCode("en"))) @@ -272,22 +395,21 @@ ) ) case true => - val editOrgPath = linkConfig.createFullUri( - Uri(path = - Uri.Path( - Vector( - Uri.Path.Segment(s"~${organisation.name.toString}"), - Uri.Path.Segment("edit") - ) - ) - ) - ) + val actionBaseUri = + uri"user/settings/organisations".addSegment(s"~${organisation.name.toString}") + val deleteOrgPath = linkConfig + .createFullUri(actionBaseUri.addSegment("delete")) + .some + .filter(_ => owner === user) + val editOrgPath = + linkConfig.createFullUri(actionBaseUri.addSegment("edit")) val possibleOwners = (List(owner, user) ::: admins).distinct val formData = OrganisationForm.from(organisation).toMap Ok( views.html .editOrganisation(lang = language)( editOrgPath, + deleteOrgPath, csrf, possibleOwners, Option(s"~$organisationName - edit"), @@ -300,6 +422,6 @@ } val protectedRoutes = - showCreateOrganisationForm <+> createOrganisation <+> showEditOrganisationForm <+> editOrganisation + showCreateOrganisationForm <+> createOrganisation <+> showEditOrganisationForm <+> deleteOrganisation <+> editOrganisation } 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-12 10:03:38.507669896 +0000 +++ new-smederee/modules/hub/src/main/scala/de/smederee/hub/VcsRepositoryRoutes.scala 2025-01-12 10:03:38.511669908 +0000 @@ -316,6 +316,11 @@ actionBaseUri <- Sync[F].delay( linkConfig.createFullUri(Uri(path = Uri.Path.unsafeFromString(s"~$repositoriesOwnerName"))) ) + organisationActionBaseUri = organisation.map(org => + configuration.external.createFullUri( + uri"user/settings/organisations".addSegment(s"~${org.name.toString}") + ) + ) resp <- (owner, organisation) match { case (Some(owner), organisation) => loadRepos(owner).compile.toList.flatMap { repos => @@ -325,7 +330,7 @@ csrf, s"Smederee/~$repositoriesOwnerName".some, user - )(repos, repositoriesOwnerName, organisation) + )(repos, repositoriesOwnerName, organisation, organisationActionBaseUri) ) } case (None, Some(organisation)) => @@ -335,7 +340,7 @@ csrf, s"Smederee/~$repositoriesOwnerName".some, user - )(Nil, repositoriesOwnerName, organisation.some) + )(Nil, repositoriesOwnerName, organisation.some, organisationActionBaseUri) ) 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 1970-01-01 00:00:00.000000000 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/account/settingsOrganisations.scala.html 2025-01-12 10:03:38.511669908 +0000 @@ -0,0 +1,42 @@ +@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) +@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-left-right"> + <h2>~@user.name / Settings</h2> + <nav class="pure-menu pure-menu-horizontal"> + <ul class="pure-menu-list"> + <li class="pure-menu-item"><a class="pure-menu-link" href="@actionBaseUri">@Messages("user.settings.account.title")</a></li> + <li class="pure-menu-item"><a class="pure-menu-link" href="@actionBaseUri.addSegment("ssh")">@Messages("user.settings.ssh.title")</a></li> + <li class="pure-menu-item pure-menu-active"><a class="pure-menu-link" href="@actionBaseUri.addSegment("organisations")">@Messages("user.settings.organisations.title")</a></li> + </ul> + </nav> + <div class="account-settings-description"> + @Messages("user.settings.organisations.description") + </div> + </div> + </div> + </div> + <div class="pure-g"> + <div class="pure-u-1-1 pure-u-md-1-1"> + <div class="l-box"> + @for(organisation <- organisations) { + @defining(organisation.name) { organisationName => + <div class="pure-g"> + <div class="pure-u-7-12" style="padding: 10px 5px 10px 5px;"><a href="@baseUri.addSegment(s"~$organisationName")">@organisationName</a></div> + <div class="pure-u-2-12" style="padding: 10px 5px 10px 5px;"><a class="pure-button" href="@organisationActionBaseUri.addSegment(s"~$organisationName").addSegment("edit")">@Messages("user.settings.organisation.edit")</a></div> + <div class="pure-u-3-12" style="padding: 10px 5px 10px 5px;"><a class="pure-button" href="@organisationActionBaseUri.addSegment(s"~$organisationName").addSegment("admins")">@Messages("user.settings.organisation.admins")</a></div> + </div> + } + } + </div> + </div> + </div> +</div> +} +} 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-12 10:03:38.507669896 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/account/settings.scala.html 2025-01-12 10:03:38.511669908 +0000 @@ -14,6 +14,7 @@ <ul class="pure-menu-list"> <li class="pure-menu-item pure-menu-active"><a class="pure-menu-link" href="@actionBaseUri">@Messages("user.settings.account.title")</a></li> <li class="pure-menu-item"><a class="pure-menu-link" href="@actionBaseUri.addSegment("ssh")">@Messages("user.settings.ssh.title")</a></li> + <li class="pure-menu-item"><a class="pure-menu-link" href="@actionBaseUri.addSegment("organisations")">@Messages("user.settings.organisations.title")</a></li> </ul> </nav> <div class="account-settings-description"> 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-12 10:03:38.507669896 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/editOrganisation.scala.html 2025-01-12 10:03:38.511669908 +0000 @@ -3,7 +3,7 @@ @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"))(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) @main(baseUri, lang)()(csrf, title, user.some) { @defining(lang.toLocale) { implicit locale => <div class="content"> @@ -22,7 +22,7 @@ } </div> <div class="organisation-form"> - <form action="@action" method="POST" accept-charset="UTF-8" class="pure-form pure-form-aligned"> + <form action="@editAction" method="POST" accept-charset="UTF-8" class="pure-form pure-form-aligned"> <fieldset id="organisation-data"> @organisationFormFields(possibleOwners)(formData, formErrors) @csrfToken(csrf) @@ -35,6 +35,31 @@ </div> </div> </div> + @for(deleteAction <- deleteAction) { + <div class="pure-g"> + <div class="pure-u-1-1 pure-u-md-1-1"> + <div class="l-box"> + <div class="organisation-delete-form"> + <h4>@Messages("form.organisation.delete.title")</h4> + @defining(formData.get(fieldName)) { organisationName => + <form action="@deleteAction" class="pure-form pure-form-aligned" method="POST" accept-charset="UTF-8"> + <fieldset> + <p class="alert alert-error"> + <span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span> + @Messages("form.organisation.delete.notice") + </p> + <input type="hidden" id="org-@{organisationName}" name="org-name" readonly="" value="@{organisationName}"> + <label class="pure-checkbox" for="org-@{organisationName}"><input id="i-am-sure-@{organisationName}" name="i-am-sure" required="" type="checkbox" value="yes"/> @Messages("form.organisation.delete.i-am-sure")</label> + @csrfToken(csrf) + <button type="submit" class="pure-button pure-button-warning">@Messages("form.organisation.button.delete.submit")</button> + </fieldset> + </form> + } + </div> + </div> + </div> + </div> + } </div> } } 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-12 10:03:38.507669896 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/navbar.scala.html 2025-01-12 10:03:38.511669908 +0000 @@ -12,7 +12,7 @@ <li class="pure-menu-item"><a class="pure-menu-link" href="@{baseUri.addPath(s"~${account.name}")}">@Messages("global.navbar.top.repositories.yours")</a></li> } <li class="pure-menu-item"><a class="pure-menu-link" href="@{baseUri.addPath("repo/create")}">+ @Messages("global.navbar.top.repository.new")</a></li> - <li class="pure-menu-item"><a class="pure-menu-link" href="@{baseUri.addPath("org/create")}">+ @Messages("global.navbar.top.organisation.new")</a></li> + <li class="pure-menu-item"><a class="pure-menu-link" href="@{baseUri.addPath("user/settings/organisations/create")}">+ @Messages("global.navbar.top.organisation.new")</a></li> <li class="pure-menu-item"><a class="pure-menu-link" href="@{baseUri.addPath("user/settings")}">@Messages("global.navbar.top.settings")</a></li> <li class="pure-menu-item"> <form action="@{baseUri.addPath("logout")}" method="POST" accept-charset="UTF-8" class="pure-form"> 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-12 10:03:38.507669896 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showRepositories.scala.html 2025-01-12 10:03:38.511669908 +0000 @@ -1,7 +1,7 @@ @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]) +@(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]) @main(baseUri, lang)()(csrf, title, user) { @defining(lang.toLocale) { implicit locale => <div class="content"> @@ -13,9 +13,11 @@ </div> <div class="pure-u-1-5 pure-u-md-1-5"> @if(user.exists(user => organisation.exists(_.owner === user.uid))) { + @defining(organisationActionBaseUri.map(_.addSegment("edit"))) { orgEditUri => <div class="l-box"> - <a href="@actionBaseUri.addSegment("edit")">@Messages("organisation.menu.edit")</a> + <a href="@orgEditUri">@Messages("organisation.menu.edit")</a> </div> + } } else { } </div>