mirror of
https://github.com/gitbucket/gitbucket.git
synced 2025-11-04 20:45:58 +01:00
Compare commits
342 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5913bcd309 | ||
|
|
2a10acd76f | ||
|
|
2c6a8cf08a | ||
|
|
1cd0759325 | ||
|
|
915cfd06c3 | ||
|
|
4cb198bc05 | ||
|
|
1ea0e827a5 | ||
|
|
8f4744f93d | ||
|
|
7c0d0be876 | ||
|
|
81c2d988a3 | ||
|
|
eef01262bd | ||
|
|
921298cf92 | ||
|
|
56eb2435e3 | ||
|
|
069d84e249 | ||
|
|
c63c8d3cd2 | ||
|
|
ca66faebdf | ||
|
|
9b5530b3fa | ||
|
|
b3319daf95 | ||
|
|
cf038ebd38 | ||
|
|
163ed5c4a0 | ||
|
|
9bff4b1e97 | ||
|
|
332836119d | ||
|
|
110646fe9f | ||
|
|
e578875d51 | ||
|
|
86903a7a22 | ||
|
|
dd1dbd429c | ||
|
|
efe891a348 | ||
|
|
a720c6bee4 | ||
|
|
c0a7c3537a | ||
|
|
2d1f08bc01 | ||
|
|
ddbc2e6a56 | ||
|
|
2c201aae61 | ||
|
|
3f74745fc5 | ||
|
|
0c17a23cca | ||
|
|
8b2b36df0e | ||
|
|
07182cf946 | ||
|
|
ea69463fd2 | ||
|
|
3abd934d6a | ||
|
|
7c10cb0ec7 | ||
|
|
c1d6839c18 | ||
|
|
f62f83888c | ||
|
|
10cec316ee | ||
|
|
545846cab5 | ||
|
|
acf5767e4f | ||
|
|
e4520247fc | ||
|
|
a39a0292b6 | ||
|
|
6150625e99 | ||
|
|
154f080c1c | ||
|
|
b0ec5307a2 | ||
|
|
e5d4bc6653 | ||
|
|
a701182f0c | ||
|
|
f1db6e3c7c | ||
|
|
6926aa7aec | ||
|
|
1a0f282f23 | ||
|
|
255aa7476c | ||
|
|
34df9ae739 | ||
|
|
1fb0eeff22 | ||
|
|
8b963a32f0 | ||
|
|
db17508559 | ||
|
|
79fb95c149 | ||
|
|
f61149ae61 | ||
|
|
3abe398244 | ||
|
|
6e0deb6a6c | ||
|
|
13622c5970 | ||
|
|
0c3c25b7c0 | ||
|
|
6e31f19e9b | ||
|
|
52ec97b87e | ||
|
|
a5ccea6413 | ||
|
|
cc091a65a4 | ||
|
|
884d4935cc | ||
|
|
35655f33c7 | ||
|
|
5022702796 | ||
|
|
243833c3fc | ||
|
|
3f1ea419d6 | ||
|
|
0c2c590678 | ||
|
|
3ca73aaafb | ||
|
|
b4cf4bfb17 | ||
|
|
5cb26247fc | ||
|
|
2e391144f2 | ||
|
|
bd69821f1d | ||
|
|
d802ed099b | ||
|
|
170a48de36 | ||
|
|
1d2d33ba71 | ||
|
|
9bcc2a3202 | ||
|
|
d65637ce0c | ||
|
|
2e910aebdc | ||
|
|
a1621b49a6 | ||
|
|
ac6fbd0bfa | ||
|
|
5305b1b09a | ||
|
|
4f92739d73 | ||
|
|
cadd128299 | ||
|
|
b642783610 | ||
|
|
4aaaff5de7 | ||
|
|
2fda39ddbe | ||
|
|
6753bd085f | ||
|
|
2d8513e18a | ||
|
|
047c877d83 | ||
|
|
b09c69afd7 | ||
|
|
6713dd97f5 | ||
|
|
d5b2a9d6b5 | ||
|
|
964f6d3c82 | ||
|
|
f1164e7790 | ||
|
|
b8f1736a09 | ||
|
|
de8e553906 | ||
|
|
d6c9101b83 | ||
|
|
50137c5546 | ||
|
|
9e131c8970 | ||
|
|
8733b8eddd | ||
|
|
a0c06855d2 | ||
|
|
64ce44243c | ||
|
|
66d2af1ef5 | ||
|
|
d155cb67b3 | ||
|
|
7d7b13de6e | ||
|
|
329a8ebc2b | ||
|
|
1a16edd140 | ||
|
|
28fed98924 | ||
|
|
08ac28902d | ||
|
|
976f5e5a32 | ||
|
|
ff53329cd6 | ||
|
|
98de258abf | ||
|
|
5afef6713f | ||
|
|
da21db8776 | ||
|
|
f004c1c75b | ||
|
|
c96dbf4db5 | ||
|
|
742bdc0252 | ||
|
|
850429c507 | ||
|
|
17f9f066d8 | ||
|
|
d8e5a89ac5 | ||
|
|
33812cb337 | ||
|
|
b89c99d388 | ||
|
|
f5ec9ac1bf | ||
|
|
3fd8cb2b3e | ||
|
|
af62ccd72d | ||
|
|
f097dd2316 | ||
|
|
19640bd2d4 | ||
|
|
d2d5e9cc43 | ||
|
|
3a7581b391 | ||
|
|
d87305c11d | ||
|
|
e935e6e6b1 | ||
|
|
04a7548a26 | ||
|
|
64528cb4a8 | ||
|
|
ae26e8ec6a | ||
|
|
8a13721c90 | ||
|
|
e91d098055 | ||
|
|
5ca4a1a233 | ||
|
|
a39fe7f51c | ||
|
|
e1c05eb961 | ||
|
|
b25f97a96f | ||
|
|
1c0f99bd64 | ||
|
|
800d48d6d2 | ||
|
|
02a8540875 | ||
|
|
048cdfc050 | ||
|
|
13f40d2b59 | ||
|
|
fe2920a08f | ||
|
|
f59beded21 | ||
|
|
c75119badf | ||
|
|
7b1292a9af | ||
|
|
9ae26800c8 | ||
|
|
1eb8c83061 | ||
|
|
a0069fde57 | ||
|
|
ca5b121272 | ||
|
|
6f4c081f10 | ||
|
|
227ee12e4c | ||
|
|
fc1cfe3f55 | ||
|
|
76fa5fb474 | ||
|
|
3c847bf957 | ||
|
|
88427b893c | ||
|
|
737b0a9bdf | ||
|
|
c8a7ef2fdb | ||
|
|
6d6331bbf3 | ||
|
|
d1f2a72f06 | ||
|
|
576daa0669 | ||
|
|
934d1df991 | ||
|
|
aa7db68e68 | ||
|
|
a1bb667ec4 | ||
|
|
90ae489d35 | ||
|
|
5bbd4f533f | ||
|
|
283baaed57 | ||
|
|
c1e2191120 | ||
|
|
28b0ea7f88 | ||
|
|
95acb8593a | ||
|
|
17d682f83b | ||
|
|
952c916e33 | ||
|
|
3793a51e3f | ||
|
|
c8b6eb1bd9 | ||
|
|
e46f1b0efc | ||
|
|
395d6e4292 | ||
|
|
770b49cc55 | ||
|
|
95cd6b6e90 | ||
|
|
1f79ed95c2 | ||
|
|
9aef90d214 | ||
|
|
f4d1c72f08 | ||
|
|
5d5dcad32c | ||
|
|
0d195a2e90 | ||
|
|
eb76db5f2c | ||
|
|
9cc5d0ea17 | ||
|
|
1d98308a21 | ||
|
|
1ceace5539 | ||
|
|
f13a473b4e | ||
|
|
4e7c10c0dc | ||
|
|
6db34cbb6b | ||
|
|
10205a8f9b | ||
|
|
d6df35f072 | ||
|
|
ab10b77c50 | ||
|
|
fb34b0909e | ||
|
|
1a869f47e0 | ||
|
|
d9aebbda62 | ||
|
|
987407909e | ||
|
|
ba9c780602 | ||
|
|
ea5834f236 | ||
|
|
c3400f1091 | ||
|
|
7bd4d0970e | ||
|
|
4a9303d7a7 | ||
|
|
5f0cacd7c1 | ||
|
|
f075132878 | ||
|
|
b72556c007 | ||
|
|
47489d9cb1 | ||
|
|
2ee70dc1b2 | ||
|
|
3400b9a0ab | ||
|
|
ad054d2f80 | ||
|
|
b0c2e5588c | ||
|
|
1fe379111c | ||
|
|
2180e31d13 | ||
|
|
275772ad00 | ||
|
|
e80da63515 | ||
|
|
71cce5b470 | ||
|
|
bb188ec948 | ||
|
|
281522fc88 | ||
|
|
a045fc6ae4 | ||
|
|
8e8e794574 | ||
|
|
735e425984 | ||
|
|
5f47b126e3 | ||
|
|
33d82beb72 | ||
|
|
3de5d806b5 | ||
|
|
8eb522fb38 | ||
|
|
370e4339f3 | ||
|
|
5b0eb7ece5 | ||
|
|
18434854d8 | ||
|
|
d3f57bdb45 | ||
|
|
37734ce26b | ||
|
|
b6cf080822 | ||
|
|
bbc817d86d | ||
|
|
5e88f3f787 | ||
|
|
f64d4843f3 | ||
|
|
bcb3450e2b | ||
|
|
c607045b7c | ||
|
|
f8e9093273 | ||
|
|
40c06417e5 | ||
|
|
c3c5535022 | ||
|
|
b7fc76d932 | ||
|
|
c8d666baba | ||
|
|
a64741011c | ||
|
|
ae9ee4779f | ||
|
|
5fd2d61861 | ||
|
|
939c9156ad | ||
|
|
d17aed2357 | ||
|
|
13382b47d1 | ||
|
|
5e5a1ea5a8 | ||
|
|
cf6d1ea137 | ||
|
|
f735e4a133 | ||
|
|
86b67863f8 | ||
|
|
718582af44 | ||
|
|
23024cacaa | ||
|
|
f62cf409eb | ||
|
|
47845dfe1b | ||
|
|
b7bb6b0787 | ||
|
|
ea41786f8c | ||
|
|
962ae2130e | ||
|
|
90ea05f2a1 | ||
|
|
f8bda516d6 | ||
|
|
378c031ecb | ||
|
|
9a5db80dea | ||
|
|
992eb0ceda | ||
|
|
39e1ac2398 | ||
|
|
d1c77de5a0 | ||
|
|
3f8069638c | ||
|
|
d62fc1185c | ||
|
|
768706e1d1 | ||
|
|
8cc9771237 | ||
|
|
8df30ef01b | ||
|
|
dd2e5bfedf | ||
|
|
e3c7eb092f | ||
|
|
5b3c3e2e7c | ||
|
|
0e04925b6b | ||
|
|
9a127256f3 | ||
|
|
1033122fec | ||
|
|
847f96d537 | ||
|
|
70f40846bb | ||
|
|
3a540aa660 | ||
|
|
1adc9b3223 | ||
|
|
0309496df6 | ||
|
|
f83ecac7ae | ||
|
|
cd4d75e35e | ||
|
|
eb61bc50d6 | ||
|
|
4bbb22f73b | ||
|
|
fcb374c5c2 | ||
|
|
a03d1c97c2 | ||
|
|
2d58b7f2d7 | ||
|
|
332a1b4b0b | ||
|
|
6bd58b0c45 | ||
|
|
fb175df851 | ||
|
|
b41aad92f2 | ||
|
|
aabae2ef7f | ||
|
|
0c3d1fd86d | ||
|
|
adba849ec5 | ||
|
|
8539486c6e | ||
|
|
86f4b41beb | ||
|
|
aa54eff3d6 | ||
|
|
27ab21c9a7 | ||
|
|
557ed827d0 | ||
|
|
9cc466a727 | ||
|
|
9a9be12324 | ||
|
|
8e91b9f0b5 | ||
|
|
2862ceb5ad | ||
|
|
d157426d66 | ||
|
|
58635674cb | ||
|
|
f6a048e0f7 | ||
|
|
c4dc1d7334 | ||
|
|
efd5a64749 | ||
|
|
13800a7023 | ||
|
|
43d19d7d52 | ||
|
|
8a8278906a | ||
|
|
d15b3fb2f6 | ||
|
|
bcd92916ca | ||
|
|
810cbda123 | ||
|
|
fee7cebdf1 | ||
|
|
28105d6d3a | ||
|
|
1673832607 | ||
|
|
298e43e612 | ||
|
|
00b88d6b6e | ||
|
|
0a12b82b48 | ||
|
|
7ef74ac3ee | ||
|
|
5853691844 | ||
|
|
3a8b93d44a | ||
|
|
806a5aecef | ||
|
|
44d2918dee | ||
|
|
64f7db6585 | ||
|
|
fb0cd272ce | ||
|
|
239c7371a8 | ||
|
|
981b228a88 | ||
|
|
0f70e5b1d6 | ||
|
|
fd30facd8f |
11
.scalafmt.conf
Normal file
11
.scalafmt.conf
Normal file
@@ -0,0 +1,11 @@
|
||||
project.git = true
|
||||
|
||||
maxColumn = 120
|
||||
docstrings = JavaDoc
|
||||
|
||||
align.tokens = ["%", "%%", {code = "=>", owner = "Case"}]
|
||||
align.openParenCallSite = false
|
||||
align.openParenDefnSite = false
|
||||
continuationIndent.callSite = 2
|
||||
continuationIndent.defnSite = 2
|
||||
danglingParentheses = true
|
||||
@@ -3,7 +3,7 @@ sudo: true
|
||||
jdk:
|
||||
- oraclejdk8
|
||||
script:
|
||||
- sbt test
|
||||
- sbt scalafmtSbtCheck scalafmtCheck test:scalafmtCheck test
|
||||
before_script:
|
||||
- sudo /etc/init.d/mysql stop
|
||||
- sudo /etc/init.d/postgresql stop
|
||||
|
||||
62
CHANGELOG.md
62
CHANGELOG.md
@@ -1,11 +1,73 @@
|
||||
# Changelog
|
||||
All changes to the project will be documented in this file.
|
||||
|
||||
### 4.24.0 - 30 Apr 2018
|
||||
- Diff for each review comment on pull requests
|
||||
- Extra mail addresses support
|
||||
- Show tags at the commit list
|
||||
- Keep wrap mode of the online editor
|
||||
- Renew layout of gitbucket-gist-plugin
|
||||
- Web API of gitbucket-ci-plugin
|
||||
|
||||
### 4.23.1 - 10 Apr 2018
|
||||
- Fix bug that the contents API doesn't work for the repository root
|
||||
- Fix shutdown problem in Tomcat deployment
|
||||
- Render by plugins at the blob view even if it's a binary file
|
||||
|
||||
### 4.23.0 - 31 Mar 2018
|
||||
- Allow tail slash in URL
|
||||
- Display commit message of tags at the releases page
|
||||
- Add labels property to issues and pull requests API response
|
||||
- Plugins list API
|
||||
- Git authentication with personal access token
|
||||
- Max parallel builds and max stored history in CI plugin became configurable
|
||||
|
||||
### 4.22.0 - 3 Mar 2018
|
||||
- Pull request merge strategy settings
|
||||
- Create repository with an empty commit
|
||||
- Improve database viewer
|
||||
- Update maven-repository-plugin
|
||||
|
||||
### 4.21.2 - 27 Jan 2018
|
||||
- Bugfix
|
||||
|
||||
### 4.21.1 - 27 Jan 2018
|
||||
- Bugfix
|
||||
|
||||
### 4.21.0 - 27 Jan 2018
|
||||
- Release page
|
||||
- OpenID Connect support
|
||||
- New database viewer
|
||||
- Submodule links to web page
|
||||
- Clarify close/reopen button
|
||||
|
||||
## 4.20.0 - 23 Dec 2017
|
||||
- Squash and rebase merge strategy for pull requests
|
||||
- Quick pull request creation
|
||||
- Download patch from the diff view
|
||||
- Fork and create repository are proceeded asynchronously
|
||||
- Create new repository by copying existing git repository
|
||||
- Hide overflowed repository names in the sidebar
|
||||
- Support CreateEvent web hook
|
||||
- Display conflicting files if pull request can't be merged
|
||||
|
||||
## 4.19.3 - 7 Dec 2017
|
||||
- Fix file uploading bug
|
||||
- Fix reply comment form behavior in the diff view
|
||||
|
||||
## 4.19.2 - 3 Dec 2017
|
||||
- Fix routing bug in `CompositeScalatraFilter`
|
||||
- Resolve id attribute collision in the web hook editing form
|
||||
|
||||
## 4.19.1 - 2 Dec 2017
|
||||
- Update gitbucket-notifications-plugin because it had a version compatibility issue
|
||||
|
||||
## 4.19.0 - 2 Dec 2017
|
||||
- [gitbucket-maven-repository-plugin](https://github.com/takezoe/gitbucket-maven-repository-plugin) is available
|
||||
- Upgrade to Scalatra 2.6
|
||||
- Improve layout of the system settings page
|
||||
- New extension point (`sshCommandProvider`)
|
||||
- Dropped [gitbucket-pages-plugin](https://github.com/gitbucket/gitbucket-pages-plugin) from bundled plugins temporary because we couldn't complete update for Scalatra 2.6 before this release.
|
||||
|
||||
## 4.18.0 - 14 Oct 2017
|
||||
- Form to reply to review comment
|
||||
|
||||
16
README.md
16
README.md
@@ -1,4 +1,4 @@
|
||||
GitBucket [](https://gitter.im/gitbucket/gitbucket) [](https://travis-ci.org/gitbucket/gitbucket)
|
||||
GitBucket [](https://gitter.im/gitbucket/gitbucket) [](https://travis-ci.org/gitbucket/gitbucket) [](https://maven-badges.herokuapp.com/maven-central/io.github.gitbucket/gitbucket_2.12)
|
||||
=========
|
||||
|
||||
GitBucket is a Git web platform powered by Scala offering:
|
||||
@@ -68,12 +68,14 @@ Support
|
||||
- If you can't find same question and report, send it to [gitter room](https://gitter.im/gitbucket/gitbucket) before raising an issue.
|
||||
- The highest priority of GitBucket is the ease of installation and API compatibility with GitHub, so your feature request might be rejected if they go against those principles.
|
||||
|
||||
What's New in 4.19.0 - 2 Dec 2017
|
||||
What's New in 4.24.x
|
||||
-------------
|
||||
|
||||
- [gitbucket-maven-repository-plugin](https://github.com/takezoe/gitbucket-maven-repository-plugin) is available
|
||||
- Upgrade to Scalatra 2.6
|
||||
- Improve layout of the system settings page
|
||||
- New extension point (`sshCommandProvider`)
|
||||
### 4.24.0 - 30 Apr 2018
|
||||
- Diff for each review comment on pull requests
|
||||
- Extra mail addresses support
|
||||
- Show tags at the commit list
|
||||
- Keep wrap mode of the online editor
|
||||
- Renew layout of gitbucket-gist-plugin
|
||||
- Web API of gitbucket-ci-plugin
|
||||
|
||||
See the [change log](CHANGELOG.md) for all of the updates.
|
||||
|
||||
174
build.sbt
174
build.sbt
@@ -1,21 +1,24 @@
|
||||
import com.typesafe.sbt.license.{LicenseInfo, DepModuleInfo}
|
||||
import com.typesafe.sbt.license.{DepModuleInfo, LicenseInfo}
|
||||
import com.typesafe.sbt.pgp.PgpKeys._
|
||||
|
||||
val Organization = "io.github.gitbucket"
|
||||
val Name = "gitbucket"
|
||||
val GitBucketVersion = "4.19.0"
|
||||
val GitBucketVersion = "4.24.0"
|
||||
val ScalatraVersion = "2.6.1"
|
||||
val JettyVersion = "9.4.7.v20170914"
|
||||
|
||||
lazy val root = (project in file(".")).enablePlugins(SbtTwirl, ScalatraPlugin, JRebelPlugin).settings(
|
||||
|
||||
)
|
||||
lazy val root = (project in file("."))
|
||||
.enablePlugins(SbtTwirl, ScalatraPlugin, JRebelPlugin)
|
||||
.settings(
|
||||
)
|
||||
|
||||
sourcesInBase := false
|
||||
organization := Organization
|
||||
name := Name
|
||||
version := GitBucketVersion
|
||||
scalaVersion := "2.12.4"
|
||||
scalaVersion := "2.12.6"
|
||||
|
||||
scalafmtOnCompile := true
|
||||
|
||||
// dependency settings
|
||||
resolvers ++= Seq(
|
||||
@@ -25,55 +28,58 @@ resolvers ++= Seq(
|
||||
"sonatype-snapshot" at "https://oss.sonatype.org/content/repositories/snapshots/",
|
||||
"amateras-snapshot" at "http://amateras.sourceforge.jp/mvn-snapshot/"
|
||||
)
|
||||
|
||||
libraryDependencies ++= Seq(
|
||||
"org.eclipse.jgit" % "org.eclipse.jgit.http.server" % "4.9.0.201710071750-r",
|
||||
"org.eclipse.jgit" % "org.eclipse.jgit.archive" % "4.9.0.201710071750-r",
|
||||
"org.scalatra" %% "scalatra" % ScalatraVersion,
|
||||
"org.scalatra" %% "scalatra-json" % ScalatraVersion,
|
||||
"org.scalatra" %% "scalatra-forms" % ScalatraVersion,
|
||||
"org.json4s" %% "json4s-jackson" % "3.5.1",
|
||||
"commons-io" % "commons-io" % "2.5",
|
||||
"io.github.gitbucket" % "solidbase" % "1.0.2",
|
||||
"io.github.gitbucket" % "markedj" % "1.0.15",
|
||||
"org.apache.commons" % "commons-compress" % "1.13",
|
||||
"org.apache.commons" % "commons-email" % "1.4",
|
||||
"org.apache.httpcomponents" % "httpclient" % "4.5.3",
|
||||
"org.apache.sshd" % "apache-sshd" % "1.4.0" exclude("org.slf4j","slf4j-jdk14"),
|
||||
"org.apache.tika" % "tika-core" % "1.14",
|
||||
"com.github.takezoe" %% "blocking-slick-32" % "0.0.10",
|
||||
"com.novell.ldap" % "jldap" % "2009-10-07",
|
||||
"com.h2database" % "h2" % "1.4.195",
|
||||
"org.mariadb.jdbc" % "mariadb-java-client" % "2.1.2",
|
||||
"org.postgresql" % "postgresql" % "42.0.0",
|
||||
"ch.qos.logback" % "logback-classic" % "1.2.3",
|
||||
"com.zaxxer" % "HikariCP" % "2.6.1",
|
||||
"com.typesafe" % "config" % "1.3.1",
|
||||
"com.typesafe.akka" %% "akka-actor" % "2.5.0",
|
||||
"fr.brouillard.oss.security.xhub" % "xhub4j-core" % "1.0.0",
|
||||
"com.github.bkromhout" % "java-diff-utils" % "2.1.1",
|
||||
"org.cache2k" % "cache2k-all" % "1.0.0.CR1",
|
||||
"com.enragedginger" %% "akka-quartz-scheduler" % "1.6.0-akka-2.4.x" exclude("c3p0","c3p0"),
|
||||
"net.coobird" % "thumbnailator" % "0.4.8",
|
||||
"com.github.zafarkhaja" % "java-semver" % "0.9.0",
|
||||
"org.eclipse.jetty" % "jetty-webapp" % JettyVersion % "provided",
|
||||
"javax.servlet" % "javax.servlet-api" % "3.1.0" % "provided",
|
||||
"junit" % "junit" % "4.12" % "test",
|
||||
"org.scalatra" %% "scalatra-scalatest" % ScalatraVersion % "test",
|
||||
"org.mockito" % "mockito-core" % "2.7.22" % "test",
|
||||
"com.wix" % "wix-embedded-mysql" % "2.1.4" % "test",
|
||||
"ru.yandex.qatools.embed" % "postgresql-embedded" % "2.0" % "test",
|
||||
"net.i2p.crypto" % "eddsa" % "0.1.0"
|
||||
"org.eclipse.jgit" % "org.eclipse.jgit.http.server" % "4.11.0.201803080745-r",
|
||||
"org.eclipse.jgit" % "org.eclipse.jgit.archive" % "4.11.0.201803080745-r",
|
||||
"org.scalatra" %% "scalatra" % ScalatraVersion,
|
||||
"org.scalatra" %% "scalatra-json" % ScalatraVersion,
|
||||
"org.scalatra" %% "scalatra-forms" % ScalatraVersion,
|
||||
"org.json4s" %% "json4s-jackson" % "3.5.3",
|
||||
"commons-io" % "commons-io" % "2.6",
|
||||
"io.github.gitbucket" % "solidbase" % "1.0.2",
|
||||
"io.github.gitbucket" % "markedj" % "1.0.15",
|
||||
"org.apache.commons" % "commons-compress" % "1.15",
|
||||
"org.apache.commons" % "commons-email" % "1.5",
|
||||
"org.apache.httpcomponents" % "httpclient" % "4.5.4",
|
||||
"org.apache.sshd" % "apache-sshd" % "1.6.0" exclude ("org.slf4j", "slf4j-jdk14"),
|
||||
"org.apache.tika" % "tika-core" % "1.17",
|
||||
"com.github.takezoe" %% "blocking-slick-32" % "0.0.10",
|
||||
"com.novell.ldap" % "jldap" % "2009-10-07",
|
||||
"com.h2database" % "h2" % "1.4.196",
|
||||
"org.mariadb.jdbc" % "mariadb-java-client" % "2.2.3",
|
||||
"org.postgresql" % "postgresql" % "42.1.4",
|
||||
"ch.qos.logback" % "logback-classic" % "1.2.3",
|
||||
"com.zaxxer" % "HikariCP" % "2.7.4",
|
||||
"com.typesafe" % "config" % "1.3.2",
|
||||
"com.typesafe.akka" %% "akka-actor" % "2.5.8",
|
||||
"fr.brouillard.oss.security.xhub" % "xhub4j-core" % "1.0.0",
|
||||
"com.github.bkromhout" % "java-diff-utils" % "2.1.1",
|
||||
"org.cache2k" % "cache2k-all" % "1.0.1.Final",
|
||||
"com.enragedginger" %% "akka-quartz-scheduler" % "1.6.1-akka-2.5.x" exclude ("c3p0", "c3p0"),
|
||||
"net.coobird" % "thumbnailator" % "0.4.8",
|
||||
"com.github.zafarkhaja" % "java-semver" % "0.9.0",
|
||||
"com.nimbusds" % "oauth2-oidc-sdk" % "5.45",
|
||||
"org.eclipse.jetty" % "jetty-webapp" % JettyVersion % "provided",
|
||||
"javax.servlet" % "javax.servlet-api" % "3.1.0" % "provided",
|
||||
"junit" % "junit" % "4.12" % "test",
|
||||
"org.scalatra" %% "scalatra-scalatest" % ScalatraVersion % "test",
|
||||
"org.mockito" % "mockito-core" % "2.13.0" % "test",
|
||||
"com.wix" % "wix-embedded-mysql" % "3.0.0" % "test",
|
||||
"ru.yandex.qatools.embed" % "postgresql-embedded" % "2.6" % "test",
|
||||
"net.i2p.crypto" % "eddsa" % "0.2.0",
|
||||
"is.tagomor.woothee" % "woothee-java" % "1.7.0"
|
||||
)
|
||||
|
||||
// Compiler settings
|
||||
scalacOptions := Seq("-deprecation", "-language:postfixOps", "-opt:l:method")
|
||||
scalacOptions := Seq("-deprecation", "-language:postfixOps", "-opt:l:method", "-Xfuture")
|
||||
javacOptions in compile ++= Seq("-target", "8", "-source", "8")
|
||||
javaOptions in Jetty += "-Dlogback.configurationFile=/logback-dev.xml"
|
||||
|
||||
// Test settings
|
||||
//testOptions in Test += Tests.Argument("-l", "ExternalDBTest")
|
||||
javaOptions in Test += "-Dgitbucket.home=target/gitbucket_home_for_test"
|
||||
testOptions in Test += Tests.Setup( () => new java.io.File("target/gitbucket_home_for_test").mkdir() )
|
||||
testOptions in Test += Tests.Setup(() => new java.io.File("target/gitbucket_home_for_test").mkdir())
|
||||
fork in Test := true
|
||||
|
||||
// Packaging options
|
||||
@@ -83,9 +89,9 @@ packageOptions += Package.MainClass("JettyLauncher")
|
||||
test in assembly := {}
|
||||
assemblyMergeStrategy in assembly := {
|
||||
case PathList("META-INF", xs @ _*) =>
|
||||
(xs map {_.toLowerCase}) match {
|
||||
(xs map { _.toLowerCase }) match {
|
||||
case ("manifest.mf" :: Nil) => MergeStrategy.discard
|
||||
case _ => MergeStrategy.discard
|
||||
case _ => MergeStrategy.discard
|
||||
}
|
||||
case x => MergeStrategy.first
|
||||
}
|
||||
@@ -96,12 +102,20 @@ assemblyMergeStrategy in assembly := {
|
||||
//jrebel.webLinks += (target in webappPrepare).value
|
||||
//jrebel.enabled := System.getenv().get("JREBEL") != null
|
||||
javaOptions in Jetty ++= Option(System.getenv().get("JREBEL")).toSeq.flatMap { path =>
|
||||
Seq("-noverify", "-XX:+UseConcMarkSweepGC", "-XX:+CMSClassUnloadingEnabled", s"-javaagent:${path}")
|
||||
if (path.endsWith(".jar")) {
|
||||
// Legacy JRebel agent
|
||||
Seq("-noverify", "-XX:+UseConcMarkSweepGC", "-XX:+CMSClassUnloadingEnabled", s"-javaagent:${path}")
|
||||
} else {
|
||||
// New JRebel agent
|
||||
Seq(s"-agentpath:${path}")
|
||||
}
|
||||
}
|
||||
|
||||
// Exclude a war file from published artifacts
|
||||
signedArtifacts := {
|
||||
signedArtifacts.value.filterNot { case (_, file) => file.getName.endsWith(".war") || file.getName.endsWith(".war.asc") }
|
||||
signedArtifacts.value.filterNot {
|
||||
case (_, file) => file.getName.endsWith(".war") || file.getName.endsWith(".war.asc")
|
||||
}
|
||||
}
|
||||
|
||||
// Create executable war file
|
||||
@@ -121,35 +135,34 @@ libraryDependencies ++= Seq(
|
||||
|
||||
val executableKey = TaskKey[File]("executable")
|
||||
executableKey := {
|
||||
import java.util.jar.{ Manifest => JarManifest }
|
||||
import java.util.jar.Attributes.{ Name => AttrName }
|
||||
import java.util.jar.Attributes.{Name => AttrName}
|
||||
import java.util.jar.{Manifest => JarManifest}
|
||||
|
||||
val workDir = Keys.target.value / "executable"
|
||||
val warName = Keys.name.value + ".war"
|
||||
val workDir = Keys.target.value / "executable"
|
||||
val warName = Keys.name.value + ".war"
|
||||
|
||||
val log = streams.value.log
|
||||
val log = streams.value.log
|
||||
log info s"building executable webapp in ${workDir}"
|
||||
|
||||
// initialize temp directory
|
||||
val temp = workDir / "webapp"
|
||||
val temp = workDir / "webapp"
|
||||
IO delete temp
|
||||
|
||||
// include jetty classes
|
||||
val jettyJars = Keys.update.value select configurationFilter(name = ExecutableConfig.name)
|
||||
jettyJars foreach { jar =>
|
||||
IO unzip (jar, temp, (name:String) =>
|
||||
IO unzip (jar, temp, (name: String) =>
|
||||
(name startsWith "javax/") ||
|
||||
(name startsWith "org/")
|
||||
)
|
||||
(name startsWith "org/"))
|
||||
}
|
||||
|
||||
// include original war file
|
||||
val warFile = (Keys.`package`).value
|
||||
val warFile = (Keys.`package`).value
|
||||
IO unzip (warFile, temp)
|
||||
|
||||
// include launcher classes
|
||||
val classDir = (Keys.classDirectory in Compile).value
|
||||
val launchClasses = Seq("JettyLauncher.class" /*, "HttpsSupportConnector.class" */)
|
||||
val classDir = (Keys.classDirectory in Compile).value
|
||||
val launchClasses = Seq("JettyLauncher.class" /*, "HttpsSupportConnector.class" */ )
|
||||
launchClasses foreach { name =>
|
||||
IO copyFile (classDir / name, temp / name)
|
||||
}
|
||||
@@ -157,33 +170,34 @@ executableKey := {
|
||||
// include plugins
|
||||
val pluginsDir = temp / "WEB-INF" / "classes" / "plugins"
|
||||
IO createDirectory (pluginsDir)
|
||||
IO copyFile(Keys.baseDirectory.value / "plugins.json", pluginsDir / "plugins.json")
|
||||
IO copyFile (Keys.baseDirectory.value / "plugins.json", pluginsDir / "plugins.json")
|
||||
|
||||
val json = IO read(Keys.baseDirectory.value / "plugins.json")
|
||||
PluginsJson.parse(json).foreach { case (plugin, version, file) =>
|
||||
val url = s"https://github.com/gitbucket/${plugin}/releases/download/${version}/${file}"
|
||||
val json = IO read (Keys.baseDirectory.value / "plugins.json")
|
||||
PluginsJson.getUrls(json).foreach { url =>
|
||||
log info s"Download: ${url}"
|
||||
IO transfer(new java.net.URL(url).openStream, pluginsDir / file)
|
||||
IO transfer (new java.net.URL(url).openStream, pluginsDir / url.substring(url.lastIndexOf("/") + 1))
|
||||
}
|
||||
|
||||
// zip it up
|
||||
IO delete (temp / "META-INF" / "MANIFEST.MF")
|
||||
val contentMappings = (temp.allPaths --- PathFinder(temp)).get pair { file => IO.relativizeFile(temp, file) }
|
||||
val manifest = new JarManifest
|
||||
val contentMappings = (temp.allPaths --- PathFinder(temp)).get pair { file =>
|
||||
IO.relativizeFile(temp, file)
|
||||
}
|
||||
val manifest = new JarManifest
|
||||
manifest.getMainAttributes put (AttrName.MANIFEST_VERSION, "1.0")
|
||||
manifest.getMainAttributes put (AttrName.MAIN_CLASS, "JettyLauncher")
|
||||
val outputFile = workDir / warName
|
||||
IO jar (contentMappings.map { case (file, path) => (file, path.toString) } , outputFile, manifest)
|
||||
manifest.getMainAttributes put (AttrName.MAIN_CLASS, "JettyLauncher")
|
||||
val outputFile = workDir / warName
|
||||
IO jar (contentMappings.map { case (file, path) => (file, path.toString) }, outputFile, manifest)
|
||||
|
||||
// generate checksums
|
||||
Seq(
|
||||
"md5" -> "MD5",
|
||||
"sha1" -> "SHA-1",
|
||||
"md5" -> "MD5",
|
||||
"sha1" -> "SHA-1",
|
||||
"sha256" -> "SHA-256"
|
||||
)
|
||||
.foreach { case (extension, algorithm) =>
|
||||
val checksumFile = workDir / (warName + "." + extension)
|
||||
Checksums generate (outputFile, checksumFile, algorithm)
|
||||
).foreach {
|
||||
case (extension, algorithm) =>
|
||||
val checksumFile = workDir / (warName + "." + extension)
|
||||
Checksums generate (outputFile, checksumFile, algorithm)
|
||||
}
|
||||
|
||||
// done
|
||||
@@ -193,10 +207,12 @@ executableKey := {
|
||||
publishTo := {
|
||||
val nexus = "https://oss.sonatype.org/"
|
||||
if (version.value.trim.endsWith("SNAPSHOT")) Some("snapshots" at nexus + "content/repositories/snapshots")
|
||||
else Some("releases" at nexus + "service/local/staging/deploy/maven2")
|
||||
else Some("releases" at nexus + "service/local/staging/deploy/maven2")
|
||||
}
|
||||
publishMavenStyle := true
|
||||
pomIncludeRepository := { _ => false }
|
||||
pomIncludeRepository := { _ =>
|
||||
false
|
||||
}
|
||||
pomExtra := (
|
||||
<url>https://github.com/gitbucket/gitbucket</url>
|
||||
<licenses>
|
||||
|
||||
21
contrib/linux/redhat/selinux/gitbucket.te
Normal file
21
contrib/linux/redhat/selinux/gitbucket.te
Normal file
@@ -0,0 +1,21 @@
|
||||
module gitbucket 1.0;
|
||||
|
||||
require {
|
||||
type smtp_port_t;
|
||||
type tomcat_t;
|
||||
type tomcat_var_lib_t;
|
||||
type unreserved_port_t;
|
||||
|
||||
class file { execute };
|
||||
class tcp_socket { name_bind };
|
||||
class tcp_socket { name_connect };
|
||||
}
|
||||
|
||||
# allow tomcat to send emails
|
||||
allow tomcat_t smtp_port_t:tcp_socket { name_connect };
|
||||
|
||||
# allow file executes, required during repo creation
|
||||
allow tomcat_t tomcat_var_lib_t:file { execute };
|
||||
|
||||
# allow tomcat to serve repositories via SSH
|
||||
allow tomcat_t unreserved_port_t:tcp_socket { name_bind };
|
||||
32
contrib/linux/redhat/selinux/readme.md
Normal file
32
contrib/linux/redhat/selinux/readme.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# Red Hat Enterprise Linux / CentOS SELinux policy module for GitBucket
|
||||
|
||||
One way to run GitBucket on Enterprise Linux is under Tomcat. Since EL 7.4, Tomcat is no longer unconfined.
|
||||
Thus since 7.4, Enterprise Linux blocks certain operations that are required for GitBucket to work properly:
|
||||
|
||||
* Tomcat is not allowed to connect to SMTP ports, which is required to send email notifications.
|
||||
* Tomcat is not allowed to execute files, which is required for creating repositories.
|
||||
* Tomcat is not allowed to act as a server on unreserved ports, which is required for serving repositories via SSH.
|
||||
|
||||
To mitigate this, you can use the SELinux policy module provided as `gitbucket.te`. You can deploy the module with the
|
||||
attached script, e.g.:
|
||||
|
||||
~~~
|
||||
./sedeploy.sh gitbucket
|
||||
~~~
|
||||
|
||||
You most likely also need to fix file contexts on your system. Assuming a new, default Tomcat installation on 7.4, you
|
||||
can do so by issuing the following commands:
|
||||
|
||||
~~~
|
||||
GITBUCKET_HOME='/usr/share/tomcat/.gitbucket'
|
||||
mkdir -p ${GITBUCKET_HOME}
|
||||
chown tomcat.tomcat ${GITBUCKET_HOME}
|
||||
semanage fcontext -a -t tomcat_var_lib_t "${GITBUCKET_HOME}(/.*)?"
|
||||
restorecon -rv ${GITBUCKET_HOME}
|
||||
|
||||
JAVA_CONF='/usr/share/tomcat/.java'
|
||||
mkdir -p ${JAVA_CONF}
|
||||
chown tomcat.tomcat ${JAVA_CONF}
|
||||
semanage fcontext -a -t tomcat_cache_t "${JAVA_CONF}(/.*)?"
|
||||
restorecon -rv ${JAVA_CONF}
|
||||
~~~
|
||||
14
contrib/linux/redhat/selinux/sedeploy.sh
Executable file
14
contrib/linux/redhat/selinux/sedeploy.sh
Executable file
@@ -0,0 +1,14 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
MODULE=${1}
|
||||
|
||||
# this will create a .mod file
|
||||
checkmodule -M -m -o ${MODULE}.mod ${MODULE}.te
|
||||
|
||||
# this will create a compiled semodule
|
||||
semodule_package -m ${MODULE}.mod -o ${MODULE}.pp
|
||||
|
||||
# this will install the module
|
||||
semodule -i ${MODULE}.pp
|
||||
@@ -6,17 +6,22 @@ The details are saved at `ISSUE_COMMENT` table.
|
||||
To determine if it was any operation, you see the `ACTION` column.
|
||||
And in the case of some actions, `CONTENT` column value contains additional information.
|
||||
|
||||
|ACTION |CONTENT |
|
||||
|---------------|-----------------|
|
||||
|comment |comment |
|
||||
|close_comment |comment |
|
||||
|reopen_comment |comment |
|
||||
|close |"Close" |
|
||||
|reopen |"Reopen" |
|
||||
|commit |comment commitId |
|
||||
|merge |comment |
|
||||
|delete_branch |branchName |
|
||||
|refer |issueId:title |
|
||||
|ACTION |CONTENT |
|
||||
|----------------|----------------------|
|
||||
|comment |comment |
|
||||
|close_comment |comment |
|
||||
|reopen_comment |comment |
|
||||
|close |"Close" |
|
||||
|reopen |"Reopen" |
|
||||
|commit |comment commitId |
|
||||
|merge |comment |
|
||||
|delete_branch |branchName |
|
||||
|refer |issueId:title |
|
||||
|add_label |labelName |
|
||||
|delete_label |labelName |
|
||||
|change_priority |oldPriority:priority |
|
||||
|change_milestone|oldMilestone:milestone|
|
||||
|assign |oldAssigned:assigned |
|
||||
|
||||
### comment
|
||||
|
||||
@@ -54,3 +59,23 @@ Therefore, this comment is not displayed, and not counted as a comment.
|
||||
|
||||
This value is saved when other issue or issue comment contains reference to the issue like `#issueId`.
|
||||
At the same time, store id and title of the referrer issue as `id:title`.
|
||||
|
||||
### add_label
|
||||
|
||||
This value is saved when users have added the label.
|
||||
|
||||
### delete_label
|
||||
|
||||
This value is saved when users have deleted the label.
|
||||
|
||||
### change_priority
|
||||
|
||||
This value is saved when users have changed the priority.
|
||||
|
||||
### change_milestone
|
||||
|
||||
This value is saved when users have changed the milestone.
|
||||
|
||||
### assign
|
||||
|
||||
This value is saved when users have assign issue/PR to user or remove the assign.
|
||||
|
||||
@@ -28,17 +28,16 @@ You don't need to integrate with your IDE, since we're using sbt to do the servl
|
||||
Fortunately, the gitbucket project is already set up to use JRebel.
|
||||
You only need to tell jvm where to find the jrebel jar.
|
||||
|
||||
To do so, edit your shell resource file (usually `~/.bash_profile` on Mac, and `~/.bashrc` on Linux), and add the following line:
|
||||
To do so, edit your shell resource file (usually `~/.bash_profile` on Mac, and `~/.bashrc` on Linux) and set the environment variable `JREBEL`.
|
||||
For example, if you unzipped your JRebel download in your home directory, you would use:
|
||||
|
||||
```bash
|
||||
export JREBEL=/path/to/jrebel/legacy/jrebel.jar
|
||||
export JREBEL=~/jrebel/legacy/jrebel.jar # legacy agent
|
||||
export JREBEL=~/jrebel/lib/libjrebel64.dylib # new agent
|
||||
```
|
||||
|
||||
For example, if you unzipped your JRebel download in your home directory, you whould use:
|
||||
|
||||
```bash
|
||||
export JREBEL=~/jrebel/legacy/jrebel.jar
|
||||
```
|
||||
You can choose the legacy JRebel agent or the new one.
|
||||
See [the document](https://zeroturnaround.com/software/jrebel/jrebel7-agent-upgrade-cli/) for details.
|
||||
|
||||
Now reload your shell:
|
||||
|
||||
|
||||
27
plugins.json
27
plugins.json
@@ -5,9 +5,9 @@
|
||||
"description": "Provides notifications feature on GitBucket.",
|
||||
"versions": [
|
||||
{
|
||||
"version": "1.3.0",
|
||||
"range": ">=4.17.0",
|
||||
"file": "gitbucket-notifications-plugin_2.12-1.3.0.jar"
|
||||
"version": "1.5.0",
|
||||
"range": ">=4.23.0",
|
||||
"url": "https://github.com/gitbucket/gitbucket-notifications-plugin/releases/download/1.5.0/gitbucket-notifications-plugin-assembly-1.5.0.jar"
|
||||
}
|
||||
],
|
||||
"default": true
|
||||
@@ -20,7 +20,7 @@
|
||||
{
|
||||
"version": "4.5.0",
|
||||
"range": ">=4.18.0",
|
||||
"file": "gitbucket-emoji-plugin_2.12-4.5.0.jar"
|
||||
"url": "https://github.com/gitbucket/gitbucket-emoji-plugin/releases/download/4.5.0/gitbucket-emoji-plugin_2.12-4.5.0.jar"
|
||||
}
|
||||
],
|
||||
"default": false
|
||||
@@ -31,9 +31,22 @@
|
||||
"description": "Provides Gist feature on GitBucket.",
|
||||
"versions": [
|
||||
{
|
||||
"version": "4.11.0",
|
||||
"range": ">=4.19.0",
|
||||
"file": "gitbucket-gist-plugin-assembly-4.11.0.jar"
|
||||
"version": "4.14.0",
|
||||
"range": ">=4.23.0",
|
||||
"url": "https://github.com/gitbucket/gitbucket-gist-plugin/releases/download/4.14.0/gitbucket-gist-plugin-assembly-4.14.0.jar"
|
||||
}
|
||||
],
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"id": "pages",
|
||||
"name": "Pages Plugin",
|
||||
"description": "Project pages for gitbucket",
|
||||
"versions": [
|
||||
{
|
||||
"version": "1.7.0",
|
||||
"range": ">=4.23.0",
|
||||
"url": "https://github.com/gitbucket/gitbucket-pages-plugin/releases/download/v1.7.0/gitbucket-pages-plugin_2.12-1.7.0.jar"
|
||||
}
|
||||
],
|
||||
"default": false
|
||||
|
||||
@@ -6,29 +6,31 @@ import io._
|
||||
object Checksums {
|
||||
private val bufferSize = 2048
|
||||
|
||||
def generate(source:File, target:File, algorithm:String):Unit =
|
||||
sbt.IO write (target, compute(source, algorithm))
|
||||
def generate(source: File, target: File, algorithm: String): Unit =
|
||||
sbt.IO write (target, compute(source, algorithm))
|
||||
|
||||
def compute(file:File, algorithm:String):String =
|
||||
hex(raw(file, algorithm))
|
||||
def compute(file: File, algorithm: String): String =
|
||||
hex(raw(file, algorithm))
|
||||
|
||||
def raw(file:File, algorithm:String):Array[Byte] =
|
||||
(Using fileInputStream file) { is =>
|
||||
val md = MessageDigest getInstance algorithm
|
||||
val buf = new Array[Byte](bufferSize)
|
||||
md.reset()
|
||||
@tailrec
|
||||
def loop() {
|
||||
val len = is read buf
|
||||
if (len != -1) {
|
||||
md update (buf, 0, len)
|
||||
loop()
|
||||
}
|
||||
def raw(file: File, algorithm: String): Array[Byte] =
|
||||
(Using fileInputStream file) { is =>
|
||||
val md = MessageDigest getInstance algorithm
|
||||
val buf = new Array[Byte](bufferSize)
|
||||
md.reset()
|
||||
@tailrec
|
||||
def loop(): Unit = {
|
||||
val len = is read buf
|
||||
if (len != -1) {
|
||||
md update (buf, 0, len)
|
||||
loop()
|
||||
}
|
||||
loop()
|
||||
md.digest()
|
||||
}
|
||||
loop()
|
||||
md.digest()
|
||||
}
|
||||
|
||||
def hex(bytes:Array[Byte]):String =
|
||||
bytes map { it => "%02x" format (it.toInt & 0xff) } mkString ""
|
||||
def hex(bytes: Array[Byte]): String =
|
||||
bytes map { it =>
|
||||
"%02x" format (it.toInt & 0xff)
|
||||
} mkString ""
|
||||
}
|
||||
|
||||
@@ -3,19 +3,13 @@ import scala.collection.JavaConverters._
|
||||
|
||||
object PluginsJson {
|
||||
|
||||
def parse(json: String): Seq[(String, String, String)] = {
|
||||
def getUrls(json: String): Seq[String] = {
|
||||
val value = Json.parse(json)
|
||||
value.asArray.values.asScala.map { plugin =>
|
||||
val pluginObject = plugin.asObject
|
||||
val pluginName = "gitbucket-" + pluginObject.get("id").asString + "-plugin"
|
||||
|
||||
val latestVersionObject = pluginObject.get("versions").asArray.asScala.head.asObject
|
||||
val file = latestVersionObject.get("file").asString
|
||||
val version = latestVersionObject.get("version").asString
|
||||
|
||||
(pluginName, version, file)
|
||||
latestVersionObject.get("url").asString
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
sbt.version=1.0.4
|
||||
sbt.version=1.1.3
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
scalacOptions ++= Seq("-unchecked", "-deprecation", "-feature")
|
||||
|
||||
addSbtPlugin("com.typesafe.sbt" % "sbt-twirl" % "1.3.12")
|
||||
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.5")
|
||||
addSbtPlugin("com.geirsson" % "sbt-scalafmt" % "1.5.0")
|
||||
addSbtPlugin("com.typesafe.sbt" % "sbt-twirl" % "1.3.13")
|
||||
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.5")
|
||||
//addSbtPlugin("com.earldouglas" % "xsbt-web-plugin" % "4.0.0")
|
||||
//addSbtPlugin("fi.gekkio.sbtplugins" % "sbt-jrebel-plugin" % "0.10.0")
|
||||
addSbtPlugin("org.scalatra.sbt" % "sbt-scalatra" % "1.0.1")
|
||||
addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.1.0")
|
||||
addSbtPlugin("io.get-coursier" % "sbt-coursier" % "1.0.0-RC13")
|
||||
addSbtPlugin("com.typesafe.sbt" % "sbt-license-report" % "1.2.0")
|
||||
addSbtPlugin("org.scalatra.sbt" % "sbt-scalatra" % "1.0.1")
|
||||
addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.1.0")
|
||||
addSbtCoursier
|
||||
addSbtPlugin("com.typesafe.sbt" % "sbt-license-report" % "1.2.0")
|
||||
|
||||
@@ -1 +1 @@
|
||||
addSbtPlugin("io.get-coursier" % "sbt-coursier" % "1.0.0-RC11")
|
||||
addSbtPlugin("io.get-coursier" % "sbt-coursier" % "1.0.0")
|
||||
|
||||
39
src/main/resources/update/gitbucket-core_4.21.xml
Normal file
39
src/main/resources/update/gitbucket-core_4.21.xml
Normal file
@@ -0,0 +1,39 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<changeSet>
|
||||
<createTable tableName="RELEASE">
|
||||
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
|
||||
<column name="REPOSITORY_NAME" type="varchar(100)" nullable="false"/>
|
||||
<column name="TAG" type="varchar(100)" nullable="false"/>
|
||||
<column name="NAME" type="varchar(100)" nullable="false"/>
|
||||
<column name="AUTHOR" type="varchar(100)" nullable="false"/>
|
||||
<column name="CONTENT" type="text" nullable="true"/>
|
||||
<column name="REGISTERED_DATE" type="datetime" nullable="false"/>
|
||||
<column name="UPDATED_DATE" type="datetime" nullable="false"/>
|
||||
</createTable>
|
||||
|
||||
<addPrimaryKey constraintName="IDX_RELEASE_PK" tableName="RELEASE" columnNames="USER_NAME, REPOSITORY_NAME, TAG"/>
|
||||
<addForeignKeyConstraint constraintName="IDX_RELEASE_FK0" baseTableName="RELEASE" baseColumnNames="USER_NAME, REPOSITORY_NAME" referencedTableName="REPOSITORY" referencedColumnNames="USER_NAME, REPOSITORY_NAME"/>
|
||||
|
||||
<createTable tableName="RELEASE_ASSET">
|
||||
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
|
||||
<column name="REPOSITORY_NAME" type="varchar(100)" nullable="false"/>
|
||||
<column name="TAG" type="varchar(100)" nullable="false"/>
|
||||
<column name="RELEASE_ASSET_ID" type="int" nullable="false" autoIncrement="true" unique="true"/>
|
||||
<column name="FILE_NAME" type="varchar(260)" nullable="false"/>
|
||||
<column name="LABEL" type="varchar(100)" nullable="true"/>
|
||||
<column name="SIZE" type="bigint" nullable="false"/>
|
||||
<column name="UPLOADER" type="varchar(100)" nullable="false"/>
|
||||
<column name="REGISTERED_DATE" type="datetime" nullable="false"/>
|
||||
<column name="UPDATED_DATE" type="datetime" nullable="false"/>
|
||||
</createTable>
|
||||
<addPrimaryKey constraintName="IDX_RELEASE_ASSET_PK" tableName="RELEASE_ASSET" columnNames="USER_NAME, REPOSITORY_NAME, TAG, FILE_NAME"/>
|
||||
<addForeignKeyConstraint constraintName="IDX_RELEASE_ASSET_FK1" baseTableName="RELEASE_ASSET" baseColumnNames="USER_NAME, REPOSITORY_NAME, TAG" referencedTableName="RELEASE" referencedColumnNames="USER_NAME, REPOSITORY_NAME, TAG"/>
|
||||
|
||||
<createTable tableName="ACCOUNT_FEDERATION">
|
||||
<column name="ISSUER" type="varchar(100)" nullable="false"/>
|
||||
<column name="SUBJECT" type="varchar(100)" nullable="false"/>
|
||||
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
|
||||
</createTable>
|
||||
<addPrimaryKey constraintName="IDX_ACCOUNT_FEDERATION_PK" tableName="ACCOUNT_FEDERATION" columnNames="ISSUER, SUBJECT"/>
|
||||
<addForeignKeyConstraint constraintName="IDX_ACCOUNT_FEDERATION_FK0" baseTableName="ACCOUNT_FEDERATION" baseColumnNames="USER_NAME" referencedTableName="ACCOUNT" referencedColumnNames="USER_NAME"/>
|
||||
</changeSet>
|
||||
7
src/main/resources/update/gitbucket-core_4.22.xml
Normal file
7
src/main/resources/update/gitbucket-core_4.22.xml
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<changeSet>
|
||||
<addColumn tableName="REPOSITORY">
|
||||
<column name="MERGE_OPTIONS" type="varchar(200)" nullable="false" defaultValue="merge-commit,squash,rebase"/>
|
||||
<column name="DEFAULT_MERGE_OPTION" type="varchar(100)" nullable="false" defaultValue="merge-commit"/>
|
||||
</addColumn>
|
||||
</changeSet>
|
||||
14
src/main/resources/update/gitbucket-core_4.23.xml
Normal file
14
src/main/resources/update/gitbucket-core_4.23.xml
Normal file
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<changeSet>
|
||||
<dropForeignKeyConstraint baseTableName="RELEASE_ASSET" constraintName="IDX_RELEASE_ASSET_FK1"/>
|
||||
|
||||
<dropForeignKeyConstraint baseTableName="RELEASE" constraintName="IDX_RELEASE_FK0"/>
|
||||
<dropPrimaryKey tableName="RELEASE" constraintName="IDX_RELEASE_PK"/>
|
||||
|
||||
<renameTable newTableName="RELEASE_TAG" oldTableName="RELEASE" />
|
||||
|
||||
<addPrimaryKey constraintName="IDX_RELEASE_TAG_PK" tableName="RELEASE_TAG" columnNames="USER_NAME, REPOSITORY_NAME, TAG"/>
|
||||
<addForeignKeyConstraint constraintName="IDX_RELEASE_TAG_FK0" baseTableName="RELEASE_TAG" baseColumnNames="USER_NAME, REPOSITORY_NAME" referencedTableName="REPOSITORY" referencedColumnNames="USER_NAME, REPOSITORY_NAME"/>
|
||||
|
||||
<addForeignKeyConstraint constraintName="IDX_RELEASE_ASSET_FK0" baseTableName="RELEASE_ASSET" baseColumnNames="USER_NAME, REPOSITORY_NAME, TAG" referencedTableName="RELEASE_TAG" referencedColumnNames="USER_NAME, REPOSITORY_NAME, TAG"/>
|
||||
</changeSet>
|
||||
10
src/main/resources/update/gitbucket-core_4.24.xml
Normal file
10
src/main/resources/update/gitbucket-core_4.24.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<changeSet>
|
||||
<createTable tableName="ACCOUNT_EXTRA_MAIL_ADDRESS">
|
||||
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
|
||||
<column name="EXTRA_MAIL_ADDRESS" type="varchar(100)" nullable="false"/>
|
||||
</createTable>
|
||||
|
||||
<addPrimaryKey constraintName="IDX_ACCOUNT_EXTRA_MAIL_ADDRESS_PK" tableName="ACCOUNT_EXTRA_MAIL_ADDRESS" columnNames="USER_NAME, EXTRA_MAIL_ADDRESS"/>
|
||||
<addUniqueConstraint constraintName="IDX_ACCOUNT_EXTRA_MAIL_ADDRESS_1" tableName="ACCOUNT_EXTRA_MAIL_ADDRESS" columnNames="EXTRA_MAIL_ADDRESS"/>
|
||||
</changeSet>
|
||||
@@ -1,36 +1,42 @@
|
||||
|
||||
import java.util.EnumSet
|
||||
import javax.servlet._
|
||||
|
||||
import gitbucket.core.controller._
|
||||
import gitbucket.core.controller.{ReleaseController, _}
|
||||
import gitbucket.core.plugin.PluginRegistry
|
||||
import gitbucket.core.service.SystemSettingsService
|
||||
import gitbucket.core.servlet._
|
||||
import gitbucket.core.util.Directory
|
||||
import org.scalatra._
|
||||
|
||||
|
||||
class ScalatraBootstrap extends LifeCycle with SystemSettingsService {
|
||||
override def init(context: ServletContext) {
|
||||
override def init(context: ServletContext): Unit = {
|
||||
|
||||
val settings = loadSystemSettings()
|
||||
if(settings.baseUrl.exists(_.startsWith("https://"))) {
|
||||
if (settings.baseUrl.exists(_.startsWith("https://"))) {
|
||||
context.getSessionCookieConfig.setSecure(true)
|
||||
}
|
||||
|
||||
// Register TransactionFilter and BasicAuthenticationFilter at first
|
||||
context.addFilter("transactionFilter", new TransactionFilter)
|
||||
context.getFilterRegistration("transactionFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/*")
|
||||
context
|
||||
.getFilterRegistration("transactionFilter")
|
||||
.addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/*")
|
||||
context.addFilter("gitAuthenticationFilter", new GitAuthenticationFilter)
|
||||
context.getFilterRegistration("gitAuthenticationFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/git/*")
|
||||
context
|
||||
.getFilterRegistration("gitAuthenticationFilter")
|
||||
.addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/git/*")
|
||||
context.addFilter("apiAuthenticationFilter", new ApiAuthenticationFilter)
|
||||
context.getFilterRegistration("apiAuthenticationFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/api/v3/*")
|
||||
context
|
||||
.getFilterRegistration("apiAuthenticationFilter")
|
||||
.addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/api/*")
|
||||
|
||||
// Register controllers
|
||||
context.mount(new PreProcessController, "/*")
|
||||
|
||||
context.addFilter("pluginControllerFilter", new PluginControllerFilter)
|
||||
context.getFilterRegistration("pluginControllerFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/*")
|
||||
context
|
||||
.getFilterRegistration("pluginControllerFilter")
|
||||
.addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/*")
|
||||
|
||||
context.mount(new FileUploadController, "/upload")
|
||||
|
||||
@@ -47,14 +53,17 @@ class ScalatraBootstrap extends LifeCycle with SystemSettingsService {
|
||||
filter.mount(new MilestonesController, "/*")
|
||||
filter.mount(new IssuesController, "/*")
|
||||
filter.mount(new PullRequestsController, "/*")
|
||||
filter.mount(new ReleaseController, "/*")
|
||||
filter.mount(new RepositorySettingsController, "/*")
|
||||
|
||||
context.addFilter("compositeScalatraFilter", filter)
|
||||
context.getFilterRegistration("compositeScalatraFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/*")
|
||||
context
|
||||
.getFilterRegistration("compositeScalatraFilter")
|
||||
.addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/*")
|
||||
|
||||
// Create GITBUCKET_HOME directory if it does not exist
|
||||
val dir = new java.io.File(Directory.GitBucketHome)
|
||||
if(!dir.exists){
|
||||
if (!dir.exists) {
|
||||
dir.mkdirs()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,46 +3,54 @@ package gitbucket.core
|
||||
import io.github.gitbucket.solidbase.migration.{SqlMigration, LiquibaseMigration}
|
||||
import io.github.gitbucket.solidbase.model.{Version, Module}
|
||||
|
||||
object GitBucketCoreModule extends Module("gitbucket-core",
|
||||
new Version("4.0.0",
|
||||
new LiquibaseMigration("update/gitbucket-core_4.0.xml"),
|
||||
new SqlMigration("update/gitbucket-core_4.0.sql")
|
||||
),
|
||||
new Version("4.1.0"),
|
||||
new Version("4.2.0",
|
||||
new LiquibaseMigration("update/gitbucket-core_4.2.xml")
|
||||
),
|
||||
new Version("4.2.1"),
|
||||
new Version("4.3.0"),
|
||||
new Version("4.4.0"),
|
||||
new Version("4.5.0"),
|
||||
new Version("4.6.0",
|
||||
new LiquibaseMigration("update/gitbucket-core_4.6.xml")
|
||||
),
|
||||
new Version("4.7.0",
|
||||
new LiquibaseMigration("update/gitbucket-core_4.7.xml"),
|
||||
new SqlMigration("update/gitbucket-core_4.7.sql")
|
||||
),
|
||||
new Version("4.7.1"),
|
||||
new Version("4.8"),
|
||||
new Version("4.9.0",
|
||||
new LiquibaseMigration("update/gitbucket-core_4.9.xml")
|
||||
),
|
||||
new Version("4.10.0"),
|
||||
new Version("4.11.0",
|
||||
new LiquibaseMigration("update/gitbucket-core_4.11.xml")
|
||||
),
|
||||
new Version("4.12.0"),
|
||||
new Version("4.12.1"),
|
||||
new Version("4.13.0"),
|
||||
new Version("4.14.0",
|
||||
new LiquibaseMigration("update/gitbucket-core_4.14.xml"),
|
||||
new SqlMigration("update/gitbucket-core_4.14.sql")
|
||||
),
|
||||
new Version("4.14.1"),
|
||||
new Version("4.15.0"),
|
||||
new Version("4.16.0"),
|
||||
new Version("4.17.0"),
|
||||
new Version("4.18.0"),
|
||||
new Version("4.19.0")
|
||||
)
|
||||
object GitBucketCoreModule
|
||||
extends Module(
|
||||
"gitbucket-core",
|
||||
new Version(
|
||||
"4.0.0",
|
||||
new LiquibaseMigration("update/gitbucket-core_4.0.xml"),
|
||||
new SqlMigration("update/gitbucket-core_4.0.sql")
|
||||
),
|
||||
new Version("4.1.0"),
|
||||
new Version("4.2.0", new LiquibaseMigration("update/gitbucket-core_4.2.xml")),
|
||||
new Version("4.2.1"),
|
||||
new Version("4.3.0"),
|
||||
new Version("4.4.0"),
|
||||
new Version("4.5.0"),
|
||||
new Version("4.6.0", new LiquibaseMigration("update/gitbucket-core_4.6.xml")),
|
||||
new Version(
|
||||
"4.7.0",
|
||||
new LiquibaseMigration("update/gitbucket-core_4.7.xml"),
|
||||
new SqlMigration("update/gitbucket-core_4.7.sql")
|
||||
),
|
||||
new Version("4.7.1"),
|
||||
new Version("4.8"),
|
||||
new Version("4.9.0", new LiquibaseMigration("update/gitbucket-core_4.9.xml")),
|
||||
new Version("4.10.0"),
|
||||
new Version("4.11.0", new LiquibaseMigration("update/gitbucket-core_4.11.xml")),
|
||||
new Version("4.12.0"),
|
||||
new Version("4.12.1"),
|
||||
new Version("4.13.0"),
|
||||
new Version(
|
||||
"4.14.0",
|
||||
new LiquibaseMigration("update/gitbucket-core_4.14.xml"),
|
||||
new SqlMigration("update/gitbucket-core_4.14.sql")
|
||||
),
|
||||
new Version("4.14.1"),
|
||||
new Version("4.15.0"),
|
||||
new Version("4.16.0"),
|
||||
new Version("4.17.0"),
|
||||
new Version("4.18.0"),
|
||||
new Version("4.19.0"),
|
||||
new Version("4.19.1"),
|
||||
new Version("4.19.2"),
|
||||
new Version("4.19.3"),
|
||||
new Version("4.20.0"),
|
||||
new Version("4.21.0", new LiquibaseMigration("update/gitbucket-core_4.21.xml")),
|
||||
new Version("4.21.1"),
|
||||
new Version("4.21.2"),
|
||||
new Version("4.22.0", new LiquibaseMigration("update/gitbucket-core_4.22.xml")),
|
||||
new Version("4.23.0", new LiquibaseMigration("update/gitbucket-core_4.23.xml")),
|
||||
new Version("4.23.1"),
|
||||
new Version("4.24.0", new LiquibaseMigration("update/gitbucket-core_4.24.xml"))
|
||||
)
|
||||
|
||||
@@ -6,13 +6,14 @@ import gitbucket.core.util.RepositoryName
|
||||
* https://developer.github.com/v3/repos/#get-branch
|
||||
* https://developer.github.com/v3/repos/#enabling-and-disabling-branch-protection
|
||||
*/
|
||||
case class ApiBranch(
|
||||
name: String,
|
||||
commit: ApiBranchCommit,
|
||||
protection: ApiBranchProtection)(repositoryName:RepositoryName) extends FieldSerializable {
|
||||
def _links = Map(
|
||||
"self" -> ApiPath(s"/api/v3/repos/${repositoryName.fullName}/branches/${name}"),
|
||||
"html" -> ApiPath(s"/${repositoryName.fullName}/tree/${name}"))
|
||||
case class ApiBranch(name: String, commit: ApiBranchCommit, protection: ApiBranchProtection)(
|
||||
repositoryName: RepositoryName
|
||||
) extends FieldSerializable {
|
||||
def _links =
|
||||
Map(
|
||||
"self" -> ApiPath(s"/api/v3/repos/${repositoryName.fullName}/branches/${name}"),
|
||||
"html" -> ApiPath(s"/${repositoryName.fullName}/tree/${name}")
|
||||
)
|
||||
}
|
||||
|
||||
case class ApiBranchCommit(sha: String)
|
||||
|
||||
@@ -4,17 +4,22 @@ import gitbucket.core.service.ProtectedBranchService
|
||||
import org.json4s._
|
||||
|
||||
/** https://developer.github.com/v3/repos/#enabling-and-disabling-branch-protection */
|
||||
case class ApiBranchProtection(enabled: Boolean, required_status_checks: Option[ApiBranchProtection.Status]){
|
||||
case class ApiBranchProtection(enabled: Boolean, required_status_checks: Option[ApiBranchProtection.Status]) {
|
||||
def status: ApiBranchProtection.Status = required_status_checks.getOrElse(ApiBranchProtection.statusNone)
|
||||
}
|
||||
|
||||
object ApiBranchProtection{
|
||||
object ApiBranchProtection {
|
||||
|
||||
/** form for enabling-and-disabling-branch-protection */
|
||||
case class EnablingAndDisabling(protection: ApiBranchProtection)
|
||||
|
||||
def apply(info: ProtectedBranchService.ProtectedBranchInfo): ApiBranchProtection = ApiBranchProtection(
|
||||
enabled = info.enabled,
|
||||
required_status_checks = Some(Status(EnforcementLevel(info.enabled && info.contexts.nonEmpty, info.includeAdministrators), info.contexts)))
|
||||
def apply(info: ProtectedBranchService.ProtectedBranchInfo): ApiBranchProtection =
|
||||
ApiBranchProtection(
|
||||
enabled = info.enabled,
|
||||
required_status_checks = Some(
|
||||
Status(EnforcementLevel(info.enabled && info.contexts.nonEmpty, info.includeAdministrators), info.contexts)
|
||||
)
|
||||
)
|
||||
val statusNone = Status(Off, Seq.empty)
|
||||
case class Status(enforcement_level: EnforcementLevel, contexts: Seq[String])
|
||||
sealed class EnforcementLevel(val name: String)
|
||||
@@ -22,25 +27,28 @@ object ApiBranchProtection{
|
||||
case object NonAdmins extends EnforcementLevel("non_admins")
|
||||
case object Everyone extends EnforcementLevel("everyone")
|
||||
object EnforcementLevel {
|
||||
def apply(enabled: Boolean, includeAdministrators: Boolean): EnforcementLevel = if(enabled){
|
||||
if(includeAdministrators){
|
||||
Everyone
|
||||
}else{
|
||||
NonAdmins
|
||||
def apply(enabled: Boolean, includeAdministrators: Boolean): EnforcementLevel =
|
||||
if (enabled) {
|
||||
if (includeAdministrators) {
|
||||
Everyone
|
||||
} else {
|
||||
NonAdmins
|
||||
}
|
||||
} else {
|
||||
Off
|
||||
}
|
||||
}else{
|
||||
Off
|
||||
}
|
||||
}
|
||||
|
||||
implicit val enforcementLevelSerializer = new CustomSerializer[EnforcementLevel](format => (
|
||||
{
|
||||
case JString("off") => Off
|
||||
case JString("non_admins") => NonAdmins
|
||||
case JString("everyone") => Everyone
|
||||
},
|
||||
{
|
||||
case x: EnforcementLevel => JString(x.name)
|
||||
}
|
||||
))
|
||||
implicit val enforcementLevelSerializer = new CustomSerializer[EnforcementLevel](
|
||||
format =>
|
||||
(
|
||||
{
|
||||
case JString("off") => Off
|
||||
case JString("non_admins") => NonAdmins
|
||||
case JString("everyone") => Everyone
|
||||
}, {
|
||||
case x: EnforcementLevel => JString(x.name)
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package gitbucket.core.api
|
||||
|
||||
import gitbucket.core.model.{Account, CommitState, CommitStatus}
|
||||
|
||||
|
||||
/**
|
||||
* https://developer.github.com/v3/repos/statuses/#get-the-combined-status-for-a-specific-ref
|
||||
*/
|
||||
@@ -11,15 +10,22 @@ case class ApiCombinedCommitStatus(
|
||||
sha: String,
|
||||
total_count: Int,
|
||||
statuses: Iterable[ApiCommitStatus],
|
||||
repository: ApiRepository){
|
||||
repository: ApiRepository
|
||||
) {
|
||||
// val commit_url = ApiPath(s"/api/v3/repos/${repository.full_name}/${sha}")
|
||||
val url = ApiPath(s"/api/v3/repos/${repository.full_name}/commits/${sha}/status")
|
||||
}
|
||||
object ApiCombinedCommitStatus {
|
||||
def apply(sha:String, statuses: Iterable[(CommitStatus, Account)], repository:ApiRepository): ApiCombinedCommitStatus = ApiCombinedCommitStatus(
|
||||
state = CommitState.combine(statuses.map(_._1.state).toSet).name,
|
||||
sha = sha,
|
||||
total_count= statuses.size,
|
||||
statuses = statuses.map{ case (s, a)=> ApiCommitStatus(s, ApiUser(a)) },
|
||||
repository = repository)
|
||||
def apply(
|
||||
sha: String,
|
||||
statuses: Iterable[(CommitStatus, Account)],
|
||||
repository: ApiRepository
|
||||
): ApiCombinedCommitStatus =
|
||||
ApiCombinedCommitStatus(
|
||||
state = CommitState.combine(statuses.map(_._1.state).toSet).name,
|
||||
sha = sha,
|
||||
total_count = statuses.size,
|
||||
statuses = statuses.map { case (s, a) => ApiCommitStatus(s, ApiUser(a)) },
|
||||
repository = repository
|
||||
)
|
||||
}
|
||||
|
||||
@@ -5,25 +5,32 @@ import gitbucket.core.util.RepositoryName
|
||||
|
||||
import java.util.Date
|
||||
|
||||
|
||||
/**
|
||||
* https://developer.github.com/v3/issues/comments/
|
||||
*/
|
||||
case class ApiComment(
|
||||
id: Int,
|
||||
user: ApiUser,
|
||||
body: String,
|
||||
created_at: Date,
|
||||
updated_at: Date)(repositoryName: RepositoryName, issueId: Int, isPullRequest: Boolean){
|
||||
val html_url = ApiPath(s"/${repositoryName.fullName}/${if(isPullRequest){ "pull" }else{ "issues" }}/${issueId}#comment-${id}")
|
||||
case class ApiComment(id: Int, user: ApiUser, body: String, created_at: Date, updated_at: Date)(
|
||||
repositoryName: RepositoryName,
|
||||
issueId: Int,
|
||||
isPullRequest: Boolean
|
||||
) {
|
||||
val html_url = ApiPath(
|
||||
s"/${repositoryName.fullName}/${if (isPullRequest) { "pull" } else { "issues" }}/${issueId}#comment-${id}"
|
||||
)
|
||||
}
|
||||
|
||||
object ApiComment{
|
||||
def apply(comment: IssueComment, repositoryName: RepositoryName, issueId: Int, user: ApiUser, isPullRequest: Boolean): ApiComment =
|
||||
object ApiComment {
|
||||
def apply(
|
||||
comment: IssueComment,
|
||||
repositoryName: RepositoryName,
|
||||
issueId: Int,
|
||||
user: ApiUser,
|
||||
isPullRequest: Boolean
|
||||
): ApiComment =
|
||||
ApiComment(
|
||||
id = comment.commentId,
|
||||
user = user,
|
||||
body = comment.content,
|
||||
created_at = comment.registeredDate,
|
||||
updated_at = comment.updatedDate)(repositoryName, issueId, isPullRequest)
|
||||
updated_at = comment.updatedDate
|
||||
)(repositoryName, issueId, isPullRequest)
|
||||
}
|
||||
|
||||
@@ -20,38 +20,41 @@ case class ApiCommit(
|
||||
removed: List[String],
|
||||
modified: List[String],
|
||||
author: ApiPersonIdent,
|
||||
committer: ApiPersonIdent)(repositoryName:RepositoryName, urlIsHtmlUrl: Boolean) extends FieldSerializable{
|
||||
val url = if(urlIsHtmlUrl){
|
||||
committer: ApiPersonIdent
|
||||
)(repositoryName: RepositoryName, urlIsHtmlUrl: Boolean)
|
||||
extends FieldSerializable {
|
||||
val url = if (urlIsHtmlUrl) {
|
||||
ApiPath(s"/${repositoryName.fullName}/commit/${id}")
|
||||
}else{
|
||||
} else {
|
||||
ApiPath(s"/api/v3/${repositoryName.fullName}/commits/${id}")
|
||||
}
|
||||
val html_url = if(urlIsHtmlUrl){
|
||||
val html_url = if (urlIsHtmlUrl) {
|
||||
None
|
||||
}else{
|
||||
} else {
|
||||
Some(ApiPath(s"/${repositoryName.fullName}/commit/${id}"))
|
||||
}
|
||||
}
|
||||
|
||||
object ApiCommit{
|
||||
object ApiCommit {
|
||||
def apply(git: Git, repositoryName: RepositoryName, commit: CommitInfo, urlIsHtmlUrl: Boolean = false): ApiCommit = {
|
||||
val diffs = JGitUtil.getDiffs(git, commit.id, false)
|
||||
val diffs = JGitUtil.getDiffs(git, None, commit.id, false, false)
|
||||
ApiCommit(
|
||||
id = commit.id,
|
||||
message = commit.fullMessage,
|
||||
id = commit.id,
|
||||
message = commit.fullMessage,
|
||||
timestamp = commit.commitTime,
|
||||
added = diffs._1.collect {
|
||||
case x if x.changeType == DiffEntry.ChangeType.ADD => x.newPath
|
||||
added = diffs.collect {
|
||||
case x if x.changeType == DiffEntry.ChangeType.ADD => x.newPath
|
||||
},
|
||||
removed = diffs._1.collect {
|
||||
removed = diffs.collect {
|
||||
case x if x.changeType == DiffEntry.ChangeType.DELETE => x.oldPath
|
||||
},
|
||||
modified = diffs._1.collect {
|
||||
modified = diffs.collect {
|
||||
case x if x.changeType != DiffEntry.ChangeType.ADD && x.changeType != DiffEntry.ChangeType.DELETE => x.newPath
|
||||
},
|
||||
author = ApiPersonIdent.author(commit),
|
||||
author = ApiPersonIdent.author(commit),
|
||||
committer = ApiPersonIdent.committer(commit)
|
||||
)(repositoryName, urlIsHtmlUrl)
|
||||
}
|
||||
def forPushPayload(git: Git, repositoryName: RepositoryName, commit: CommitInfo): ApiCommit = apply(git, repositoryName, commit, true)
|
||||
def forWebhookPayload(git: Git, repositoryName: RepositoryName, commit: CommitInfo): ApiCommit =
|
||||
apply(git, repositoryName, commit, true)
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import gitbucket.core.api.ApiCommitListItem._
|
||||
import gitbucket.core.util.JGitUtil.CommitInfo
|
||||
import gitbucket.core.util.RepositoryName
|
||||
|
||||
|
||||
/**
|
||||
* https://developer.github.com/v3/repos/commits/
|
||||
*/
|
||||
@@ -13,30 +12,33 @@ case class ApiCommitListItem(
|
||||
commit: Commit,
|
||||
author: Option[ApiUser],
|
||||
committer: Option[ApiUser],
|
||||
parents: Seq[Parent])(repositoryName: RepositoryName) {
|
||||
parents: Seq[Parent]
|
||||
)(repositoryName: RepositoryName) {
|
||||
val url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/commits/${sha}")
|
||||
}
|
||||
|
||||
object ApiCommitListItem {
|
||||
def apply(commit: CommitInfo, repositoryName: RepositoryName): ApiCommitListItem = ApiCommitListItem(
|
||||
sha = commit.id,
|
||||
commit = Commit(
|
||||
message = commit.fullMessage,
|
||||
author = ApiPersonIdent.author(commit),
|
||||
committer = ApiPersonIdent.committer(commit)
|
||||
def apply(commit: CommitInfo, repositoryName: RepositoryName): ApiCommitListItem =
|
||||
ApiCommitListItem(
|
||||
sha = commit.id,
|
||||
commit = Commit(
|
||||
message = commit.fullMessage,
|
||||
author = ApiPersonIdent.author(commit),
|
||||
committer = ApiPersonIdent.committer(commit)
|
||||
)(commit.id, repositoryName),
|
||||
author = None,
|
||||
committer = None,
|
||||
parents = commit.parents.map(Parent(_)(repositoryName)))(repositoryName)
|
||||
author = None,
|
||||
committer = None,
|
||||
parents = commit.parents.map(Parent(_)(repositoryName))
|
||||
)(repositoryName)
|
||||
|
||||
case class Parent(sha: String)(repositoryName: RepositoryName){
|
||||
case class Parent(sha: String)(repositoryName: RepositoryName) {
|
||||
val url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/commits/${sha}")
|
||||
}
|
||||
|
||||
case class Commit(
|
||||
message: String,
|
||||
author: ApiPersonIdent,
|
||||
committer: ApiPersonIdent)(sha:String, repositoryName: RepositoryName) {
|
||||
case class Commit(message: String, author: ApiPersonIdent, committer: ApiPersonIdent)(
|
||||
sha: String,
|
||||
repositoryName: RepositoryName
|
||||
) {
|
||||
val url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/git/commits/${sha}")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import gitbucket.core.util.RepositoryName
|
||||
|
||||
import java.util.Date
|
||||
|
||||
|
||||
/**
|
||||
* https://developer.github.com/v3/repos/statuses/#create-a-status
|
||||
* https://developer.github.com/v3/repos/statuses/#list-statuses-for-a-specific-ref
|
||||
@@ -23,16 +22,16 @@ case class ApiCommitStatus(
|
||||
val url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/commits/${sha}/statuses")
|
||||
}
|
||||
|
||||
|
||||
object ApiCommitStatus {
|
||||
def apply(status: CommitStatus, creator:ApiUser): ApiCommitStatus = ApiCommitStatus(
|
||||
created_at = status.registeredDate,
|
||||
updated_at = status.updatedDate,
|
||||
state = status.state.name,
|
||||
target_url = status.targetUrl,
|
||||
description= status.description,
|
||||
id = status.commitStatusId,
|
||||
context = status.context,
|
||||
creator = creator
|
||||
)(status.commitId, RepositoryName(status))
|
||||
def apply(status: CommitStatus, creator: ApiUser): ApiCommitStatus =
|
||||
ApiCommitStatus(
|
||||
created_at = status.registeredDate,
|
||||
updated_at = status.updatedDate,
|
||||
state = status.state.name,
|
||||
target_url = status.targetUrl,
|
||||
description = status.description,
|
||||
id = status.commitStatusId,
|
||||
context = status.context,
|
||||
creator = creator
|
||||
)(status.commitId, RepositoryName(status))
|
||||
}
|
||||
|
||||
@@ -51,20 +51,25 @@ object ApiCommits {
|
||||
patch: String
|
||||
)
|
||||
|
||||
|
||||
def apply(repositoryName: RepositoryName, commitInfo: CommitInfo, diffs: Seq[DiffInfo], author: Account, committer: Account,
|
||||
commentCount: Int): ApiCommits = {
|
||||
def apply(
|
||||
repositoryName: RepositoryName,
|
||||
commitInfo: CommitInfo,
|
||||
diffs: Seq[DiffInfo],
|
||||
author: Account,
|
||||
committer: Account,
|
||||
commentCount: Int
|
||||
): ApiCommits = {
|
||||
val files = diffs.map { diff =>
|
||||
var additions = 0
|
||||
var deletions = 0
|
||||
|
||||
diff.patch.getOrElse("").split("\n").foreach { line =>
|
||||
if(line.startsWith("+")) additions = additions + 1
|
||||
if(line.startsWith("-")) deletions = deletions + 1
|
||||
if (line.startsWith("+")) additions = additions + 1
|
||||
if (line.startsWith("-")) deletions = deletions + 1
|
||||
}
|
||||
|
||||
File(
|
||||
filename = if(diff.changeType == ChangeType.DELETE){ diff.oldPath } else { diff.newPath },
|
||||
filename = if (diff.changeType == ChangeType.DELETE) { diff.oldPath } else { diff.newPath },
|
||||
additions = additions,
|
||||
deletions = deletions,
|
||||
changes = additions + deletions,
|
||||
@@ -75,12 +80,12 @@ object ApiCommits {
|
||||
case ChangeType.RENAME => "renamed"
|
||||
case ChangeType.COPY => "copied"
|
||||
},
|
||||
raw_url = if(diff.changeType == ChangeType.DELETE){
|
||||
raw_url = if (diff.changeType == ChangeType.DELETE) {
|
||||
ApiPath(s"/${repositoryName.fullName}/raw/${commitInfo.parents.head}/${diff.oldPath}")
|
||||
} else {
|
||||
ApiPath(s"/${repositoryName.fullName}/raw/${commitInfo.id}/${diff.newPath}")
|
||||
},
|
||||
blob_url = if(diff.changeType == ChangeType.DELETE){
|
||||
blob_url = if (diff.changeType == ChangeType.DELETE) {
|
||||
ApiPath(s"/${repositoryName.fullName}/blob/${commitInfo.parents.head}/${diff.oldPath}")
|
||||
} else {
|
||||
ApiPath(s"/${repositoryName.fullName}/blob/${commitInfo.id}/${diff.newPath}")
|
||||
|
||||
@@ -11,18 +11,29 @@ case class ApiContents(
|
||||
path: String,
|
||||
sha: String,
|
||||
content: Option[String],
|
||||
encoding: Option[String])(repositoryName: RepositoryName){
|
||||
encoding: Option[String]
|
||||
)(repositoryName: RepositoryName) {
|
||||
val download_url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/raw/${sha}/${path}")
|
||||
}
|
||||
|
||||
object ApiContents{
|
||||
object ApiContents {
|
||||
def apply(fileInfo: FileInfo, repositoryName: RepositoryName, content: Option[Array[Byte]]): ApiContents = {
|
||||
if(fileInfo.isDirectory) {
|
||||
if (fileInfo.isDirectory) {
|
||||
ApiContents("dir", fileInfo.name, fileInfo.path, fileInfo.commitId, None, None)(repositoryName)
|
||||
} else {
|
||||
content.map(arr =>
|
||||
ApiContents("file", fileInfo.name, fileInfo.path, fileInfo.commitId, Some(Base64.getEncoder.encodeToString(arr)), Some("base64"))(repositoryName)
|
||||
).getOrElse(ApiContents("file", fileInfo.name, fileInfo.path, fileInfo.commitId, None, None)(repositoryName))
|
||||
content
|
||||
.map(
|
||||
arr =>
|
||||
ApiContents(
|
||||
"file",
|
||||
fileInfo.name,
|
||||
fileInfo.path,
|
||||
fileInfo.commitId,
|
||||
Some(Base64.getEncoder.encodeToString(arr)),
|
||||
Some("base64")
|
||||
)(repositoryName)
|
||||
)
|
||||
.getOrElse(ApiContents("file", fileInfo.name, fileInfo.path, fileInfo.commitId, None, None)(repositoryName))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
package gitbucket.core.api
|
||||
|
||||
case class ApiEndPoint(rate_limit_url: ApiPath = ApiPath("/api/v3/rate_limit"))
|
||||
case class ApiEndPoint(rate_limit_url: ApiPath = ApiPath("/api/v3/rate_limit"))
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
package gitbucket.core.api
|
||||
|
||||
case class ApiError(
|
||||
message: String,
|
||||
documentation_url: Option[String] = None)
|
||||
case class ApiError(message: String, documentation_url: Option[String] = None)
|
||||
|
||||
@@ -5,7 +5,6 @@ import gitbucket.core.util.RepositoryName
|
||||
|
||||
import java.util.Date
|
||||
|
||||
|
||||
/**
|
||||
* https://developer.github.com/v3/issues/
|
||||
*/
|
||||
@@ -13,33 +12,39 @@ case class ApiIssue(
|
||||
number: Int,
|
||||
title: String,
|
||||
user: ApiUser,
|
||||
// labels,
|
||||
labels: List[ApiLabel],
|
||||
state: String,
|
||||
created_at: Date,
|
||||
updated_at: Date,
|
||||
body: String)(repositoryName: RepositoryName, isPullRequest: Boolean){
|
||||
body: String
|
||||
)(repositoryName: RepositoryName, isPullRequest: Boolean) {
|
||||
val id = 0 // dummy id
|
||||
val comments_url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/issues/${number}/comments")
|
||||
val html_url = ApiPath(s"/${repositoryName.fullName}/${if(isPullRequest){ "pull" }else{ "issues" }}/${number}")
|
||||
val html_url = ApiPath(s"/${repositoryName.fullName}/${if (isPullRequest) { "pull" } else { "issues" }}/${number}")
|
||||
val pull_request = if (isPullRequest) {
|
||||
Some(Map(
|
||||
"url" -> ApiPath(s"/api/v3/repos/${repositoryName.fullName}/pulls/${number}"),
|
||||
"html_url" -> ApiPath(s"/${repositoryName.fullName}/pull/${number}")
|
||||
// "diff_url" -> ApiPath(s"/${repositoryName.fullName}/pull/${number}.diff"),
|
||||
// "patch_url" -> ApiPath(s"/${repositoryName.fullName}/pull/${number}.patch")
|
||||
))
|
||||
Some(
|
||||
Map(
|
||||
"url" -> ApiPath(s"/api/v3/repos/${repositoryName.fullName}/pulls/${number}"),
|
||||
"html_url" -> ApiPath(s"/${repositoryName.fullName}/pull/${number}")
|
||||
// "diff_url" -> ApiPath(s"/${repositoryName.fullName}/pull/${number}.diff"),
|
||||
// "patch_url" -> ApiPath(s"/${repositoryName.fullName}/pull/${number}.patch")
|
||||
)
|
||||
)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
object ApiIssue{
|
||||
def apply(issue: Issue, repositoryName: RepositoryName, user: ApiUser): ApiIssue =
|
||||
object ApiIssue {
|
||||
def apply(issue: Issue, repositoryName: RepositoryName, user: ApiUser, labels: List[ApiLabel]): ApiIssue =
|
||||
ApiIssue(
|
||||
number = issue.issueId,
|
||||
title = issue.title,
|
||||
user = user,
|
||||
state = if(issue.closed){ "closed" }else{ "open" },
|
||||
body = issue.content.getOrElse(""),
|
||||
title = issue.title,
|
||||
user = user,
|
||||
labels = labels,
|
||||
state = if (issue.closed) { "closed" } else { "open" },
|
||||
body = issue.content.getOrElse(""),
|
||||
created_at = issue.registeredDate,
|
||||
updated_at = issue.updatedDate)(repositoryName, issue.isPullRequest)
|
||||
updated_at = issue.updatedDate
|
||||
)(repositoryName, issue.isPullRequest)
|
||||
}
|
||||
|
||||
@@ -4,18 +4,16 @@ import gitbucket.core.model.Label
|
||||
import gitbucket.core.util.RepositoryName
|
||||
|
||||
/**
|
||||
* https://developer.github.com/v3/issues/labels/
|
||||
*/
|
||||
case class ApiLabel(
|
||||
name: String,
|
||||
color: String)(repositoryName: RepositoryName){
|
||||
* https://developer.github.com/v3/issues/labels/
|
||||
*/
|
||||
case class ApiLabel(name: String, color: String)(repositoryName: RepositoryName) {
|
||||
var url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/labels/${name}")
|
||||
}
|
||||
|
||||
object ApiLabel{
|
||||
def apply(label:Label, repositoryName: RepositoryName): ApiLabel =
|
||||
object ApiLabel {
|
||||
def apply(label: Label, repositoryName: RepositoryName): ApiLabel =
|
||||
ApiLabel(
|
||||
name = label.labelName,
|
||||
color = label.color
|
||||
)(repositoryName)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,22 +4,11 @@ import gitbucket.core.util.JGitUtil.CommitInfo
|
||||
|
||||
import java.util.Date
|
||||
|
||||
|
||||
case class ApiPersonIdent(
|
||||
name: String,
|
||||
email: String,
|
||||
date: Date)
|
||||
|
||||
case class ApiPersonIdent(name: String, email: String, date: Date)
|
||||
|
||||
object ApiPersonIdent {
|
||||
def author(commit: CommitInfo): ApiPersonIdent =
|
||||
ApiPersonIdent(
|
||||
name = commit.authorName,
|
||||
email = commit.authorEmailAddress,
|
||||
date = commit.authorTime)
|
||||
ApiPersonIdent(name = commit.authorName, email = commit.authorEmailAddress, date = commit.authorTime)
|
||||
def committer(commit: CommitInfo): ApiPersonIdent =
|
||||
ApiPersonIdent(
|
||||
name = commit.committerName,
|
||||
email = commit.committerEmailAddress,
|
||||
date = commit.commitTime)
|
||||
ApiPersonIdent(name = commit.committerName, email = commit.committerEmailAddress, date = commit.commitTime)
|
||||
}
|
||||
|
||||
17
src/main/scala/gitbucket/core/api/ApiPlugin.scala
Normal file
17
src/main/scala/gitbucket/core/api/ApiPlugin.scala
Normal file
@@ -0,0 +1,17 @@
|
||||
package gitbucket.core.api
|
||||
|
||||
import gitbucket.core.plugin.{PluginRegistry, PluginInfo}
|
||||
|
||||
case class ApiPlugin(
|
||||
id: String,
|
||||
name: String,
|
||||
version: String,
|
||||
description: String,
|
||||
jarFileName: String
|
||||
)
|
||||
|
||||
object ApiPlugin {
|
||||
def apply(plugin: PluginInfo): ApiPlugin = {
|
||||
ApiPlugin(plugin.pluginId, plugin.pluginName, plugin.pluginVersion, plugin.description, plugin.pluginJar.getName)
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import java.util.Date
|
||||
*/
|
||||
case class ApiPullRequest(
|
||||
number: Int,
|
||||
state: String,
|
||||
updated_at: Date,
|
||||
created_at: Date,
|
||||
head: ApiPullRequest.Commit,
|
||||
@@ -19,56 +20,53 @@ case class ApiPullRequest(
|
||||
title: String,
|
||||
body: String,
|
||||
user: ApiUser,
|
||||
assignee: Option[ApiUser]){
|
||||
val html_url = ApiPath(s"${base.repo.html_url.path}/pull/${number}")
|
||||
labels: List[ApiLabel],
|
||||
assignee: Option[ApiUser]
|
||||
) {
|
||||
val id = 0 // dummy id
|
||||
val html_url = ApiPath(s"${base.repo.html_url.path}/pull/${number}")
|
||||
//val diff_url = ApiPath(s"${base.repo.html_url.path}/pull/${number}.diff")
|
||||
//val patch_url = ApiPath(s"${base.repo.html_url.path}/pull/${number}.patch")
|
||||
val url = ApiPath(s"${base.repo.url.path}/pulls/${number}")
|
||||
val url = ApiPath(s"${base.repo.url.path}/pulls/${number}")
|
||||
//val issue_url = ApiPath(s"${base.repo.url.path}/issues/${number}")
|
||||
val commits_url = ApiPath(s"${base.repo.url.path}/pulls/${number}/commits")
|
||||
val commits_url = ApiPath(s"${base.repo.url.path}/pulls/${number}/commits")
|
||||
val review_comments_url = ApiPath(s"${base.repo.url.path}/pulls/${number}/comments")
|
||||
val review_comment_url = ApiPath(s"${base.repo.url.path}/pulls/comments/{number}")
|
||||
val comments_url = ApiPath(s"${base.repo.url.path}/issues/${number}/comments")
|
||||
val statuses_url = ApiPath(s"${base.repo.url.path}/statuses/${head.sha}")
|
||||
val review_comment_url = ApiPath(s"${base.repo.url.path}/pulls/comments/{number}")
|
||||
val comments_url = ApiPath(s"${base.repo.url.path}/issues/${number}/comments")
|
||||
val statuses_url = ApiPath(s"${base.repo.url.path}/statuses/${head.sha}")
|
||||
}
|
||||
|
||||
object ApiPullRequest{
|
||||
object ApiPullRequest {
|
||||
def apply(
|
||||
issue: Issue,
|
||||
pullRequest: PullRequest,
|
||||
headRepo: ApiRepository,
|
||||
baseRepo: ApiRepository,
|
||||
user: ApiUser,
|
||||
labels: List[ApiLabel],
|
||||
assignee: Option[ApiUser],
|
||||
mergedComment: Option[(IssueComment, Account)]
|
||||
): ApiPullRequest =
|
||||
ApiPullRequest(
|
||||
number = issue.issueId,
|
||||
number = issue.issueId,
|
||||
state = if (issue.closed) "closed" else "open",
|
||||
updated_at = issue.updatedDate,
|
||||
created_at = issue.registeredDate,
|
||||
head = Commit(
|
||||
sha = pullRequest.commitIdTo,
|
||||
ref = pullRequest.requestBranch,
|
||||
repo = headRepo)(issue.userName),
|
||||
base = Commit(
|
||||
sha = pullRequest.commitIdFrom,
|
||||
ref = pullRequest.branch,
|
||||
repo = baseRepo)(issue.userName),
|
||||
mergeable = None, // TODO: need check mergeable.
|
||||
merged = mergedComment.isDefined,
|
||||
merged_at = mergedComment.map { case (comment, _) => comment.registeredDate },
|
||||
merged_by = mergedComment.map { case (_, account) => ApiUser(account) },
|
||||
title = issue.title,
|
||||
body = issue.content.getOrElse(""),
|
||||
user = user,
|
||||
assignee = assignee
|
||||
head = Commit(sha = pullRequest.commitIdTo, ref = pullRequest.requestBranch, repo = headRepo)(issue.userName),
|
||||
base = Commit(sha = pullRequest.commitIdFrom, ref = pullRequest.branch, repo = baseRepo)(issue.userName),
|
||||
mergeable = None, // TODO: need check mergeable.
|
||||
merged = mergedComment.isDefined,
|
||||
merged_at = mergedComment.map { case (comment, _) => comment.registeredDate },
|
||||
merged_by = mergedComment.map { case (_, account) => ApiUser(account) },
|
||||
title = issue.title,
|
||||
body = issue.content.getOrElse(""),
|
||||
user = user,
|
||||
labels = labels,
|
||||
assignee = assignee
|
||||
)
|
||||
|
||||
case class Commit(
|
||||
sha: String,
|
||||
ref: String,
|
||||
repo: ApiRepository)(baseOwner:String){
|
||||
val label = if( baseOwner == repo.owner.login ){ ref } else { s"${repo.owner.login}:${ref}" }
|
||||
case class Commit(sha: String, ref: String, repo: ApiRepository)(baseOwner: String) {
|
||||
val label = if (baseOwner == repo.owner.login) { ref } else { s"${repo.owner.login}:${ref}" }
|
||||
val user = repo.owner
|
||||
}
|
||||
|
||||
|
||||
@@ -18,9 +18,10 @@ case class ApiPullRequestReviewComment(
|
||||
// "original_commit_id": "0d1a26e67d8f5eaf1f6ba5c57fc3c7d91ac0fd1c",
|
||||
user: ApiUser,
|
||||
body: String, // "Maybe you should use more emojji on this line.",
|
||||
created_at: Date, // "2015-05-05T23:40:27Z",
|
||||
created_at: Date, // "2015-05-05T23:40:27Z",
|
||||
updated_at: Date // "2015-05-05T23:40:27Z",
|
||||
)(repositoryName:RepositoryName, issueId: Int) extends FieldSerializable {
|
||||
)(repositoryName: RepositoryName, issueId: Int)
|
||||
extends FieldSerializable {
|
||||
// "url": "https://api.github.com/repos/baxterthehacker/public-repo/pulls/comments/29724692",
|
||||
val url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/pulls/comments/${id}")
|
||||
// "html_url": "https://github.com/baxterthehacker/public-repo/pull/1#discussion_r29724692",
|
||||
@@ -40,22 +41,28 @@ case class ApiPullRequestReviewComment(
|
||||
"href": "https://api.github.com/repos/baxterthehacker/public-repo/pulls/1"
|
||||
}
|
||||
}
|
||||
*/
|
||||
*/
|
||||
val _links = Map(
|
||||
"self" -> Map("href" -> url),
|
||||
"html" -> Map("href" -> html_url),
|
||||
"pull_request" -> Map("href" -> pull_request_url))
|
||||
"pull_request" -> Map("href" -> pull_request_url)
|
||||
)
|
||||
}
|
||||
|
||||
object ApiPullRequestReviewComment{
|
||||
def apply(comment: CommitComment, commentedUser: ApiUser, repositoryName: RepositoryName, issueId: Int): ApiPullRequestReviewComment =
|
||||
object ApiPullRequestReviewComment {
|
||||
def apply(
|
||||
comment: CommitComment,
|
||||
commentedUser: ApiUser,
|
||||
repositoryName: RepositoryName,
|
||||
issueId: Int
|
||||
): ApiPullRequestReviewComment =
|
||||
new ApiPullRequestReviewComment(
|
||||
id = comment.commentId,
|
||||
path = comment.fileName.getOrElse(""),
|
||||
commit_id = comment.commitId,
|
||||
user = commentedUser,
|
||||
body = comment.content,
|
||||
created_at = comment.registeredDate,
|
||||
updated_at = comment.updatedDate
|
||||
)(repositoryName, issueId)
|
||||
id = comment.commentId,
|
||||
path = comment.fileName.getOrElse(""),
|
||||
commit_id = comment.commitId,
|
||||
user = commentedUser,
|
||||
body = comment.content,
|
||||
created_at = comment.registeredDate,
|
||||
updated_at = comment.updatedDate
|
||||
)(repositoryName, issueId)
|
||||
}
|
||||
|
||||
@@ -5,7 +5,5 @@ import gitbucket.core.model.Account
|
||||
case class ApiPusher(name: String, email: String)
|
||||
|
||||
object ApiPusher {
|
||||
def apply(user: Account): ApiPusher = ApiPusher(
|
||||
name = user.userName,
|
||||
email = user.mailAddress)
|
||||
}
|
||||
def apply(user: Account): ApiPusher = ApiPusher(name = user.userName, email = user.mailAddress)
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package gitbucket.core.api
|
||||
import gitbucket.core.model.{Account, Repository}
|
||||
import gitbucket.core.service.RepositoryService.RepositoryInfo
|
||||
|
||||
|
||||
// https://developer.github.com/v3/repos/
|
||||
case class ApiRepository(
|
||||
name: String,
|
||||
@@ -13,56 +12,59 @@ case class ApiRepository(
|
||||
forks: Int,
|
||||
`private`: Boolean,
|
||||
default_branch: String,
|
||||
owner: ApiUser)(urlIsHtmlUrl: Boolean) {
|
||||
owner: ApiUser
|
||||
)(urlIsHtmlUrl: Boolean) {
|
||||
val id = 0 // dummy id
|
||||
val forks_count = forks
|
||||
val watchers_count = watchers
|
||||
val url = if(urlIsHtmlUrl){
|
||||
val url = if (urlIsHtmlUrl) {
|
||||
ApiPath(s"/${full_name}")
|
||||
} else {
|
||||
ApiPath(s"/api/v3/repos/${full_name}")
|
||||
}
|
||||
val http_url = ApiPath(s"/git/${full_name}.git")
|
||||
val http_url = ApiPath(s"/git/${full_name}.git")
|
||||
val clone_url = ApiPath(s"/git/${full_name}.git")
|
||||
val html_url = ApiPath(s"/${full_name}")
|
||||
val ssh_url = Some(SshPath(s":${full_name}.git"))
|
||||
val html_url = ApiPath(s"/${full_name}")
|
||||
val ssh_url = Some(SshPath(s":${full_name}.git"))
|
||||
}
|
||||
|
||||
object ApiRepository{
|
||||
object ApiRepository {
|
||||
def apply(
|
||||
repository: Repository,
|
||||
owner: ApiUser,
|
||||
forkedCount: Int =0,
|
||||
watchers: Int = 0,
|
||||
urlIsHtmlUrl: Boolean = false): ApiRepository =
|
||||
repository: Repository,
|
||||
owner: ApiUser,
|
||||
forkedCount: Int = 0,
|
||||
watchers: Int = 0,
|
||||
urlIsHtmlUrl: Boolean = false
|
||||
): ApiRepository =
|
||||
ApiRepository(
|
||||
name = repository.repositoryName,
|
||||
full_name = s"${repository.userName}/${repository.repositoryName}",
|
||||
description = repository.description.getOrElse(""),
|
||||
watchers = watchers,
|
||||
forks = forkedCount,
|
||||
`private` = repository.isPrivate,
|
||||
name = repository.repositoryName,
|
||||
full_name = s"${repository.userName}/${repository.repositoryName}",
|
||||
description = repository.description.getOrElse(""),
|
||||
watchers = watchers,
|
||||
forks = forkedCount,
|
||||
`private` = repository.isPrivate,
|
||||
default_branch = repository.defaultBranch,
|
||||
owner = owner
|
||||
owner = owner
|
||||
)(urlIsHtmlUrl)
|
||||
|
||||
def apply(repositoryInfo: RepositoryInfo, owner: ApiUser): ApiRepository =
|
||||
ApiRepository(repositoryInfo.repository, owner, forkedCount=repositoryInfo.forkedCount)
|
||||
ApiRepository(repositoryInfo.repository, owner, forkedCount = repositoryInfo.forkedCount)
|
||||
|
||||
def apply(repositoryInfo: RepositoryInfo, owner: Account): ApiRepository =
|
||||
this(repositoryInfo.repository, ApiUser(owner))
|
||||
|
||||
def forPushPayload(repositoryInfo: RepositoryInfo, owner: ApiUser): ApiRepository =
|
||||
ApiRepository(repositoryInfo.repository, owner, forkedCount=repositoryInfo.forkedCount, urlIsHtmlUrl=true)
|
||||
def forWebhookPayload(repositoryInfo: RepositoryInfo, owner: ApiUser): ApiRepository =
|
||||
ApiRepository(repositoryInfo.repository, owner, forkedCount = repositoryInfo.forkedCount, urlIsHtmlUrl = true)
|
||||
|
||||
def forDummyPayload(owner: ApiUser): ApiRepository =
|
||||
ApiRepository(
|
||||
name = "dummy",
|
||||
full_name = s"${owner.login}/dummy",
|
||||
description = "",
|
||||
watchers = 0,
|
||||
forks = 0,
|
||||
`private` = false,
|
||||
name = "dummy",
|
||||
full_name = s"${owner.login}/dummy",
|
||||
description = "",
|
||||
watchers = 0,
|
||||
forks = 0,
|
||||
`private` = false,
|
||||
default_branch = "master",
|
||||
owner = owner
|
||||
owner = owner
|
||||
)(true)
|
||||
}
|
||||
|
||||
@@ -4,16 +4,11 @@ import gitbucket.core.model.Account
|
||||
|
||||
import java.util.Date
|
||||
|
||||
|
||||
case class ApiUser(
|
||||
login: String,
|
||||
email: String,
|
||||
`type`: String,
|
||||
site_admin: Boolean,
|
||||
created_at: Date) {
|
||||
val url = ApiPath(s"/api/v3/users/${login}")
|
||||
val html_url = ApiPath(s"/${login}")
|
||||
val avatar_url = ApiPath(s"/${login}/_avatar")
|
||||
case class ApiUser(login: String, email: String, `type`: String, site_admin: Boolean, created_at: Date) {
|
||||
val id = 0 // dummy id
|
||||
val url = ApiPath(s"/api/v3/users/${login}")
|
||||
val html_url = ApiPath(s"/${login}")
|
||||
val avatar_url = ApiPath(s"/${login}/_avatar")
|
||||
// val followers_url = ApiPath(s"/api/v3/users/${login}/followers")
|
||||
// val following_url = ApiPath(s"/api/v3/users/${login}/following{/other_user}")
|
||||
// val gists_url = ApiPath(s"/api/v3/users/${login}/gists{/gist_id}")
|
||||
@@ -25,12 +20,11 @@ case class ApiUser(
|
||||
// val received_events_url = ApiPath(s"/api/v3/users/${login}/received_events")
|
||||
}
|
||||
|
||||
|
||||
object ApiUser{
|
||||
object ApiUser {
|
||||
def apply(user: Account): ApiUser = ApiUser(
|
||||
login = user.userName,
|
||||
email = user.mailAddress,
|
||||
`type` = if(user.isGroupAccount){ "Organization" } else { "User" },
|
||||
login = user.userName,
|
||||
email = user.mailAddress,
|
||||
`type` = if (user.isGroupAccount) { "Organization" } else { "User" },
|
||||
site_admin = user.isAdmin,
|
||||
created_at = user.registeredDate
|
||||
)
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
package gitbucket.core.api
|
||||
|
||||
/**
|
||||
* https://developer.github.com/v3/issues/labels/#create-a-label
|
||||
* api form
|
||||
*/
|
||||
* https://developer.github.com/v3/issues/labels/#create-a-label
|
||||
* api form
|
||||
*/
|
||||
case class CreateALabel(
|
||||
name: String,
|
||||
color: String
|
||||
name: String,
|
||||
color: String
|
||||
) {
|
||||
def isValid: Boolean = {
|
||||
name.length<=100 &&
|
||||
!name.startsWith("_") &&
|
||||
!name.startsWith("-") &&
|
||||
color.length==6 &&
|
||||
color.matches("[a-fA-F0-9+_.]+")
|
||||
name.length <= 100 &&
|
||||
!name.startsWith("_") &&
|
||||
!name.startsWith("-") &&
|
||||
color.length == 6 &&
|
||||
color.matches("[a-fA-F0-9+_.]+")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,15 +5,15 @@ package gitbucket.core.api
|
||||
* api form
|
||||
*/
|
||||
case class CreateARepository(
|
||||
name: String,
|
||||
description: Option[String],
|
||||
`private`: Boolean = false,
|
||||
auto_init: Boolean = false
|
||||
name: String,
|
||||
description: Option[String],
|
||||
`private`: Boolean = false,
|
||||
auto_init: Boolean = false
|
||||
) {
|
||||
def isValid: Boolean = {
|
||||
name.length <= 100 &&
|
||||
name.matches("[a-zA-Z0-9\\-\\+_.]+") &&
|
||||
!name.startsWith("_") &&
|
||||
!name.startsWith("-")
|
||||
name.matches("[a-zA-Z0-9\\-\\+_.]+") &&
|
||||
!name.startsWith("_") &&
|
||||
!name.startsWith("-")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,9 +18,9 @@ case class CreateAStatus(
|
||||
) {
|
||||
def isValid: Boolean = {
|
||||
CommitState.valueOf(state).isDefined &&
|
||||
// only http
|
||||
target_url.forall(f => "\\Ahttps?://".r.findPrefixOf(f).isDefined && f.length < 255) &&
|
||||
context.forall(f => f.length < 255) &&
|
||||
description.forall(f => f.length < 1000)
|
||||
// only http
|
||||
target_url.forall(f => "\\Ahttps?://".r.findPrefixOf(f).isDefined && f.length < 255) &&
|
||||
context.forall(f => f.length < 255) &&
|
||||
description.forall(f => f.length < 1000)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
package gitbucket.core.api
|
||||
|
||||
/**
|
||||
* https://developer.github.com/v3/issues/#create-an-issue
|
||||
*/
|
||||
* https://developer.github.com/v3/issues/#create-an-issue
|
||||
*/
|
||||
case class CreateAnIssue(
|
||||
title: String,
|
||||
body: Option[String],
|
||||
assignees: List[String],
|
||||
milestone: Option[Int],
|
||||
labels: List[String])
|
||||
title: String,
|
||||
body: Option[String],
|
||||
assignees: List[String],
|
||||
milestone: Option[Int],
|
||||
labels: List[String]
|
||||
)
|
||||
|
||||
@@ -15,10 +15,13 @@ object JsonFormat {
|
||||
|
||||
val parserISO = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'")
|
||||
|
||||
val jsonFormats = Serialization.formats(NoTypeHints) + new CustomSerializer[Date](format =>
|
||||
(
|
||||
{ case JString(s) => Try(Date.from(Instant.parse(s))).getOrElse(throw new MappingException("Can't convert " + s + " to Date")) },
|
||||
{ case x: Date => JString(OffsetDateTime.ofInstant(x.toInstant, ZoneId.of("UTC")).format(parserISO)) }
|
||||
val jsonFormats = Serialization.formats(NoTypeHints) + new CustomSerializer[Date](
|
||||
format =>
|
||||
(
|
||||
{
|
||||
case JString(s) =>
|
||||
Try(Date.from(Instant.parse(s))).getOrElse(throw new MappingException("Can't convert " + s + " to Date"))
|
||||
}, { case x: Date => JString(OffsetDateTime.ofInstant(x.toInstant, ZoneId.of("UTC")).format(parserISO)) }
|
||||
)
|
||||
) + FieldSerializer[ApiUser]() +
|
||||
FieldSerializer[ApiPullRequest]() +
|
||||
@@ -41,19 +44,31 @@ object JsonFormat {
|
||||
FieldSerializer[ApiCommits.File]() +
|
||||
ApiBranchProtection.enforcementLevelSerializer
|
||||
|
||||
def apiPathSerializer(c: Context) = new CustomSerializer[ApiPath](_ => ({
|
||||
case JString(s) if s.startsWith(c.baseUrl) => ApiPath(s.substring(c.baseUrl.length))
|
||||
case JString(s) => throw new MappingException("Can't convert " + s + " to ApiPath")
|
||||
}, {
|
||||
case ApiPath(path) => JString(c.baseUrl + path)
|
||||
}))
|
||||
def apiPathSerializer(c: Context) =
|
||||
new CustomSerializer[ApiPath](
|
||||
_ =>
|
||||
({
|
||||
case JString(s) if s.startsWith(c.baseUrl) => ApiPath(s.substring(c.baseUrl.length))
|
||||
case JString(s) => throw new MappingException("Can't convert " + s + " to ApiPath")
|
||||
}, {
|
||||
case ApiPath(path) => JString(c.baseUrl + path)
|
||||
})
|
||||
)
|
||||
|
||||
def sshPathSerializer(c: Context) = new CustomSerializer[SshPath](_ => ({
|
||||
case JString(s) if c.sshUrl.exists(sshUrl => s.startsWith(sshUrl)) => SshPath(s.substring(c.sshUrl.get.length))
|
||||
case JString(s) => throw new MappingException("Can't convert " + s + " to ApiPath")
|
||||
}, {
|
||||
case SshPath(path) => c.sshUrl.map { sshUrl => JString(sshUrl + path) } getOrElse JNothing
|
||||
}))
|
||||
def sshPathSerializer(c: Context) =
|
||||
new CustomSerializer[SshPath](
|
||||
_ =>
|
||||
({
|
||||
case JString(s) if c.sshUrl.exists(sshUrl => s.startsWith(sshUrl)) =>
|
||||
SshPath(s.substring(c.sshUrl.get.length))
|
||||
case JString(s) => throw new MappingException("Can't convert " + s + " to ApiPath")
|
||||
}, {
|
||||
case SshPath(path) =>
|
||||
c.sshUrl.map { sshUrl =>
|
||||
JString(sshUrl + path)
|
||||
} getOrElse JNothing
|
||||
})
|
||||
)
|
||||
|
||||
/**
|
||||
* convert object to json string
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
package gitbucket.core.controller
|
||||
|
||||
import java.io.File
|
||||
|
||||
import gitbucket.core.account.html
|
||||
import gitbucket.core.helper
|
||||
import gitbucket.core.model.{AccountWebHook, GroupMember, RepositoryWebHook, RepositoryWebHookEvent, Role, WebHook, WebHookContentType}
|
||||
import gitbucket.core.model._
|
||||
import gitbucket.core.plugin.PluginRegistry
|
||||
import gitbucket.core.service._
|
||||
import gitbucket.core.service.WebHookService._
|
||||
@@ -12,95 +14,159 @@ import gitbucket.core.util.Directory._
|
||||
import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.util.StringUtil._
|
||||
import gitbucket.core.util._
|
||||
import org.apache.commons.io.FileUtils
|
||||
import org.scalatra.i18n.Messages
|
||||
import org.scalatra.BadRequest
|
||||
import org.scalatra.forms._
|
||||
|
||||
class AccountController extends AccountControllerBase
|
||||
with AccountService with RepositoryService with ActivityService with WikiService with LabelsService with SshKeyService
|
||||
with OneselfAuthenticator with UsersAuthenticator with GroupManagerAuthenticator with ReadableUsersAuthenticator
|
||||
with AccessTokenService with WebHookService with PrioritiesService with RepositoryCreationService
|
||||
|
||||
class AccountController
|
||||
extends AccountControllerBase
|
||||
with AccountService
|
||||
with RepositoryService
|
||||
with ActivityService
|
||||
with WikiService
|
||||
with LabelsService
|
||||
with SshKeyService
|
||||
with OneselfAuthenticator
|
||||
with UsersAuthenticator
|
||||
with GroupManagerAuthenticator
|
||||
with ReadableUsersAuthenticator
|
||||
with AccessTokenService
|
||||
with WebHookService
|
||||
with PrioritiesService
|
||||
with RepositoryCreationService
|
||||
|
||||
trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
self: AccountService with RepositoryService with ActivityService with WikiService with LabelsService with SshKeyService
|
||||
with OneselfAuthenticator with UsersAuthenticator with GroupManagerAuthenticator with ReadableUsersAuthenticator
|
||||
with AccessTokenService with WebHookService with PrioritiesService with RepositoryCreationService =>
|
||||
self: AccountService
|
||||
with RepositoryService
|
||||
with ActivityService
|
||||
with WikiService
|
||||
with LabelsService
|
||||
with SshKeyService
|
||||
with OneselfAuthenticator
|
||||
with UsersAuthenticator
|
||||
with GroupManagerAuthenticator
|
||||
with ReadableUsersAuthenticator
|
||||
with AccessTokenService
|
||||
with WebHookService
|
||||
with PrioritiesService
|
||||
with RepositoryCreationService =>
|
||||
|
||||
case class AccountNewForm(userName: String, password: String, fullName: String, mailAddress: String,
|
||||
description: Option[String], url: Option[String], fileId: Option[String])
|
||||
case class AccountNewForm(
|
||||
userName: String,
|
||||
password: String,
|
||||
fullName: String,
|
||||
mailAddress: String,
|
||||
extraMailAddresses: List[String],
|
||||
description: Option[String],
|
||||
url: Option[String],
|
||||
fileId: Option[String]
|
||||
)
|
||||
|
||||
case class AccountEditForm(password: Option[String], fullName: String, mailAddress: String,
|
||||
description: Option[String], url: Option[String], fileId: Option[String], clearImage: Boolean)
|
||||
case class AccountEditForm(
|
||||
password: Option[String],
|
||||
fullName: String,
|
||||
mailAddress: String,
|
||||
extraMailAddresses: List[String],
|
||||
description: Option[String],
|
||||
url: Option[String],
|
||||
fileId: Option[String],
|
||||
clearImage: Boolean
|
||||
)
|
||||
|
||||
case class SshKeyForm(title: String, publicKey: String)
|
||||
|
||||
case class PersonalTokenForm(note: String)
|
||||
|
||||
val newForm = mapping(
|
||||
"userName" -> trim(label("User name" , text(required, maxlength(100), identifier, uniqueUserName, reservedNames))),
|
||||
"password" -> trim(label("Password" , text(required, maxlength(20), password))),
|
||||
"fullName" -> trim(label("Full Name" , text(required, maxlength(100)))),
|
||||
"mailAddress" -> trim(label("Mail Address" , text(required, maxlength(100), uniqueMailAddress()))),
|
||||
"description" -> trim(label("bio" , optional(text()))),
|
||||
"url" -> trim(label("URL" , optional(text(maxlength(200))))),
|
||||
"fileId" -> trim(label("File ID" , optional(text())))
|
||||
"userName" -> trim(label("User name", text(required, maxlength(100), identifier, uniqueUserName, reservedNames))),
|
||||
"password" -> trim(label("Password", text(required, maxlength(20), password))),
|
||||
"fullName" -> trim(label("Full Name", text(required, maxlength(100)))),
|
||||
"mailAddress" -> trim(label("Mail Address", text(required, maxlength(100), uniqueMailAddress()))),
|
||||
"extraMailAddresses" -> list(
|
||||
trim(label("Additional Mail Address", text(maxlength(100), uniqueExtraMailAddress())))
|
||||
),
|
||||
"description" -> trim(label("bio", optional(text()))),
|
||||
"url" -> trim(label("URL", optional(text(maxlength(200))))),
|
||||
"fileId" -> trim(label("File ID", optional(text())))
|
||||
)(AccountNewForm.apply)
|
||||
|
||||
val editForm = mapping(
|
||||
"password" -> trim(label("Password" , optional(text(maxlength(20), password)))),
|
||||
"fullName" -> trim(label("Full Name" , text(required, maxlength(100)))),
|
||||
"mailAddress" -> trim(label("Mail Address" , text(required, maxlength(100), uniqueMailAddress("userName")))),
|
||||
"description" -> trim(label("bio" , optional(text()))),
|
||||
"url" -> trim(label("URL" , optional(text(maxlength(200))))),
|
||||
"fileId" -> trim(label("File ID" , optional(text()))),
|
||||
"clearImage" -> trim(label("Clear image" , boolean()))
|
||||
"password" -> trim(label("Password", optional(text(maxlength(20), password)))),
|
||||
"fullName" -> trim(label("Full Name", text(required, maxlength(100)))),
|
||||
"mailAddress" -> trim(label("Mail Address", text(required, maxlength(100), uniqueMailAddress("userName")))),
|
||||
"extraMailAddresses" -> list(
|
||||
trim(label("Additional Mail Address", text(maxlength(100), uniqueExtraMailAddress("userName"))))
|
||||
),
|
||||
"description" -> trim(label("bio", optional(text()))),
|
||||
"url" -> trim(label("URL", optional(text(maxlength(200))))),
|
||||
"fileId" -> trim(label("File ID", optional(text()))),
|
||||
"clearImage" -> trim(label("Clear image", boolean()))
|
||||
)(AccountEditForm.apply)
|
||||
|
||||
val sshKeyForm = mapping(
|
||||
"title" -> trim(label("Title", text(required, maxlength(100)))),
|
||||
"publicKey" -> trim2(label("Key" , text(required, validPublicKey)))
|
||||
"title" -> trim(label("Title", text(required, maxlength(100)))),
|
||||
"publicKey" -> trim2(label("Key", text(required, validPublicKey)))
|
||||
)(SshKeyForm.apply)
|
||||
|
||||
val personalTokenForm = mapping(
|
||||
"note" -> trim(label("Token", text(required, maxlength(100))))
|
||||
)(PersonalTokenForm.apply)
|
||||
|
||||
case class NewGroupForm(groupName: String, description: Option[String], url: Option[String], fileId: Option[String], members: String)
|
||||
case class EditGroupForm(groupName: String, description: Option[String], url: Option[String], fileId: Option[String], members: String, clearImage: Boolean)
|
||||
case class NewGroupForm(
|
||||
groupName: String,
|
||||
description: Option[String],
|
||||
url: Option[String],
|
||||
fileId: Option[String],
|
||||
members: String
|
||||
)
|
||||
case class EditGroupForm(
|
||||
groupName: String,
|
||||
description: Option[String],
|
||||
url: Option[String],
|
||||
fileId: Option[String],
|
||||
members: String,
|
||||
clearImage: Boolean
|
||||
)
|
||||
|
||||
val newGroupForm = mapping(
|
||||
"groupName" -> trim(label("Group name" ,text(required, maxlength(100), identifier, uniqueUserName, reservedNames))),
|
||||
"groupName" -> trim(label("Group name", text(required, maxlength(100), identifier, uniqueUserName, reservedNames))),
|
||||
"description" -> trim(label("Group description", optional(text()))),
|
||||
"url" -> trim(label("URL" ,optional(text(maxlength(200))))),
|
||||
"fileId" -> trim(label("File ID" ,optional(text()))),
|
||||
"members" -> trim(label("Members" ,text(required, members)))
|
||||
"url" -> trim(label("URL", optional(text(maxlength(200))))),
|
||||
"fileId" -> trim(label("File ID", optional(text()))),
|
||||
"members" -> trim(label("Members", text(required, members)))
|
||||
)(NewGroupForm.apply)
|
||||
|
||||
val editGroupForm = mapping(
|
||||
"groupName" -> trim(label("Group name" ,text(required, maxlength(100), identifier))),
|
||||
"groupName" -> trim(label("Group name", text(required, maxlength(100), identifier))),
|
||||
"description" -> trim(label("Group description", optional(text()))),
|
||||
"url" -> trim(label("URL" ,optional(text(maxlength(200))))),
|
||||
"fileId" -> trim(label("File ID" ,optional(text()))),
|
||||
"members" -> trim(label("Members" ,text(required, members))),
|
||||
"clearImage" -> trim(label("Clear image" ,boolean()))
|
||||
"url" -> trim(label("URL", optional(text(maxlength(200))))),
|
||||
"fileId" -> trim(label("File ID", optional(text()))),
|
||||
"members" -> trim(label("Members", text(required, members))),
|
||||
"clearImage" -> trim(label("Clear image", boolean()))
|
||||
)(EditGroupForm.apply)
|
||||
|
||||
case class RepositoryCreationForm(owner: String, name: String, description: Option[String], isPrivate: Boolean, createReadme: Boolean)
|
||||
case class RepositoryCreationForm(
|
||||
owner: String,
|
||||
name: String,
|
||||
description: Option[String],
|
||||
isPrivate: Boolean,
|
||||
initOption: String,
|
||||
sourceUrl: Option[String]
|
||||
)
|
||||
case class ForkRepositoryForm(owner: String, name: String)
|
||||
|
||||
val newRepositoryForm = mapping(
|
||||
"owner" -> trim(label("Owner" , text(required, maxlength(100), identifier, existsAccount))),
|
||||
"name" -> trim(label("Repository name", text(required, maxlength(100), repository, uniqueRepository))),
|
||||
"description" -> trim(label("Description" , optional(text()))),
|
||||
"isPrivate" -> trim(label("Repository Type", boolean())),
|
||||
"createReadme" -> trim(label("Create README" , boolean()))
|
||||
"owner" -> trim(label("Owner", text(required, maxlength(100), identifier, existsAccount))),
|
||||
"name" -> trim(label("Repository name", text(required, maxlength(100), repository, uniqueRepository))),
|
||||
"description" -> trim(label("Description", optional(text()))),
|
||||
"isPrivate" -> trim(label("Repository Type", boolean())),
|
||||
"initOption" -> trim(label("Initialize option", text(required))),
|
||||
"sourceUrl" -> trim(label("Source URL", optionalRequired(_.value("initOption") == "COPY", text())))
|
||||
)(RepositoryCreationForm.apply)
|
||||
|
||||
val forkRepositoryForm = mapping(
|
||||
"owner" -> trim(label("Repository owner", text(required))),
|
||||
"name" -> trim(label("Repository name", text(required)))
|
||||
"name" -> trim(label("Repository name", text(required)))
|
||||
)(ForkRepositoryForm.apply)
|
||||
|
||||
case class AccountForm(accountName: String)
|
||||
@@ -110,23 +176,30 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
)(AccountForm.apply)
|
||||
|
||||
// for account web hook url addition.
|
||||
case class AccountWebHookForm(url: String, events: Set[WebHook.Event], ctype: WebHookContentType, token: Option[String])
|
||||
|
||||
def accountWebHookForm(update:Boolean) = mapping(
|
||||
"url" -> trim(label("url", text(required, accountWebHook(update)))),
|
||||
"events" -> accountWebhookEvents,
|
||||
"ctype" -> label("ctype", text()),
|
||||
"token" -> optional(trim(label("token", text(maxlength(100)))))
|
||||
)(
|
||||
(url, events, ctype, token) => AccountWebHookForm(url, events, WebHookContentType.valueOf(ctype), token)
|
||||
case class AccountWebHookForm(
|
||||
url: String,
|
||||
events: Set[WebHook.Event],
|
||||
ctype: WebHookContentType,
|
||||
token: Option[String]
|
||||
)
|
||||
|
||||
def accountWebHookForm(update: Boolean) =
|
||||
mapping(
|
||||
"url" -> trim(label("url", text(required, accountWebHook(update)))),
|
||||
"events" -> accountWebhookEvents,
|
||||
"ctype" -> label("ctype", text()),
|
||||
"token" -> optional(trim(label("token", text(maxlength(100)))))
|
||||
)(
|
||||
(url, events, ctype, token) => AccountWebHookForm(url, events, WebHookContentType.valueOf(ctype), token)
|
||||
)
|
||||
|
||||
/**
|
||||
* Provides duplication check for web hook url. duplicated from RepositorySettingsController.scala
|
||||
*/
|
||||
private def accountWebHook(needExists: Boolean): Constraint = new Constraint(){
|
||||
* Provides duplication check for web hook url. duplicated from RepositorySettingsController.scala
|
||||
*/
|
||||
private def accountWebHook(needExists: Boolean): Constraint = new Constraint() {
|
||||
override def validate(name: String, value: String, messages: Messages): Option[String] =
|
||||
if(getAccountWebHook(params("userName"), value).isDefined != needExists){
|
||||
Some(if(needExists){
|
||||
if (getAccountWebHook(params("userName"), value).isDefined != needExists) {
|
||||
Some(if (needExists) {
|
||||
"URL had not been registered yet."
|
||||
} else {
|
||||
"URL had been registered already."
|
||||
@@ -136,21 +209,20 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
}
|
||||
}
|
||||
|
||||
private def accountWebhookEvents = new ValueType[Set[WebHook.Event]]{
|
||||
private def accountWebhookEvents = new ValueType[Set[WebHook.Event]] {
|
||||
def convert(name: String, params: Map[String, Seq[String]], messages: Messages): Set[WebHook.Event] = {
|
||||
WebHook.Event.values.flatMap { t =>
|
||||
params.optionValue(name + "." + t.name).map(_ => t)
|
||||
}.toSet
|
||||
}
|
||||
def validate(name: String, params: Map[String, Seq[String]], messages: Messages): Seq[(String, String)] =
|
||||
if(convert(name, params, messages).isEmpty){
|
||||
if (convert(name, params, messages).isEmpty) {
|
||||
Seq(name -> messages("error.required").format(name))
|
||||
} else {
|
||||
Nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Displays user information.
|
||||
*/
|
||||
@@ -160,24 +232,41 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
params.getOrElse("tab", "repositories") match {
|
||||
// Public Activity
|
||||
case "activity" =>
|
||||
gitbucket.core.account.html.activity(account,
|
||||
if(account.isGroupAccount) Nil else getGroupsByUserName(userName),
|
||||
getActivitiesByUser(userName, true))
|
||||
gitbucket.core.account.html.activity(
|
||||
account,
|
||||
if (account.isGroupAccount) Nil else getGroupsByUserName(userName),
|
||||
getActivitiesByUser(userName, true)
|
||||
)
|
||||
|
||||
// Members
|
||||
case "members" if(account.isGroupAccount) => {
|
||||
case "members" if (account.isGroupAccount) => {
|
||||
val members = getGroupMembers(account.userName)
|
||||
gitbucket.core.account.html.members(account, members,
|
||||
context.loginAccount.exists(x => members.exists { member => member.userName == x.userName && member.isManager }))
|
||||
gitbucket.core.account.html.members(
|
||||
account,
|
||||
members,
|
||||
context.loginAccount.exists(
|
||||
x =>
|
||||
members.exists { member =>
|
||||
member.userName == x.userName && member.isManager
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// Repositories
|
||||
case _ => {
|
||||
val members = getGroupMembers(account.userName)
|
||||
gitbucket.core.account.html.repositories(account,
|
||||
if(account.isGroupAccount) Nil else getGroupsByUserName(userName),
|
||||
gitbucket.core.account.html.repositories(
|
||||
account,
|
||||
if (account.isGroupAccount) Nil else getGroupsByUserName(userName),
|
||||
getVisibleRepositories(context.loginAccount, Some(userName)),
|
||||
context.loginAccount.exists(x => members.exists { member => member.userName == x.userName && member.isManager }))
|
||||
context.loginAccount.exists(
|
||||
x =>
|
||||
members.exists { member =>
|
||||
member.userName == x.userName && member.isManager
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
} getOrElse NotFound()
|
||||
@@ -189,62 +278,71 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
helper.xml.feed(getActivitiesByUser(userName, true))
|
||||
}
|
||||
|
||||
get("/:userName/_avatar"){
|
||||
get("/:userName/_avatar") {
|
||||
val userName = params("userName")
|
||||
contentType = "image/png"
|
||||
getAccountByUserName(userName).flatMap{ account =>
|
||||
response.setDateHeader("Last-Modified", account.updatedDate.getTime)
|
||||
account.image.map{ image =>
|
||||
Some(RawData(FileUtil.getMimeType(image), new java.io.File(getUserUploadDir(userName), image)))
|
||||
}.getOrElse{
|
||||
if (account.isGroupAccount) {
|
||||
TextAvatarUtil.textGroupAvatar(account.fullName)
|
||||
} else {
|
||||
TextAvatarUtil.textAvatar(account.fullName)
|
||||
}
|
||||
getAccountByUserName(userName)
|
||||
.flatMap { account =>
|
||||
response.setDateHeader("Last-Modified", account.updatedDate.getTime)
|
||||
account.image
|
||||
.map { image =>
|
||||
Some(
|
||||
RawData(FileUtil.getMimeType(image), new File(getUserUploadDir(userName), FileUtil.checkFilename(image)))
|
||||
)
|
||||
}
|
||||
.getOrElse {
|
||||
if (account.isGroupAccount) {
|
||||
TextAvatarUtil.textGroupAvatar(account.fullName)
|
||||
} else {
|
||||
TextAvatarUtil.textAvatar(account.fullName)
|
||||
}
|
||||
}
|
||||
}
|
||||
.getOrElse {
|
||||
response.setHeader("Cache-Control", "max-age=3600")
|
||||
Thread.currentThread.getContextClassLoader.getResourceAsStream("noimage.png")
|
||||
}
|
||||
}.getOrElse{
|
||||
response.setHeader("Cache-Control", "max-age=3600")
|
||||
Thread.currentThread.getContextClassLoader.getResourceAsStream("noimage.png")
|
||||
}
|
||||
}
|
||||
|
||||
get("/:userName/_edit")(oneselfOnly {
|
||||
val userName = params("userName")
|
||||
getAccountByUserName(userName).map { x =>
|
||||
html.edit(x, flash.get("info"), flash.get("error"))
|
||||
val extraMails = getAccountExtraMailAddresses(userName)
|
||||
html.edit(x, extraMails, flash.get("info"), flash.get("error"))
|
||||
} getOrElse NotFound()
|
||||
})
|
||||
|
||||
post("/:userName/_edit", editForm)(oneselfOnly { form =>
|
||||
val userName = params("userName")
|
||||
getAccountByUserName(userName).map { account =>
|
||||
updateAccount(account.copy(
|
||||
password = form.password.map(sha1).getOrElse(account.password),
|
||||
fullName = form.fullName,
|
||||
mailAddress = form.mailAddress,
|
||||
description = form.description,
|
||||
url = form.url))
|
||||
getAccountByUserName(userName).map {
|
||||
account =>
|
||||
updateAccount(
|
||||
account.copy(
|
||||
password = form.password.map(sha1).getOrElse(account.password),
|
||||
fullName = form.fullName,
|
||||
mailAddress = form.mailAddress,
|
||||
description = form.description,
|
||||
url = form.url
|
||||
)
|
||||
)
|
||||
|
||||
updateImage(userName, form.fileId, form.clearImage)
|
||||
flash += "info" -> "Account information has been updated."
|
||||
redirect(s"/${userName}/_edit")
|
||||
updateImage(userName, form.fileId, form.clearImage)
|
||||
updateAccountExtraMailAddresses(userName, form.extraMailAddresses.filter(_ != ""))
|
||||
flash += "info" -> "Account information has been updated."
|
||||
redirect(s"/${userName}/_edit")
|
||||
|
||||
} getOrElse NotFound()
|
||||
})
|
||||
|
||||
get("/captures/(.*)".r) {
|
||||
multiParams("captures").head
|
||||
}
|
||||
|
||||
get("/:userName/_delete")(oneselfOnly {
|
||||
val userName = params("userName")
|
||||
|
||||
getAccountByUserName(userName, true).map { account =>
|
||||
if(isLastAdministrator(account)){
|
||||
flash += "error" -> "Account can't be removed because this is last one administrator."
|
||||
redirect(s"/${userName}/_edit")
|
||||
} else {
|
||||
getAccountByUserName(userName, true).map {
|
||||
account =>
|
||||
if (isLastAdministrator(account)) {
|
||||
flash += "error" -> "Account can't be removed because this is last one administrator."
|
||||
redirect(s"/${userName}/_edit")
|
||||
} else {
|
||||
// // Remove repositories
|
||||
// getRepositoryNamesOfUser(userName).foreach { repositoryName =>
|
||||
// deleteRepository(userName, repositoryName)
|
||||
@@ -252,16 +350,16 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
// FileUtils.deleteDirectory(getWikiRepositoryDir(userName, repositoryName))
|
||||
// FileUtils.deleteDirectory(getTemporaryDir(userName, repositoryName))
|
||||
// }
|
||||
// Remove from GROUP_MEMBER and COLLABORATOR
|
||||
removeUserRelatedData(userName)
|
||||
updateAccount(account.copy(isRemoved = true))
|
||||
// Remove from GROUP_MEMBER and COLLABORATOR
|
||||
removeUserRelatedData(userName)
|
||||
updateAccount(account.copy(isRemoved = true))
|
||||
|
||||
// call hooks
|
||||
PluginRegistry().getAccountHooks.foreach(_.deleted(userName))
|
||||
// call hooks
|
||||
PluginRegistry().getAccountHooks.foreach(_.deleted(userName))
|
||||
|
||||
session.invalidate
|
||||
redirect("/")
|
||||
}
|
||||
session.invalidate
|
||||
redirect("/")
|
||||
}
|
||||
} getOrElse NotFound()
|
||||
})
|
||||
|
||||
@@ -290,9 +388,9 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
getAccountByUserName(userName).map { x =>
|
||||
var tokens = getAccessTokens(x.userName)
|
||||
val generatedToken = flash.get("generatedToken") match {
|
||||
case Some((tokenId:Int, token:String)) => {
|
||||
case Some((tokenId: Int, token: String)) => {
|
||||
val gt = tokens.find(_.accessTokenId == tokenId)
|
||||
gt.map{ t =>
|
||||
gt.map { t =>
|
||||
tokens = tokens.filterNot(_ == t)
|
||||
(t, token)
|
||||
}
|
||||
@@ -363,8 +461,9 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
get("/:userName/_hooks/edit")(oneselfOnly {
|
||||
val userName = params("userName")
|
||||
getAccountByUserName(userName).flatMap { account =>
|
||||
getAccountWebHook(userName, params("url")).map { case (webhook, events) =>
|
||||
html.edithook(webhook, events, account, false)
|
||||
getAccountWebHook(userName, params("url")).map {
|
||||
case (webhook, events) =>
|
||||
html.edithook(webhook, events, account, false)
|
||||
}
|
||||
} getOrElse NotFound()
|
||||
})
|
||||
@@ -390,7 +489,9 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
import org.apache.http.util.EntityUtils
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
|
||||
def _headers(h: Array[org.apache.http.Header]): Array[Array[String]] = h.map { h => Array(h.getName, h.getValue) }
|
||||
def _headers(h: Array[org.apache.http.Header]): Array[Array[String]] = h.map { h =>
|
||||
Array(h.getName, h.getValue)
|
||||
}
|
||||
|
||||
val userName = params("userName")
|
||||
val url = params("url")
|
||||
@@ -404,31 +505,49 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
|
||||
val (webHook, json, reqFuture, resFuture) = callWebHook(WebHook.Push, List(dummyWebHookInfo), dummyPayload).head
|
||||
|
||||
val toErrorMap: PartialFunction[Throwable, Map[String,String]] = {
|
||||
case e: java.net.UnknownHostException => Map("error"-> ("Unknown host " + e.getMessage))
|
||||
case e: java.lang.IllegalArgumentException => Map("error"-> ("invalid url"))
|
||||
case e: org.apache.http.client.ClientProtocolException => Map("error"-> ("invalid url"))
|
||||
case NonFatal(e) => Map("error"-> (e.getClass + " "+ e.getMessage))
|
||||
val toErrorMap: PartialFunction[Throwable, Map[String, String]] = {
|
||||
case e: java.net.UnknownHostException => Map("error" -> ("Unknown host " + e.getMessage))
|
||||
case e: java.lang.IllegalArgumentException => Map("error" -> ("invalid url"))
|
||||
case e: org.apache.http.client.ClientProtocolException => Map("error" -> ("invalid url"))
|
||||
case NonFatal(e) => Map("error" -> (e.getClass + " " + e.getMessage))
|
||||
}
|
||||
|
||||
contentType = formats("json")
|
||||
org.json4s.jackson.Serialization.write(Map(
|
||||
"url" -> url,
|
||||
"request" -> Await.result(reqFuture.map(req => Map(
|
||||
"headers" -> _headers(req.getAllHeaders),
|
||||
"payload" -> json
|
||||
)).recover(toErrorMap), 20 seconds),
|
||||
"response" -> Await.result(resFuture.map(res => Map(
|
||||
"status" -> res.getStatusLine(),
|
||||
"body" -> EntityUtils.toString(res.getEntity()),
|
||||
"headers" -> _headers(res.getAllHeaders())
|
||||
)).recover(toErrorMap), 20 seconds)
|
||||
))
|
||||
org.json4s.jackson.Serialization.write(
|
||||
Map(
|
||||
"url" -> url,
|
||||
"request" -> Await.result(
|
||||
reqFuture
|
||||
.map(
|
||||
req =>
|
||||
Map(
|
||||
"headers" -> _headers(req.getAllHeaders),
|
||||
"payload" -> json
|
||||
)
|
||||
)
|
||||
.recover(toErrorMap),
|
||||
20 seconds
|
||||
),
|
||||
"response" -> Await.result(
|
||||
resFuture
|
||||
.map(
|
||||
res =>
|
||||
Map(
|
||||
"status" -> res.getStatusLine(),
|
||||
"body" -> EntityUtils.toString(res.getEntity()),
|
||||
"headers" -> _headers(res.getAllHeaders())
|
||||
)
|
||||
)
|
||||
.recover(toErrorMap),
|
||||
20 seconds
|
||||
)
|
||||
)
|
||||
)
|
||||
})
|
||||
|
||||
get("/register"){
|
||||
if(context.settings.allowAccountRegistration){
|
||||
if(context.loginAccount.isDefined){
|
||||
get("/register") {
|
||||
if (context.settings.allowAccountRegistration) {
|
||||
if (context.loginAccount.isDefined) {
|
||||
redirect("/")
|
||||
} else {
|
||||
html.register()
|
||||
@@ -436,10 +555,19 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
} else NotFound()
|
||||
}
|
||||
|
||||
post("/register", newForm){ form =>
|
||||
if(context.settings.allowAccountRegistration){
|
||||
createAccount(form.userName, sha1(form.password), form.fullName, form.mailAddress, false, form.description, form.url)
|
||||
post("/register", newForm) { form =>
|
||||
if (context.settings.allowAccountRegistration) {
|
||||
createAccount(
|
||||
form.userName,
|
||||
sha1(form.password),
|
||||
form.fullName,
|
||||
form.mailAddress,
|
||||
false,
|
||||
form.description,
|
||||
form.url
|
||||
)
|
||||
updateImage(form.userName, form.fileId, false)
|
||||
updateAccountExtraMailAddresses(form.userName, form.extraMailAddresses)
|
||||
redirect("/signin")
|
||||
} else NotFound()
|
||||
}
|
||||
@@ -450,18 +578,23 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
|
||||
post("/groups/new", newGroupForm)(usersOnly { form =>
|
||||
createGroup(form.groupName, form.description, form.url)
|
||||
updateGroupMembers(form.groupName, form.members.split(",").map {
|
||||
_.split(":") match {
|
||||
case Array(userName, isManager) => (userName, isManager.toBoolean)
|
||||
}
|
||||
}.toList)
|
||||
updateGroupMembers(
|
||||
form.groupName,
|
||||
form.members
|
||||
.split(",")
|
||||
.map {
|
||||
_.split(":") match {
|
||||
case Array(userName, isManager) => (userName, isManager.toBoolean)
|
||||
}
|
||||
}
|
||||
.toList
|
||||
)
|
||||
updateImage(form.groupName, form.fileId, false)
|
||||
redirect(s"/${form.groupName}")
|
||||
})
|
||||
|
||||
get("/:groupName/_editgroup")(managersOnly {
|
||||
defining(params("groupName")){ groupName =>
|
||||
// TODO Don't use Option.get
|
||||
defining(params("groupName")) { groupName =>
|
||||
getAccountByUserName(groupName, true).map { account =>
|
||||
html.editgroup(account, getGroupMembers(groupName), flash.get("info"))
|
||||
} getOrElse NotFound()
|
||||
@@ -469,13 +602,14 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
})
|
||||
|
||||
get("/:groupName/_deletegroup")(managersOnly {
|
||||
defining(params("groupName")){ groupName =>
|
||||
// Remove from GROUP_MEMBER
|
||||
updateGroupMembers(groupName, Nil)
|
||||
// Disable group
|
||||
getAccountByUserName(groupName, false).foreach { account =>
|
||||
updateGroup(groupName, account.description, account.url, true)
|
||||
}
|
||||
defining(params("groupName")) {
|
||||
groupName =>
|
||||
// Remove from GROUP_MEMBER
|
||||
updateGroupMembers(groupName, Nil)
|
||||
// Disable group
|
||||
getAccountByUserName(groupName, false).foreach { account =>
|
||||
updateGroup(groupName, account.description, account.url, true)
|
||||
}
|
||||
// // Remove repositories
|
||||
// getRepositoryNamesOfUser(groupName).foreach { repositoryName =>
|
||||
// deleteRepository(groupName, repositoryName)
|
||||
@@ -488,16 +622,23 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
})
|
||||
|
||||
post("/:groupName/_editgroup", editGroupForm)(managersOnly { form =>
|
||||
defining(params("groupName"), form.members.split(",").map {
|
||||
_.split(":") match {
|
||||
case Array(userName, isManager) => (userName, isManager.toBoolean)
|
||||
}
|
||||
}.toList){ case (groupName, members) =>
|
||||
getAccountByUserName(groupName, true).map { account =>
|
||||
updateGroup(groupName, form.description, form.url, false)
|
||||
defining(
|
||||
params("groupName"),
|
||||
form.members
|
||||
.split(",")
|
||||
.map {
|
||||
_.split(":") match {
|
||||
case Array(userName, isManager) => (userName, isManager.toBoolean)
|
||||
}
|
||||
}
|
||||
.toList
|
||||
) {
|
||||
case (groupName, members) =>
|
||||
getAccountByUserName(groupName, true).map { account =>
|
||||
updateGroup(groupName, form.description, form.url, false)
|
||||
|
||||
// Update GROUP_MEMBER
|
||||
updateGroupMembers(form.groupName, members)
|
||||
// Update GROUP_MEMBER
|
||||
updateGroupMembers(form.groupName, members)
|
||||
// // Update COLLABORATOR for group repositories
|
||||
// getRepositoryNamesOfUser(form.groupName).foreach { repositoryName =>
|
||||
// removeCollaborators(form.groupName, repositoryName)
|
||||
@@ -506,12 +647,12 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
// }
|
||||
// }
|
||||
|
||||
updateImage(form.groupName, form.fileId, form.clearImage)
|
||||
updateImage(form.groupName, form.fileId, form.clearImage)
|
||||
|
||||
flash += "info" -> "Account information has been updated."
|
||||
redirect(s"/${groupName}/_editgroup")
|
||||
flash += "info" -> "Account information has been updated."
|
||||
redirect(s"/${groupName}/_editgroup")
|
||||
|
||||
} getOrElse NotFound()
|
||||
} getOrElse NotFound()
|
||||
}
|
||||
})
|
||||
|
||||
@@ -526,13 +667,17 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
* Create new repository.
|
||||
*/
|
||||
post("/new", newRepositoryForm)(usersOnly { form =>
|
||||
LockUtil.lock(s"${form.owner}/${form.name}"){
|
||||
if(getRepository(form.owner, form.name).isEmpty){
|
||||
// Create the repository
|
||||
createRepository(context.loginAccount.get, form.owner, form.name, form.description, form.isPrivate, form.createReadme)
|
||||
|
||||
// Call hooks
|
||||
PluginRegistry().getRepositoryHooks.foreach(_.created(form.owner, form.name))
|
||||
LockUtil.lock(s"${form.owner}/${form.name}") {
|
||||
if (getRepository(form.owner, form.name).isEmpty) {
|
||||
createRepository(
|
||||
context.loginAccount.get,
|
||||
form.owner,
|
||||
form.name,
|
||||
form.description,
|
||||
form.isPrivate,
|
||||
form.initOption,
|
||||
form.sourceUrl
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -541,15 +686,20 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
})
|
||||
|
||||
get("/:owner/:repository/fork")(readableUsersOnly { repository =>
|
||||
if(repository.repository.options.allowFork){
|
||||
val loginAccount = context.loginAccount.get
|
||||
val loginUserName = loginAccount.userName
|
||||
val groups = getGroupsByUserName(loginUserName)
|
||||
if (repository.repository.options.allowFork) {
|
||||
val loginAccount = context.loginAccount.get
|
||||
val loginUserName = loginAccount.userName
|
||||
val groups = getGroupsByUserName(loginUserName)
|
||||
groups match {
|
||||
case _: List[String] =>
|
||||
val managerPermissions = groups.map { group =>
|
||||
val members = getGroupMembers(group)
|
||||
context.loginAccount.exists(x => members.exists { member => member.userName == x.userName && member.isManager })
|
||||
context.loginAccount.exists(
|
||||
x =>
|
||||
members.exists { member =>
|
||||
member.userName == x.userName && member.isManager
|
||||
}
|
||||
)
|
||||
}
|
||||
helper.html.forkrepository(
|
||||
repository,
|
||||
@@ -561,111 +711,67 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
})
|
||||
|
||||
post("/:owner/:repository/fork", accountForm)(readableUsersOnly { (form, repository) =>
|
||||
if(repository.repository.options.allowFork){
|
||||
val loginAccount = context.loginAccount.get
|
||||
if (repository.repository.options.allowFork) {
|
||||
val loginAccount = context.loginAccount.get
|
||||
val loginUserName = loginAccount.userName
|
||||
val accountName = form.accountName
|
||||
val accountName = form.accountName
|
||||
|
||||
LockUtil.lock(s"${accountName}/${repository.name}"){
|
||||
if(getRepository(accountName, repository.name).isDefined ||
|
||||
(accountName != loginUserName && !getGroupsByUserName(loginUserName).contains(accountName))){
|
||||
// redirect to the repository if repository already exists
|
||||
redirect(s"/${accountName}/${repository.name}")
|
||||
} else {
|
||||
// Insert to the database at first
|
||||
val originUserName = repository.repository.originUserName.getOrElse(repository.owner)
|
||||
val originRepositoryName = repository.repository.originRepositoryName.getOrElse(repository.name)
|
||||
|
||||
insertRepository(
|
||||
repositoryName = repository.name,
|
||||
userName = accountName,
|
||||
description = repository.repository.description,
|
||||
isPrivate = repository.repository.isPrivate,
|
||||
originRepositoryName = Some(originRepositoryName),
|
||||
originUserName = Some(originUserName),
|
||||
parentRepositoryName = Some(repository.name),
|
||||
parentUserName = Some(repository.owner)
|
||||
)
|
||||
|
||||
// Set default collaborators for the private fork
|
||||
if(repository.repository.isPrivate){
|
||||
// Copy collaborators from the source repository
|
||||
getCollaborators(repository.owner, repository.name).foreach { case (collaborator, _) =>
|
||||
addCollaborator(accountName, repository.name, collaborator.collaboratorName, collaborator.role)
|
||||
}
|
||||
// Register an owner of the source repository as a collaborator
|
||||
addCollaborator(accountName, repository.name, repository.owner, Role.ADMIN.name)
|
||||
}
|
||||
|
||||
// Insert default labels
|
||||
insertDefaultLabels(accountName, repository.name)
|
||||
// Insert default priorities
|
||||
insertDefaultPriorities(accountName, repository.name)
|
||||
|
||||
// clone repository actually
|
||||
JGitUtil.cloneRepository(
|
||||
getRepositoryDir(repository.owner, repository.name),
|
||||
FileUtil.deleteIfExists(getRepositoryDir(accountName, repository.name)))
|
||||
|
||||
// Create Wiki repository
|
||||
JGitUtil.cloneRepository(getWikiRepositoryDir(repository.owner, repository.name),
|
||||
FileUtil.deleteIfExists(getWikiRepositoryDir(accountName, repository.name)))
|
||||
|
||||
// Copy LFS files
|
||||
val lfsDir = getLfsDir(repository.owner, repository.name)
|
||||
if(lfsDir.exists){
|
||||
FileUtils.copyDirectory(lfsDir, FileUtil.deleteIfExists(getLfsDir(accountName, repository.name)))
|
||||
}
|
||||
|
||||
// Record activity
|
||||
recordForkActivity(repository.owner, repository.name, loginUserName, accountName)
|
||||
|
||||
// Call hooks
|
||||
PluginRegistry().getRepositoryHooks.foreach(_.forked(repository.owner, accountName, repository.name))
|
||||
|
||||
// redirect to the repository
|
||||
redirect(s"/${accountName}/${repository.name}")
|
||||
}
|
||||
if (getRepository(accountName, repository.name).isDefined ||
|
||||
(accountName != loginUserName && !getGroupsByUserName(loginUserName).contains(accountName))) {
|
||||
// redirect to the repository if repository already exists
|
||||
redirect(s"/${accountName}/${repository.name}")
|
||||
} else {
|
||||
// fork repository asynchronously
|
||||
forkRepository(accountName, repository, loginUserName)
|
||||
// redirect to the repository
|
||||
redirect(s"/${accountName}/${repository.name}")
|
||||
}
|
||||
} else BadRequest()
|
||||
})
|
||||
|
||||
private def existsAccount: Constraint = new Constraint(){
|
||||
private def existsAccount: Constraint = new Constraint() {
|
||||
override def validate(name: String, value: String, messages: Messages): Option[String] =
|
||||
if(getAccountByUserName(value).isEmpty) Some("User or group does not exist.") else None
|
||||
if (getAccountByUserNameIgnoreCase(value).isEmpty) Some("User or group does not exist.") else None
|
||||
}
|
||||
|
||||
private def uniqueRepository: Constraint = new Constraint(){
|
||||
override def validate(name: String, value: String, params: Map[String, Seq[String]], messages: Messages): Option[String] = {
|
||||
private def uniqueRepository: Constraint = new Constraint() {
|
||||
override def validate(
|
||||
name: String,
|
||||
value: String,
|
||||
params: Map[String, Seq[String]],
|
||||
messages: Messages
|
||||
): Option[String] = {
|
||||
for {
|
||||
userName <- params.optionValue("owner")
|
||||
_ <- getRepositoryNamesOfUser(userName).find(_ == value)
|
||||
_ <- getRepositoryNamesOfUser(userName).find(_.equalsIgnoreCase(value))
|
||||
} yield {
|
||||
"Repository already exists."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def members: Constraint = new Constraint(){
|
||||
private def members: Constraint = new Constraint() {
|
||||
override def validate(name: String, value: String, messages: Messages): Option[String] = {
|
||||
if(value.split(",").exists {
|
||||
_.split(":") match { case Array(userName, isManager) => isManager.toBoolean }
|
||||
}) None else Some("Must select one manager at least.")
|
||||
if (value.split(",").exists {
|
||||
_.split(":") match { case Array(userName, isManager) => isManager.toBoolean }
|
||||
}) None
|
||||
else Some("Must select one manager at least.")
|
||||
}
|
||||
}
|
||||
|
||||
private def validPublicKey: Constraint = new Constraint(){
|
||||
override def validate(name: String, value: String, messages: Messages): Option[String] = SshUtil.str2PublicKey(value) match {
|
||||
case Some(_) if !getAllKeys().exists(_.publicKey == value) => None
|
||||
case _ => Some("Key is invalid.")
|
||||
}
|
||||
private def validPublicKey: Constraint = new Constraint() {
|
||||
override def validate(name: String, value: String, messages: Messages): Option[String] =
|
||||
SshUtil.str2PublicKey(value) match {
|
||||
case Some(_) if !getAllKeys().exists(_.publicKey == value) => None
|
||||
case _ => Some("Key is invalid.")
|
||||
}
|
||||
}
|
||||
|
||||
private def validAccountName: Constraint = new Constraint(){
|
||||
private def validAccountName: Constraint = new Constraint() {
|
||||
override def validate(name: String, value: String, messages: Messages): Option[String] = {
|
||||
getAccountByUserName(value) match {
|
||||
case Some(_) => None
|
||||
case None => Some("Invalid Group/User Account.")
|
||||
case None => Some("Invalid Group/User Account.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,38 +10,42 @@ import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.util.JGitUtil._
|
||||
import gitbucket.core.util.SyntaxSugars._
|
||||
import gitbucket.core.util._
|
||||
import gitbucket.core.plugin.PluginRegistry
|
||||
import gitbucket.core.view.helpers.{isRenderable, renderMarkup}
|
||||
import org.eclipse.jgit.api.Git
|
||||
import org.eclipse.jgit.revwalk.RevWalk
|
||||
import org.scalatra.{Created, NoContent, UnprocessableEntity}
|
||||
|
||||
import scala.collection.JavaConverters._
|
||||
import scala.concurrent.Await
|
||||
import scala.concurrent.duration.Duration
|
||||
|
||||
class ApiController extends ApiControllerBase
|
||||
with RepositoryService
|
||||
with AccountService
|
||||
with ProtectedBranchService
|
||||
with IssuesService
|
||||
with LabelsService
|
||||
with MilestonesService
|
||||
with PullRequestService
|
||||
with CommitsService
|
||||
with CommitStatusService
|
||||
with RepositoryCreationService
|
||||
with IssueCreationService
|
||||
with HandleCommentService
|
||||
with WebHookService
|
||||
with WebHookPullRequestService
|
||||
with WebHookIssueCommentService
|
||||
with WikiService
|
||||
with ActivityService
|
||||
with PrioritiesService
|
||||
with OwnerAuthenticator
|
||||
with UsersAuthenticator
|
||||
with GroupManagerAuthenticator
|
||||
with ReferrerAuthenticator
|
||||
with ReadableUsersAuthenticator
|
||||
with WritableUsersAuthenticator
|
||||
class ApiController
|
||||
extends ApiControllerBase
|
||||
with RepositoryService
|
||||
with AccountService
|
||||
with ProtectedBranchService
|
||||
with IssuesService
|
||||
with LabelsService
|
||||
with MilestonesService
|
||||
with PullRequestService
|
||||
with CommitsService
|
||||
with CommitStatusService
|
||||
with RepositoryCreationService
|
||||
with IssueCreationService
|
||||
with HandleCommentService
|
||||
with WebHookService
|
||||
with WebHookPullRequestService
|
||||
with WebHookIssueCommentService
|
||||
with WikiService
|
||||
with ActivityService
|
||||
with PrioritiesService
|
||||
with OwnerAuthenticator
|
||||
with UsersAuthenticator
|
||||
with GroupManagerAuthenticator
|
||||
with ReferrerAuthenticator
|
||||
with ReadableUsersAuthenticator
|
||||
with WritableUsersAuthenticator
|
||||
|
||||
trait ApiControllerBase extends ControllerBase {
|
||||
self: RepositoryService
|
||||
@@ -65,22 +69,22 @@ trait ApiControllerBase extends ControllerBase {
|
||||
with WritableUsersAuthenticator =>
|
||||
|
||||
/**
|
||||
* 404 for non-implemented api
|
||||
*/
|
||||
* 404 for non-implemented api
|
||||
*/
|
||||
get("/api/v3/*") {
|
||||
NotFound()
|
||||
}
|
||||
|
||||
/**
|
||||
* https://developer.github.com/v3/#root-endpoint
|
||||
*/
|
||||
get("/api/v3/") {
|
||||
* https://developer.github.com/v3/#root-endpoint
|
||||
*/
|
||||
get("/api/v3") {
|
||||
JsonFormat(ApiEndPoint())
|
||||
}
|
||||
|
||||
/**
|
||||
* https://developer.github.com/v3/orgs/#get-an-organization
|
||||
*/
|
||||
* https://developer.github.com/v3/orgs/#get-an-organization
|
||||
*/
|
||||
get("/api/v3/orgs/:groupName") {
|
||||
getAccountByUserName(params("groupName")).filter(account => account.isGroupAccount).map { account =>
|
||||
JsonFormat(ApiUser(account))
|
||||
@@ -98,123 +102,174 @@ trait ApiControllerBase extends ControllerBase {
|
||||
}
|
||||
|
||||
/**
|
||||
* https://developer.github.com/v3/repos/#list-organization-repositories
|
||||
*/
|
||||
* https://developer.github.com/v3/repos/#list-organization-repositories
|
||||
*/
|
||||
get("/api/v3/orgs/:orgName/repos") {
|
||||
JsonFormat(getVisibleRepositories(context.loginAccount, Some(params("orgName"))).map{ r => ApiRepository(r, getAccountByUserName(r.owner).get)})
|
||||
JsonFormat(getVisibleRepositories(context.loginAccount, Some(params("orgName"))).map { r =>
|
||||
ApiRepository(r, getAccountByUserName(r.owner).get)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* https://developer.github.com/v3/repos/#list-user-repositories
|
||||
*/
|
||||
get("/api/v3/users/:userName/repos") {
|
||||
JsonFormat(getVisibleRepositories(context.loginAccount, Some(params("userName"))).map{ r => ApiRepository(r, getAccountByUserName(r.owner).get)})
|
||||
JsonFormat(getVisibleRepositories(context.loginAccount, Some(params("userName"))).map { r =>
|
||||
ApiRepository(r, getAccountByUserName(r.owner).get)
|
||||
})
|
||||
}
|
||||
|
||||
/*
|
||||
* https://developer.github.com/v3/repos/branches/#list-branches
|
||||
*/
|
||||
get ("/api/v3/repos/:owner/:repo/branches")(referrersOnly { repository =>
|
||||
JsonFormat(JGitUtil.getBranches(
|
||||
owner = repository.owner,
|
||||
name = repository.name,
|
||||
defaultBranch = repository.repository.defaultBranch,
|
||||
origin = repository.repository.originUserName.isEmpty
|
||||
).map { br =>
|
||||
ApiBranchForList(br.name, ApiBranchCommit(br.commitId))
|
||||
})
|
||||
get("/api/v3/repos/:owner/:repo/branches")(referrersOnly { repository =>
|
||||
JsonFormat(
|
||||
JGitUtil
|
||||
.getBranches(
|
||||
owner = repository.owner,
|
||||
name = repository.name,
|
||||
defaultBranch = repository.repository.defaultBranch,
|
||||
origin = repository.repository.originUserName.isEmpty
|
||||
)
|
||||
.map { br =>
|
||||
ApiBranchForList(br.name, ApiBranchCommit(br.commitId))
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
/**
|
||||
* https://developer.github.com/v3/repos/branches/#get-branch
|
||||
*/
|
||||
get ("/api/v3/repos/:owner/:repo/branches/*")(referrersOnly { repository =>
|
||||
* https://developer.github.com/v3/repos/branches/#get-branch
|
||||
*/
|
||||
get("/api/v3/repos/:owner/:repo/branches/*")(referrersOnly { repository =>
|
||||
//import gitbucket.core.api._
|
||||
(for{
|
||||
(for {
|
||||
branch <- params.get("splat") if repository.branchList.contains(branch)
|
||||
br <- getBranches(repository.owner, repository.name, repository.repository.defaultBranch, repository.repository.originUserName.isEmpty).find(_.name == branch)
|
||||
br <- getBranches(
|
||||
repository.owner,
|
||||
repository.name,
|
||||
repository.repository.defaultBranch,
|
||||
repository.repository.originUserName.isEmpty
|
||||
).find(_.name == branch)
|
||||
} yield {
|
||||
val protection = getProtectedBranchInfo(repository.owner, repository.name, branch)
|
||||
JsonFormat(ApiBranch(branch, ApiBranchCommit(br.commitId), ApiBranchProtection(protection))(RepositoryName(repository)))
|
||||
JsonFormat(
|
||||
ApiBranch(branch, ApiBranchCommit(br.commitId), ApiBranchProtection(protection))(RepositoryName(repository))
|
||||
)
|
||||
}) getOrElse NotFound()
|
||||
})
|
||||
|
||||
/*
|
||||
* https://developer.github.com/v3/repos/contents/#get-contents
|
||||
*/
|
||||
get("/api/v3/repos/:owner/:repo/contents")(referrersOnly { repository =>
|
||||
getContents(repository, ".", params.getOrElse("ref", repository.repository.defaultBranch))
|
||||
})
|
||||
|
||||
/*
|
||||
* https://developer.github.com/v3/repos/contents/#get-contents
|
||||
*/
|
||||
get("/api/v3/repos/:owner/:repo/contents/*")(referrersOnly { repository =>
|
||||
getContents(repository, multiParams("splat").head, params.getOrElse("ref", repository.repository.defaultBranch))
|
||||
})
|
||||
|
||||
private def getContents(repository: RepositoryService.RepositoryInfo, path: String, refStr: String) = {
|
||||
def getFileInfo(git: Git, revision: String, pathStr: String): Option[FileInfo] = {
|
||||
val path = new java.io.File(pathStr)
|
||||
val dirName = path.getParent match {
|
||||
case null => "."
|
||||
case s => s
|
||||
val (dirName, fileName) = pathStr.lastIndexOf('/') match {
|
||||
case -1 =>
|
||||
(".", pathStr)
|
||||
case n =>
|
||||
(pathStr.take(n), pathStr.drop(n + 1))
|
||||
}
|
||||
getFileList(git, revision, dirName).find(f => f.name.equals(path.getName))
|
||||
getFileList(git, revision, dirName).find(f => f.name.equals(fileName))
|
||||
}
|
||||
|
||||
val path = multiParams("splat").head match {
|
||||
case s if s.isEmpty => "."
|
||||
case s => s
|
||||
}
|
||||
val refStr = params.getOrElse("ref", repository.repository.defaultBranch)
|
||||
|
||||
using(Git.open(getRepositoryDir(params("owner"), params("repo")))){ git =>
|
||||
using(Git.open(getRepositoryDir(params("owner"), params("repo")))) { git =>
|
||||
val fileList = getFileList(git, refStr, path)
|
||||
if (fileList.isEmpty) { // file or NotFound
|
||||
getFileInfo(git, refStr, path).flatMap(f => {
|
||||
val largeFile = params.get("large_file").exists(s => s.equals("true"))
|
||||
val content = getContentFromId(git, f.id, largeFile)
|
||||
request.getHeader("Accept") match {
|
||||
case "application/vnd.github.v3.raw" => {
|
||||
contentType = "application/vnd.github.v3.raw"
|
||||
content
|
||||
getFileInfo(git, refStr, path)
|
||||
.flatMap { f =>
|
||||
val largeFile = params.get("large_file").exists(s => s.equals("true"))
|
||||
val content = getContentFromId(git, f.id, largeFile)
|
||||
request.getHeader("Accept") match {
|
||||
case "application/vnd.github.v3.raw" => {
|
||||
contentType = "application/vnd.github.v3.raw"
|
||||
content
|
||||
}
|
||||
case "application/vnd.github.v3.html" if isRenderable(f.name) => {
|
||||
contentType = "application/vnd.github.v3.html"
|
||||
content.map { c =>
|
||||
List(
|
||||
"<div data-path=\"",
|
||||
path,
|
||||
"\" id=\"file\">",
|
||||
"<article>",
|
||||
renderMarkup(path.split("/").toList, new String(c), refStr, repository, false, false, true).body,
|
||||
"</article>",
|
||||
"</div>"
|
||||
).mkString
|
||||
}
|
||||
}
|
||||
case "application/vnd.github.v3.html" => {
|
||||
contentType = "application/vnd.github.v3.html"
|
||||
content.map { c =>
|
||||
List(
|
||||
"<div data-path=\"",
|
||||
path,
|
||||
"\" id=\"file\">",
|
||||
"<div class=\"plain\">",
|
||||
"<pre>",
|
||||
play.twirl.api.HtmlFormat.escape(new String(c)).body,
|
||||
"</pre>",
|
||||
"</div>",
|
||||
"</div>"
|
||||
).mkString
|
||||
}
|
||||
}
|
||||
case _ =>
|
||||
Some(JsonFormat(ApiContents(f, RepositoryName(repository), content)))
|
||||
}
|
||||
case "application/vnd.github.v3.html" if isRenderable(f.name) => {
|
||||
contentType = "application/vnd.github.v3.html"
|
||||
content.map(c =>
|
||||
List(
|
||||
"<div data-path=\"", path, "\" id=\"file\">", "<article>",
|
||||
renderMarkup(path.split("/").toList, new String(c), refStr, repository, false, false, true).body,
|
||||
"</article>", "</div>"
|
||||
).mkString
|
||||
)
|
||||
}
|
||||
case "application/vnd.github.v3.html" => {
|
||||
contentType = "application/vnd.github.v3.html"
|
||||
content.map(c =>
|
||||
List(
|
||||
"<div data-path=\"", path, "\" id=\"file\">", "<div class=\"plain\">", "<pre>",
|
||||
play.twirl.api.HtmlFormat.escape(new String(c)).body,
|
||||
"</pre>", "</div>", "</div>"
|
||||
).mkString
|
||||
)
|
||||
}
|
||||
case _ =>
|
||||
Some(JsonFormat(ApiContents(f, RepositoryName(repository), content)))
|
||||
}
|
||||
}).getOrElse(NotFound())
|
||||
.getOrElse(NotFound())
|
||||
|
||||
} else { // directory
|
||||
JsonFormat(fileList.map{f => ApiContents(f, RepositoryName(repository), None)})
|
||||
JsonFormat(fileList.map { f =>
|
||||
ApiContents(f, RepositoryName(repository), None)
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/*
|
||||
* https://developer.github.com/v3/git/refs/#get-a-reference
|
||||
*/
|
||||
get("/api/v3/repos/:owner/:repo/git/*") (referrersOnly { repository =>
|
||||
get("/api/v3/repos/:owner/:repo/git/refs/*")(referrersOnly { repository =>
|
||||
val revstr = multiParams("splat").head
|
||||
using(Git.open(getRepositoryDir(params("owner"), params("repo")))) { git =>
|
||||
//JsonFormat( (revstr, git.getRepository().resolve(revstr)) )
|
||||
// getRef is deprecated by jgit-4.2. use exactRef() or findRef()
|
||||
val sha = git.getRepository().exactRef(revstr).getObjectId().name()
|
||||
JsonFormat(ApiRef(revstr, ApiObject(sha)))
|
||||
val ref = git.getRepository().findRef(revstr)
|
||||
|
||||
if (ref != null) {
|
||||
val sha = ref.getObjectId().name()
|
||||
JsonFormat(ApiRef(revstr, ApiObject(sha)))
|
||||
|
||||
} else {
|
||||
val refs = git
|
||||
.getRepository()
|
||||
.getAllRefs()
|
||||
.asScala
|
||||
.collect { case (str, ref) if str.startsWith("refs/" + revstr) => ref }
|
||||
|
||||
JsonFormat(refs.map { ref =>
|
||||
val sha = ref.getObjectId().name()
|
||||
ApiRef(revstr, ApiObject(sha))
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* https://developer.github.com/v3/repos/collaborators/#list-collaborators
|
||||
*/
|
||||
get("/api/v3/repos/:owner/:repo/collaborators") (referrersOnly { repository =>
|
||||
get("/api/v3/repos/:owner/:repo/collaborators")(referrersOnly { repository =>
|
||||
// TODO Should ApiUser take permission? getCollaboratorUserNames does not return owner group members.
|
||||
JsonFormat(getCollaboratorUserNames(params("owner"), params("repo")).map(u => ApiUser(getAccountByUserName(u).get)))
|
||||
})
|
||||
@@ -232,9 +287,9 @@ trait ApiControllerBase extends ControllerBase {
|
||||
* List user's own repository
|
||||
* https://developer.github.com/v3/repos/#list-your-repositories
|
||||
*/
|
||||
get("/api/v3/user/repos")(usersOnly{
|
||||
JsonFormat(getVisibleRepositories(context.loginAccount, Option(context.loginAccount.get.userName)).map{
|
||||
r => ApiRepository(r, getAccountByUserName(r.owner).get)
|
||||
get("/api/v3/user/repos")(usersOnly {
|
||||
JsonFormat(getVisibleRepositories(context.loginAccount, Option(context.loginAccount.get.userName)).map { r =>
|
||||
ApiRepository(r, getAccountByUserName(r.owner).get)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -248,8 +303,16 @@ trait ApiControllerBase extends ControllerBase {
|
||||
data <- extractFromJsonBody[CreateARepository] if data.isValid
|
||||
} yield {
|
||||
LockUtil.lock(s"${owner}/${data.name}") {
|
||||
if(getRepository(owner, data.name).isEmpty){
|
||||
createRepository(context.loginAccount.get, owner, data.name, data.description, data.`private`, data.auto_init)
|
||||
if (getRepository(owner, data.name).isEmpty) {
|
||||
val f = createRepository(
|
||||
context.loginAccount.get,
|
||||
owner,
|
||||
data.name,
|
||||
data.description,
|
||||
data.`private`,
|
||||
data.auto_init
|
||||
)
|
||||
Await.result(f, Duration.Inf)
|
||||
val repository = getRepository(owner, data.name).get
|
||||
JsonFormat(ApiRepository(repository, ApiUser(getAccountByUserName(owner).get)))
|
||||
} else {
|
||||
@@ -272,8 +335,16 @@ trait ApiControllerBase extends ControllerBase {
|
||||
data <- extractFromJsonBody[CreateARepository] if data.isValid
|
||||
} yield {
|
||||
LockUtil.lock(s"${groupName}/${data.name}") {
|
||||
if(getRepository(groupName, data.name).isEmpty){
|
||||
createRepository(context.loginAccount.get, groupName, data.name, data.description, data.`private`, data.auto_init)
|
||||
if (getRepository(groupName, data.name).isEmpty) {
|
||||
val f = createRepository(
|
||||
context.loginAccount.get,
|
||||
groupName,
|
||||
data.name,
|
||||
data.description,
|
||||
data.`private`,
|
||||
data.auto_init
|
||||
)
|
||||
Await.result(f, Duration.Inf)
|
||||
val repository = getRepository(groupName, data.name).get
|
||||
JsonFormat(ApiRepository(repository, ApiUser(getAccountByUserName(groupName).get)))
|
||||
} else {
|
||||
@@ -291,13 +362,24 @@ trait ApiControllerBase extends ControllerBase {
|
||||
*/
|
||||
patch("/api/v3/repos/:owner/:repo/branches/*")(ownerOnly { repository =>
|
||||
import gitbucket.core.api._
|
||||
(for{
|
||||
branch <- params.get("splat") if repository.branchList.contains(branch)
|
||||
(for {
|
||||
branch <- params.get("splat") if repository.branchList.contains(branch)
|
||||
protection <- extractFromJsonBody[ApiBranchProtection.EnablingAndDisabling].map(_.protection)
|
||||
br <- getBranches(repository.owner, repository.name, repository.repository.defaultBranch, repository.repository.originUserName.isEmpty).find(_.name == branch)
|
||||
br <- getBranches(
|
||||
repository.owner,
|
||||
repository.name,
|
||||
repository.repository.defaultBranch,
|
||||
repository.repository.originUserName.isEmpty
|
||||
).find(_.name == branch)
|
||||
} yield {
|
||||
if(protection.enabled){
|
||||
enableBranchProtection(repository.owner, repository.name, branch, protection.status.enforcement_level == ApiBranchProtection.Everyone, protection.status.contexts)
|
||||
if (protection.enabled) {
|
||||
enableBranchProtection(
|
||||
repository.owner,
|
||||
repository.name,
|
||||
branch,
|
||||
protection.status.enforcement_level == ApiBranchProtection.Everyone,
|
||||
protection.status.contexts
|
||||
)
|
||||
} else {
|
||||
disableBranchProtection(repository.owner, repository.name, branch)
|
||||
}
|
||||
@@ -309,15 +391,15 @@ trait ApiControllerBase extends ControllerBase {
|
||||
* @see https://developer.github.com/v3/rate_limit/#get-your-current-rate-limit-status
|
||||
* but not enabled.
|
||||
*/
|
||||
get("/api/v3/rate_limit"){
|
||||
get("/api/v3/rate_limit") {
|
||||
contentType = formats("json")
|
||||
// this message is same as github enterprise...
|
||||
org.scalatra.NotFound(ApiError("Rate limiting is not enabled."))
|
||||
}
|
||||
|
||||
/**
|
||||
* https://developer.github.com/v3/issues/#list-issues-for-a-repository
|
||||
*/
|
||||
* https://developer.github.com/v3/issues/#list-issues-for-a-repository
|
||||
*/
|
||||
get("/api/v3/repos/:owner/:repository/issues")(referrersOnly { repository =>
|
||||
val page = IssueSearchCondition.page(request)
|
||||
// TODO: more api spec condition
|
||||
@@ -327,17 +409,20 @@ trait ApiControllerBase extends ControllerBase {
|
||||
val issues: List[(Issue, Account)] =
|
||||
searchIssueByApi(
|
||||
condition = condition,
|
||||
offset = (page - 1) * PullRequestLimit,
|
||||
limit = PullRequestLimit,
|
||||
repos = repository.owner -> repository.name
|
||||
offset = (page - 1) * PullRequestLimit,
|
||||
limit = PullRequestLimit,
|
||||
repos = repository.owner -> repository.name
|
||||
)
|
||||
|
||||
JsonFormat(issues.map { case (issue, issueUser) =>
|
||||
ApiIssue(
|
||||
issue = issue,
|
||||
repositoryName = RepositoryName(repository),
|
||||
user = ApiUser(issueUser)
|
||||
)
|
||||
JsonFormat(issues.map {
|
||||
case (issue, issueUser) =>
|
||||
ApiIssue(
|
||||
issue = issue,
|
||||
repositoryName = RepositoryName(repository),
|
||||
user = ApiUser(issueUser),
|
||||
labels = getIssueLabels(repository.owner, repository.name, issue.issueId)
|
||||
.map(ApiLabel(_, RepositoryName(repository)))
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -345,21 +430,28 @@ trait ApiControllerBase extends ControllerBase {
|
||||
* https://developer.github.com/v3/issues/#get-a-single-issue
|
||||
*/
|
||||
get("/api/v3/repos/:owner/:repository/issues/:id")(referrersOnly { repository =>
|
||||
(for{
|
||||
issueId <- params("id").toIntOpt
|
||||
(for {
|
||||
issueId <- params("id").toIntOpt
|
||||
issue <- getIssue(repository.owner, repository.name, issueId.toString)
|
||||
openedUser <- getAccountByUserName(issue.openedUserName)
|
||||
} yield {
|
||||
JsonFormat(ApiIssue(issue, RepositoryName(repository), ApiUser(openedUser)))
|
||||
JsonFormat(
|
||||
ApiIssue(
|
||||
issue,
|
||||
RepositoryName(repository),
|
||||
ApiUser(openedUser),
|
||||
getIssueLabels(repository.owner, repository.name, issue.issueId).map(ApiLabel(_, RepositoryName(repository)))
|
||||
)
|
||||
)
|
||||
}) getOrElse NotFound()
|
||||
})
|
||||
|
||||
/**
|
||||
* https://developer.github.com/v3/issues/#create-an-issue
|
||||
*/
|
||||
* https://developer.github.com/v3/issues/#create-an-issue
|
||||
*/
|
||||
post("/api/v3/repos/:owner/:repository/issues")(readableUsersOnly { repository =>
|
||||
if(isIssueEditable(repository)){ // TODO Should this check is provided by authenticator?
|
||||
(for{
|
||||
if (isIssueEditable(repository)) { // TODO Should this check is provided by authenticator?
|
||||
(for {
|
||||
data <- extractFromJsonBody[CreateAnIssue]
|
||||
loginAccount <- context.loginAccount
|
||||
} yield {
|
||||
@@ -372,8 +464,17 @@ trait ApiControllerBase extends ControllerBase {
|
||||
milestone.map(_.milestoneId),
|
||||
None,
|
||||
data.labels,
|
||||
loginAccount)
|
||||
JsonFormat(ApiIssue(issue, RepositoryName(repository), ApiUser(loginAccount)))
|
||||
loginAccount
|
||||
)
|
||||
JsonFormat(
|
||||
ApiIssue(
|
||||
issue,
|
||||
RepositoryName(repository),
|
||||
ApiUser(loginAccount),
|
||||
getIssueLabels(repository.owner, repository.name, issue.issueId)
|
||||
.map(ApiLabel(_, RepositoryName(repository)))
|
||||
)
|
||||
)
|
||||
}) getOrElse NotFound()
|
||||
} else Unauthorized()
|
||||
})
|
||||
@@ -382,11 +483,14 @@ trait ApiControllerBase extends ControllerBase {
|
||||
* https://developer.github.com/v3/issues/comments/#list-comments-on-an-issue
|
||||
*/
|
||||
get("/api/v3/repos/:owner/:repository/issues/:id/comments")(referrersOnly { repository =>
|
||||
(for{
|
||||
issueId <- params("id").toIntOpt
|
||||
comments = getCommentsForApi(repository.owner, repository.name, issueId)
|
||||
(for {
|
||||
issueId <- params("id").toIntOpt
|
||||
comments = getCommentsForApi(repository.owner, repository.name, issueId)
|
||||
} yield {
|
||||
JsonFormat(comments.map{ case (issueComment, user, issue) => ApiComment(issueComment, RepositoryName(repository), issueId, ApiUser(user), issue.isPullRequest) })
|
||||
JsonFormat(comments.map {
|
||||
case (issueComment, user, issue) =>
|
||||
ApiComment(issueComment, RepositoryName(repository), issueId, ApiUser(user), issue.isPullRequest)
|
||||
})
|
||||
}) getOrElse NotFound()
|
||||
})
|
||||
|
||||
@@ -394,15 +498,23 @@ trait ApiControllerBase extends ControllerBase {
|
||||
* https://developer.github.com/v3/issues/comments/#create-a-comment
|
||||
*/
|
||||
post("/api/v3/repos/:owner/:repository/issues/:id/comments")(readableUsersOnly { repository =>
|
||||
(for{
|
||||
issueId <- params("id").toIntOpt
|
||||
issue <- getIssue(repository.owner, repository.name, issueId.toString)
|
||||
body <- extractFromJsonBody[CreateAComment].map(_.body) if ! body.isEmpty
|
||||
action = params.get("action").filter(_ => isEditable(issue.userName, issue.repositoryName, issue.openedUserName))
|
||||
(issue, id) <- handleComment(issue, Some(body), repository, action)
|
||||
(for {
|
||||
issueId <- params("id").toIntOpt
|
||||
issue <- getIssue(repository.owner, repository.name, issueId.toString)
|
||||
body <- extractFromJsonBody[CreateAComment].map(_.body) if !body.isEmpty
|
||||
action = params.get("action").filter(_ => isEditable(issue.userName, issue.repositoryName, issue.openedUserName))
|
||||
(issue, id) <- handleComment(issue, Some(body), repository, action)
|
||||
issueComment <- getComment(repository.owner, repository.name, id.toString())
|
||||
} yield {
|
||||
JsonFormat(ApiComment(issueComment, RepositoryName(repository), issueId, ApiUser(context.loginAccount.get), issue.isPullRequest))
|
||||
JsonFormat(
|
||||
ApiComment(
|
||||
issueComment,
|
||||
RepositoryName(repository),
|
||||
issueId,
|
||||
ApiUser(context.loginAccount.get),
|
||||
issue.isPullRequest
|
||||
)
|
||||
)
|
||||
}) getOrElse NotFound()
|
||||
})
|
||||
|
||||
@@ -431,7 +543,7 @@ trait ApiControllerBase extends ControllerBase {
|
||||
* https://developer.github.com/v3/issues/labels/#create-a-label
|
||||
*/
|
||||
post("/api/v3/repos/:owner/:repository/labels")(writableUsersOnly { repository =>
|
||||
(for{
|
||||
(for {
|
||||
data <- extractFromJsonBody[CreateALabel] if data.isValid
|
||||
} yield {
|
||||
LockUtil.lock(RepositoryName(repository).fullName) {
|
||||
@@ -442,10 +554,12 @@ trait ApiControllerBase extends ControllerBase {
|
||||
} getOrElse NotFound()
|
||||
} else {
|
||||
// TODO ApiError should support errors field to enhance compatibility of GitHub API
|
||||
UnprocessableEntity(ApiError(
|
||||
"Validation Failed",
|
||||
Some("https://developer.github.com/v3/issues/labels/#create-a-label")
|
||||
))
|
||||
UnprocessableEntity(
|
||||
ApiError(
|
||||
"Validation Failed",
|
||||
Some("https://developer.github.com/v3/issues/labels/#create-a-label")
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}) getOrElse NotFound()
|
||||
@@ -456,24 +570,29 @@ trait ApiControllerBase extends ControllerBase {
|
||||
* https://developer.github.com/v3/issues/labels/#update-a-label
|
||||
*/
|
||||
patch("/api/v3/repos/:owner/:repository/labels/:labelName")(writableUsersOnly { repository =>
|
||||
(for{
|
||||
(for {
|
||||
data <- extractFromJsonBody[CreateALabel] if data.isValid
|
||||
} yield {
|
||||
LockUtil.lock(RepositoryName(repository).fullName) {
|
||||
getLabel(repository.owner, repository.name, params("labelName")).map { label =>
|
||||
if (getLabel(repository.owner, repository.name, data.name).isEmpty) {
|
||||
updateLabel(repository.owner, repository.name, label.labelId, data.name, data.color)
|
||||
JsonFormat(ApiLabel(
|
||||
getLabel(repository.owner, repository.name, label.labelId).get,
|
||||
RepositoryName(repository)
|
||||
))
|
||||
} else {
|
||||
// TODO ApiError should support errors field to enhance compatibility of GitHub API
|
||||
UnprocessableEntity(ApiError(
|
||||
"Validation Failed",
|
||||
Some("https://developer.github.com/v3/issues/labels/#create-a-label")
|
||||
))
|
||||
}
|
||||
getLabel(repository.owner, repository.name, params("labelName")).map {
|
||||
label =>
|
||||
if (getLabel(repository.owner, repository.name, data.name).isEmpty) {
|
||||
updateLabel(repository.owner, repository.name, label.labelId, data.name, data.color)
|
||||
JsonFormat(
|
||||
ApiLabel(
|
||||
getLabel(repository.owner, repository.name, label.labelId).get,
|
||||
RepositoryName(repository)
|
||||
)
|
||||
)
|
||||
} else {
|
||||
// TODO ApiError should support errors field to enhance compatibility of GitHub API
|
||||
UnprocessableEntity(
|
||||
ApiError(
|
||||
"Validation Failed",
|
||||
Some("https://developer.github.com/v3/issues/labels/#create-a-label")
|
||||
)
|
||||
)
|
||||
}
|
||||
} getOrElse NotFound()
|
||||
}
|
||||
}) getOrElse NotFound()
|
||||
@@ -504,21 +623,24 @@ trait ApiControllerBase extends ControllerBase {
|
||||
val issues: List[(Issue, Account, Int, PullRequest, Repository, Account, Option[Account])] =
|
||||
searchPullRequestByApi(
|
||||
condition = condition,
|
||||
offset = (page - 1) * PullRequestLimit,
|
||||
limit = PullRequestLimit,
|
||||
repos = repository.owner -> repository.name
|
||||
offset = (page - 1) * PullRequestLimit,
|
||||
limit = PullRequestLimit,
|
||||
repos = repository.owner -> repository.name
|
||||
)
|
||||
|
||||
JsonFormat(issues.map { case (issue, issueUser, commentCount, pullRequest, headRepo, headOwner, assignee) =>
|
||||
ApiPullRequest(
|
||||
issue = issue,
|
||||
pullRequest = pullRequest,
|
||||
headRepo = ApiRepository(headRepo, ApiUser(headOwner)),
|
||||
baseRepo = ApiRepository(repository, ApiUser(baseOwner)),
|
||||
user = ApiUser(issueUser),
|
||||
assignee = assignee.map(ApiUser.apply),
|
||||
mergedComment = getMergedComment(repository.owner, repository.name, issue.issueId)
|
||||
)
|
||||
JsonFormat(issues.map {
|
||||
case (issue, issueUser, commentCount, pullRequest, headRepo, headOwner, assignee) =>
|
||||
ApiPullRequest(
|
||||
issue = issue,
|
||||
pullRequest = pullRequest,
|
||||
headRepo = ApiRepository(headRepo, ApiUser(headOwner)),
|
||||
baseRepo = ApiRepository(repository, ApiUser(baseOwner)),
|
||||
user = ApiUser(issueUser),
|
||||
labels = getIssueLabels(repository.owner, repository.name, issue.issueId)
|
||||
.map(ApiLabel(_, RepositoryName(repository))),
|
||||
assignee = assignee.map(ApiUser.apply),
|
||||
mergedComment = getMergedComment(repository.owner, repository.name, issue.issueId)
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -526,25 +648,34 @@ trait ApiControllerBase extends ControllerBase {
|
||||
* https://developer.github.com/v3/pulls/#get-a-single-pull-request
|
||||
*/
|
||||
get("/api/v3/repos/:owner/:repository/pulls/:id")(referrersOnly { repository =>
|
||||
(for{
|
||||
issueId <- params("id").toIntOpt
|
||||
(for {
|
||||
issueId <- params("id").toIntOpt
|
||||
(issue, pullRequest) <- getPullRequest(repository.owner, repository.name, issueId)
|
||||
users = getAccountsByUserNames(Set(repository.owner, pullRequest.requestUserName, issue.openedUserName), Set.empty)
|
||||
users = getAccountsByUserNames(
|
||||
Set(repository.owner, pullRequest.requestUserName, issue.openedUserName),
|
||||
Set.empty
|
||||
)
|
||||
baseOwner <- users.get(repository.owner)
|
||||
headOwner <- users.get(pullRequest.requestUserName)
|
||||
issueUser <- users.get(issue.openedUserName)
|
||||
assignee = issue.assignedUserName.flatMap { userName => getAccountByUserName(userName, false) }
|
||||
headRepo <- getRepository(pullRequest.requestUserName, pullRequest.requestRepositoryName)
|
||||
assignee = issue.assignedUserName.flatMap { userName =>
|
||||
getAccountByUserName(userName, false)
|
||||
}
|
||||
headRepo <- getRepository(pullRequest.requestUserName, pullRequest.requestRepositoryName)
|
||||
} yield {
|
||||
JsonFormat(ApiPullRequest(
|
||||
issue = issue,
|
||||
pullRequest = pullRequest,
|
||||
headRepo = ApiRepository(headRepo, ApiUser(headOwner)),
|
||||
baseRepo = ApiRepository(repository, ApiUser(baseOwner)),
|
||||
user = ApiUser(issueUser),
|
||||
assignee = assignee.map(ApiUser.apply),
|
||||
mergedComment = getMergedComment(repository.owner, repository.name, issue.issueId)
|
||||
))
|
||||
JsonFormat(
|
||||
ApiPullRequest(
|
||||
issue = issue,
|
||||
pullRequest = pullRequest,
|
||||
headRepo = ApiRepository(headRepo, ApiUser(headOwner)),
|
||||
baseRepo = ApiRepository(repository, ApiUser(baseOwner)),
|
||||
user = ApiUser(issueUser),
|
||||
labels = getIssueLabels(repository.owner, repository.name, issue.issueId)
|
||||
.map(ApiLabel(_, RepositoryName(repository))),
|
||||
assignee = assignee.map(ApiUser.apply),
|
||||
mergedComment = getMergedComment(repository.owner, repository.name, issue.issueId)
|
||||
)
|
||||
)
|
||||
}) getOrElse NotFound()
|
||||
})
|
||||
|
||||
@@ -554,16 +685,26 @@ trait ApiControllerBase extends ControllerBase {
|
||||
get("/api/v3/repos/:owner/:repository/pulls/:id/commits")(referrersOnly { repository =>
|
||||
val owner = repository.owner
|
||||
val name = repository.name
|
||||
params("id").toIntOpt.flatMap{ issueId =>
|
||||
getPullRequest(owner, name, issueId) map { case(issue, pullreq) =>
|
||||
using(Git.open(getRepositoryDir(owner, name))){ git =>
|
||||
val oldId = git.getRepository.resolve(pullreq.commitIdFrom)
|
||||
val newId = git.getRepository.resolve(pullreq.commitIdTo)
|
||||
val repoFullName = RepositoryName(repository)
|
||||
val commits = git.log.addRange(oldId, newId).call.iterator.asScala.map { c => ApiCommitListItem(new CommitInfo(c), repoFullName) }.toList
|
||||
JsonFormat(commits)
|
||||
params("id").toIntOpt.flatMap {
|
||||
issueId =>
|
||||
getPullRequest(owner, name, issueId) map {
|
||||
case (issue, pullreq) =>
|
||||
using(Git.open(getRepositoryDir(owner, name))) { git =>
|
||||
val oldId = git.getRepository.resolve(pullreq.commitIdFrom)
|
||||
val newId = git.getRepository.resolve(pullreq.commitIdTo)
|
||||
val repoFullName = RepositoryName(repository)
|
||||
val commits = git.log
|
||||
.addRange(oldId, newId)
|
||||
.call
|
||||
.iterator
|
||||
.asScala
|
||||
.map { c =>
|
||||
ApiCommitListItem(new CommitInfo(c), repoFullName)
|
||||
}
|
||||
.toList
|
||||
JsonFormat(commits)
|
||||
}
|
||||
}
|
||||
}
|
||||
} getOrElse NotFound()
|
||||
})
|
||||
|
||||
@@ -578,15 +719,24 @@ trait ApiControllerBase extends ControllerBase {
|
||||
* https://developer.github.com/v3/repos/statuses/#create-a-status
|
||||
*/
|
||||
post("/api/v3/repos/:owner/:repo/statuses/:sha")(writableUsersOnly { repository =>
|
||||
(for{
|
||||
ref <- params.get("sha")
|
||||
sha <- JGitUtil.getShaByRef(repository.owner, repository.name, ref)
|
||||
data <- extractFromJsonBody[CreateAStatus] if data.isValid
|
||||
creator <- context.loginAccount
|
||||
state <- CommitState.valueOf(data.state)
|
||||
statusId = createCommitStatus(repository.owner, repository.name, sha, data.context.getOrElse("default"),
|
||||
state, data.target_url, data.description, new java.util.Date(), creator)
|
||||
status <- getCommitStatus(repository.owner, repository.name, statusId)
|
||||
(for {
|
||||
ref <- params.get("sha")
|
||||
sha <- JGitUtil.getShaByRef(repository.owner, repository.name, ref)
|
||||
data <- extractFromJsonBody[CreateAStatus] if data.isValid
|
||||
creator <- context.loginAccount
|
||||
state <- CommitState.valueOf(data.state)
|
||||
statusId = createCommitStatus(
|
||||
repository.owner,
|
||||
repository.name,
|
||||
sha,
|
||||
data.context.getOrElse("default"),
|
||||
state,
|
||||
data.target_url,
|
||||
data.description,
|
||||
new java.util.Date(),
|
||||
creator
|
||||
)
|
||||
status <- getCommitStatus(repository.owner, repository.name, statusId)
|
||||
} yield {
|
||||
JsonFormat(ApiCommitStatus(status, ApiUser(creator)))
|
||||
}) getOrElse NotFound()
|
||||
@@ -598,12 +748,13 @@ trait ApiControllerBase extends ControllerBase {
|
||||
* ref is Ref to list the statuses from. It can be a SHA, a branch name, or a tag name.
|
||||
*/
|
||||
val listStatusesRoute = get("/api/v3/repos/:owner/:repo/commits/:ref/statuses")(referrersOnly { repository =>
|
||||
(for{
|
||||
(for {
|
||||
ref <- params.get("ref")
|
||||
sha <- JGitUtil.getShaByRef(repository.owner, repository.name, ref)
|
||||
} yield {
|
||||
JsonFormat(getCommitStatuesWithCreator(repository.owner, repository.name, sha).map{ case(status, creator) =>
|
||||
ApiCommitStatus(status, ApiUser(creator))
|
||||
JsonFormat(getCommitStatuesWithCreator(repository.owner, repository.name, sha).map {
|
||||
case (status, creator) =>
|
||||
ApiCommitStatus(status, ApiUser(creator))
|
||||
})
|
||||
}) getOrElse NotFound()
|
||||
})
|
||||
@@ -613,7 +764,7 @@ trait ApiControllerBase extends ControllerBase {
|
||||
*
|
||||
* legacy route
|
||||
*/
|
||||
get("/api/v3/repos/:owner/:repo/statuses/:ref"){
|
||||
get("/api/v3/repos/:owner/:repo/statuses/:ref") {
|
||||
listStatusesRoute.action()
|
||||
}
|
||||
|
||||
@@ -623,10 +774,10 @@ trait ApiControllerBase extends ControllerBase {
|
||||
* ref is Ref to list the statuses from. It can be a SHA, a branch name, or a tag name.
|
||||
*/
|
||||
get("/api/v3/repos/:owner/:repo/commits/:ref/status")(referrersOnly { repository =>
|
||||
(for{
|
||||
ref <- params.get("ref")
|
||||
(for {
|
||||
ref <- params.get("ref")
|
||||
owner <- getAccountByUserName(repository.owner)
|
||||
sha <- JGitUtil.getShaByRef(repository.owner, repository.name, ref)
|
||||
sha <- JGitUtil.getShaByRef(repository.owner, repository.name, ref)
|
||||
} yield {
|
||||
val statuses = getCommitStatuesWithCreator(repository.owner, repository.name, sha)
|
||||
JsonFormat(ApiCombinedCommitStatus(sha, statuses, ApiRepository(repository, owner)))
|
||||
@@ -638,24 +789,27 @@ trait ApiControllerBase extends ControllerBase {
|
||||
*/
|
||||
get("/api/v3/repos/:owner/:repo/commits/:sha")(referrersOnly { repository =>
|
||||
val owner = repository.owner
|
||||
val name = repository.name
|
||||
val sha = params("sha")
|
||||
val name = repository.name
|
||||
val sha = params("sha")
|
||||
|
||||
using(Git.open(getRepositoryDir(owner, name))){ git =>
|
||||
val repo = git.getRepository
|
||||
val objectId = repo.resolve(sha)
|
||||
val commitInfo = using(new RevWalk(repo)){ revWalk =>
|
||||
new CommitInfo(revWalk.parseCommit(objectId))
|
||||
}
|
||||
using(Git.open(getRepositoryDir(owner, name))) {
|
||||
git =>
|
||||
val repo = git.getRepository
|
||||
val objectId = repo.resolve(sha)
|
||||
val commitInfo = using(new RevWalk(repo)) { revWalk =>
|
||||
new CommitInfo(revWalk.parseCommit(objectId))
|
||||
}
|
||||
|
||||
JsonFormat(ApiCommits(
|
||||
repositoryName = RepositoryName(repository),
|
||||
commitInfo = commitInfo,
|
||||
diffs = JGitUtil.getDiffs(git, commitInfo.parents.head, commitInfo.id, false, true),
|
||||
author = getAccount(commitInfo.authorName, commitInfo.authorEmailAddress),
|
||||
committer = getAccount(commitInfo.committerName, commitInfo.committerEmailAddress),
|
||||
commentCount = getCommitComment(repository.owner, repository.name, sha).size
|
||||
))
|
||||
JsonFormat(
|
||||
ApiCommits(
|
||||
repositoryName = RepositoryName(repository),
|
||||
commitInfo = commitInfo,
|
||||
diffs = JGitUtil.getDiffs(git, Some(commitInfo.parents.head), commitInfo.id, false, true),
|
||||
author = getAccount(commitInfo.authorName, commitInfo.authorEmailAddress),
|
||||
committer = getAccount(commitInfo.committerName, commitInfo.committerEmailAddress),
|
||||
commentCount = getCommitComment(repository.owner, repository.name, sha).size
|
||||
)
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -683,11 +837,11 @@ trait ApiControllerBase extends ControllerBase {
|
||||
hasDeveloperRole(owner, repository, context.loginAccount) || author == context.loginAccount.get.userName
|
||||
|
||||
/**
|
||||
* non-GitHub compatible API for Jenkins-Plugin
|
||||
*/
|
||||
* non-GitHub compatible API for Jenkins-Plugin
|
||||
*/
|
||||
get("/api/v3/repos/:owner/:repo/raw/*")(referrersOnly { repository =>
|
||||
val (id, path) = repository.splitPath(multiParams("splat").head)
|
||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
|
||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(id))
|
||||
|
||||
getPathObjectId(git, path, revCommit).map { objectId =>
|
||||
@@ -696,5 +850,10 @@ trait ApiControllerBase extends ControllerBase {
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* non-GitHub compatible API for listing plugins
|
||||
*/
|
||||
get("/api/v3/gitbucket/plugins") {
|
||||
PluginRegistry().getPlugins().map { ApiPlugin(_) }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package gitbucket.core.controller
|
||||
|
||||
import java.io.FileInputStream
|
||||
import java.io.{File, FileInputStream}
|
||||
|
||||
import gitbucket.core.api.ApiError
|
||||
import gitbucket.core.api.{ApiError, JsonFormat}
|
||||
import gitbucket.core.model.Account
|
||||
import gitbucket.core.service.{AccountService, SystemSettingsService,RepositoryService}
|
||||
import gitbucket.core.service.{AccountService, RepositoryService, SystemSettingsService}
|
||||
import gitbucket.core.util.SyntaxSugars._
|
||||
import gitbucket.core.util.Directory._
|
||||
import gitbucket.core.util.Implicits._
|
||||
@@ -17,9 +17,10 @@ import org.scalatra.forms._
|
||||
import javax.servlet.http.{HttpServletRequest, HttpServletResponse}
|
||||
import javax.servlet.{FilterChain, ServletRequest, ServletResponse}
|
||||
|
||||
import is.tagomor.woothee.Classifier
|
||||
|
||||
import scala.util.Try
|
||||
import net.coobird.thumbnailator.Thumbnails
|
||||
|
||||
import org.eclipse.jgit.api.Git
|
||||
import org.eclipse.jgit.lib.ObjectId
|
||||
import org.eclipse.jgit.revwalk.RevCommit
|
||||
@@ -30,9 +31,14 @@ import org.slf4j.LoggerFactory
|
||||
/**
|
||||
* Provides generic features for controller implementations.
|
||||
*/
|
||||
abstract class ControllerBase extends ScalatraFilter
|
||||
with ValidationSupport with JacksonJsonSupport with I18nSupport with FlashMapSupport with Validations
|
||||
with SystemSettingsService {
|
||||
abstract class ControllerBase
|
||||
extends ScalatraFilter
|
||||
with ValidationSupport
|
||||
with JacksonJsonSupport
|
||||
with I18nSupport
|
||||
with FlashMapSupport
|
||||
with Validations
|
||||
with SystemSettingsService {
|
||||
|
||||
private val logger = LoggerFactory.getLogger(getClass)
|
||||
|
||||
@@ -40,40 +46,34 @@ abstract class ControllerBase extends ScalatraFilter
|
||||
|
||||
before("/api/v3/*") {
|
||||
contentType = formats("json")
|
||||
request.setAttribute(Keys.Request.APIv3, true)
|
||||
}
|
||||
|
||||
override def doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain): Unit = try {
|
||||
val httpRequest = request.asInstanceOf[HttpServletRequest]
|
||||
val httpResponse = response.asInstanceOf[HttpServletResponse]
|
||||
val context = request.getServletContext.getContextPath
|
||||
val path = httpRequest.getRequestURI.substring(context.length)
|
||||
override def requestPath(uri: String, idx: Int): String = {
|
||||
val path = super.requestPath(uri, idx)
|
||||
if (path != "/" && path.endsWith("/")) {
|
||||
path.substring(0, path.length - 1)
|
||||
} else {
|
||||
path
|
||||
}
|
||||
}
|
||||
|
||||
if(path.startsWith("/console/")){
|
||||
val account = httpRequest.getSession.getAttribute(Keys.Session.LoginAccount).asInstanceOf[Account]
|
||||
val baseUrl = this.baseUrl(httpRequest)
|
||||
if(account == null){
|
||||
// Redirect to login form
|
||||
httpResponse.sendRedirect(baseUrl + "/signin?redirect=" + StringUtil.urlEncode(path))
|
||||
} else if(account.isAdmin){
|
||||
// H2 Console (administrators only)
|
||||
override def doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain): Unit =
|
||||
try {
|
||||
val httpRequest = request.asInstanceOf[HttpServletRequest]
|
||||
val context = request.getServletContext.getContextPath
|
||||
val path = httpRequest.getRequestURI.substring(context.length)
|
||||
|
||||
if (path.startsWith("/git/") || path.startsWith("/git-lfs/")) {
|
||||
// Git repository
|
||||
chain.doFilter(request, response)
|
||||
} else {
|
||||
// Redirect to dashboard
|
||||
httpResponse.sendRedirect(baseUrl + "/")
|
||||
// Scalatra actions
|
||||
super.doFilter(request, response, chain)
|
||||
}
|
||||
} else if(path.startsWith("/git/") || path.startsWith("/git-lfs/")){
|
||||
// Git repository
|
||||
chain.doFilter(request, response)
|
||||
} else {
|
||||
if(path.startsWith("/api/v3/")){
|
||||
httpRequest.setAttribute(Keys.Request.APIv3, true)
|
||||
}
|
||||
// Scalatra actions
|
||||
super.doFilter(request, response, chain)
|
||||
} finally {
|
||||
contextCache.remove();
|
||||
}
|
||||
} finally {
|
||||
contextCache.remove();
|
||||
}
|
||||
|
||||
private val contextCache = new java.lang.ThreadLocal[Context]()
|
||||
|
||||
@@ -91,102 +91,130 @@ abstract class ControllerBase extends ScalatraFilter
|
||||
}
|
||||
}
|
||||
|
||||
private def LoginAccount: Option[Account] = request.getAs[Account](Keys.Session.LoginAccount).orElse(session.getAs[Account](Keys.Session.LoginAccount))
|
||||
private def LoginAccount: Option[Account] =
|
||||
request.getAs[Account](Keys.Session.LoginAccount).orElse(session.getAs[Account](Keys.Session.LoginAccount))
|
||||
|
||||
def ajaxGet(path : String)(action : => Any) : Route =
|
||||
super.get(path){
|
||||
def ajaxGet(path: String)(action: => Any): Route =
|
||||
super.get(path) {
|
||||
request.setAttribute(Keys.Request.Ajax, "true")
|
||||
action
|
||||
}
|
||||
|
||||
override def ajaxGet[T](path : String, form : ValueType[T])(action : T => Any) : Route =
|
||||
super.ajaxGet(path, form){ form =>
|
||||
override def ajaxGet[T](path: String, form: ValueType[T])(action: T => Any): Route =
|
||||
super.ajaxGet(path, form) { form =>
|
||||
request.setAttribute(Keys.Request.Ajax, "true")
|
||||
action(form)
|
||||
}
|
||||
|
||||
def ajaxPost(path : String)(action : => Any) : Route =
|
||||
super.post(path){
|
||||
def ajaxPost(path: String)(action: => Any): Route =
|
||||
super.post(path) {
|
||||
request.setAttribute(Keys.Request.Ajax, "true")
|
||||
action
|
||||
}
|
||||
|
||||
override def ajaxPost[T](path : String, form : ValueType[T])(action : T => Any) : Route =
|
||||
super.ajaxPost(path, form){ form =>
|
||||
override def ajaxPost[T](path: String, form: ValueType[T])(action: T => Any): Route =
|
||||
super.ajaxPost(path, form) { form =>
|
||||
request.setAttribute(Keys.Request.Ajax, "true")
|
||||
action(form)
|
||||
}
|
||||
|
||||
protected def NotFound() =
|
||||
if(request.hasAttribute(Keys.Request.Ajax)){
|
||||
if (request.hasAttribute(Keys.Request.Ajax)) {
|
||||
org.scalatra.NotFound()
|
||||
} else if(request.hasAttribute(Keys.Request.APIv3)){
|
||||
} else if (request.hasAttribute(Keys.Request.APIv3)) {
|
||||
contentType = formats("json")
|
||||
org.scalatra.NotFound(ApiError("Not Found"))
|
||||
org.scalatra.NotFound(JsonFormat(ApiError("Not Found")))
|
||||
} else {
|
||||
org.scalatra.NotFound(gitbucket.core.html.error("Not Found"))
|
||||
}
|
||||
|
||||
protected def Unauthorized()(implicit context: Context) =
|
||||
if(request.hasAttribute(Keys.Request.Ajax)){
|
||||
org.scalatra.Unauthorized()
|
||||
} else if(request.hasAttribute(Keys.Request.APIv3)){
|
||||
contentType = formats("json")
|
||||
org.scalatra.Unauthorized(ApiError("Requires authentication"))
|
||||
private def isBrowser(userAgent: String): Boolean = {
|
||||
if (userAgent == null || userAgent.isEmpty) {
|
||||
false
|
||||
} else {
|
||||
if(context.loginAccount.isDefined){
|
||||
val data = Classifier.parse(userAgent)
|
||||
val category = data.get("category")
|
||||
category == "pc" || category == "smartphone" || category == "mobilephone"
|
||||
}
|
||||
}
|
||||
|
||||
protected def Unauthorized()(implicit context: Context) =
|
||||
if (request.hasAttribute(Keys.Request.Ajax)) {
|
||||
org.scalatra.Unauthorized()
|
||||
} else if (request.hasAttribute(Keys.Request.APIv3)) {
|
||||
contentType = formats("json")
|
||||
org.scalatra.Unauthorized(JsonFormat(ApiError("Requires authentication")))
|
||||
} else if (!isBrowser(request.getHeader("USER-AGENT"))) {
|
||||
org.scalatra.Unauthorized()
|
||||
} else {
|
||||
if (context.loginAccount.isDefined) {
|
||||
org.scalatra.Unauthorized(redirect("/"))
|
||||
} else {
|
||||
if(request.getMethod.toUpperCase == "POST"){
|
||||
if (request.getMethod.toUpperCase == "POST") {
|
||||
org.scalatra.Unauthorized(redirect("/signin"))
|
||||
} else {
|
||||
org.scalatra.Unauthorized(redirect("/signin?redirect=" + StringUtil.urlEncode(
|
||||
defining(request.getQueryString){ queryString =>
|
||||
request.getRequestURI.substring(request.getContextPath.length) + (if(queryString != null) "?" + queryString else "")
|
||||
}
|
||||
)))
|
||||
org.scalatra.Unauthorized(
|
||||
redirect(
|
||||
"/signin?redirect=" + StringUtil.urlEncode(
|
||||
defining(request.getQueryString) { queryString =>
|
||||
request.getRequestURI.substring(request.getContextPath.length) + (if (queryString != null)
|
||||
"?" + queryString
|
||||
else "")
|
||||
}
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
error{
|
||||
error {
|
||||
case e => {
|
||||
logger.error(s"Catch unhandled error in request: ${request}", e)
|
||||
if(request.hasAttribute(Keys.Request.Ajax)){
|
||||
if (request.hasAttribute(Keys.Request.Ajax)) {
|
||||
org.scalatra.InternalServerError()
|
||||
} else if(request.hasAttribute(Keys.Request.APIv3)){
|
||||
} else if (request.hasAttribute(Keys.Request.APIv3)) {
|
||||
contentType = formats("json")
|
||||
org.scalatra.InternalServerError(ApiError("Internal Server Error"))
|
||||
org.scalatra.InternalServerError(JsonFormat(ApiError("Internal Server Error")))
|
||||
} else {
|
||||
org.scalatra.InternalServerError(gitbucket.core.html.error("Internal Server Error", Some(e)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override def url(path: String, params: Iterable[(String, Any)] = Iterable.empty,
|
||||
includeContextPath: Boolean = true, includeServletPath: Boolean = true,
|
||||
absolutize: Boolean = true, withSessionId: Boolean = true)
|
||||
(implicit request: HttpServletRequest, response: HttpServletResponse): String =
|
||||
override def url(
|
||||
path: String,
|
||||
params: Iterable[(String, Any)] = Iterable.empty,
|
||||
includeContextPath: Boolean = true,
|
||||
includeServletPath: Boolean = true,
|
||||
absolutize: Boolean = true,
|
||||
withSessionId: Boolean = true
|
||||
)(implicit request: HttpServletRequest, response: HttpServletResponse): String =
|
||||
if (path.startsWith("http")) path
|
||||
else baseUrl + super.url(path, params, false, false, false)
|
||||
|
||||
/**
|
||||
* Extends scalatra-form's trim rule to eliminate CR and LF.
|
||||
*/
|
||||
protected def trim2[T](valueType: SingleValueType[T]): SingleValueType[T] = new SingleValueType[T](){
|
||||
protected def trim2[T](valueType: SingleValueType[T]): SingleValueType[T] = new SingleValueType[T]() {
|
||||
def convert(value: String, messages: Messages): T = valueType.convert(trim(value), messages)
|
||||
|
||||
override def validate(name: String, value: String, params: Map[String, Seq[String]], messages: Messages): Seq[(String, String)] =
|
||||
override def validate(
|
||||
name: String,
|
||||
value: String,
|
||||
params: Map[String, Seq[String]],
|
||||
messages: Messages
|
||||
): Seq[(String, String)] =
|
||||
valueType.validate(name, trim(value), params, messages)
|
||||
|
||||
private def trim(value: String): String = if(value == null) null else value.replace("\r\n", "").trim
|
||||
private def trim(value: String): String = if (value == null) null else value.replace("\r\n", "").trim
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this method to response the raw data against XSS.
|
||||
*/
|
||||
protected def RawData[T](contentType: String, rawData: T): T = {
|
||||
if(contentType.split(";").head.trim.toLowerCase.startsWith("text/html")){
|
||||
if (contentType.split(";").head.trim.toLowerCase.startsWith("text/html")) {
|
||||
this.contentType = "text/plain"
|
||||
} else {
|
||||
this.contentType = contentType
|
||||
@@ -196,35 +224,39 @@ abstract class ControllerBase extends ScalatraFilter
|
||||
}
|
||||
|
||||
// jenkins send message as 'application/x-www-form-urlencoded' but scalatra already parsed as multi-part-request.
|
||||
def extractFromJsonBody[A](implicit request:HttpServletRequest, mf:Manifest[A]): Option[A] = {
|
||||
(request.contentType.map(_.split(";").head.toLowerCase) match{
|
||||
def extractFromJsonBody[A](implicit request: HttpServletRequest, mf: Manifest[A]): Option[A] = {
|
||||
(request.contentType.map(_.split(";").head.toLowerCase) match {
|
||||
case Some("application/x-www-form-urlencoded") => multiParams.keys.headOption.map(parse(_))
|
||||
case Some("application/json") => Some(parsedBody)
|
||||
case _ => Some(parse(request.body))
|
||||
case Some("application/json") => Some(parsedBody)
|
||||
case _ => Some(parse(request.body))
|
||||
}).filterNot(_ == JNothing).flatMap(j => Try(j.extract[A]).toOption)
|
||||
}
|
||||
|
||||
protected def getPathObjectId(git: Git, path: String, revCommit: RevCommit): Option[ObjectId] = {
|
||||
@scala.annotation.tailrec
|
||||
def _getPathObjectId(path: String, walk: TreeWalk): Option[ObjectId] = walk.next match {
|
||||
case true if(walk.getPathString == path) => Some(walk.getObjectId(0))
|
||||
case true => _getPathObjectId(path, walk)
|
||||
case false => None
|
||||
case true if (walk.getPathString == path) => Some(walk.getObjectId(0))
|
||||
case true => _getPathObjectId(path, walk)
|
||||
case false => None
|
||||
}
|
||||
|
||||
using(new TreeWalk(git.getRepository)){ treeWalk =>
|
||||
using(new TreeWalk(git.getRepository)) { treeWalk =>
|
||||
treeWalk.addTree(revCommit.getTree)
|
||||
treeWalk.setRecursive(true)
|
||||
_getPathObjectId(path, treeWalk)
|
||||
}
|
||||
}
|
||||
|
||||
protected def responseRawFile(git: Git, objectId: ObjectId, path: String,
|
||||
repository: RepositoryService.RepositoryInfo): Unit = {
|
||||
JGitUtil.getObjectLoaderFromId(git, objectId){ loader =>
|
||||
protected def responseRawFile(
|
||||
git: Git,
|
||||
objectId: ObjectId,
|
||||
path: String,
|
||||
repository: RepositoryService.RepositoryInfo
|
||||
): Unit = {
|
||||
JGitUtil.getObjectLoaderFromId(git, objectId) { loader =>
|
||||
contentType = FileUtil.getMimeType(path)
|
||||
|
||||
if(loader.isLarge){
|
||||
if (loader.isLarge) {
|
||||
response.setContentLength(loader.getSize.toInt)
|
||||
loader.copyTo(response.outputStream)
|
||||
} else {
|
||||
@@ -232,11 +264,11 @@ abstract class ControllerBase extends ScalatraFilter
|
||||
val text = new String(bytes, "UTF-8")
|
||||
|
||||
val attrs = JGitUtil.getLfsObjects(text)
|
||||
if(attrs.nonEmpty) {
|
||||
if (attrs.nonEmpty) {
|
||||
response.setContentLength(attrs("size").toInt)
|
||||
val oid = attrs("oid").split(":")(1)
|
||||
|
||||
using(new FileInputStream(FileUtil.getLfsFilePath(repository.owner, repository.name, oid))){ in =>
|
||||
using(new FileInputStream(FileUtil.getLfsFilePath(repository.owner, repository.name, oid))) { in =>
|
||||
IOUtils.copy(in, response.getOutputStream)
|
||||
}
|
||||
} else {
|
||||
@@ -251,17 +283,21 @@ abstract class ControllerBase extends ScalatraFilter
|
||||
/**
|
||||
* Context object for the current request.
|
||||
*/
|
||||
case class Context(settings: SystemSettingsService.SystemSettings, loginAccount: Option[Account], request: HttpServletRequest){
|
||||
case class Context(
|
||||
settings: SystemSettingsService.SystemSettings,
|
||||
loginAccount: Option[Account],
|
||||
request: HttpServletRequest
|
||||
) {
|
||||
val path = settings.baseUrl.getOrElse(request.getContextPath)
|
||||
val currentPath = request.getRequestURI.substring(request.getContextPath.length)
|
||||
val baseUrl = settings.baseUrl(request)
|
||||
val host = new java.net.URL(baseUrl).getHost
|
||||
val platform = request.getHeader("User-Agent") match {
|
||||
case null => null
|
||||
case agent if agent.contains("Mac") => "mac"
|
||||
case null => null
|
||||
case agent if agent.contains("Mac") => "mac"
|
||||
case agent if agent.contains("Linux") => "linux"
|
||||
case agent if agent.contains("Win") => "windows"
|
||||
case _ => null
|
||||
case agent if agent.contains("Win") => "windows"
|
||||
case _ => null
|
||||
}
|
||||
val sidebarCollapse = request.getSession.getAttribute("sidebar-collapse") != null
|
||||
|
||||
@@ -272,7 +308,7 @@ case class Context(settings: SystemSettingsService.SystemSettings, loginAccount:
|
||||
* Cached object are available during a request.
|
||||
*/
|
||||
def cache[A](key: String)(action: => A): A =
|
||||
defining(Keys.Request.Cache(key)){ cacheKey =>
|
||||
defining(Keys.Request.Cache(key)) { cacheKey =>
|
||||
Option(request.getAttribute(cacheKey).asInstanceOf[A]).getOrElse {
|
||||
val newObject = action
|
||||
request.setAttribute(cacheKey, newObject)
|
||||
@@ -286,48 +322,105 @@ case class Context(settings: SystemSettingsService.SystemSettings, loginAccount:
|
||||
* Base trait for controllers which manages account information.
|
||||
*/
|
||||
trait AccountManagementControllerBase extends ControllerBase {
|
||||
self: AccountService =>
|
||||
self: AccountService =>
|
||||
|
||||
protected def updateImage(userName: String, fileId: Option[String], clearImage: Boolean): Unit =
|
||||
if(clearImage){
|
||||
if (clearImage) {
|
||||
getAccountByUserName(userName).flatMap(_.image).map { image =>
|
||||
new java.io.File(getUserUploadDir(userName), image).delete()
|
||||
new File(getUserUploadDir(userName), FileUtil.checkFilename(image)).delete()
|
||||
updateAvatarImage(userName, None)
|
||||
}
|
||||
} else {
|
||||
fileId.map { fileId =>
|
||||
val filename = "avatar." + FileUtil.getExtension(session.getAndRemove(Keys.Session.Upload(fileId)).get)
|
||||
val uploadDir = getUserUploadDir(userName)
|
||||
if(!uploadDir.exists){
|
||||
if (!uploadDir.exists) {
|
||||
uploadDir.mkdirs()
|
||||
}
|
||||
Thumbnails.of(new java.io.File(getTemporaryDir(session.getId), fileId))
|
||||
Thumbnails
|
||||
.of(new File(getTemporaryDir(session.getId), FileUtil.checkFilename(fileId)))
|
||||
.size(324, 324)
|
||||
.toFile(new java.io.File(uploadDir, filename))
|
||||
.toFile(new File(uploadDir, FileUtil.checkFilename(filename)))
|
||||
updateAvatarImage(userName, Some(filename))
|
||||
}
|
||||
}
|
||||
|
||||
protected def uniqueUserName: Constraint = new Constraint(){
|
||||
protected def uniqueUserName: Constraint = new Constraint() {
|
||||
override def validate(name: String, value: String, messages: Messages): Option[String] =
|
||||
getAccountByUserName(value, true).map { _ => "User already exists." }
|
||||
getAccountByUserNameIgnoreCase(value, true).map { _ =>
|
||||
"User already exists."
|
||||
}
|
||||
}
|
||||
|
||||
protected def uniqueMailAddress(paramName: String = ""): Constraint = new Constraint(){
|
||||
override def validate(name: String, value: String, params: Map[String, Seq[String]], messages: Messages): Option[String] = {
|
||||
getAccountByMailAddress(value, true)
|
||||
.filter { x => if(paramName.isEmpty) true else Some(x.userName) != params.optionValue(paramName) }
|
||||
.map { _ => "Mail address is already registered." }
|
||||
protected def uniqueMailAddress(paramName: String = ""): Constraint = new Constraint() {
|
||||
override def validate(
|
||||
name: String,
|
||||
value: String,
|
||||
params: Map[String, Seq[String]],
|
||||
messages: Messages
|
||||
): Option[String] = {
|
||||
val extraMailAddresses = params.filterKeys(k => k.startsWith("extraMailAddresses"))
|
||||
if (extraMailAddresses.exists {
|
||||
case (k, v) =>
|
||||
v.contains(value)
|
||||
}) {
|
||||
Some("These mail addresses are duplicated.")
|
||||
} else {
|
||||
getAccountByMailAddress(value, true)
|
||||
.collect {
|
||||
case x if paramName.isEmpty || Some(x.userName) != params.optionValue(paramName) =>
|
||||
"Mail address is already registered."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val allReservedNames = Set("git", "admin", "upload", "api", "assets", "plugin-assets", "signin", "signout", "register", "activities.atom", "sidebar-collapse", "groups", "new")
|
||||
protected def reservedNames(): Constraint = new Constraint(){
|
||||
override def validate(name: String, value: String, messages: Messages): Option[String] = if(allReservedNames.contains(value)){
|
||||
Some(s"${value} is reserved")
|
||||
} else {
|
||||
None
|
||||
protected def uniqueExtraMailAddress(paramName: String = ""): Constraint = new Constraint() {
|
||||
override def validate(
|
||||
name: String,
|
||||
value: String,
|
||||
params: Map[String, Seq[String]],
|
||||
messages: Messages
|
||||
): Option[String] = {
|
||||
val extraMailAddresses = params.filterKeys(k => k.startsWith("extraMailAddresses"))
|
||||
if (Some(value) == params.optionValue("mailAddress") || extraMailAddresses.count {
|
||||
case (k, v) =>
|
||||
v.contains(value)
|
||||
} > 1) {
|
||||
Some("These mail addresses are duplicated.")
|
||||
} else {
|
||||
getAccountByMailAddress(value, true)
|
||||
.collect {
|
||||
case x if paramName.isEmpty || Some(x.userName) != params.optionValue(paramName) =>
|
||||
"Mail address is already registered."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val allReservedNames = Set(
|
||||
"git",
|
||||
"admin",
|
||||
"upload",
|
||||
"api",
|
||||
"assets",
|
||||
"plugin-assets",
|
||||
"signin",
|
||||
"signout",
|
||||
"register",
|
||||
"activities.atom",
|
||||
"sidebar-collapse",
|
||||
"groups",
|
||||
"new"
|
||||
)
|
||||
|
||||
protected def reservedNames(): Constraint = new Constraint() {
|
||||
override def validate(name: String, value: String, messages: Messages): Option[String] =
|
||||
if (allReservedNames.contains(value.toLowerCase)) {
|
||||
Some(s"${value} is reserved")
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,13 +6,20 @@ import gitbucket.core.util.{Keys, UsersAuthenticator}
|
||||
import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.service.IssuesService._
|
||||
|
||||
class DashboardController extends DashboardControllerBase
|
||||
with IssuesService with PullRequestService with RepositoryService with AccountService with CommitsService
|
||||
with UsersAuthenticator
|
||||
class DashboardController
|
||||
extends DashboardControllerBase
|
||||
with IssuesService
|
||||
with PullRequestService
|
||||
with RepositoryService
|
||||
with AccountService
|
||||
with CommitsService
|
||||
with LabelsService
|
||||
with PrioritiesService
|
||||
with MilestonesService
|
||||
with UsersAuthenticator
|
||||
|
||||
trait DashboardControllerBase extends ControllerBase {
|
||||
self: IssuesService with PullRequestService with RepositoryService with AccountService
|
||||
with UsersAuthenticator =>
|
||||
self: IssuesService with PullRequestService with RepositoryService with AccountService with UsersAuthenticator =>
|
||||
|
||||
get("/dashboard/issues")(usersOnly {
|
||||
searchIssues("created_by")
|
||||
@@ -59,51 +66,52 @@ trait DashboardControllerBase extends ControllerBase {
|
||||
private def searchIssues(filter: String) = {
|
||||
import IssuesService._
|
||||
|
||||
val userName = context.loginAccount.get.userName
|
||||
val userName = context.loginAccount.get.userName
|
||||
val condition = getOrCreateCondition(Keys.Session.DashboardIssues, filter, userName)
|
||||
val userRepos = getUserRepositories(userName, true).map(repo => repo.owner -> repo.name)
|
||||
val page = IssueSearchCondition.page(request)
|
||||
val page = IssueSearchCondition.page(request)
|
||||
|
||||
html.issues(
|
||||
searchIssue(condition, false, (page - 1) * IssueLimit, IssueLimit, userRepos: _*),
|
||||
page,
|
||||
countIssue(condition.copy(state = "open" ), false, userRepos: _*),
|
||||
countIssue(condition.copy(state = "open"), false, userRepos: _*),
|
||||
countIssue(condition.copy(state = "closed"), false, userRepos: _*),
|
||||
filter match {
|
||||
case "assigned" => condition.copy(assigned = Some(Some(userName)))
|
||||
case "assigned" => condition.copy(assigned = Some(Some(userName)))
|
||||
case "mentioned" => condition.copy(mentioned = Some(userName))
|
||||
case _ => condition.copy(author = Some(userName))
|
||||
case _ => condition.copy(author = Some(userName))
|
||||
},
|
||||
filter,
|
||||
getGroupNames(userName),
|
||||
Nil,
|
||||
getUserRepositories(userName, withoutPhysicalInfo = true))
|
||||
getUserRepositories(userName, withoutPhysicalInfo = true)
|
||||
)
|
||||
}
|
||||
|
||||
private def searchPullRequests(filter: String) = {
|
||||
import IssuesService._
|
||||
import PullRequestService._
|
||||
|
||||
val userName = context.loginAccount.get.userName
|
||||
val userName = context.loginAccount.get.userName
|
||||
val condition = getOrCreateCondition(Keys.Session.DashboardPulls, filter, userName)
|
||||
val allRepos = getAllRepositories(userName)
|
||||
val page = IssueSearchCondition.page(request)
|
||||
val allRepos = getAllRepositories(userName)
|
||||
val page = IssueSearchCondition.page(request)
|
||||
|
||||
html.pulls(
|
||||
searchIssue(condition, true, (page - 1) * PullRequestLimit, PullRequestLimit, allRepos: _*),
|
||||
page,
|
||||
countIssue(condition.copy(state = "open" ), true, allRepos: _*),
|
||||
countIssue(condition.copy(state = "open"), true, allRepos: _*),
|
||||
countIssue(condition.copy(state = "closed"), true, allRepos: _*),
|
||||
filter match {
|
||||
case "assigned" => condition.copy(assigned = Some(Some(userName)))
|
||||
case "assigned" => condition.copy(assigned = Some(Some(userName)))
|
||||
case "mentioned" => condition.copy(mentioned = Some(userName))
|
||||
case _ => condition.copy(author = Some(userName))
|
||||
case _ => condition.copy(author = Some(userName))
|
||||
},
|
||||
filter,
|
||||
getGroupNames(userName),
|
||||
Nil,
|
||||
getUserRepositories(userName, withoutPhysicalInfo = true))
|
||||
getUserRepositories(userName, withoutPhysicalInfo = true)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package gitbucket.core.controller
|
||||
|
||||
import java.io.File
|
||||
|
||||
import gitbucket.core.model.Account
|
||||
import gitbucket.core.service.{AccountService, RepositoryService}
|
||||
import gitbucket.core.service.{AccountService, ReleaseService, RepositoryService}
|
||||
import gitbucket.core.servlet.Database
|
||||
import gitbucket.core.util._
|
||||
import gitbucket.core.util.SyntaxSugars._
|
||||
@@ -19,82 +21,138 @@ import org.apache.commons.io.{FileUtils, IOUtils}
|
||||
*
|
||||
* This servlet saves uploaded file.
|
||||
*/
|
||||
class FileUploadController extends ScalatraServlet with FileUploadSupport with RepositoryService with AccountService {
|
||||
class FileUploadController
|
||||
extends ScalatraServlet
|
||||
with FileUploadSupport
|
||||
with RepositoryService
|
||||
with AccountService
|
||||
with ReleaseService {
|
||||
|
||||
val maxFileSize = if (System.getProperty("gitbucket.maxFileSize") != null)
|
||||
System.getProperty("gitbucket.maxFileSize").toLong
|
||||
else
|
||||
3 * 1024 * 1024
|
||||
configureMultipartHandling(MultipartConfig(maxFileSize = Some(FileUtil.MaxFileSize)))
|
||||
|
||||
configureMultipartHandling(MultipartConfig(maxFileSize = Some(maxFileSize)))
|
||||
|
||||
post("/image"){
|
||||
execute({ (file, fileId) =>
|
||||
FileUtils.writeByteArrayToFile(new java.io.File(getTemporaryDir(session.getId), fileId), file.get)
|
||||
session += Keys.Session.Upload(fileId) -> file.name
|
||||
}, FileUtil.isImage)
|
||||
post("/image") {
|
||||
execute(
|
||||
{ (file, fileId) =>
|
||||
FileUtils
|
||||
.writeByteArrayToFile(new File(getTemporaryDir(session.getId), FileUtil.checkFilename(fileId)), file.get)
|
||||
session += Keys.Session.Upload(fileId) -> file.name
|
||||
},
|
||||
FileUtil.isImage
|
||||
)
|
||||
}
|
||||
|
||||
post("/tmp"){
|
||||
execute({ (file, fileId) =>
|
||||
FileUtils.writeByteArrayToFile(new java.io.File(getTemporaryDir(session.getId), fileId), file.get)
|
||||
session += Keys.Session.Upload(fileId) -> file.name
|
||||
}, _ => true)
|
||||
post("/tmp") {
|
||||
execute(
|
||||
{ (file, fileId) =>
|
||||
FileUtils
|
||||
.writeByteArrayToFile(new File(getTemporaryDir(session.getId), FileUtil.checkFilename(fileId)), file.get)
|
||||
session += Keys.Session.Upload(fileId) -> file.name
|
||||
},
|
||||
_ => true
|
||||
)
|
||||
}
|
||||
|
||||
post("/file/:owner/:repository"){
|
||||
execute({ (file, fileId) =>
|
||||
FileUtils.writeByteArrayToFile(new java.io.File(
|
||||
getAttachedDir(params("owner"), params("repository")),
|
||||
fileId + "." + FileUtil.getExtension(file.getName)), file.get)
|
||||
}, _ => true)
|
||||
post("/file/:owner/:repository") {
|
||||
execute(
|
||||
{ (file, fileId) =>
|
||||
FileUtils.writeByteArrayToFile(
|
||||
new File(
|
||||
getAttachedDir(params("owner"), params("repository")),
|
||||
FileUtil.checkFilename(fileId + "." + FileUtil.getExtension(file.getName))
|
||||
),
|
||||
file.get
|
||||
)
|
||||
},
|
||||
_ => true
|
||||
)
|
||||
}
|
||||
|
||||
post("/wiki/:owner/:repository"){
|
||||
post("/wiki/:owner/:repository") {
|
||||
// Don't accept not logged-in users
|
||||
session.get(Keys.Session.LoginAccount).collect { case loginAccount: Account =>
|
||||
val owner = params("owner")
|
||||
val repository = params("repository")
|
||||
session.get(Keys.Session.LoginAccount).collect {
|
||||
case loginAccount: Account =>
|
||||
val owner = params("owner")
|
||||
val repository = params("repository")
|
||||
|
||||
// Check whether logged-in user is collaborator
|
||||
onlyWikiEditable(owner, repository, loginAccount){
|
||||
execute({ (file, fileId) =>
|
||||
val fileName = file.getName
|
||||
LockUtil.lock(s"${owner}/${repository}/wiki") {
|
||||
using(Git.open(Directory.getWikiRepositoryDir(owner, repository))) { git =>
|
||||
val builder = DirCache.newInCore.builder()
|
||||
val inserter = git.getRepository.newObjectInserter()
|
||||
val headId = git.getRepository.resolve(Constants.HEAD + "^{commit}")
|
||||
// Check whether logged-in user is collaborator
|
||||
onlyWikiEditable(owner, repository, loginAccount) {
|
||||
execute(
|
||||
{ (file, fileId) =>
|
||||
val fileName = file.getName
|
||||
LockUtil.lock(s"${owner}/${repository}/wiki") {
|
||||
using(Git.open(Directory.getWikiRepositoryDir(owner, repository))) {
|
||||
git =>
|
||||
val builder = DirCache.newInCore.builder()
|
||||
val inserter = git.getRepository.newObjectInserter()
|
||||
val headId = git.getRepository.resolve(Constants.HEAD + "^{commit}")
|
||||
|
||||
if(headId != null){
|
||||
JGitUtil.processTree(git, headId){ (path, tree) =>
|
||||
if(path != fileName){
|
||||
builder.add(JGitUtil.createDirCacheEntry(path, tree.getEntryFileMode, tree.getEntryObjectId))
|
||||
}
|
||||
if (headId != null) {
|
||||
JGitUtil.processTree(git, headId) { (path, tree) =>
|
||||
if (path != fileName) {
|
||||
builder.add(JGitUtil.createDirCacheEntry(path, tree.getEntryFileMode, tree.getEntryObjectId))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val bytes = IOUtils.toByteArray(file.getInputStream)
|
||||
builder.add(
|
||||
JGitUtil.createDirCacheEntry(
|
||||
fileName,
|
||||
FileMode.REGULAR_FILE,
|
||||
inserter.insert(Constants.OBJ_BLOB, bytes)
|
||||
)
|
||||
)
|
||||
builder.finish()
|
||||
|
||||
val newHeadId = JGitUtil.createNewCommit(
|
||||
git,
|
||||
inserter,
|
||||
headId,
|
||||
builder.getDirCache.writeTree(inserter),
|
||||
Constants.HEAD,
|
||||
loginAccount.fullName,
|
||||
loginAccount.mailAddress,
|
||||
s"Uploaded ${fileName}"
|
||||
)
|
||||
|
||||
fileName
|
||||
}
|
||||
}
|
||||
|
||||
val bytes = IOUtils.toByteArray(file.getInputStream)
|
||||
builder.add(JGitUtil.createDirCacheEntry(fileName, FileMode.REGULAR_FILE, inserter.insert(Constants.OBJ_BLOB, bytes)))
|
||||
builder.finish()
|
||||
|
||||
val newHeadId = JGitUtil.createNewCommit(git, inserter, headId, builder.getDirCache.writeTree(inserter),
|
||||
Constants.HEAD, loginAccount.fullName, loginAccount.mailAddress, s"Uploaded ${fileName}")
|
||||
|
||||
fileName
|
||||
}
|
||||
}
|
||||
}, _ => true)
|
||||
}
|
||||
},
|
||||
_ => true
|
||||
)
|
||||
}
|
||||
} getOrElse BadRequest()
|
||||
}
|
||||
|
||||
post("/release/:owner/:repository/:tag") {
|
||||
session
|
||||
.get(Keys.Session.LoginAccount)
|
||||
.collect {
|
||||
case _: Account =>
|
||||
val owner = params("owner")
|
||||
val repository = params("repository")
|
||||
val tag = params("tag")
|
||||
execute(
|
||||
{ (file, fileId) =>
|
||||
FileUtils.writeByteArrayToFile(
|
||||
new File(getReleaseFilesDir(owner, repository), FileUtil.checkFilename(tag + "/" + fileId)),
|
||||
file.get
|
||||
)
|
||||
},
|
||||
_ => true
|
||||
)
|
||||
}
|
||||
.getOrElse(BadRequest())
|
||||
}
|
||||
|
||||
post("/import") {
|
||||
import JDBCUtil._
|
||||
session.get(Keys.Session.LoginAccount).collect { case loginAccount: Account if loginAccount.isAdmin =>
|
||||
execute({ (file, fileId) =>
|
||||
request2Session(request).conn.importAsSQL(file.getInputStream)
|
||||
}, _ => true)
|
||||
session.get(Keys.Session.LoginAccount).collect {
|
||||
case loginAccount: Account if loginAccount.isAdmin =>
|
||||
execute({ (file, fileId) =>
|
||||
request2Session(request).conn.importAsSQL(file.getInputStream)
|
||||
}, _ => true)
|
||||
}
|
||||
redirect("/admin/data")
|
||||
}
|
||||
@@ -102,23 +160,26 @@ class FileUploadController extends ScalatraServlet with FileUploadSupport with R
|
||||
private def onlyWikiEditable(owner: String, repository: String, loginAccount: Account)(action: => Any): Any = {
|
||||
implicit val session = Database.getSession(request)
|
||||
getRepository(owner, repository) match {
|
||||
case Some(x) => x.repository.options.wikiOption match {
|
||||
case "ALL" if !x.repository.isPrivate => action
|
||||
case "PUBLIC" if hasGuestRole(owner, repository, Some(loginAccount)) => action
|
||||
case "PRIVATE" if hasDeveloperRole(owner, repository, Some(loginAccount)) => action
|
||||
case _ => BadRequest()
|
||||
}
|
||||
case Some(x) =>
|
||||
x.repository.options.wikiOption match {
|
||||
case "ALL" if !x.repository.isPrivate => action
|
||||
case "PUBLIC" if hasGuestRole(owner, repository, Some(loginAccount)) => action
|
||||
case "PRIVATE" if hasDeveloperRole(owner, repository, Some(loginAccount)) => action
|
||||
case _ => BadRequest()
|
||||
}
|
||||
case None => BadRequest()
|
||||
}
|
||||
}
|
||||
|
||||
private def execute(f: (FileItem, String) => Unit , mimeTypeChcker: (String) => Boolean) = fileParams.get("file") match {
|
||||
case Some(file) if(mimeTypeChcker(file.name)) =>
|
||||
defining(FileUtil.generateFileId){ fileId =>
|
||||
f(file, fileId)
|
||||
Ok(fileId)
|
||||
}
|
||||
case _ => BadRequest()
|
||||
}
|
||||
private def execute(f: (FileItem, String) => Unit, mimeTypeChcker: (String) => Boolean) =
|
||||
fileParams.get("file") match {
|
||||
case Some(file) if (mimeTypeChcker(file.name)) =>
|
||||
defining(FileUtil.generateFileId) { fileId =>
|
||||
f(file, fileId)
|
||||
contentType = "text/plain"
|
||||
Ok(fileId)
|
||||
}
|
||||
case _ => BadRequest()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,23 +1,44 @@
|
||||
package gitbucket.core.controller
|
||||
|
||||
import java.net.URI
|
||||
|
||||
import com.nimbusds.oauth2.sdk.id.State
|
||||
import com.nimbusds.openid.connect.sdk.Nonce
|
||||
import gitbucket.core.helper.xml
|
||||
import gitbucket.core.model.Account
|
||||
import gitbucket.core.service._
|
||||
import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.util.SyntaxSugars._
|
||||
import gitbucket.core.util.{Keys, LDAPUtil, ReferrerAuthenticator, UsersAuthenticator}
|
||||
import org.scalatra.forms._
|
||||
import org.scalatra.Ok
|
||||
import org.scalatra.forms._
|
||||
|
||||
|
||||
class IndexController extends IndexControllerBase
|
||||
with RepositoryService with ActivityService with AccountService with RepositorySearchService with IssuesService
|
||||
with UsersAuthenticator with ReferrerAuthenticator
|
||||
|
||||
class IndexController
|
||||
extends IndexControllerBase
|
||||
with RepositoryService
|
||||
with ActivityService
|
||||
with AccountService
|
||||
with RepositorySearchService
|
||||
with IssuesService
|
||||
with LabelsService
|
||||
with MilestonesService
|
||||
with PrioritiesService
|
||||
with UsersAuthenticator
|
||||
with ReferrerAuthenticator
|
||||
with AccessTokenService
|
||||
with AccountFederationService
|
||||
with OpenIDConnectService
|
||||
|
||||
trait IndexControllerBase extends ControllerBase {
|
||||
self: RepositoryService with ActivityService with AccountService with RepositorySearchService
|
||||
with UsersAuthenticator with ReferrerAuthenticator =>
|
||||
self: RepositoryService
|
||||
with ActivityService
|
||||
with AccountService
|
||||
with RepositorySearchService
|
||||
with UsersAuthenticator
|
||||
with ReferrerAuthenticator
|
||||
with AccessTokenService
|
||||
with AccountFederationService
|
||||
with OpenIDConnectService =>
|
||||
|
||||
case class SignInForm(userName: String, password: String, hash: Option[String])
|
||||
|
||||
@@ -35,50 +56,114 @@ trait IndexControllerBase extends ControllerBase {
|
||||
//
|
||||
// case class SearchForm(query: String, owner: String, repository: String)
|
||||
|
||||
case class OidcContext(state: State, nonce: Nonce, redirectBackURI: String)
|
||||
|
||||
get("/"){
|
||||
context.loginAccount.map { account =>
|
||||
val visibleOwnerSet: Set[String] = Set(account.userName) ++ getGroupsByUserName(account.userName)
|
||||
gitbucket.core.html.index(getRecentActivitiesByOwners(visibleOwnerSet), Nil, getUserRepositories(account.userName, withoutPhysicalInfo = true))
|
||||
}.getOrElse {
|
||||
gitbucket.core.html.index(getRecentActivities(), getVisibleRepositories(None, withoutPhysicalInfo = true), Nil)
|
||||
}
|
||||
get("/") {
|
||||
context.loginAccount
|
||||
.map { account =>
|
||||
val visibleOwnerSet: Set[String] = Set(account.userName) ++ getGroupsByUserName(account.userName)
|
||||
gitbucket.core.html.index(
|
||||
getRecentActivitiesByOwners(visibleOwnerSet),
|
||||
Nil,
|
||||
getUserRepositories(account.userName, withoutPhysicalInfo = true),
|
||||
showBannerToCreatePersonalAccessToken = hasAccountFederation(account.userName) && !hasAccessToken(
|
||||
account.userName
|
||||
)
|
||||
)
|
||||
}
|
||||
.getOrElse {
|
||||
gitbucket.core.html.index(
|
||||
getRecentActivities(),
|
||||
getVisibleRepositories(None, withoutPhysicalInfo = true),
|
||||
Nil,
|
||||
showBannerToCreatePersonalAccessToken = false
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
get("/signin"){
|
||||
get("/signin") {
|
||||
val redirect = params.get("redirect")
|
||||
if(redirect.isDefined && redirect.get.startsWith("/")){
|
||||
if (redirect.isDefined && redirect.get.startsWith("/")) {
|
||||
flash += Keys.Flash.Redirect -> redirect.get
|
||||
}
|
||||
gitbucket.core.html.signin(flash.get("userName"), flash.get("password"), flash.get("error"))
|
||||
}
|
||||
|
||||
post("/signin", signinForm){ form =>
|
||||
post("/signin", signinForm) { form =>
|
||||
authenticate(context.settings, form.userName, form.password) match {
|
||||
case Some(account) => signin(account, form.hash)
|
||||
case None => {
|
||||
case Some(account) =>
|
||||
flash.get(Keys.Flash.Redirect) match {
|
||||
case Some(redirectUrl: String) => signin(account, redirectUrl + form.hash.getOrElse(""))
|
||||
case _ => signin(account)
|
||||
}
|
||||
case None =>
|
||||
flash += "userName" -> form.userName
|
||||
flash += "password" -> form.password
|
||||
flash += "error" -> "Sorry, your Username and/or Password is incorrect. Please try again."
|
||||
redirect("/signin")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get("/signout"){
|
||||
/**
|
||||
* Initiate an OpenID Connect authentication request.
|
||||
*/
|
||||
post("/signin/oidc") {
|
||||
context.settings.oidc.map { oidc =>
|
||||
val redirectURI = new URI(s"$baseUrl/signin/oidc")
|
||||
val authenticationRequest = createOIDCAuthenticationRequest(oidc.issuer, oidc.clientID, redirectURI)
|
||||
val redirectBackURI = flash.get(Keys.Flash.Redirect) match {
|
||||
case Some(redirectBackURI: String) => redirectBackURI + params.getOrElse("hash", "")
|
||||
case _ => "/"
|
||||
}
|
||||
session.setAttribute(
|
||||
Keys.Session.OidcContext,
|
||||
OidcContext(authenticationRequest.getState, authenticationRequest.getNonce, redirectBackURI)
|
||||
)
|
||||
redirect(authenticationRequest.toURI.toString)
|
||||
} getOrElse {
|
||||
NotFound()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an OpenID Connect authentication response.
|
||||
*/
|
||||
get("/signin/oidc") {
|
||||
context.settings.oidc.map { oidc =>
|
||||
val redirectURI = new URI(s"$baseUrl/signin/oidc")
|
||||
session.get(Keys.Session.OidcContext) match {
|
||||
case Some(context: OidcContext) =>
|
||||
authenticate(params, redirectURI, context.state, context.nonce, oidc) map { account =>
|
||||
signin(account, context.redirectBackURI)
|
||||
} orElse {
|
||||
flash += "error" -> "Sorry, authentication failed. Please try again."
|
||||
session.invalidate()
|
||||
redirect("/signin")
|
||||
}
|
||||
case _ =>
|
||||
flash += "error" -> "Sorry, something wrong. Please try again."
|
||||
session.invalidate()
|
||||
redirect("/signin")
|
||||
}
|
||||
} getOrElse {
|
||||
NotFound()
|
||||
}
|
||||
}
|
||||
|
||||
get("/signout") {
|
||||
session.invalidate
|
||||
redirect("/")
|
||||
}
|
||||
|
||||
get("/activities.atom"){
|
||||
get("/activities.atom") {
|
||||
contentType = "application/atom+xml; type=feed"
|
||||
xml.feed(getRecentActivities())
|
||||
}
|
||||
|
||||
post("/sidebar-collapse"){
|
||||
if(params("collapse") == "true"){
|
||||
post("/sidebar-collapse") {
|
||||
if (params("collapse") == "true") {
|
||||
session.setAttribute("sidebar-collapse", "true")
|
||||
} else {
|
||||
} else {
|
||||
session.setAttribute("sidebar-collapse", null)
|
||||
}
|
||||
Ok()
|
||||
@@ -87,22 +172,18 @@ trait IndexControllerBase extends ControllerBase {
|
||||
/**
|
||||
* Set account information into HttpSession and redirect.
|
||||
*/
|
||||
private def signin(account: Account, hash: Option[String]) = {
|
||||
private def signin(account: Account, redirectUrl: String = "/") = {
|
||||
session.setAttribute(Keys.Session.LoginAccount, account)
|
||||
updateLastLoginDate(account.userName)
|
||||
|
||||
if(LDAPUtil.isDummyMailAddress(account)) {
|
||||
if (LDAPUtil.isDummyMailAddress(account)) {
|
||||
redirect("/" + account.userName + "/_edit")
|
||||
}
|
||||
|
||||
flash.get(Keys.Flash.Redirect).asInstanceOf[Option[String]].map { redirectUrl =>
|
||||
if(redirectUrl.stripSuffix("/") == request.getContextPath){
|
||||
redirect("/")
|
||||
} else {
|
||||
redirect(redirectUrl + hash.getOrElse(""))
|
||||
}
|
||||
}.getOrElse {
|
||||
if (redirectUrl.stripSuffix("/") == request.getContextPath) {
|
||||
redirect("/")
|
||||
} else {
|
||||
redirect(redirectUrl)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,23 +192,28 @@ trait IndexControllerBase extends ControllerBase {
|
||||
*/
|
||||
get("/_user/proposals")(usersOnly {
|
||||
contentType = formats("json")
|
||||
val user = params("user").toBoolean
|
||||
val user = params("user").toBoolean
|
||||
val group = params("group").toBoolean
|
||||
org.json4s.jackson.Serialization.write(
|
||||
Map("options" -> (
|
||||
getAllUsers(false)
|
||||
.withFilter { t => (user, group) match {
|
||||
case (true, true) => true
|
||||
case (true, false) => !t.isGroupAccount
|
||||
case (false, true) => t.isGroupAccount
|
||||
case (false, false) => false
|
||||
}}.map { t =>
|
||||
Map(
|
||||
"label" -> s"<b>@${t.userName}</b> ${t.fullName}",
|
||||
"value" -> t.userName
|
||||
)
|
||||
}
|
||||
))
|
||||
Map(
|
||||
"options" -> (
|
||||
getAllUsers(false)
|
||||
.withFilter { t =>
|
||||
(user, group) match {
|
||||
case (true, true) => true
|
||||
case (true, false) => !t.isGroupAccount
|
||||
case (false, true) => t.isGroupAccount
|
||||
case (false, false) => false
|
||||
}
|
||||
}
|
||||
.map { t =>
|
||||
Map(
|
||||
"label" -> s"<b>@${t.userName}</b> ${t.fullName}",
|
||||
"value" -> t.userName
|
||||
)
|
||||
}
|
||||
)
|
||||
)
|
||||
)
|
||||
})
|
||||
|
||||
@@ -136,48 +222,69 @@ trait IndexControllerBase extends ControllerBase {
|
||||
* Returns a single string which is any of "group", "user" or "".
|
||||
*/
|
||||
post("/_user/existence")(usersOnly {
|
||||
getAccountByUserName(params("userName")).map { account =>
|
||||
if(account.isGroupAccount) "group" else "user"
|
||||
getAccountByUserNameIgnoreCase(params("userName")).map { account =>
|
||||
if (account.isGroupAccount) "group" else "user"
|
||||
} getOrElse ""
|
||||
})
|
||||
|
||||
// TODO Move to RepositoryViwerController?
|
||||
get("/:owner/:repository/search")(referrersOnly { repository =>
|
||||
defining(params.getOrElse("q", "").trim, params.getOrElse("type", "code")){ case (query, target) =>
|
||||
val page = try {
|
||||
val i = params.getOrElse("page", "1").toInt
|
||||
if(i <= 0) 1 else i
|
||||
} catch {
|
||||
case e: NumberFormatException => 1
|
||||
}
|
||||
defining(params.getOrElse("q", "").trim, params.getOrElse("type", "code")) {
|
||||
case (query, target) =>
|
||||
val page = try {
|
||||
val i = params.getOrElse("page", "1").toInt
|
||||
if (i <= 0) 1 else i
|
||||
} catch {
|
||||
case e: NumberFormatException => 1
|
||||
}
|
||||
|
||||
target.toLowerCase match {
|
||||
case "issue" => gitbucket.core.search.html.issues(
|
||||
if(query.nonEmpty) searchIssues(repository.owner, repository.name, query) else Nil,
|
||||
query, page, repository)
|
||||
target.toLowerCase match {
|
||||
case "issue" =>
|
||||
gitbucket.core.search.html.issues(
|
||||
if (query.nonEmpty) searchIssues(repository.owner, repository.name, query) else Nil,
|
||||
query,
|
||||
page,
|
||||
repository
|
||||
)
|
||||
|
||||
case "wiki" => gitbucket.core.search.html.wiki(
|
||||
if(query.nonEmpty) searchWikiPages(repository.owner, repository.name, query) else Nil,
|
||||
query, page, repository)
|
||||
case "wiki" =>
|
||||
gitbucket.core.search.html.wiki(
|
||||
if (query.nonEmpty) searchWikiPages(repository.owner, repository.name, query) else Nil,
|
||||
query,
|
||||
page,
|
||||
repository
|
||||
)
|
||||
|
||||
case _ => gitbucket.core.search.html.code(
|
||||
if(query.nonEmpty) searchFiles(repository.owner, repository.name, query) else Nil,
|
||||
query, page, repository)
|
||||
}
|
||||
case _ =>
|
||||
gitbucket.core.search.html.code(
|
||||
if (query.nonEmpty) searchFiles(repository.owner, repository.name, query) else Nil,
|
||||
query,
|
||||
page,
|
||||
repository
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
get("/search"){
|
||||
get("/search") {
|
||||
val query = params.getOrElse("query", "").trim.toLowerCase
|
||||
val visibleRepositories = getVisibleRepositories(context.loginAccount, repositoryUserName = None, withoutPhysicalInfo = true)
|
||||
val visibleRepositories =
|
||||
getVisibleRepositories(context.loginAccount, repositoryUserName = None, withoutPhysicalInfo = true)
|
||||
val repositories = visibleRepositories.filter { repository =>
|
||||
repository.name.toLowerCase.indexOf(query) >= 0 || repository.owner.toLowerCase.indexOf(query) >= 0
|
||||
}
|
||||
context.loginAccount.map { account =>
|
||||
gitbucket.core.search.html.repositories(query, repositories, Nil, getUserRepositories(account.userName, withoutPhysicalInfo = true))
|
||||
}.getOrElse {
|
||||
gitbucket.core.search.html.repositories(query, repositories, visibleRepositories, Nil)
|
||||
}
|
||||
context.loginAccount
|
||||
.map { account =>
|
||||
gitbucket.core.search.html.repositories(
|
||||
query,
|
||||
repositories,
|
||||
Nil,
|
||||
getUserRepositories(account.userName, withoutPhysicalInfo = true)
|
||||
)
|
||||
}
|
||||
.getOrElse {
|
||||
gitbucket.core.search.html.repositories(query, repositories, visibleRepositories, Nil)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -11,23 +11,23 @@ import gitbucket.core.view.Markdown
|
||||
import org.scalatra.forms._
|
||||
import org.scalatra.{BadRequest, Ok}
|
||||
|
||||
|
||||
class IssuesController extends IssuesControllerBase
|
||||
with IssuesService
|
||||
with RepositoryService
|
||||
with AccountService
|
||||
with LabelsService
|
||||
with MilestonesService
|
||||
with ActivityService
|
||||
with HandleCommentService
|
||||
with IssueCreationService
|
||||
with ReadableUsersAuthenticator
|
||||
with ReferrerAuthenticator
|
||||
with WritableUsersAuthenticator
|
||||
with PullRequestService
|
||||
with WebHookIssueCommentService
|
||||
with CommitsService
|
||||
with PrioritiesService
|
||||
class IssuesController
|
||||
extends IssuesControllerBase
|
||||
with IssuesService
|
||||
with RepositoryService
|
||||
with AccountService
|
||||
with LabelsService
|
||||
with MilestonesService
|
||||
with ActivityService
|
||||
with HandleCommentService
|
||||
with IssueCreationService
|
||||
with ReadableUsersAuthenticator
|
||||
with ReferrerAuthenticator
|
||||
with WritableUsersAuthenticator
|
||||
with PullRequestService
|
||||
with WebHookIssueCommentService
|
||||
with CommitsService
|
||||
with PrioritiesService
|
||||
|
||||
trait IssuesControllerBase extends ControllerBase {
|
||||
self: IssuesService
|
||||
@@ -45,40 +45,46 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
with WebHookIssueCommentService
|
||||
with PrioritiesService =>
|
||||
|
||||
case class IssueCreateForm(title: String, content: Option[String],
|
||||
assignedUserName: Option[String], milestoneId: Option[Int], priorityId: Option[Int], labelNames: Option[String])
|
||||
case class IssueCreateForm(
|
||||
title: String,
|
||||
content: Option[String],
|
||||
assignedUserName: Option[String],
|
||||
milestoneId: Option[Int],
|
||||
priorityId: Option[Int],
|
||||
labelNames: Option[String]
|
||||
)
|
||||
case class CommentForm(issueId: Int, content: String)
|
||||
case class IssueStateForm(issueId: Int, content: Option[String])
|
||||
|
||||
val issueCreateForm = mapping(
|
||||
"title" -> trim(label("Title", text(required))),
|
||||
"content" -> trim(optional(text())),
|
||||
"assignedUserName" -> trim(optional(text())),
|
||||
"milestoneId" -> trim(optional(number())),
|
||||
"priorityId" -> trim(optional(number())),
|
||||
"labelNames" -> trim(optional(text()))
|
||||
)(IssueCreateForm.apply)
|
||||
"title" -> trim(label("Title", text(required))),
|
||||
"content" -> trim(optional(text())),
|
||||
"assignedUserName" -> trim(optional(text())),
|
||||
"milestoneId" -> trim(optional(number())),
|
||||
"priorityId" -> trim(optional(number())),
|
||||
"labelNames" -> trim(optional(text()))
|
||||
)(IssueCreateForm.apply)
|
||||
|
||||
val issueTitleEditForm = mapping(
|
||||
"title" -> trim(label("Title", text(required)))
|
||||
)(x => x)
|
||||
)(x => x)
|
||||
val issueEditForm = mapping(
|
||||
"content" -> trim(optional(text()))
|
||||
)(x => x)
|
||||
)(x => x)
|
||||
|
||||
val commentForm = mapping(
|
||||
"issueId" -> label("Issue Id", number()),
|
||||
"content" -> trim(label("Comment", text(required)))
|
||||
)(CommentForm.apply)
|
||||
"issueId" -> label("Issue Id", number()),
|
||||
"content" -> trim(label("Comment", text(required)))
|
||||
)(CommentForm.apply)
|
||||
|
||||
val issueStateForm = mapping(
|
||||
"issueId" -> label("Issue Id", number()),
|
||||
"content" -> trim(optional(text()))
|
||||
)(IssueStateForm.apply)
|
||||
"issueId" -> label("Issue Id", number()),
|
||||
"content" -> trim(optional(text()))
|
||||
)(IssueStateForm.apply)
|
||||
|
||||
get("/:owner/:repository/issues")(referrersOnly { repository =>
|
||||
val q = request.getParameter("q")
|
||||
if(Option(q).exists(_.contains("is:pr"))){
|
||||
if (Option(q).exists(_.contains("is:pr"))) {
|
||||
redirect(s"/${repository.owner}/${repository.name}/pulls?q=${StringUtil.urlEncode(q)}")
|
||||
} else {
|
||||
searchIssues(repository)
|
||||
@@ -86,45 +92,50 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
})
|
||||
|
||||
get("/:owner/:repository/issues/:id")(referrersOnly { repository =>
|
||||
defining(repository.owner, repository.name, params("id")){ case (owner, name, issueId) =>
|
||||
getIssue(owner, name, issueId) map { issue =>
|
||||
if(issue.isPullRequest){
|
||||
redirect(s"/${repository.owner}/${repository.name}/pull/${issueId}")
|
||||
} else {
|
||||
html.issue(
|
||||
issue,
|
||||
getComments(owner, name, issueId.toInt),
|
||||
getIssueLabels(owner, name, issueId.toInt),
|
||||
getAssignableUserNames(owner, name),
|
||||
getMilestonesWithIssueCount(owner, name),
|
||||
getPriorities(owner, name),
|
||||
getLabels(owner, name),
|
||||
isIssueEditable(repository),
|
||||
isIssueManageable(repository),
|
||||
repository)
|
||||
}
|
||||
} getOrElse NotFound()
|
||||
defining(repository.owner, repository.name, params("id")) {
|
||||
case (owner, name, issueId) =>
|
||||
getIssue(owner, name, issueId) map {
|
||||
issue =>
|
||||
if (issue.isPullRequest) {
|
||||
redirect(s"/${repository.owner}/${repository.name}/pull/${issueId}")
|
||||
} else {
|
||||
html.issue(
|
||||
issue,
|
||||
getComments(owner, name, issueId.toInt),
|
||||
getIssueLabels(owner, name, issueId.toInt),
|
||||
getAssignableUserNames(owner, name),
|
||||
getMilestonesWithIssueCount(owner, name),
|
||||
getPriorities(owner, name),
|
||||
getLabels(owner, name),
|
||||
isIssueEditable(repository),
|
||||
isIssueManageable(repository),
|
||||
repository
|
||||
)
|
||||
}
|
||||
} getOrElse NotFound()
|
||||
}
|
||||
})
|
||||
|
||||
get("/:owner/:repository/issues/new")(readableUsersOnly { repository =>
|
||||
if(isIssueEditable(repository)){ // TODO Should this check is provided by authenticator?
|
||||
defining(repository.owner, repository.name){ case (owner, name) =>
|
||||
html.create(
|
||||
getAssignableUserNames(owner, name),
|
||||
getMilestones(owner, name),
|
||||
getPriorities(owner, name),
|
||||
getDefaultPriority(owner, name),
|
||||
getLabels(owner, name),
|
||||
isIssueManageable(repository),
|
||||
getContentTemplate(repository, "ISSUE_TEMPLATE"),
|
||||
repository)
|
||||
if (isIssueEditable(repository)) { // TODO Should this check is provided by authenticator?
|
||||
defining(repository.owner, repository.name) {
|
||||
case (owner, name) =>
|
||||
html.create(
|
||||
getAssignableUserNames(owner, name),
|
||||
getMilestones(owner, name),
|
||||
getPriorities(owner, name),
|
||||
getDefaultPriority(owner, name),
|
||||
getLabels(owner, name),
|
||||
isIssueManageable(repository),
|
||||
getContentTemplate(repository, "ISSUE_TEMPLATE"),
|
||||
repository
|
||||
)
|
||||
}
|
||||
} else Unauthorized()
|
||||
})
|
||||
|
||||
post("/:owner/:repository/issues/new", issueCreateForm)(readableUsersOnly { (form, repository) =>
|
||||
if(isIssueEditable(repository)){ // TODO Should this check is provided by authenticator?
|
||||
if (isIssueEditable(repository)) { // TODO Should this check is provided by authenticator?
|
||||
val issue = createIssue(
|
||||
repository,
|
||||
form.title,
|
||||
@@ -133,133 +144,146 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
form.milestoneId,
|
||||
form.priorityId,
|
||||
form.labelNames.toArray.flatMap(_.split(",")),
|
||||
context.loginAccount.get)
|
||||
context.loginAccount.get
|
||||
)
|
||||
|
||||
redirect(s"/${issue.userName}/${issue.repositoryName}/issues/${issue.issueId}")
|
||||
} else Unauthorized()
|
||||
})
|
||||
|
||||
ajaxPost("/:owner/:repository/issues/edit_title/:id", issueTitleEditForm)(readableUsersOnly { (title, repository) =>
|
||||
defining(repository.owner, repository.name){ case (owner, name) =>
|
||||
getIssue(owner, name, params("id")).map { issue =>
|
||||
if(isEditableContent(owner, name, issue.openedUserName)){
|
||||
// update issue
|
||||
updateIssue(owner, name, issue.issueId, title, issue.content)
|
||||
// extract references and create refer comment
|
||||
createReferComment(owner, name, issue.copy(title = title), title, context.loginAccount.get)
|
||||
defining(repository.owner, repository.name) {
|
||||
case (owner, name) =>
|
||||
getIssue(owner, name, params("id")).map { issue =>
|
||||
if (isEditableContent(owner, name, issue.openedUserName)) {
|
||||
// update issue
|
||||
updateIssue(owner, name, issue.issueId, title, issue.content)
|
||||
// extract references and create refer comment
|
||||
createReferComment(owner, name, issue.copy(title = title), title, context.loginAccount.get)
|
||||
|
||||
redirect(s"/${owner}/${name}/issues/_data/${issue.issueId}")
|
||||
} else Unauthorized()
|
||||
} getOrElse NotFound()
|
||||
redirect(s"/${owner}/${name}/issues/_data/${issue.issueId}")
|
||||
} else Unauthorized()
|
||||
} getOrElse NotFound()
|
||||
}
|
||||
})
|
||||
|
||||
ajaxPost("/:owner/:repository/issues/edit/:id", issueEditForm)(readableUsersOnly { (content, repository) =>
|
||||
defining(repository.owner, repository.name){ case (owner, name) =>
|
||||
getIssue(owner, name, params("id")).map { issue =>
|
||||
if(isEditableContent(owner, name, issue.openedUserName)){
|
||||
// update issue
|
||||
updateIssue(owner, name, issue.issueId, issue.title, content)
|
||||
// extract references and create refer comment
|
||||
createReferComment(owner, name, issue, content.getOrElse(""), context.loginAccount.get)
|
||||
defining(repository.owner, repository.name) {
|
||||
case (owner, name) =>
|
||||
getIssue(owner, name, params("id")).map { issue =>
|
||||
if (isEditableContent(owner, name, issue.openedUserName)) {
|
||||
// update issue
|
||||
updateIssue(owner, name, issue.issueId, issue.title, content)
|
||||
// extract references and create refer comment
|
||||
createReferComment(owner, name, issue, content.getOrElse(""), context.loginAccount.get)
|
||||
|
||||
redirect(s"/${owner}/${name}/issues/_data/${issue.issueId}")
|
||||
} else Unauthorized()
|
||||
} getOrElse NotFound()
|
||||
redirect(s"/${owner}/${name}/issues/_data/${issue.issueId}")
|
||||
} else Unauthorized()
|
||||
} getOrElse NotFound()
|
||||
}
|
||||
})
|
||||
|
||||
post("/:owner/:repository/issue_comments/new", commentForm)(readableUsersOnly { (form, repository) =>
|
||||
getIssue(repository.owner, repository.name, form.issueId.toString).flatMap { issue =>
|
||||
val actionOpt = params.get("action").filter(_ => isEditableContent(issue.userName, issue.repositoryName, issue.openedUserName))
|
||||
handleComment(issue, Some(form.content), repository, actionOpt) map { case (issue, id) =>
|
||||
redirect(s"/${repository.owner}/${repository.name}/${
|
||||
if(issue.isPullRequest) "pull" else "issues"}/${form.issueId}#comment-${id}")
|
||||
val actionOpt =
|
||||
params.get("action").filter(_ => isEditableContent(issue.userName, issue.repositoryName, issue.openedUserName))
|
||||
handleComment(issue, Some(form.content), repository, actionOpt) map {
|
||||
case (issue, id) =>
|
||||
redirect(
|
||||
s"/${repository.owner}/${repository.name}/${if (issue.isPullRequest) "pull" else "issues"}/${form.issueId}#comment-${id}"
|
||||
)
|
||||
}
|
||||
} getOrElse NotFound()
|
||||
})
|
||||
|
||||
post("/:owner/:repository/issue_comments/state", issueStateForm)(readableUsersOnly { (form, repository) =>
|
||||
getIssue(repository.owner, repository.name, form.issueId.toString).flatMap { issue =>
|
||||
val actionOpt = params.get("action").filter(_ => isEditableContent(issue.userName, issue.repositoryName, issue.openedUserName))
|
||||
handleComment(issue, form.content, repository, actionOpt) map { case (issue, id) =>
|
||||
redirect(s"/${repository.owner}/${repository.name}/${
|
||||
if(issue.isPullRequest) "pull" else "issues"}/${form.issueId}#comment-${id}")
|
||||
val actionOpt =
|
||||
params.get("action").filter(_ => isEditableContent(issue.userName, issue.repositoryName, issue.openedUserName))
|
||||
handleComment(issue, form.content, repository, actionOpt) map {
|
||||
case (issue, id) =>
|
||||
redirect(
|
||||
s"/${repository.owner}/${repository.name}/${if (issue.isPullRequest) "pull" else "issues"}/${form.issueId}#comment-${id}"
|
||||
)
|
||||
}
|
||||
} getOrElse NotFound()
|
||||
})
|
||||
|
||||
ajaxPost("/:owner/:repository/issue_comments/edit/:id", commentForm)(readableUsersOnly { (form, repository) =>
|
||||
defining(repository.owner, repository.name){ case (owner, name) =>
|
||||
getComment(owner, name, params("id")).map { comment =>
|
||||
if(isEditableContent(owner, name, comment.commentedUserName)){
|
||||
updateComment(comment.issueId, comment.commentId, form.content)
|
||||
redirect(s"/${owner}/${name}/issue_comments/_data/${comment.commentId}")
|
||||
} else Unauthorized()
|
||||
} getOrElse NotFound()
|
||||
defining(repository.owner, repository.name) {
|
||||
case (owner, name) =>
|
||||
getComment(owner, name, params("id")).map { comment =>
|
||||
if (isEditableContent(owner, name, comment.commentedUserName)) {
|
||||
updateComment(comment.issueId, comment.commentId, form.content)
|
||||
redirect(s"/${owner}/${name}/issue_comments/_data/${comment.commentId}")
|
||||
} else Unauthorized()
|
||||
} getOrElse NotFound()
|
||||
}
|
||||
})
|
||||
|
||||
ajaxPost("/:owner/:repository/issue_comments/delete/:id")(readableUsersOnly { repository =>
|
||||
defining(repository.owner, repository.name){ case (owner, name) =>
|
||||
getComment(owner, name, params("id")).map { comment =>
|
||||
if(isEditableContent(owner, name, comment.commentedUserName)){
|
||||
Ok(deleteComment(comment.issueId, comment.commentId))
|
||||
} else Unauthorized()
|
||||
} getOrElse NotFound()
|
||||
defining(repository.owner, repository.name) {
|
||||
case (owner, name) =>
|
||||
getComment(owner, name, params("id")).map { comment =>
|
||||
if (isEditableContent(owner, name, comment.commentedUserName)) {
|
||||
Ok(deleteComment(comment.issueId, comment.commentId))
|
||||
} else Unauthorized()
|
||||
} getOrElse NotFound()
|
||||
}
|
||||
})
|
||||
|
||||
ajaxGet("/:owner/:repository/issues/_data/:id")(readableUsersOnly { repository =>
|
||||
getIssue(repository.owner, repository.name, params("id")) map { x =>
|
||||
if(isEditableContent(x.userName, x.repositoryName, x.openedUserName)){
|
||||
params.get("dataType") collect {
|
||||
case t if t == "html" => html.editissue(x.content, x.issueId, repository)
|
||||
} getOrElse {
|
||||
contentType = formats("json")
|
||||
org.json4s.jackson.Serialization.write(
|
||||
Map(
|
||||
"title" -> x.title,
|
||||
"content" -> Markdown.toHtml(
|
||||
markdown = x.content getOrElse "No description given.",
|
||||
repository = repository,
|
||||
enableWikiLink = false,
|
||||
enableRefsLink = true,
|
||||
enableAnchor = true,
|
||||
enableLineBreaks = true,
|
||||
enableTaskList = true,
|
||||
hasWritePermission = true
|
||||
getIssue(repository.owner, repository.name, params("id")) map {
|
||||
x =>
|
||||
if (isEditableContent(x.userName, x.repositoryName, x.openedUserName)) {
|
||||
params.get("dataType") collect {
|
||||
case t if t == "html" => html.editissue(x.content, x.issueId, repository)
|
||||
} getOrElse {
|
||||
contentType = formats("json")
|
||||
org.json4s.jackson.Serialization.write(
|
||||
Map(
|
||||
"title" -> x.title,
|
||||
"content" -> Markdown.toHtml(
|
||||
markdown = x.content getOrElse "No description given.",
|
||||
repository = repository,
|
||||
enableWikiLink = false,
|
||||
enableRefsLink = true,
|
||||
enableAnchor = true,
|
||||
enableLineBreaks = true,
|
||||
enableTaskList = true,
|
||||
hasWritePermission = true
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
} else Unauthorized()
|
||||
}
|
||||
} else Unauthorized()
|
||||
} getOrElse NotFound()
|
||||
})
|
||||
|
||||
ajaxGet("/:owner/:repository/issue_comments/_data/:id")(readableUsersOnly { repository =>
|
||||
getComment(repository.owner, repository.name, params("id")) map { x =>
|
||||
if(isEditableContent(x.userName, x.repositoryName, x.commentedUserName)){
|
||||
params.get("dataType") collect {
|
||||
case t if t == "html" => html.editcomment(x.content, x.commentId, repository)
|
||||
} getOrElse {
|
||||
contentType = formats("json")
|
||||
org.json4s.jackson.Serialization.write(
|
||||
Map(
|
||||
"content" -> view.Markdown.toHtml(
|
||||
markdown = x.content,
|
||||
repository = repository,
|
||||
enableWikiLink = false,
|
||||
enableRefsLink = true,
|
||||
enableAnchor = true,
|
||||
enableLineBreaks = true,
|
||||
enableTaskList = true,
|
||||
hasWritePermission = true
|
||||
getComment(repository.owner, repository.name, params("id")) map {
|
||||
x =>
|
||||
if (isEditableContent(x.userName, x.repositoryName, x.commentedUserName)) {
|
||||
params.get("dataType") collect {
|
||||
case t if t == "html" => html.editcomment(x.content, x.commentId, repository)
|
||||
} getOrElse {
|
||||
contentType = formats("json")
|
||||
org.json4s.jackson.Serialization.write(
|
||||
Map(
|
||||
"content" -> view.Markdown.toHtml(
|
||||
markdown = x.content,
|
||||
repository = repository,
|
||||
enableWikiLink = false,
|
||||
enableRefsLink = true,
|
||||
enableAnchor = true,
|
||||
enableLineBreaks = true,
|
||||
enableTaskList = true,
|
||||
hasWritePermission = true
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
} else Unauthorized()
|
||||
}
|
||||
} else Unauthorized()
|
||||
} getOrElse NotFound()
|
||||
})
|
||||
|
||||
@@ -270,94 +294,106 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
})
|
||||
|
||||
ajaxPost("/:owner/:repository/issues/:id/label/new")(writableUsersOnly { repository =>
|
||||
defining(params("id").toInt){ issueId =>
|
||||
registerIssueLabel(repository.owner, repository.name, issueId, params("labelId").toInt)
|
||||
defining(params("id").toInt) { issueId =>
|
||||
registerIssueLabel(repository.owner, repository.name, issueId, params("labelId").toInt, true)
|
||||
html.labellist(getIssueLabels(repository.owner, repository.name, issueId))
|
||||
}
|
||||
})
|
||||
|
||||
ajaxPost("/:owner/:repository/issues/:id/label/delete")(writableUsersOnly { repository =>
|
||||
defining(params("id").toInt){ issueId =>
|
||||
deleteIssueLabel(repository.owner, repository.name, issueId, params("labelId").toInt)
|
||||
defining(params("id").toInt) { issueId =>
|
||||
deleteIssueLabel(repository.owner, repository.name, issueId, params("labelId").toInt, true)
|
||||
html.labellist(getIssueLabels(repository.owner, repository.name, issueId))
|
||||
}
|
||||
})
|
||||
|
||||
ajaxPost("/:owner/:repository/issues/:id/assign")(writableUsersOnly { repository =>
|
||||
updateAssignedUserName(repository.owner, repository.name, params("id").toInt, assignedUserName("assignedUserName"))
|
||||
updateAssignedUserName(
|
||||
repository.owner,
|
||||
repository.name,
|
||||
params("id").toInt,
|
||||
assignedUserName("assignedUserName"),
|
||||
true
|
||||
)
|
||||
Ok("updated")
|
||||
})
|
||||
|
||||
ajaxPost("/:owner/:repository/issues/:id/milestone")(writableUsersOnly { repository =>
|
||||
updateMilestoneId(repository.owner, repository.name, params("id").toInt, milestoneId("milestoneId"))
|
||||
updateMilestoneId(repository.owner, repository.name, params("id").toInt, milestoneId("milestoneId"), true)
|
||||
milestoneId("milestoneId").map { milestoneId =>
|
||||
getMilestonesWithIssueCount(repository.owner, repository.name)
|
||||
.find(_._1.milestoneId == milestoneId).map { case (_, openCount, closeCount) =>
|
||||
gitbucket.core.issues.milestones.html.progress(openCount + closeCount, closeCount)
|
||||
} getOrElse NotFound()
|
||||
.find(_._1.milestoneId == milestoneId)
|
||||
.map {
|
||||
case (_, openCount, closeCount) =>
|
||||
gitbucket.core.issues.milestones.html.progress(openCount + closeCount, closeCount)
|
||||
} getOrElse NotFound()
|
||||
} getOrElse Ok()
|
||||
})
|
||||
|
||||
ajaxPost("/:owner/:repository/issues/:id/priority")(writableUsersOnly { repository =>
|
||||
updatePriorityId(repository.owner, repository.name, params("id").toInt, priorityId("priorityId"))
|
||||
val priority = priorityId("priorityId")
|
||||
updatePriorityId(repository.owner, repository.name, params("id").toInt, priority, true)
|
||||
Ok("updated")
|
||||
})
|
||||
|
||||
post("/:owner/:repository/issues/batchedit/state")(writableUsersOnly { repository =>
|
||||
defining(params.get("value")){ action =>
|
||||
action match {
|
||||
case Some("open") => executeBatch(repository) { issueId =>
|
||||
getIssue(repository.owner, repository.name, issueId.toString).foreach { issue =>
|
||||
handleComment(issue, None, repository, Some("reopen"))
|
||||
}
|
||||
defining(params.get("value")) {
|
||||
action =>
|
||||
action match {
|
||||
case Some("open") =>
|
||||
executeBatch(repository) { issueId =>
|
||||
getIssue(repository.owner, repository.name, issueId.toString).foreach { issue =>
|
||||
handleComment(issue, None, repository, Some("reopen"))
|
||||
}
|
||||
}
|
||||
case Some("close") =>
|
||||
executeBatch(repository) { issueId =>
|
||||
getIssue(repository.owner, repository.name, issueId.toString).foreach { issue =>
|
||||
handleComment(issue, None, repository, Some("close"))
|
||||
}
|
||||
}
|
||||
case _ => BadRequest()
|
||||
}
|
||||
case Some("close") => executeBatch(repository) { issueId =>
|
||||
getIssue(repository.owner, repository.name, issueId.toString).foreach { issue =>
|
||||
handleComment(issue, None, repository, Some("close"))
|
||||
}
|
||||
}
|
||||
case _ => BadRequest()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
post("/:owner/:repository/issues/batchedit/label")(writableUsersOnly { repository =>
|
||||
params("value").toIntOpt.map{ labelId =>
|
||||
params("value").toIntOpt.map { labelId =>
|
||||
executeBatch(repository) { issueId =>
|
||||
getIssueLabel(repository.owner, repository.name, issueId, labelId) getOrElse {
|
||||
registerIssueLabel(repository.owner, repository.name, issueId, labelId)
|
||||
registerIssueLabel(repository.owner, repository.name, issueId, labelId, true)
|
||||
}
|
||||
}
|
||||
} getOrElse NotFound()
|
||||
})
|
||||
|
||||
post("/:owner/:repository/issues/batchedit/assign")(writableUsersOnly { repository =>
|
||||
defining(assignedUserName("value")){ value =>
|
||||
defining(assignedUserName("value")) { value =>
|
||||
executeBatch(repository) {
|
||||
updateAssignedUserName(repository.owner, repository.name, _, value)
|
||||
updateAssignedUserName(repository.owner, repository.name, _, value, true)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
post("/:owner/:repository/issues/batchedit/milestone")(writableUsersOnly { repository =>
|
||||
defining(milestoneId("value")){ value =>
|
||||
defining(milestoneId("value")) { value =>
|
||||
executeBatch(repository) {
|
||||
updateMilestoneId(repository.owner, repository.name, _, value)
|
||||
updateMilestoneId(repository.owner, repository.name, _, value, true)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
post("/:owner/:repository/issues/batchedit/priority")(writableUsersOnly { repository =>
|
||||
defining(priorityId("value")){ value =>
|
||||
defining(priorityId("value")) { value =>
|
||||
executeBatch(repository) {
|
||||
updatePriorityId(repository.owner, repository.name, _, value)
|
||||
updatePriorityId(repository.owner, repository.name, _, value, true)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
get("/:owner/:repository/_attached/:file")(referrersOnly { repository =>
|
||||
(Directory.getAttachedDir(repository.owner, repository.name) match {
|
||||
case dir if(dir.exists && dir.isDirectory) =>
|
||||
case dir if (dir.exists && dir.isDirectory) =>
|
||||
dir.listFiles.find(_.getName.startsWith(params("file") + ".")).map { file =>
|
||||
response.setHeader("Content-Disposition", f"""inline; filename=${file.getName}""")
|
||||
RawData(FileUtil.getMimeType(file.getName), file)
|
||||
@@ -371,7 +407,7 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
val priorityId: String => Option[Int] = (key: String) => params.get(key).flatMap(_.toIntOpt)
|
||||
|
||||
private def executeBatch(repository: RepositoryService.RepositoryInfo)(execute: Int => Unit) = {
|
||||
params("checked").split(',') map(_.toInt) foreach execute
|
||||
params("checked").split(',') map (_.toInt) foreach execute
|
||||
params("from") match {
|
||||
case "issues" => redirect(s"/${repository.owner}/${repository.name}/issues")
|
||||
case "pulls" => redirect(s"/${repository.owner}/${repository.name}/pulls")
|
||||
@@ -379,13 +415,14 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
}
|
||||
|
||||
private def searchIssues(repository: RepositoryService.RepositoryInfo) = {
|
||||
defining(repository.owner, repository.name){ case (owner, repoName) =>
|
||||
val page = IssueSearchCondition.page(request)
|
||||
defining(repository.owner, repository.name) {
|
||||
case (owner, repoName) =>
|
||||
val page = IssueSearchCondition.page(request)
|
||||
|
||||
// retrieve search condition
|
||||
val condition = IssueSearchCondition(request)
|
||||
// retrieve search condition
|
||||
val condition = IssueSearchCondition(request)
|
||||
|
||||
html.list(
|
||||
html.list(
|
||||
"issues",
|
||||
searchIssue(condition, false, (page - 1) * IssueLimit, IssueLimit, owner -> repoName),
|
||||
page,
|
||||
@@ -393,19 +430,22 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
getMilestones(owner, repoName),
|
||||
getPriorities(owner, repoName),
|
||||
getLabels(owner, repoName),
|
||||
countIssue(condition.copy(state = "open" ), false, owner -> repoName),
|
||||
countIssue(condition.copy(state = "open"), false, owner -> repoName),
|
||||
countIssue(condition.copy(state = "closed"), false, owner -> repoName),
|
||||
condition,
|
||||
repository,
|
||||
isIssueEditable(repository),
|
||||
isIssueManageable(repository))
|
||||
isIssueManageable(repository)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether an issue or a comment is editable by a logged-in user.
|
||||
*/
|
||||
private def isEditableContent(owner: String, repository: String, author: String)(implicit context: Context): Boolean = {
|
||||
private def isEditableContent(owner: String, repository: String, author: String)(
|
||||
implicit context: Context
|
||||
): Boolean = {
|
||||
hasDeveloperRole(owner, repository, context.loginAccount) || author == context.loginAccount.get.userName
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
package gitbucket.core.controller
|
||||
|
||||
import gitbucket.core.issues.labels.html
|
||||
import gitbucket.core.service.{RepositoryService, AccountService, IssuesService, LabelsService}
|
||||
import gitbucket.core.service.{
|
||||
RepositoryService,
|
||||
AccountService,
|
||||
IssuesService,
|
||||
LabelsService,
|
||||
MilestonesService,
|
||||
PrioritiesService
|
||||
}
|
||||
import gitbucket.core.util.{ReferrerAuthenticator, WritableUsersAuthenticator}
|
||||
import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.util.SyntaxSugars._
|
||||
@@ -9,28 +16,38 @@ import org.scalatra.forms._
|
||||
import org.scalatra.i18n.Messages
|
||||
import org.scalatra.Ok
|
||||
|
||||
class LabelsController extends LabelsControllerBase
|
||||
with LabelsService with IssuesService with RepositoryService with AccountService
|
||||
with ReferrerAuthenticator with WritableUsersAuthenticator
|
||||
class LabelsController
|
||||
extends LabelsControllerBase
|
||||
with IssuesService
|
||||
with RepositoryService
|
||||
with AccountService
|
||||
with LabelsService
|
||||
with PrioritiesService
|
||||
with MilestonesService
|
||||
with ReferrerAuthenticator
|
||||
with WritableUsersAuthenticator
|
||||
|
||||
trait LabelsControllerBase extends ControllerBase {
|
||||
self: LabelsService with IssuesService with RepositoryService
|
||||
with ReferrerAuthenticator with WritableUsersAuthenticator =>
|
||||
self: LabelsService
|
||||
with IssuesService
|
||||
with RepositoryService
|
||||
with ReferrerAuthenticator
|
||||
with WritableUsersAuthenticator =>
|
||||
|
||||
case class LabelForm(labelName: String, color: String)
|
||||
|
||||
val labelForm = mapping(
|
||||
"labelName" -> trim(label("Label name", text(required, labelName, uniqueLabelName, maxlength(100)))),
|
||||
"labelColor" -> trim(label("Color", text(required, color)))
|
||||
"labelName" -> trim(label("Label name", text(required, labelName, uniqueLabelName, maxlength(100)))),
|
||||
"labelColor" -> trim(label("Color", text(required, color)))
|
||||
)(LabelForm.apply)
|
||||
|
||||
|
||||
get("/:owner/:repository/issues/labels")(referrersOnly { repository =>
|
||||
html.list(
|
||||
getLabels(repository.owner, repository.name),
|
||||
countIssueGroupByLabels(repository.owner, repository.name, IssuesService.IssueSearchCondition(), Map.empty),
|
||||
repository,
|
||||
hasDeveloperRole(repository.owner, repository.name, context.loginAccount))
|
||||
hasDeveloperRole(repository.owner, repository.name, context.loginAccount)
|
||||
)
|
||||
})
|
||||
|
||||
ajaxGet("/:owner/:repository/issues/labels/new")(writableUsersOnly { repository =>
|
||||
@@ -44,7 +61,8 @@ trait LabelsControllerBase extends ControllerBase {
|
||||
// TODO futility
|
||||
countIssueGroupByLabels(repository.owner, repository.name, IssuesService.IssueSearchCondition(), Map.empty),
|
||||
repository,
|
||||
hasDeveloperRole(repository.owner, repository.name, context.loginAccount))
|
||||
hasDeveloperRole(repository.owner, repository.name, context.loginAccount)
|
||||
)
|
||||
})
|
||||
|
||||
ajaxGet("/:owner/:repository/issues/labels/:labelId/edit")(writableUsersOnly { repository =>
|
||||
@@ -60,7 +78,8 @@ trait LabelsControllerBase extends ControllerBase {
|
||||
// TODO futility
|
||||
countIssueGroupByLabels(repository.owner, repository.name, IssuesService.IssueSearchCondition(), Map.empty),
|
||||
repository,
|
||||
hasDeveloperRole(repository.owner, repository.name, context.loginAccount))
|
||||
hasDeveloperRole(repository.owner, repository.name, context.loginAccount)
|
||||
)
|
||||
})
|
||||
|
||||
ajaxPost("/:owner/:repository/issues/labels/:labelId/delete")(writableUsersOnly { repository =>
|
||||
@@ -71,26 +90,34 @@ trait LabelsControllerBase extends ControllerBase {
|
||||
/**
|
||||
* Constraint for the identifier such as user name, repository name or page name.
|
||||
*/
|
||||
private def labelName: Constraint = new Constraint(){
|
||||
private def labelName: Constraint = new Constraint() {
|
||||
override def validate(name: String, value: String, messages: Messages): Option[String] =
|
||||
if(value.contains(',')){
|
||||
if (value.contains(',')) {
|
||||
Some(s"${name} contains invalid character.")
|
||||
} else if(value.startsWith("_") || value.startsWith("-")){
|
||||
} else if (value.startsWith("_") || value.startsWith("-")) {
|
||||
Some(s"${name} starts with invalid character.")
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
private def uniqueLabelName: Constraint = new Constraint(){
|
||||
override def validate(name: String, value: String, params: Map[String, Seq[String]], messages: Messages): Option[String] = {
|
||||
val owner = params.value("owner")
|
||||
private def uniqueLabelName: Constraint = new Constraint() {
|
||||
override def validate(
|
||||
name: String,
|
||||
value: String,
|
||||
params: Map[String, Seq[String]],
|
||||
messages: Messages
|
||||
): Option[String] = {
|
||||
val owner = params.value("owner")
|
||||
val repository = params.value("repository")
|
||||
params.optionValue("labelId").map { labelId =>
|
||||
getLabel(owner, repository, value).filter(_.labelId != labelId.toInt).map(_ => "Name has already been taken.")
|
||||
}.getOrElse {
|
||||
getLabel(owner, repository, value).map(_ => "Name has already been taken.")
|
||||
}
|
||||
params
|
||||
.optionValue("labelId")
|
||||
.map { labelId =>
|
||||
getLabel(owner, repository, value).filter(_.labelId != labelId.toInt).map(_ => "Name has already been taken.")
|
||||
}
|
||||
.getOrElse {
|
||||
getLabel(owner, repository, value).map(_ => "Name has already been taken.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,20 +6,23 @@ import gitbucket.core.util.{ReferrerAuthenticator, WritableUsersAuthenticator}
|
||||
import gitbucket.core.util.Implicits._
|
||||
import org.scalatra.forms._
|
||||
|
||||
class MilestonesController extends MilestonesControllerBase
|
||||
with MilestonesService with RepositoryService with AccountService
|
||||
with ReferrerAuthenticator with WritableUsersAuthenticator
|
||||
class MilestonesController
|
||||
extends MilestonesControllerBase
|
||||
with MilestonesService
|
||||
with RepositoryService
|
||||
with AccountService
|
||||
with ReferrerAuthenticator
|
||||
with WritableUsersAuthenticator
|
||||
|
||||
trait MilestonesControllerBase extends ControllerBase {
|
||||
self: MilestonesService with RepositoryService
|
||||
with ReferrerAuthenticator with WritableUsersAuthenticator =>
|
||||
self: MilestonesService with RepositoryService with ReferrerAuthenticator with WritableUsersAuthenticator =>
|
||||
|
||||
case class MilestoneForm(title: String, description: Option[String], dueDate: Option[java.util.Date])
|
||||
|
||||
val milestoneForm = mapping(
|
||||
"title" -> trim(label("Title", text(required, maxlength(100)))),
|
||||
"title" -> trim(label("Title", text(required, maxlength(100)))),
|
||||
"description" -> trim(label("Description", optional(text()))),
|
||||
"dueDate" -> trim(label("Due Date", optional(date())))
|
||||
"dueDate" -> trim(label("Due Date", optional(date())))
|
||||
)(MilestoneForm.apply)
|
||||
|
||||
get("/:owner/:repository/issues/milestones")(referrersOnly { repository =>
|
||||
@@ -27,7 +30,8 @@ trait MilestonesControllerBase extends ControllerBase {
|
||||
params.getOrElse("state", "open"),
|
||||
getMilestonesWithIssueCount(repository.owner, repository.name),
|
||||
repository,
|
||||
hasDeveloperRole(repository.owner, repository.name, context.loginAccount))
|
||||
hasDeveloperRole(repository.owner, repository.name, context.loginAccount)
|
||||
)
|
||||
})
|
||||
|
||||
get("/:owner/:repository/issues/milestones/new")(writableUsersOnly {
|
||||
@@ -40,22 +44,23 @@ trait MilestonesControllerBase extends ControllerBase {
|
||||
})
|
||||
|
||||
get("/:owner/:repository/issues/milestones/:milestoneId/edit")(writableUsersOnly { repository =>
|
||||
params("milestoneId").toIntOpt.map{ milestoneId =>
|
||||
params("milestoneId").toIntOpt.map { milestoneId =>
|
||||
html.edit(getMilestone(repository.owner, repository.name, milestoneId), repository)
|
||||
} getOrElse NotFound()
|
||||
})
|
||||
|
||||
post("/:owner/:repository/issues/milestones/:milestoneId/edit", milestoneForm)(writableUsersOnly { (form, repository) =>
|
||||
params("milestoneId").toIntOpt.flatMap{ milestoneId =>
|
||||
getMilestone(repository.owner, repository.name, milestoneId).map { milestone =>
|
||||
updateMilestone(milestone.copy(title = form.title, description = form.description, dueDate = form.dueDate))
|
||||
redirect(s"/${repository.owner}/${repository.name}/issues/milestones")
|
||||
}
|
||||
} getOrElse NotFound()
|
||||
post("/:owner/:repository/issues/milestones/:milestoneId/edit", milestoneForm)(writableUsersOnly {
|
||||
(form, repository) =>
|
||||
params("milestoneId").toIntOpt.flatMap { milestoneId =>
|
||||
getMilestone(repository.owner, repository.name, milestoneId).map { milestone =>
|
||||
updateMilestone(milestone.copy(title = form.title, description = form.description, dueDate = form.dueDate))
|
||||
redirect(s"/${repository.owner}/${repository.name}/issues/milestones")
|
||||
}
|
||||
} getOrElse NotFound()
|
||||
})
|
||||
|
||||
get("/:owner/:repository/issues/milestones/:milestoneId/close")(writableUsersOnly { repository =>
|
||||
params("milestoneId").toIntOpt.flatMap{ milestoneId =>
|
||||
params("milestoneId").toIntOpt.flatMap { milestoneId =>
|
||||
getMilestone(repository.owner, repository.name, milestoneId).map { milestone =>
|
||||
closeMilestone(milestone)
|
||||
redirect(s"/${repository.owner}/${repository.name}/issues/milestones")
|
||||
@@ -64,7 +69,7 @@ trait MilestonesControllerBase extends ControllerBase {
|
||||
})
|
||||
|
||||
get("/:owner/:repository/issues/milestones/:milestoneId/open")(writableUsersOnly { repository =>
|
||||
params("milestoneId").toIntOpt.flatMap{ milestoneId =>
|
||||
params("milestoneId").toIntOpt.flatMap { milestoneId =>
|
||||
getMilestone(repository.owner, repository.name, milestoneId).map { milestone =>
|
||||
openMilestone(milestone)
|
||||
redirect(s"/${repository.owner}/${repository.name}/issues/milestones")
|
||||
@@ -73,7 +78,7 @@ trait MilestonesControllerBase extends ControllerBase {
|
||||
})
|
||||
|
||||
get("/:owner/:repository/issues/milestones/:milestoneId/delete")(writableUsersOnly { repository =>
|
||||
params("milestoneId").toIntOpt.flatMap{ milestoneId =>
|
||||
params("milestoneId").toIntOpt.flatMap { milestoneId =>
|
||||
getMilestone(repository.owner, repository.name, milestoneId).map { milestone =>
|
||||
deleteMilestone(repository.owner, repository.name, milestone.milestoneId)
|
||||
redirect(s"/${repository.owner}/${repository.name}/issues/milestones")
|
||||
|
||||
@@ -28,13 +28,12 @@ trait PreProcessControllerBase extends ControllerBase {
|
||||
* But if it's not allowed, demands authentication except some paths.
|
||||
*/
|
||||
get(!context.settings.allowAnonymousAccess, context.loginAccount.isEmpty) {
|
||||
if(!context.currentPath.startsWith("/assets") && !context.currentPath.startsWith("/signin") &&
|
||||
!context.currentPath.startsWith("/register") && !context.currentPath.endsWith("/info/refs")) {
|
||||
if (!context.currentPath.startsWith("/assets") && !context.currentPath.startsWith("/signin") &&
|
||||
!context.currentPath.startsWith("/register") && !context.currentPath.endsWith("/info/refs")) {
|
||||
Unauthorized()
|
||||
} else {
|
||||
pass()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
package gitbucket.core.controller
|
||||
|
||||
import gitbucket.core.issues.priorities.html
|
||||
import gitbucket.core.service.{RepositoryService, AccountService, IssuesService, PrioritiesService}
|
||||
import gitbucket.core.service.{
|
||||
RepositoryService,
|
||||
AccountService,
|
||||
IssuesService,
|
||||
LabelsService,
|
||||
MilestonesService,
|
||||
PrioritiesService
|
||||
}
|
||||
import gitbucket.core.util.{ReferrerAuthenticator, WritableUsersAuthenticator}
|
||||
import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.util.SyntaxSugars._
|
||||
@@ -9,29 +16,39 @@ import org.scalatra.forms._
|
||||
import org.scalatra.i18n.Messages
|
||||
import org.scalatra.Ok
|
||||
|
||||
class PrioritiesController extends PrioritiesControllerBase
|
||||
with PrioritiesService with IssuesService with RepositoryService with AccountService
|
||||
with ReferrerAuthenticator with WritableUsersAuthenticator
|
||||
class PrioritiesController
|
||||
extends PrioritiesControllerBase
|
||||
with IssuesService
|
||||
with RepositoryService
|
||||
with AccountService
|
||||
with LabelsService
|
||||
with PrioritiesService
|
||||
with MilestonesService
|
||||
with ReferrerAuthenticator
|
||||
with WritableUsersAuthenticator
|
||||
|
||||
trait PrioritiesControllerBase extends ControllerBase {
|
||||
self: PrioritiesService with IssuesService with RepositoryService
|
||||
with ReferrerAuthenticator with WritableUsersAuthenticator =>
|
||||
self: PrioritiesService
|
||||
with IssuesService
|
||||
with RepositoryService
|
||||
with ReferrerAuthenticator
|
||||
with WritableUsersAuthenticator =>
|
||||
|
||||
case class PriorityForm(priorityName: String, description: Option[String], color: String)
|
||||
|
||||
val priorityForm = mapping(
|
||||
"priorityName" -> trim(label("Priority name", text(required, priorityName, uniquePriorityName, maxlength(100)))),
|
||||
"description" -> trim(label("Description", optional(text(maxlength(255))))),
|
||||
"priorityName" -> trim(label("Priority name", text(required, priorityName, uniquePriorityName, maxlength(100)))),
|
||||
"description" -> trim(label("Description", optional(text(maxlength(255))))),
|
||||
"priorityColor" -> trim(label("Color", text(required, color)))
|
||||
)(PriorityForm.apply)
|
||||
|
||||
|
||||
get("/:owner/:repository/issues/priorities")(referrersOnly { repository =>
|
||||
html.list(
|
||||
getPriorities(repository.owner, repository.name),
|
||||
countIssueGroupByPriorities(repository.owner, repository.name, IssuesService.IssueSearchCondition(), Map.empty),
|
||||
repository,
|
||||
hasDeveloperRole(repository.owner, repository.name, context.loginAccount))
|
||||
hasDeveloperRole(repository.owner, repository.name, context.loginAccount)
|
||||
)
|
||||
})
|
||||
|
||||
ajaxGet("/:owner/:repository/issues/priorities/new")(writableUsersOnly { repository =>
|
||||
@@ -39,12 +56,14 @@ trait PrioritiesControllerBase extends ControllerBase {
|
||||
})
|
||||
|
||||
ajaxPost("/:owner/:repository/issues/priorities/new", priorityForm)(writableUsersOnly { (form, repository) =>
|
||||
val priorityId = createPriority(repository.owner, repository.name, form.priorityName, form.description, form.color.substring(1))
|
||||
val priorityId =
|
||||
createPriority(repository.owner, repository.name, form.priorityName, form.description, form.color.substring(1))
|
||||
html.priority(
|
||||
getPriority(repository.owner, repository.name, priorityId).get,
|
||||
countIssueGroupByPriorities(repository.owner, repository.name, IssuesService.IssueSearchCondition(), Map.empty),
|
||||
repository,
|
||||
hasDeveloperRole(repository.owner, repository.name, context.loginAccount))
|
||||
hasDeveloperRole(repository.owner, repository.name, context.loginAccount)
|
||||
)
|
||||
})
|
||||
|
||||
ajaxGet("/:owner/:repository/issues/priorities/:priorityId/edit")(writableUsersOnly { repository =>
|
||||
@@ -53,21 +72,34 @@ trait PrioritiesControllerBase extends ControllerBase {
|
||||
} getOrElse NotFound()
|
||||
})
|
||||
|
||||
ajaxPost("/:owner/:repository/issues/priorities/:priorityId/edit", priorityForm)(writableUsersOnly { (form, repository) =>
|
||||
updatePriority(repository.owner, repository.name, params("priorityId").toInt, form.priorityName, form.description, form.color.substring(1))
|
||||
html.priority(
|
||||
getPriority(repository.owner, repository.name, params("priorityId").toInt).get,
|
||||
countIssueGroupByPriorities(repository.owner, repository.name, IssuesService.IssueSearchCondition(), Map.empty),
|
||||
repository,
|
||||
hasDeveloperRole(repository.owner, repository.name, context.loginAccount))
|
||||
ajaxPost("/:owner/:repository/issues/priorities/:priorityId/edit", priorityForm)(writableUsersOnly {
|
||||
(form, repository) =>
|
||||
updatePriority(
|
||||
repository.owner,
|
||||
repository.name,
|
||||
params("priorityId").toInt,
|
||||
form.priorityName,
|
||||
form.description,
|
||||
form.color.substring(1)
|
||||
)
|
||||
html.priority(
|
||||
getPriority(repository.owner, repository.name, params("priorityId").toInt).get,
|
||||
countIssueGroupByPriorities(repository.owner, repository.name, IssuesService.IssueSearchCondition(), Map.empty),
|
||||
repository,
|
||||
hasDeveloperRole(repository.owner, repository.name, context.loginAccount)
|
||||
)
|
||||
})
|
||||
|
||||
ajaxPost("/:owner/:repository/issues/priorities/reorder")(writableUsersOnly { (repository) =>
|
||||
reorderPriorities(repository.owner, repository.name, params("order")
|
||||
.split(",")
|
||||
.map(id => id.toInt)
|
||||
.zipWithIndex
|
||||
.toMap)
|
||||
reorderPriorities(
|
||||
repository.owner,
|
||||
repository.name,
|
||||
params("order")
|
||||
.split(",")
|
||||
.map(id => id.toInt)
|
||||
.zipWithIndex
|
||||
.toMap
|
||||
)
|
||||
|
||||
Ok()
|
||||
})
|
||||
@@ -87,26 +119,36 @@ trait PrioritiesControllerBase extends ControllerBase {
|
||||
/**
|
||||
* Constraint for the identifier such as user name, repository name or page name.
|
||||
*/
|
||||
private def priorityName: Constraint = new Constraint(){
|
||||
private def priorityName: Constraint = new Constraint() {
|
||||
override def validate(name: String, value: String, messages: Messages): Option[String] =
|
||||
if(value.contains(',')){
|
||||
if (value.contains(',')) {
|
||||
Some(s"${name} contains invalid character.")
|
||||
} else if(value.startsWith("_") || value.startsWith("-")){
|
||||
} else if (value.startsWith("_") || value.startsWith("-")) {
|
||||
Some(s"${name} starts with invalid character.")
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
private def uniquePriorityName: Constraint = new Constraint(){
|
||||
override def validate(name: String, value: String, params: Map[String, Seq[String]], messages: Messages): Option[String] = {
|
||||
val owner = params.value("owner")
|
||||
private def uniquePriorityName: Constraint = new Constraint() {
|
||||
override def validate(
|
||||
name: String,
|
||||
value: String,
|
||||
params: Map[String, Seq[String]],
|
||||
messages: Messages
|
||||
): Option[String] = {
|
||||
val owner = params.value("owner")
|
||||
val repository = params.value("repository")
|
||||
params.optionValue("priorityId").map { priorityId =>
|
||||
getPriority(owner, repository, value).filter(_.priorityId != priorityId.toInt).map(_ => "Name has already been taken.")
|
||||
}.getOrElse {
|
||||
getPriority(owner, repository, value).map(_ => "Name has already been taken.")
|
||||
}
|
||||
params
|
||||
.optionValue("priorityId")
|
||||
.map { priorityId =>
|
||||
getPriority(owner, repository, value)
|
||||
.filter(_.priorityId != priorityId.toInt)
|
||||
.map(_ => "Name has already been taken.")
|
||||
}
|
||||
.getOrElse {
|
||||
getPriority(owner, repository, value).map(_ => "Name has already been taken.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,192 @@
|
||||
package gitbucket.core.controller
|
||||
|
||||
import java.io.File
|
||||
|
||||
import gitbucket.core.service.{AccountService, ActivityService, ReleaseService, RepositoryService}
|
||||
import gitbucket.core.util.{FileUtil, ReadableUsersAuthenticator, ReferrerAuthenticator, WritableUsersAuthenticator}
|
||||
import gitbucket.core.util.Directory._
|
||||
import gitbucket.core.util.Implicits._
|
||||
import org.scalatra.forms._
|
||||
import gitbucket.core.releases.html
|
||||
import org.apache.commons.io.FileUtils
|
||||
import scala.collection.JavaConverters._
|
||||
|
||||
class ReleaseController
|
||||
extends ReleaseControllerBase
|
||||
with RepositoryService
|
||||
with AccountService
|
||||
with ReleaseService
|
||||
with ActivityService
|
||||
with ReadableUsersAuthenticator
|
||||
with ReferrerAuthenticator
|
||||
with WritableUsersAuthenticator
|
||||
|
||||
trait ReleaseControllerBase extends ControllerBase {
|
||||
self: RepositoryService
|
||||
with AccountService
|
||||
with ReleaseService
|
||||
with ReadableUsersAuthenticator
|
||||
with ReferrerAuthenticator
|
||||
with WritableUsersAuthenticator
|
||||
with ActivityService =>
|
||||
|
||||
case class ReleaseForm(
|
||||
name: String,
|
||||
content: Option[String]
|
||||
)
|
||||
|
||||
val releaseForm = mapping(
|
||||
"name" -> trim(text(required)),
|
||||
"content" -> trim(optional(text()))
|
||||
)(ReleaseForm.apply)
|
||||
|
||||
get("/:owner/:repository/releases")(referrersOnly { repository =>
|
||||
val releases = getReleases(repository.owner, repository.name)
|
||||
val assets = getReleaseAssetsMap(repository.owner, repository.name)
|
||||
|
||||
html.list(
|
||||
repository,
|
||||
repository.tags.reverse.map { tag =>
|
||||
(tag, releases.find(_.tag == tag.name).map { release =>
|
||||
(release, assets(release))
|
||||
})
|
||||
},
|
||||
hasDeveloperRole(repository.owner, repository.name, context.loginAccount)
|
||||
)
|
||||
})
|
||||
|
||||
get("/:owner/:repository/releases/:tag")(referrersOnly { repository =>
|
||||
val tagName = params("tag")
|
||||
getRelease(repository.owner, repository.name, tagName)
|
||||
.map { release =>
|
||||
html.release(
|
||||
release,
|
||||
getReleaseAssets(repository.owner, repository.name, tagName),
|
||||
hasDeveloperRole(repository.owner, repository.name, context.loginAccount),
|
||||
repository
|
||||
)
|
||||
}
|
||||
.getOrElse(NotFound())
|
||||
})
|
||||
|
||||
get("/:owner/:repository/releases/:tag/assets/:fileId")(referrersOnly { repository =>
|
||||
val tagName = params("tag")
|
||||
val fileId = params("fileId")
|
||||
(for {
|
||||
_ <- repository.tags.find(_.name == tagName)
|
||||
_ <- getRelease(repository.owner, repository.name, tagName)
|
||||
asset <- getReleaseAsset(repository.owner, repository.name, tagName, fileId)
|
||||
} yield {
|
||||
response.setHeader("Content-Disposition", s"attachment; filename=${asset.label}")
|
||||
RawData(
|
||||
FileUtil.getMimeType(asset.label),
|
||||
new File(getReleaseFilesDir(repository.owner, repository.name), FileUtil.checkFilename(tagName + "/" + fileId))
|
||||
)
|
||||
}).getOrElse(NotFound())
|
||||
})
|
||||
|
||||
get("/:owner/:repository/releases/:tag/create")(writableUsersOnly { repository =>
|
||||
val tagName = params("tag")
|
||||
repository.tags
|
||||
.find(_.name == tagName)
|
||||
.map { tag =>
|
||||
html.form(repository, tag, None)
|
||||
}
|
||||
.getOrElse(NotFound())
|
||||
})
|
||||
|
||||
post("/:owner/:repository/releases/:tag/create", releaseForm)(writableUsersOnly { (form, repository) =>
|
||||
val tagName = params("tag")
|
||||
val loginAccount = context.loginAccount.get
|
||||
|
||||
// Insert into RELEASE
|
||||
createRelease(repository.owner, repository.name, form.name, form.content, tagName, loginAccount)
|
||||
|
||||
// Insert into RELEASE_ASSET
|
||||
val files = params.collect {
|
||||
case (name, value) if name.startsWith("file:") =>
|
||||
val Array(_, fileId) = name.split(":")
|
||||
(fileId, value)
|
||||
}
|
||||
files.foreach {
|
||||
case (fileId, fileName) =>
|
||||
val size =
|
||||
new File(
|
||||
getReleaseFilesDir(repository.owner, repository.name),
|
||||
FileUtil.checkFilename(tagName + "/" + fileId)
|
||||
).length
|
||||
createReleaseAsset(repository.owner, repository.name, tagName, fileId, fileName, size, loginAccount)
|
||||
}
|
||||
|
||||
recordReleaseActivity(repository.owner, repository.name, loginAccount.userName, form.name)
|
||||
|
||||
redirect(s"/${repository.owner}/${repository.name}/releases/${tagName}")
|
||||
})
|
||||
|
||||
get("/:owner/:repository/releases/:tag/edit")(writableUsersOnly { repository =>
|
||||
val tagName = params("tag")
|
||||
|
||||
(for {
|
||||
release <- getRelease(repository.owner, repository.name, tagName)
|
||||
tag <- repository.tags.find(_.name == tagName)
|
||||
} yield {
|
||||
html.form(repository, tag, Some(release, getReleaseAssets(repository.owner, repository.name, tagName)))
|
||||
}).getOrElse(NotFound())
|
||||
})
|
||||
|
||||
post("/:owner/:repository/releases/:tag/edit", releaseForm)(writableUsersOnly {
|
||||
(form, repository) =>
|
||||
val tagName = params("tag")
|
||||
val loginAccount = context.loginAccount.get
|
||||
|
||||
getRelease(repository.owner, repository.name, tagName)
|
||||
.map { release =>
|
||||
// Update RELEASE
|
||||
updateRelease(repository.owner, repository.name, tagName, form.name, form.content)
|
||||
|
||||
// Delete and Insert RELEASE_ASSET
|
||||
val assets = getReleaseAssets(repository.owner, repository.name, tagName)
|
||||
deleteReleaseAssets(repository.owner, repository.name, tagName)
|
||||
|
||||
val files = params.collect {
|
||||
case (name, value) if name.startsWith("file:") =>
|
||||
val Array(_, fileId) = name.split(":")
|
||||
(fileId, value)
|
||||
}
|
||||
files.foreach {
|
||||
case (fileId, fileName) =>
|
||||
val size =
|
||||
new File(
|
||||
getReleaseFilesDir(repository.owner, repository.name),
|
||||
FileUtil.checkFilename(tagName + "/" + fileId)
|
||||
).length
|
||||
createReleaseAsset(repository.owner, repository.name, tagName, fileId, fileName, size, loginAccount)
|
||||
}
|
||||
|
||||
assets.foreach { asset =>
|
||||
if (!files.exists { case (fileId, _) => fileId == asset.fileName }) {
|
||||
val file = new File(
|
||||
getReleaseFilesDir(repository.owner, repository.name),
|
||||
FileUtil.checkFilename(release.tag + "/" + asset.fileName)
|
||||
)
|
||||
FileUtils.forceDelete(file)
|
||||
}
|
||||
}
|
||||
|
||||
redirect(s"/${release.userName}/${release.repositoryName}/releases/${tagName}")
|
||||
}
|
||||
.getOrElse(NotFound())
|
||||
})
|
||||
|
||||
post("/:owner/:repository/releases/:tag/delete")(writableUsersOnly { repository =>
|
||||
val tagName = params("tag")
|
||||
getRelease(repository.owner, repository.name, tagName).foreach { release =>
|
||||
FileUtils.deleteDirectory(
|
||||
new File(getReleaseFilesDir(repository.owner, repository.name), FileUtil.checkFilename(release.tag))
|
||||
)
|
||||
}
|
||||
deleteRelease(repository.owner, repository.name, tagName)
|
||||
redirect(s"/${repository.owner}/${repository.name}/releases")
|
||||
})
|
||||
|
||||
}
|
||||
@@ -21,14 +21,26 @@ import org.eclipse.jgit.lib.ObjectId
|
||||
import gitbucket.core.model.WebHookContentType
|
||||
import gitbucket.core.plugin.PluginRegistry
|
||||
|
||||
|
||||
class RepositorySettingsController extends RepositorySettingsControllerBase
|
||||
with RepositoryService with AccountService with WebHookService with ProtectedBranchService with CommitStatusService with DeployKeyService
|
||||
with OwnerAuthenticator with UsersAuthenticator
|
||||
class RepositorySettingsController
|
||||
extends RepositorySettingsControllerBase
|
||||
with RepositoryService
|
||||
with AccountService
|
||||
with WebHookService
|
||||
with ProtectedBranchService
|
||||
with CommitStatusService
|
||||
with DeployKeyService
|
||||
with OwnerAuthenticator
|
||||
with UsersAuthenticator
|
||||
|
||||
trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
self: RepositoryService with AccountService with WebHookService with ProtectedBranchService with CommitStatusService with DeployKeyService
|
||||
with OwnerAuthenticator with UsersAuthenticator =>
|
||||
self: RepositoryService
|
||||
with AccountService
|
||||
with WebHookService
|
||||
with ProtectedBranchService
|
||||
with CommitStatusService
|
||||
with DeployKeyService
|
||||
with OwnerAuthenticator
|
||||
with UsersAuthenticator =>
|
||||
|
||||
// for repository options
|
||||
case class OptionsForm(
|
||||
@@ -39,48 +51,58 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
externalIssuesUrl: Option[String],
|
||||
wikiOption: String,
|
||||
externalWikiUrl: Option[String],
|
||||
allowFork: Boolean
|
||||
allowFork: Boolean,
|
||||
mergeOptions: Seq[String],
|
||||
defaultMergeOption: String
|
||||
)
|
||||
|
||||
val optionsForm = mapping(
|
||||
"repositoryName" -> trim(label("Repository Name" , text(required, maxlength(100), repository, renameRepositoryName))),
|
||||
"description" -> trim(label("Description" , optional(text()))),
|
||||
"isPrivate" -> trim(label("Repository Type" , boolean())),
|
||||
"issuesOption" -> trim(label("Issues Option" , text(required, featureOption))),
|
||||
"repositoryName" -> trim(
|
||||
label("Repository Name", text(required, maxlength(100), repository, renameRepositoryName))
|
||||
),
|
||||
"description" -> trim(label("Description", optional(text()))),
|
||||
"isPrivate" -> trim(label("Repository Type", boolean())),
|
||||
"issuesOption" -> trim(label("Issues Option", text(required, featureOption))),
|
||||
"externalIssuesUrl" -> trim(label("External Issues URL", optional(text(maxlength(200))))),
|
||||
"wikiOption" -> trim(label("Wiki Option" , text(required, featureOption))),
|
||||
"externalWikiUrl" -> trim(label("External Wiki URL" , optional(text(maxlength(200))))),
|
||||
"allowFork" -> trim(label("Allow Forking" , boolean()))
|
||||
)(OptionsForm.apply)
|
||||
"wikiOption" -> trim(label("Wiki Option", text(required, featureOption))),
|
||||
"externalWikiUrl" -> trim(label("External Wiki URL", optional(text(maxlength(200))))),
|
||||
"allowFork" -> trim(label("Allow Forking", boolean())),
|
||||
"mergeOptions" -> mergeOptions,
|
||||
"defaultMergeOption" -> trim(label("Default merge strategy", text(required)))
|
||||
)(OptionsForm.apply).verifying { form =>
|
||||
if (!form.mergeOptions.contains(form.defaultMergeOption)) {
|
||||
Seq("defaultMergeOption" -> s"This merge strategy isn't enabled.")
|
||||
} else Nil
|
||||
}
|
||||
|
||||
// for default branch
|
||||
case class DefaultBranchForm(defaultBranch: String)
|
||||
|
||||
val defaultBranchForm = mapping(
|
||||
"defaultBranch" -> trim(label("Default Branch" , text(required, maxlength(100))))
|
||||
"defaultBranch" -> trim(label("Default Branch", text(required, maxlength(100))))
|
||||
)(DefaultBranchForm.apply)
|
||||
|
||||
|
||||
// for deploy key
|
||||
case class DeployKeyForm(title: String, publicKey: String, allowWrite: Boolean)
|
||||
|
||||
val deployKeyForm = mapping(
|
||||
"title" -> trim(label("Title", text(required, maxlength(100)))),
|
||||
"publicKey" -> trim2(label("Key" , text(required))), // TODO duplication check in the repository?
|
||||
"allowWrite" -> trim(label("Key" , boolean()))
|
||||
"title" -> trim(label("Title", text(required, maxlength(100)))),
|
||||
"publicKey" -> trim2(label("Key", text(required))), // TODO duplication check in the repository?
|
||||
"allowWrite" -> trim(label("Key", boolean()))
|
||||
)(DeployKeyForm.apply)
|
||||
|
||||
// for web hook url addition
|
||||
case class WebHookForm(url: String, events: Set[WebHook.Event], ctype: WebHookContentType, token: Option[String])
|
||||
|
||||
def webHookForm(update:Boolean) = mapping(
|
||||
"url" -> trim(label("url", text(required, webHook(update)))),
|
||||
"events" -> webhookEvents,
|
||||
"ctype" -> label("ctype", text()),
|
||||
"token" -> optional(trim(label("token", text(maxlength(100)))))
|
||||
)(
|
||||
(url, events, ctype, token) => WebHookForm(url, events, WebHookContentType.valueOf(ctype), token)
|
||||
)
|
||||
def webHookForm(update: Boolean) =
|
||||
mapping(
|
||||
"url" -> trim(label("url", text(required, webHook(update)))),
|
||||
"events" -> webhookEvents,
|
||||
"ctype" -> label("ctype", text()),
|
||||
"token" -> optional(trim(label("token", text(maxlength(100)))))
|
||||
)(
|
||||
(url, events, ctype, token) => WebHookForm(url, events, WebHookContentType.valueOf(ctype), token)
|
||||
)
|
||||
|
||||
// for transfer ownership
|
||||
case class TransferOwnerShipForm(newOwner: String)
|
||||
@@ -118,30 +140,35 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
form.externalIssuesUrl,
|
||||
form.wikiOption,
|
||||
form.externalWikiUrl,
|
||||
form.allowFork
|
||||
form.allowFork,
|
||||
form.mergeOptions,
|
||||
form.defaultMergeOption
|
||||
)
|
||||
// Change repository name
|
||||
if(repository.name != form.repositoryName){
|
||||
if (repository.name != form.repositoryName) {
|
||||
// Update database
|
||||
renameRepository(repository.owner, repository.name, repository.owner, form.repositoryName)
|
||||
// Move git repository
|
||||
defining(getRepositoryDir(repository.owner, repository.name)){ dir =>
|
||||
if(dir.isDirectory){
|
||||
defining(getRepositoryDir(repository.owner, repository.name)) { dir =>
|
||||
if (dir.isDirectory) {
|
||||
FileUtils.moveDirectory(dir, getRepositoryDir(repository.owner, form.repositoryName))
|
||||
}
|
||||
}
|
||||
// Move wiki repository
|
||||
defining(getWikiRepositoryDir(repository.owner, repository.name)){ dir =>
|
||||
if(dir.isDirectory) {
|
||||
defining(getWikiRepositoryDir(repository.owner, repository.name)) { dir =>
|
||||
if (dir.isDirectory) {
|
||||
FileUtils.moveDirectory(dir, getWikiRepositoryDir(repository.owner, form.repositoryName))
|
||||
}
|
||||
}
|
||||
// Move files directory
|
||||
defining(getRepositoryFilesDir(repository.owner, repository.name)){ dir =>
|
||||
if(dir.isDirectory) {
|
||||
defining(getRepositoryFilesDir(repository.owner, repository.name)) { dir =>
|
||||
if (dir.isDirectory) {
|
||||
FileUtils.moveDirectory(dir, getRepositoryFilesDir(repository.owner, form.repositoryName))
|
||||
}
|
||||
}
|
||||
// Delete parent directory
|
||||
FileUtil.deleteDirectoryIfEmpty(getRepositoryFilesDir(repository.owner, repository.name))
|
||||
|
||||
// Call hooks
|
||||
PluginRegistry().getRepositoryHooks.foreach(_.renamed(repository.owner, repository.name, form.repositoryName))
|
||||
}
|
||||
@@ -157,7 +184,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
|
||||
/** Update default branch */
|
||||
post("/:owner/:repository/settings/update_default_branch", defaultBranchForm)(ownerOnly { (form, repository) =>
|
||||
if(!repository.branchList.contains(form.defaultBranch)){
|
||||
if (!repository.branchList.contains(form.defaultBranch)) {
|
||||
redirect(s"/${repository.owner}/${repository.name}/settings/options")
|
||||
} else {
|
||||
saveRepositoryDefaultBranch(repository.owner, repository.name, form.defaultBranch)
|
||||
@@ -174,12 +201,15 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
get("/:owner/:repository/settings/branches/:branch")(ownerOnly { repository =>
|
||||
import gitbucket.core.api._
|
||||
val branch = params("branch")
|
||||
if(!repository.branchList.contains(branch)){
|
||||
if (!repository.branchList.contains(branch)) {
|
||||
redirect(s"/${repository.owner}/${repository.name}/settings/branches")
|
||||
} else {
|
||||
val protection = ApiBranchProtection(getProtectedBranchInfo(repository.owner, repository.name, branch))
|
||||
val lastWeeks = getRecentStatuesContexts(repository.owner, repository.name,
|
||||
Date.from(LocalDateTime.now.minusWeeks(1).toInstant(ZoneOffset.of("UTC")))).toSet
|
||||
val lastWeeks = getRecentStatuesContexts(
|
||||
repository.owner,
|
||||
repository.name,
|
||||
Date.from(LocalDateTime.now.minusWeeks(1).toInstant(ZoneOffset.UTC))
|
||||
).toSet
|
||||
val knownContexts = (lastWeeks ++ protection.status.contexts).toSeq.sortBy(identity)
|
||||
html.branchprotection(repository, branch, protection, knownContexts, flash.get("info"))
|
||||
}
|
||||
@@ -192,7 +222,8 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
html.collaborators(
|
||||
getCollaborators(repository.owner, repository.name),
|
||||
getAccountByUserName(repository.owner).get.isGroupAccount,
|
||||
repository)
|
||||
repository
|
||||
)
|
||||
})
|
||||
|
||||
post("/:owner/:repository/settings/collaborators")(ownerOnly { repository =>
|
||||
@@ -242,62 +273,90 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
* Send the test request to registered web hook URLs.
|
||||
*/
|
||||
ajaxPost("/:owner/:repository/settings/hooks/test")(ownerOnly { repository =>
|
||||
def _headers(h: Array[org.apache.http.Header]): Array[Array[String]] = h.map { h => Array(h.getName, h.getValue) }
|
||||
def _headers(h: Array[org.apache.http.Header]): Array[Array[String]] = h.map { h =>
|
||||
Array(h.getName, h.getValue)
|
||||
}
|
||||
|
||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
|
||||
import scala.collection.JavaConverters._
|
||||
import scala.concurrent.duration._
|
||||
import scala.concurrent._
|
||||
import scala.util.control.NonFatal
|
||||
import org.apache.http.util.EntityUtils
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))) {
|
||||
git =>
|
||||
import scala.collection.JavaConverters._
|
||||
import scala.concurrent.duration._
|
||||
import scala.concurrent._
|
||||
import scala.util.control.NonFatal
|
||||
import org.apache.http.util.EntityUtils
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
|
||||
val url = params("url")
|
||||
val token = Some(params("token"))
|
||||
val ctype = WebHookContentType.valueOf(params("ctype"))
|
||||
val dummyWebHookInfo = RepositoryWebHook(repository.owner, repository.name, url, ctype, token)
|
||||
val dummyPayload = {
|
||||
val ownerAccount = getAccountByUserName(repository.owner).get
|
||||
val commits = if(JGitUtil.isEmpty(git)) List.empty else git.log
|
||||
.add(git.getRepository.resolve(repository.repository.defaultBranch))
|
||||
.setMaxCount(4)
|
||||
.call.iterator.asScala.map(new CommitInfo(_)).toList
|
||||
val pushedCommit = commits.drop(1)
|
||||
val url = params("url")
|
||||
val token = Some(params("token"))
|
||||
val ctype = WebHookContentType.valueOf(params("ctype"))
|
||||
val dummyWebHookInfo = RepositoryWebHook(repository.owner, repository.name, url, ctype, token)
|
||||
val dummyPayload = {
|
||||
val ownerAccount = getAccountByUserName(repository.owner).get
|
||||
val commits =
|
||||
if (JGitUtil.isEmpty(git)) List.empty
|
||||
else
|
||||
git.log
|
||||
.add(git.getRepository.resolve(repository.repository.defaultBranch))
|
||||
.setMaxCount(4)
|
||||
.call
|
||||
.iterator
|
||||
.asScala
|
||||
.map(new CommitInfo(_))
|
||||
.toList
|
||||
val pushedCommit = commits.drop(1)
|
||||
|
||||
WebHookPushPayload(
|
||||
git = git,
|
||||
sender = ownerAccount,
|
||||
refName = "refs/heads/" + repository.repository.defaultBranch,
|
||||
repositoryInfo = repository,
|
||||
commits = pushedCommit,
|
||||
repositoryOwner = ownerAccount,
|
||||
oldId = commits.lastOption.map(_.id).map(ObjectId.fromString).getOrElse(ObjectId.zeroId()),
|
||||
newId = commits.headOption.map(_.id).map(ObjectId.fromString).getOrElse(ObjectId.zeroId())
|
||||
WebHookPushPayload(
|
||||
git = git,
|
||||
sender = ownerAccount,
|
||||
refName = "refs/heads/" + repository.repository.defaultBranch,
|
||||
repositoryInfo = repository,
|
||||
commits = pushedCommit,
|
||||
repositoryOwner = ownerAccount,
|
||||
oldId = commits.lastOption.map(_.id).map(ObjectId.fromString).getOrElse(ObjectId.zeroId()),
|
||||
newId = commits.headOption.map(_.id).map(ObjectId.fromString).getOrElse(ObjectId.zeroId())
|
||||
)
|
||||
}
|
||||
|
||||
val (webHook, json, reqFuture, resFuture) = callWebHook(WebHook.Push, List(dummyWebHookInfo), dummyPayload).head
|
||||
|
||||
val toErrorMap: PartialFunction[Throwable, Map[String, String]] = {
|
||||
case e: java.net.UnknownHostException => Map("error" -> ("Unknown host " + e.getMessage))
|
||||
case e: java.lang.IllegalArgumentException => Map("error" -> ("invalid url"))
|
||||
case e: org.apache.http.client.ClientProtocolException => Map("error" -> ("invalid url"))
|
||||
case NonFatal(e) => Map("error" -> (e.getClass + " " + e.getMessage))
|
||||
}
|
||||
|
||||
contentType = formats("json")
|
||||
org.json4s.jackson.Serialization.write(
|
||||
Map(
|
||||
"url" -> url,
|
||||
"request" -> Await.result(
|
||||
reqFuture
|
||||
.map(
|
||||
req =>
|
||||
Map(
|
||||
"headers" -> _headers(req.getAllHeaders),
|
||||
"payload" -> json
|
||||
)
|
||||
)
|
||||
.recover(toErrorMap),
|
||||
20 seconds
|
||||
),
|
||||
"response" -> Await.result(
|
||||
resFuture
|
||||
.map(
|
||||
res =>
|
||||
Map(
|
||||
"status" -> res.getStatusLine(),
|
||||
"body" -> EntityUtils.toString(res.getEntity()),
|
||||
"headers" -> _headers(res.getAllHeaders())
|
||||
)
|
||||
)
|
||||
.recover(toErrorMap),
|
||||
20 seconds
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
val (webHook, json, reqFuture, resFuture) = callWebHook(WebHook.Push, List(dummyWebHookInfo), dummyPayload).head
|
||||
|
||||
val toErrorMap: PartialFunction[Throwable, Map[String,String]] = {
|
||||
case e: java.net.UnknownHostException => Map("error"-> ("Unknown host " + e.getMessage))
|
||||
case e: java.lang.IllegalArgumentException => Map("error"-> ("invalid url"))
|
||||
case e: org.apache.http.client.ClientProtocolException => Map("error"-> ("invalid url"))
|
||||
case NonFatal(e) => Map("error"-> (e.getClass + " "+ e.getMessage))
|
||||
}
|
||||
|
||||
contentType = formats("json")
|
||||
org.json4s.jackson.Serialization.write(Map(
|
||||
"url" -> url,
|
||||
"request" -> Await.result(reqFuture.map(req => Map(
|
||||
"headers" -> _headers(req.getAllHeaders),
|
||||
"payload" -> json
|
||||
)).recover(toErrorMap), 20 seconds),
|
||||
"response" -> Await.result(resFuture.map(res => Map(
|
||||
"status" -> res.getStatusLine(),
|
||||
"body" -> EntityUtils.toString(res.getEntity()),
|
||||
"headers" -> _headers(res.getAllHeaders())
|
||||
)).recover(toErrorMap), 20 seconds)
|
||||
))
|
||||
}
|
||||
})
|
||||
|
||||
@@ -305,8 +364,9 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
* Display the web hook edit page.
|
||||
*/
|
||||
get("/:owner/:repository/settings/hooks/edit")(ownerOnly { repository =>
|
||||
getWebHook(repository.owner, repository.name, params("url")).map{ case (webhook, events) =>
|
||||
html.edithook(webhook, events, repository, false)
|
||||
getWebHook(repository.owner, repository.name, params("url")).map {
|
||||
case (webhook, events) =>
|
||||
html.edithook(webhook, events, repository, false)
|
||||
} getOrElse NotFound()
|
||||
})
|
||||
|
||||
@@ -331,25 +391,25 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
*/
|
||||
post("/:owner/:repository/settings/transfer", transferForm)(ownerOnly { (form, repository) =>
|
||||
// Change repository owner
|
||||
if(repository.owner != form.newOwner){
|
||||
LockUtil.lock(s"${repository.owner}/${repository.name}"){
|
||||
if (repository.owner != form.newOwner) {
|
||||
LockUtil.lock(s"${repository.owner}/${repository.name}") {
|
||||
// Update database
|
||||
renameRepository(repository.owner, repository.name, form.newOwner, repository.name)
|
||||
// Move git repository
|
||||
defining(getRepositoryDir(repository.owner, repository.name)){ dir =>
|
||||
if(dir.isDirectory){
|
||||
defining(getRepositoryDir(repository.owner, repository.name)) { dir =>
|
||||
if (dir.isDirectory) {
|
||||
FileUtils.moveDirectory(dir, getRepositoryDir(form.newOwner, repository.name))
|
||||
}
|
||||
}
|
||||
// Move wiki repository
|
||||
defining(getWikiRepositoryDir(repository.owner, repository.name)){ dir =>
|
||||
if(dir.isDirectory) {
|
||||
defining(getWikiRepositoryDir(repository.owner, repository.name)) { dir =>
|
||||
if (dir.isDirectory) {
|
||||
FileUtils.moveDirectory(dir, getWikiRepositoryDir(form.newOwner, repository.name))
|
||||
}
|
||||
}
|
||||
// Move files directory
|
||||
defining(getRepositoryFilesDir(repository.owner, repository.name)){ dir =>
|
||||
if(dir.isDirectory) {
|
||||
defining(getRepositoryFilesDir(repository.owner, repository.name)) { dir =>
|
||||
if (dir.isDirectory) {
|
||||
FileUtils.moveDirectory(dir, getRepositoryFilesDir(form.newOwner, repository.name))
|
||||
}
|
||||
}
|
||||
@@ -365,7 +425,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
* Delete the repository.
|
||||
*/
|
||||
post("/:owner/:repository/settings/delete")(ownerOnly { repository =>
|
||||
LockUtil.lock(s"${repository.owner}/${repository.name}"){
|
||||
LockUtil.lock(s"${repository.owner}/${repository.name}") {
|
||||
// Delete the repository and related files
|
||||
deleteRepository(repository.owner, repository.name)
|
||||
|
||||
@@ -415,10 +475,10 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
/**
|
||||
* Provides duplication check for web hook url.
|
||||
*/
|
||||
private def webHook(needExists: Boolean): Constraint = new Constraint(){
|
||||
private def webHook(needExists: Boolean): Constraint = new Constraint() {
|
||||
override def validate(name: String, value: String, messages: Messages): Option[String] =
|
||||
if(getWebHook(params("owner"), params("repository"), value).isDefined != needExists){
|
||||
Some(if(needExists){
|
||||
if (getWebHook(params("owner"), params("repository"), value).isDefined != needExists) {
|
||||
Some(if (needExists) {
|
||||
"URL had not been registered yet."
|
||||
} else {
|
||||
"URL had been registered already."
|
||||
@@ -428,17 +488,18 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
}
|
||||
}
|
||||
|
||||
private def webhookEvents = new ValueType[Set[WebHook.Event]]{
|
||||
private def webhookEvents = new ValueType[Set[WebHook.Event]] {
|
||||
def convert(name: String, params: Map[String, Seq[String]], messages: Messages): Set[WebHook.Event] = {
|
||||
WebHook.Event.values.flatMap { t =>
|
||||
params.get(name + "." + t.name).map(_ => t)
|
||||
}.toSet
|
||||
}
|
||||
def validate(name: String, params: Map[String, Seq[String]], messages: Messages): Seq[(String, String)] = if(convert(name,params,messages).isEmpty){
|
||||
Seq(name -> messages("error.required").format(name))
|
||||
} else {
|
||||
Nil
|
||||
}
|
||||
def validate(name: String, params: Map[String, Seq[String]], messages: Messages): Seq[(String, String)] =
|
||||
if (convert(name, params, messages).isEmpty) {
|
||||
Seq(name -> messages("error.required").format(name))
|
||||
} else {
|
||||
Nil
|
||||
}
|
||||
}
|
||||
|
||||
// /**
|
||||
@@ -459,12 +520,17 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
/**
|
||||
* Duplicate check for the rename repository name.
|
||||
*/
|
||||
private def renameRepositoryName: Constraint = new Constraint(){
|
||||
override def validate(name: String, value: String, params: Map[String, Seq[String]], messages: Messages): Option[String] = {
|
||||
private def renameRepositoryName: Constraint = new Constraint() {
|
||||
override def validate(
|
||||
name: String,
|
||||
value: String,
|
||||
params: Map[String, Seq[String]],
|
||||
messages: Messages
|
||||
): Option[String] = {
|
||||
for {
|
||||
repoName <- params.optionValue("repository") if repoName != value
|
||||
userName <- params.optionValue("owner")
|
||||
_ <- getRepositoryNamesOfUser(userName).find(_ == value)
|
||||
_ <- getRepositoryNamesOfUser(userName).find(_ == value)
|
||||
} yield {
|
||||
"Repository already exists."
|
||||
}
|
||||
@@ -474,26 +540,50 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private def featureOption: Constraint = new Constraint(){
|
||||
override def validate(name: String, value: String, params: Map[String, Seq[String]], messages: Messages): Option[String] =
|
||||
if(Seq("DISABLE", "PRIVATE", "PUBLIC", "ALL").contains(value)) None else Some("Option is invalid.")
|
||||
private def featureOption: Constraint = new Constraint() {
|
||||
override def validate(
|
||||
name: String,
|
||||
value: String,
|
||||
params: Map[String, Seq[String]],
|
||||
messages: Messages
|
||||
): Option[String] =
|
||||
if (Seq("DISABLE", "PRIVATE", "PUBLIC", "ALL").contains(value)) None else Some("Option is invalid.")
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Provides Constraint to validate the repository transfer user.
|
||||
*/
|
||||
private def transferUser: Constraint = new Constraint(){
|
||||
private def transferUser: Constraint = new Constraint() {
|
||||
override def validate(name: String, value: String, messages: Messages): Option[String] =
|
||||
getAccountByUserName(value) match {
|
||||
case None => Some("User does not exist.")
|
||||
case Some(x) => if(x.userName == params("owner")){
|
||||
Some("This is current repository owner.")
|
||||
} else {
|
||||
params.get("repository").flatMap { repositoryName =>
|
||||
getRepositoryNamesOfUser(x.userName).find(_ == repositoryName).map{ _ => "User already has same repository." }
|
||||
case None => Some("User does not exist.")
|
||||
case Some(x) =>
|
||||
if (x.userName == params("owner")) {
|
||||
Some("This is current repository owner.")
|
||||
} else {
|
||||
params.get("repository").flatMap { repositoryName =>
|
||||
getRepositoryNamesOfUser(x.userName).find(_ == repositoryName).map { _ =>
|
||||
"User already has same repository."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def mergeOptions = new ValueType[Seq[String]] {
|
||||
override def convert(name: String, params: Map[String, Seq[String]], messages: Messages): Seq[String] = {
|
||||
params.get("mergeOptions").getOrElse(Nil)
|
||||
}
|
||||
override def validate(name: String, params: Map[String, Seq[String]], messages: Messages): Seq[(String, String)] = {
|
||||
val mergeOptions = params.get("mergeOptions").getOrElse(Nil)
|
||||
if (mergeOptions.isEmpty) {
|
||||
Seq("mergeOptions" -> "At least one option must be enabled.")
|
||||
} else if (!mergeOptions.forall(x => Seq("merge-commit", "squash", "rebase").contains(x))) {
|
||||
Seq("mergeOptions" -> "mergeOptions are invalid.")
|
||||
} else {
|
||||
Nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,90 +2,115 @@ package gitbucket.core.controller
|
||||
|
||||
import java.io.FileInputStream
|
||||
|
||||
import gitbucket.core.admin.html
|
||||
import gitbucket.core.service.{AccountService, RepositoryService, SystemSettingsService}
|
||||
import gitbucket.core.util.{AdminAuthenticator, Mailer}
|
||||
import gitbucket.core.ssh.SshServer
|
||||
import gitbucket.core.plugin.{PluginInfoBase, PluginRegistry, PluginRepository}
|
||||
import SystemSettingsService._
|
||||
import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.util.SyntaxSugars._
|
||||
import gitbucket.core.util.Directory._
|
||||
import gitbucket.core.util.StringUtil._
|
||||
import org.scalatra.forms._
|
||||
import org.apache.commons.io.{FileUtils, IOUtils}
|
||||
import org.scalatra.i18n.Messages
|
||||
import com.github.zafarkhaja.semver.{Version => Semver}
|
||||
import gitbucket.core.GitBucketCoreModule
|
||||
import gitbucket.core.admin.html
|
||||
import gitbucket.core.plugin.{PluginInfoBase, PluginRegistry, PluginRepository}
|
||||
import gitbucket.core.service.SystemSettingsService._
|
||||
import gitbucket.core.service.{AccountService, RepositoryService}
|
||||
import gitbucket.core.ssh.SshServer
|
||||
import gitbucket.core.util.Directory._
|
||||
import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.util.StringUtil._
|
||||
import gitbucket.core.util.SyntaxSugars._
|
||||
import gitbucket.core.util.{AdminAuthenticator, Mailer}
|
||||
import org.apache.commons.io.IOUtils
|
||||
import org.json4s.jackson.Serialization
|
||||
import org.scalatra._
|
||||
import org.scalatra.forms._
|
||||
import org.scalatra.i18n.Messages
|
||||
|
||||
import scala.collection.JavaConverters._
|
||||
import scala.collection.mutable.ListBuffer
|
||||
|
||||
class SystemSettingsController
|
||||
extends SystemSettingsControllerBase
|
||||
with AccountService
|
||||
with RepositoryService
|
||||
with AdminAuthenticator
|
||||
|
||||
class SystemSettingsController extends SystemSettingsControllerBase
|
||||
with AccountService with RepositoryService with AdminAuthenticator
|
||||
case class Table(name: String, columns: Seq[Column])
|
||||
case class Column(name: String, primaryKey: Boolean)
|
||||
|
||||
trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
||||
self: AccountService with RepositoryService with AdminAuthenticator =>
|
||||
|
||||
private val form = mapping(
|
||||
"baseUrl" -> trim(label("Base URL", optional(text()))),
|
||||
"information" -> trim(label("Information", optional(text()))),
|
||||
"baseUrl" -> trim(label("Base URL", optional(text()))),
|
||||
"information" -> trim(label("Information", optional(text()))),
|
||||
"allowAccountRegistration" -> trim(label("Account registration", boolean())),
|
||||
"allowAnonymousAccess" -> trim(label("Anonymous access", boolean())),
|
||||
"allowAnonymousAccess" -> trim(label("Anonymous access", boolean())),
|
||||
"isCreateRepoOptionPublic" -> trim(label("Default option to create a new repository", boolean())),
|
||||
"gravatar" -> trim(label("Gravatar", boolean())),
|
||||
"notification" -> trim(label("Notification", boolean())),
|
||||
"activityLogLimit" -> trim(label("Limit of activity logs", optional(number()))),
|
||||
"ssh" -> trim(label("SSH access", boolean())),
|
||||
"sshHost" -> trim(label("SSH host", optional(text()))),
|
||||
"sshPort" -> trim(label("SSH port", optional(number()))),
|
||||
"useSMTP" -> trim(label("SMTP", boolean())),
|
||||
"smtp" -> optionalIfNotChecked("useSMTP", mapping(
|
||||
"host" -> trim(label("SMTP Host", text(required))),
|
||||
"port" -> trim(label("SMTP Port", optional(number()))),
|
||||
"user" -> trim(label("SMTP User", optional(text()))),
|
||||
"password" -> trim(label("SMTP Password", optional(text()))),
|
||||
"ssl" -> trim(label("Enable SSL", optional(boolean()))),
|
||||
"starttls" -> trim(label("Enable STARTTLS", optional(boolean()))),
|
||||
"fromAddress" -> trim(label("FROM Address", optional(text()))),
|
||||
"fromName" -> trim(label("FROM Name", optional(text())))
|
||||
)(Smtp.apply)),
|
||||
"ldapAuthentication" -> trim(label("LDAP", boolean())),
|
||||
"ldap" -> optionalIfNotChecked("ldapAuthentication", mapping(
|
||||
"host" -> trim(label("LDAP host", text(required))),
|
||||
"port" -> trim(label("LDAP port", optional(number()))),
|
||||
"bindDN" -> trim(label("Bind DN", optional(text()))),
|
||||
"bindPassword" -> trim(label("Bind Password", optional(text()))),
|
||||
"baseDN" -> trim(label("Base DN", text(required))),
|
||||
"userNameAttribute" -> trim(label("User name attribute", text(required))),
|
||||
"additionalFilterCondition"-> trim(label("Additional filter condition", optional(text()))),
|
||||
"fullNameAttribute" -> trim(label("Full name attribute", optional(text()))),
|
||||
"mailAttribute" -> trim(label("Mail address attribute", optional(text()))),
|
||||
"tls" -> trim(label("Enable TLS", optional(boolean()))),
|
||||
"ssl" -> trim(label("Enable SSL", optional(boolean()))),
|
||||
"keystore" -> trim(label("Keystore", optional(text())))
|
||||
)(Ldap.apply)),
|
||||
"gravatar" -> trim(label("Gravatar", boolean())),
|
||||
"notification" -> trim(label("Notification", boolean())),
|
||||
"activityLogLimit" -> trim(label("Limit of activity logs", optional(number()))),
|
||||
"ssh" -> trim(label("SSH access", boolean())),
|
||||
"sshHost" -> trim(label("SSH host", optional(text()))),
|
||||
"sshPort" -> trim(label("SSH port", optional(number()))),
|
||||
"useSMTP" -> trim(label("SMTP", boolean())),
|
||||
"smtp" -> optionalIfNotChecked(
|
||||
"useSMTP",
|
||||
mapping(
|
||||
"host" -> trim(label("SMTP Host", text(required))),
|
||||
"port" -> trim(label("SMTP Port", optional(number()))),
|
||||
"user" -> trim(label("SMTP User", optional(text()))),
|
||||
"password" -> trim(label("SMTP Password", optional(text()))),
|
||||
"ssl" -> trim(label("Enable SSL", optional(boolean()))),
|
||||
"starttls" -> trim(label("Enable STARTTLS", optional(boolean()))),
|
||||
"fromAddress" -> trim(label("FROM Address", optional(text()))),
|
||||
"fromName" -> trim(label("FROM Name", optional(text())))
|
||||
)(Smtp.apply)
|
||||
),
|
||||
"ldapAuthentication" -> trim(label("LDAP", boolean())),
|
||||
"ldap" -> optionalIfNotChecked(
|
||||
"ldapAuthentication",
|
||||
mapping(
|
||||
"host" -> trim(label("LDAP host", text(required))),
|
||||
"port" -> trim(label("LDAP port", optional(number()))),
|
||||
"bindDN" -> trim(label("Bind DN", optional(text()))),
|
||||
"bindPassword" -> trim(label("Bind Password", optional(text()))),
|
||||
"baseDN" -> trim(label("Base DN", text(required))),
|
||||
"userNameAttribute" -> trim(label("User name attribute", text(required))),
|
||||
"additionalFilterCondition" -> trim(label("Additional filter condition", optional(text()))),
|
||||
"fullNameAttribute" -> trim(label("Full name attribute", optional(text()))),
|
||||
"mailAttribute" -> trim(label("Mail address attribute", optional(text()))),
|
||||
"tls" -> trim(label("Enable TLS", optional(boolean()))),
|
||||
"ssl" -> trim(label("Enable SSL", optional(boolean()))),
|
||||
"keystore" -> trim(label("Keystore", optional(text())))
|
||||
)(Ldap.apply)
|
||||
),
|
||||
"oidcAuthentication" -> trim(label("OIDC", boolean())),
|
||||
"oidc" -> optionalIfNotChecked(
|
||||
"oidcAuthentication",
|
||||
mapping(
|
||||
"issuer" -> trim(label("Issuer", text(required))),
|
||||
"clientID" -> trim(label("Client ID", text(required))),
|
||||
"clientSecret" -> trim(label("Client secret", text(required))),
|
||||
"jwsAlgorithm" -> trim(label("Signature algorithm", optional(text())))
|
||||
)(OIDC.apply)
|
||||
),
|
||||
"skinName" -> trim(label("AdminLTE skin name", text(required)))
|
||||
)(SystemSettings.apply).verifying { settings =>
|
||||
Vector(
|
||||
if(settings.ssh && settings.baseUrl.isEmpty){
|
||||
if (settings.ssh && settings.baseUrl.isEmpty) {
|
||||
Some("baseUrl" -> "Base URL is required if SSH access is enabled.")
|
||||
} else None,
|
||||
if(settings.ssh && settings.sshHost.isEmpty){
|
||||
if (settings.ssh && settings.sshHost.isEmpty) {
|
||||
Some("sshHost" -> "SSH host is required if SSH access is enabled.")
|
||||
} else None
|
||||
).flatten
|
||||
}
|
||||
|
||||
private val sendMailForm = mapping(
|
||||
"smtp" -> mapping(
|
||||
"host" -> trim(label("SMTP Host", text(required))),
|
||||
"port" -> trim(label("SMTP Port", optional(number()))),
|
||||
"user" -> trim(label("SMTP User", optional(text()))),
|
||||
"password" -> trim(label("SMTP Password", optional(text()))),
|
||||
"ssl" -> trim(label("Enable SSL", optional(boolean()))),
|
||||
"starttls" -> trim(label("Enable STARTTLS", optional(boolean()))),
|
||||
"smtp" -> mapping(
|
||||
"host" -> trim(label("SMTP Host", text(required))),
|
||||
"port" -> trim(label("SMTP Port", optional(number()))),
|
||||
"user" -> trim(label("SMTP User", optional(text()))),
|
||||
"password" -> trim(label("SMTP Password", optional(text()))),
|
||||
"ssl" -> trim(label("Enable SSL", optional(boolean()))),
|
||||
"starttls" -> trim(label("Enable STARTTLS", optional(boolean()))),
|
||||
"fromAddress" -> trim(label("FROM Address", optional(text()))),
|
||||
"fromName" -> trim(label("FROM Name", optional(text())))
|
||||
"fromName" -> trim(label("FROM Name", optional(text())))
|
||||
)(Smtp.apply),
|
||||
"testAddress" -> trim(label("", text(required)))
|
||||
)(SendMailForm.apply)
|
||||
@@ -94,66 +119,169 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
||||
|
||||
case class DataExportForm(tableNames: List[String])
|
||||
|
||||
case class NewUserForm(userName: String, password: String, fullName: String,
|
||||
mailAddress: String, isAdmin: Boolean,
|
||||
description: Option[String], url: Option[String], fileId: Option[String])
|
||||
case class NewUserForm(
|
||||
userName: String,
|
||||
password: String,
|
||||
fullName: String,
|
||||
mailAddress: String,
|
||||
extraMailAddresses: List[String],
|
||||
isAdmin: Boolean,
|
||||
description: Option[String],
|
||||
url: Option[String],
|
||||
fileId: Option[String]
|
||||
)
|
||||
|
||||
case class EditUserForm(userName: String, password: Option[String], fullName: String,
|
||||
mailAddress: String, isAdmin: Boolean, description: Option[String], url: Option[String],
|
||||
fileId: Option[String], clearImage: Boolean, isRemoved: Boolean)
|
||||
case class EditUserForm(
|
||||
userName: String,
|
||||
password: Option[String],
|
||||
fullName: String,
|
||||
mailAddress: String,
|
||||
extraMailAddresses: List[String],
|
||||
isAdmin: Boolean,
|
||||
description: Option[String],
|
||||
url: Option[String],
|
||||
fileId: Option[String],
|
||||
clearImage: Boolean,
|
||||
isRemoved: Boolean
|
||||
)
|
||||
|
||||
case class NewGroupForm(groupName: String, description: Option[String], url: Option[String], fileId: Option[String],
|
||||
members: String)
|
||||
|
||||
case class EditGroupForm(groupName: String, description: Option[String], url: Option[String], fileId: Option[String],
|
||||
members: String, clearImage: Boolean, isRemoved: Boolean)
|
||||
case class NewGroupForm(
|
||||
groupName: String,
|
||||
description: Option[String],
|
||||
url: Option[String],
|
||||
fileId: Option[String],
|
||||
members: String
|
||||
)
|
||||
|
||||
case class EditGroupForm(
|
||||
groupName: String,
|
||||
description: Option[String],
|
||||
url: Option[String],
|
||||
fileId: Option[String],
|
||||
members: String,
|
||||
clearImage: Boolean,
|
||||
isRemoved: Boolean
|
||||
)
|
||||
|
||||
val newUserForm = mapping(
|
||||
"userName" -> trim(label("Username" ,text(required, maxlength(100), identifier, uniqueUserName, reservedNames))),
|
||||
"password" -> trim(label("Password" ,text(required, maxlength(20), password))),
|
||||
"fullName" -> trim(label("Full Name" ,text(required, maxlength(100)))),
|
||||
"mailAddress" -> trim(label("Mail Address" ,text(required, maxlength(100), uniqueMailAddress()))),
|
||||
"isAdmin" -> trim(label("User Type" ,boolean())),
|
||||
"description" -> trim(label("bio" ,optional(text()))),
|
||||
"url" -> trim(label("URL" ,optional(text(maxlength(200))))),
|
||||
"fileId" -> trim(label("File ID" ,optional(text())))
|
||||
"userName" -> trim(label("Username", text(required, maxlength(100), identifier, uniqueUserName, reservedNames))),
|
||||
"password" -> trim(label("Password", text(required, maxlength(20), password))),
|
||||
"fullName" -> trim(label("Full Name", text(required, maxlength(100)))),
|
||||
"mailAddress" -> trim(label("Mail Address", text(required, maxlength(100), uniqueMailAddress()))),
|
||||
"extraMailAddresses" -> list(
|
||||
trim(label("Additional Mail Address", text(maxlength(100), uniqueExtraMailAddress("userName"))))
|
||||
),
|
||||
"isAdmin" -> trim(label("User Type", boolean())),
|
||||
"description" -> trim(label("bio", optional(text()))),
|
||||
"url" -> trim(label("URL", optional(text(maxlength(200))))),
|
||||
"fileId" -> trim(label("File ID", optional(text())))
|
||||
)(NewUserForm.apply)
|
||||
|
||||
val editUserForm = mapping(
|
||||
"userName" -> trim(label("Username" ,text(required, maxlength(100), identifier))),
|
||||
"password" -> trim(label("Password" ,optional(text(maxlength(20), password)))),
|
||||
"fullName" -> trim(label("Full Name" ,text(required, maxlength(100)))),
|
||||
"mailAddress" -> trim(label("Mail Address" ,text(required, maxlength(100), uniqueMailAddress("userName")))),
|
||||
"isAdmin" -> trim(label("User Type" ,boolean())),
|
||||
"description" -> trim(label("bio" ,optional(text()))),
|
||||
"url" -> trim(label("URL" ,optional(text(maxlength(200))))),
|
||||
"fileId" -> trim(label("File ID" ,optional(text()))),
|
||||
"clearImage" -> trim(label("Clear image" ,boolean())),
|
||||
"removed" -> trim(label("Disable" ,boolean(disableByNotYourself("userName"))))
|
||||
"userName" -> trim(label("Username", text(required, maxlength(100), identifier))),
|
||||
"password" -> trim(label("Password", optional(text(maxlength(20), password)))),
|
||||
"fullName" -> trim(label("Full Name", text(required, maxlength(100)))),
|
||||
"mailAddress" -> trim(label("Mail Address", text(required, maxlength(100), uniqueMailAddress("userName")))),
|
||||
"extraMailAddresses" -> list(
|
||||
trim(label("Additional Mail Address", text(maxlength(100), uniqueExtraMailAddress("userName"))))
|
||||
),
|
||||
"isAdmin" -> trim(label("User Type", boolean())),
|
||||
"description" -> trim(label("bio", optional(text()))),
|
||||
"url" -> trim(label("URL", optional(text(maxlength(200))))),
|
||||
"fileId" -> trim(label("File ID", optional(text()))),
|
||||
"clearImage" -> trim(label("Clear image", boolean())),
|
||||
"removed" -> trim(label("Disable", boolean(disableByNotYourself("userName"))))
|
||||
)(EditUserForm.apply)
|
||||
|
||||
val newGroupForm = mapping(
|
||||
"groupName" -> trim(label("Group name" ,text(required, maxlength(100), identifier, uniqueUserName, reservedNames))),
|
||||
"groupName" -> trim(label("Group name", text(required, maxlength(100), identifier, uniqueUserName, reservedNames))),
|
||||
"description" -> trim(label("Group description", optional(text()))),
|
||||
"url" -> trim(label("URL" ,optional(text(maxlength(200))))),
|
||||
"fileId" -> trim(label("File ID" ,optional(text()))),
|
||||
"members" -> trim(label("Members" ,text(required, members)))
|
||||
"url" -> trim(label("URL", optional(text(maxlength(200))))),
|
||||
"fileId" -> trim(label("File ID", optional(text()))),
|
||||
"members" -> trim(label("Members", text(required, members)))
|
||||
)(NewGroupForm.apply)
|
||||
|
||||
val editGroupForm = mapping(
|
||||
"groupName" -> trim(label("Group name" ,text(required, maxlength(100), identifier))),
|
||||
"groupName" -> trim(label("Group name", text(required, maxlength(100), identifier))),
|
||||
"description" -> trim(label("Group description", optional(text()))),
|
||||
"url" -> trim(label("URL" ,optional(text(maxlength(200))))),
|
||||
"fileId" -> trim(label("File ID" ,optional(text()))),
|
||||
"members" -> trim(label("Members" ,text(required, members))),
|
||||
"clearImage" -> trim(label("Clear image" ,boolean())),
|
||||
"removed" -> trim(label("Disable" ,boolean()))
|
||||
"url" -> trim(label("URL", optional(text(maxlength(200))))),
|
||||
"fileId" -> trim(label("File ID", optional(text()))),
|
||||
"members" -> trim(label("Members", text(required, members))),
|
||||
"clearImage" -> trim(label("Clear image", boolean())),
|
||||
"removed" -> trim(label("Disable", boolean()))
|
||||
)(EditGroupForm.apply)
|
||||
|
||||
get("/admin/dbviewer")(adminOnly {
|
||||
val conn = request2Session(request).conn
|
||||
val meta = conn.getMetaData
|
||||
val tables = ListBuffer[Table]()
|
||||
using(meta.getTables(null, "%", "%", Array("TABLE", "VIEW"))) {
|
||||
rs =>
|
||||
while (rs.next()) {
|
||||
val tableName = rs.getString("TABLE_NAME")
|
||||
|
||||
val pkColumns = ListBuffer[String]()
|
||||
using(meta.getPrimaryKeys(null, null, tableName)) { rs =>
|
||||
while (rs.next()) {
|
||||
pkColumns += rs.getString("COLUMN_NAME").toUpperCase
|
||||
}
|
||||
}
|
||||
|
||||
val columns = ListBuffer[Column]()
|
||||
using(meta.getColumns(null, "%", tableName, "%")) { rs =>
|
||||
while (rs.next()) {
|
||||
val columnName = rs.getString("COLUMN_NAME").toUpperCase
|
||||
columns += Column(columnName, pkColumns.contains(columnName))
|
||||
}
|
||||
}
|
||||
|
||||
tables += Table(tableName.toUpperCase, columns)
|
||||
}
|
||||
}
|
||||
html.dbviewer(tables)
|
||||
})
|
||||
|
||||
post("/admin/dbviewer/_query")(adminOnly {
|
||||
contentType = formats("json")
|
||||
params.get("query").collectFirst {
|
||||
case query if query.trim.nonEmpty =>
|
||||
val trimmedQuery = query.trim
|
||||
if (trimmedQuery.nonEmpty) {
|
||||
try {
|
||||
val conn = request2Session(request).conn
|
||||
using(conn.prepareStatement(query)) {
|
||||
stmt =>
|
||||
if (trimmedQuery.toUpperCase.startsWith("SELECT")) {
|
||||
using(stmt.executeQuery()) {
|
||||
rs =>
|
||||
val meta = rs.getMetaData
|
||||
val columns = for (i <- 1 to meta.getColumnCount) yield {
|
||||
meta.getColumnName(i)
|
||||
}
|
||||
val result = ListBuffer[Map[String, String]]()
|
||||
while (rs.next()) {
|
||||
val row = columns.map { columnName =>
|
||||
columnName -> Option(rs.getObject(columnName)).map(_.toString).getOrElse("<NULL>")
|
||||
}.toMap
|
||||
result += row
|
||||
}
|
||||
Ok(Serialization.write(Map("type" -> "query", "columns" -> columns, "rows" -> result)))
|
||||
}
|
||||
} else {
|
||||
val rows = stmt.executeUpdate()
|
||||
Ok(Serialization.write(Map("type" -> "update", "rows" -> rows)))
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
case e: Exception =>
|
||||
Ok(Serialization.write(Map("type" -> "error", "message" -> e.toString)))
|
||||
}
|
||||
}
|
||||
} getOrElse Ok(Serialization.write(Map("type" -> "error", "message" -> "query is empty")))
|
||||
})
|
||||
|
||||
get("/admin/system")(adminOnly {
|
||||
html.system(flash.get("info"))
|
||||
html.settings(flash.get("info"))
|
||||
})
|
||||
|
||||
post("/admin/system", form)(adminOnly { form =>
|
||||
@@ -161,11 +289,10 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
||||
|
||||
if (form.sshAddress != context.settings.sshAddress) {
|
||||
SshServer.stop()
|
||||
for {
|
||||
sshAddress <- form.sshAddress
|
||||
baseUrl <- form.baseUrl
|
||||
}
|
||||
SshServer.start(sshAddress, baseUrl)
|
||||
for {
|
||||
sshAddress <- form.sshAddress
|
||||
baseUrl <- form.baseUrl
|
||||
} SshServer.start(sshAddress, baseUrl)
|
||||
}
|
||||
|
||||
flash += "info" -> "System settings has been updated."
|
||||
@@ -175,10 +302,10 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
||||
post("/admin/system/sendmail", sendMailForm)(adminOnly { form =>
|
||||
try {
|
||||
new Mailer(context.settings.copy(smtp = Some(form.smtp), notification = true)).send(
|
||||
to = form.testAddress,
|
||||
subject = "Test message from GitBucket",
|
||||
textMsg = "This is a test message from GitBucket.",
|
||||
htmlMsg = None,
|
||||
to = form.testAddress,
|
||||
subject = "Test message from GitBucket",
|
||||
textMsg = "This is a test message from GitBucket.",
|
||||
htmlMsg = None,
|
||||
loginAccount = context.loginAccount
|
||||
)
|
||||
|
||||
@@ -196,20 +323,27 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
||||
val gitbucketVersion = Semver.valueOf(GitBucketCoreModule.getVersions.asScala.last.getVersion)
|
||||
|
||||
// Plugins in the local repository
|
||||
val repositoryPlugins = PluginRepository.getPlugins()
|
||||
val repositoryPlugins = PluginRepository
|
||||
.getPlugins()
|
||||
.filterNot { meta =>
|
||||
enabledPlugins.exists { plugin => plugin.pluginId == meta.id &&
|
||||
enabledPlugins.exists { plugin =>
|
||||
plugin.pluginId == meta.id &&
|
||||
Semver.valueOf(plugin.pluginVersion).greaterThanOrEqualTo(Semver.valueOf(meta.latestVersion.version))
|
||||
}
|
||||
}.map { meta =>
|
||||
(meta, meta.versions.reverse.find { version => gitbucketVersion.satisfies(version.range) })
|
||||
}.collect { case (meta, Some(version)) =>
|
||||
new PluginInfoBase(
|
||||
pluginId = meta.id,
|
||||
pluginName = meta.name,
|
||||
pluginVersion = version.version,
|
||||
description = meta.description
|
||||
)
|
||||
}
|
||||
.map { meta =>
|
||||
(meta, meta.versions.reverse.find { version =>
|
||||
gitbucketVersion.satisfies(version.range)
|
||||
})
|
||||
}
|
||||
.collect {
|
||||
case (meta, Some(version)) =>
|
||||
new PluginInfoBase(
|
||||
pluginId = meta.id,
|
||||
pluginName = meta.name,
|
||||
pluginVersion = version.version,
|
||||
description = meta.description
|
||||
)
|
||||
}
|
||||
|
||||
// Merge
|
||||
@@ -226,11 +360,13 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
||||
|
||||
post("/admin/plugins/:pluginId/:version/_uninstall")(adminOnly {
|
||||
val pluginId = params("pluginId")
|
||||
val version = params("version")
|
||||
PluginRegistry().getPlugins()
|
||||
val version = params("version")
|
||||
PluginRegistry()
|
||||
.getPlugins()
|
||||
.collect { case plugin if (plugin.pluginId == pluginId && plugin.pluginVersion == version) => plugin }
|
||||
.foreach { _ =>
|
||||
PluginRegistry.uninstall(pluginId, request.getServletContext, loadSystemSettings(), request2Session(request).conn)
|
||||
PluginRegistry
|
||||
.uninstall(pluginId, request.getServletContext, loadSystemSettings(), request2Session(request).conn)
|
||||
flash += "info" -> s"${pluginId} was uninstalled."
|
||||
}
|
||||
redirect("/admin/plugins")
|
||||
@@ -238,86 +374,104 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
||||
|
||||
post("/admin/plugins/:pluginId/:version/_install")(adminOnly {
|
||||
val pluginId = params("pluginId")
|
||||
val version = params("version")
|
||||
val version = params("version")
|
||||
/// TODO!!!!
|
||||
PluginRepository.getPlugins()
|
||||
.collect { case meta if meta.id == pluginId => (meta, meta.versions.find(_.version == version) )}
|
||||
.foreach { case (meta, version) =>
|
||||
version.foreach { version =>
|
||||
// TODO Install version!
|
||||
PluginRegistry.install(
|
||||
new java.io.File(PluginHome, s".repository/${version.file}"),
|
||||
request.getServletContext,
|
||||
loadSystemSettings(),
|
||||
request2Session(request).conn
|
||||
)
|
||||
flash += "info" -> s"${pluginId} was installed."
|
||||
}
|
||||
PluginRepository
|
||||
.getPlugins()
|
||||
.collect { case meta if meta.id == pluginId => (meta, meta.versions.find(_.version == version)) }
|
||||
.foreach {
|
||||
case (meta, version) =>
|
||||
version.foreach { version =>
|
||||
// TODO Install version!
|
||||
PluginRegistry.install(
|
||||
new java.io.File(PluginHome, s".repository/${version.file}"),
|
||||
request.getServletContext,
|
||||
loadSystemSettings(),
|
||||
request2Session(request).conn
|
||||
)
|
||||
flash += "info" -> s"${pluginId} was installed."
|
||||
}
|
||||
}
|
||||
redirect("/admin/plugins")
|
||||
})
|
||||
|
||||
|
||||
get("/admin/users")(adminOnly {
|
||||
val includeRemoved = params.get("includeRemoved").map(_.toBoolean).getOrElse(false)
|
||||
val users = getAllUsers(includeRemoved)
|
||||
val members = users.collect { case account if(account.isGroupAccount) =>
|
||||
account.userName -> getGroupMembers(account.userName).map(_.userName)
|
||||
val includeGroups = params.get("includeGroups").map(_.toBoolean).getOrElse(false)
|
||||
val users = getAllUsers(includeRemoved, includeGroups)
|
||||
val members = users.collect {
|
||||
case account if (account.isGroupAccount) =>
|
||||
account.userName -> getGroupMembers(account.userName).map(_.userName)
|
||||
}.toMap
|
||||
|
||||
html.userlist(users, members, includeRemoved)
|
||||
html.userlist(users, members, includeRemoved, includeGroups)
|
||||
})
|
||||
|
||||
get("/admin/users/_newuser")(adminOnly {
|
||||
html.user(None)
|
||||
html.user(None, Nil)
|
||||
})
|
||||
|
||||
post("/admin/users/_newuser", newUserForm)(adminOnly { form =>
|
||||
createAccount(form.userName, sha1(form.password), form.fullName, form.mailAddress, form.isAdmin, form.description, form.url)
|
||||
createAccount(
|
||||
form.userName,
|
||||
sha1(form.password),
|
||||
form.fullName,
|
||||
form.mailAddress,
|
||||
form.isAdmin,
|
||||
form.description,
|
||||
form.url
|
||||
)
|
||||
updateImage(form.userName, form.fileId, false)
|
||||
updateAccountExtraMailAddresses(form.userName, form.extraMailAddresses.filter(_ != ""))
|
||||
redirect("/admin/users")
|
||||
})
|
||||
|
||||
get("/admin/users/:userName/_edituser")(adminOnly {
|
||||
val userName = params("userName")
|
||||
html.user(getAccountByUserName(userName, true), flash.get("error"))
|
||||
val extraMails = getAccountExtraMailAddresses(userName)
|
||||
html.user(getAccountByUserName(userName, true), extraMails, flash.get("error"))
|
||||
})
|
||||
|
||||
post("/admin/users/:name/_edituser", editUserForm)(adminOnly { form =>
|
||||
val userName = params("userName")
|
||||
getAccountByUserName(userName, true).map { account =>
|
||||
if(account.isAdmin && (form.isRemoved || !form.isAdmin) && isLastAdministrator(account)){
|
||||
flash += "error" -> "Account can't be turned off because this is last one administrator."
|
||||
redirect(s"/admin/users/${userName}/_edituser")
|
||||
} else {
|
||||
if(form.isRemoved){
|
||||
// Remove repositories
|
||||
// getRepositoryNamesOfUser(userName).foreach { repositoryName =>
|
||||
// deleteRepository(userName, repositoryName)
|
||||
// FileUtils.deleteDirectory(getRepositoryDir(userName, repositoryName))
|
||||
// FileUtils.deleteDirectory(getWikiRepositoryDir(userName, repositoryName))
|
||||
// FileUtils.deleteDirectory(getTemporaryDir(userName, repositoryName))
|
||||
// }
|
||||
// Remove from GROUP_MEMBER and COLLABORATOR
|
||||
removeUserRelatedData(userName)
|
||||
getAccountByUserName(userName, true).map {
|
||||
account =>
|
||||
if (account.isAdmin && (form.isRemoved || !form.isAdmin) && isLastAdministrator(account)) {
|
||||
flash += "error" -> "Account can't be turned off because this is last one administrator."
|
||||
redirect(s"/admin/users/${userName}/_edituser")
|
||||
} else {
|
||||
if (form.isRemoved) {
|
||||
// Remove repositories
|
||||
// getRepositoryNamesOfUser(userName).foreach { repositoryName =>
|
||||
// deleteRepository(userName, repositoryName)
|
||||
// FileUtils.deleteDirectory(getRepositoryDir(userName, repositoryName))
|
||||
// FileUtils.deleteDirectory(getWikiRepositoryDir(userName, repositoryName))
|
||||
// FileUtils.deleteDirectory(getTemporaryDir(userName, repositoryName))
|
||||
// }
|
||||
// Remove from GROUP_MEMBER and COLLABORATOR
|
||||
removeUserRelatedData(userName)
|
||||
}
|
||||
|
||||
updateAccount(
|
||||
account.copy(
|
||||
password = form.password.map(sha1).getOrElse(account.password),
|
||||
fullName = form.fullName,
|
||||
mailAddress = form.mailAddress,
|
||||
isAdmin = form.isAdmin,
|
||||
description = form.description,
|
||||
url = form.url,
|
||||
isRemoved = form.isRemoved
|
||||
)
|
||||
)
|
||||
|
||||
updateImage(userName, form.fileId, form.clearImage)
|
||||
updateAccountExtraMailAddresses(userName, form.extraMailAddresses.filter(_ != ""))
|
||||
|
||||
// call hooks
|
||||
if (form.isRemoved) PluginRegistry().getAccountHooks.foreach(_.deleted(userName))
|
||||
|
||||
redirect("/admin/users")
|
||||
}
|
||||
|
||||
updateAccount(account.copy(
|
||||
password = form.password.map(sha1).getOrElse(account.password),
|
||||
fullName = form.fullName,
|
||||
mailAddress = form.mailAddress,
|
||||
isAdmin = form.isAdmin,
|
||||
description = form.description,
|
||||
url = form.url,
|
||||
isRemoved = form.isRemoved))
|
||||
|
||||
updateImage(userName, form.fileId, form.clearImage)
|
||||
|
||||
// call hooks
|
||||
if(form.isRemoved) PluginRegistry().getAccountHooks.foreach(_.deleted(userName))
|
||||
|
||||
redirect("/admin/users")
|
||||
}
|
||||
} getOrElse NotFound()
|
||||
})
|
||||
|
||||
@@ -327,33 +481,47 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
||||
|
||||
post("/admin/users/_newgroup", newGroupForm)(adminOnly { form =>
|
||||
createGroup(form.groupName, form.description, form.url)
|
||||
updateGroupMembers(form.groupName, form.members.split(",").map {
|
||||
_.split(":") match {
|
||||
case Array(userName, isManager) => (userName, isManager.toBoolean)
|
||||
}
|
||||
}.toList)
|
||||
updateGroupMembers(
|
||||
form.groupName,
|
||||
form.members
|
||||
.split(",")
|
||||
.map {
|
||||
_.split(":") match {
|
||||
case Array(userName, isManager) => (userName, isManager.toBoolean)
|
||||
}
|
||||
}
|
||||
.toList
|
||||
)
|
||||
updateImage(form.groupName, form.fileId, false)
|
||||
redirect("/admin/users")
|
||||
})
|
||||
|
||||
get("/admin/users/:groupName/_editgroup")(adminOnly {
|
||||
defining(params("groupName")){ groupName =>
|
||||
defining(params("groupName")) { groupName =>
|
||||
html.usergroup(getAccountByUserName(groupName, true), getGroupMembers(groupName))
|
||||
}
|
||||
})
|
||||
|
||||
post("/admin/users/:groupName/_editgroup", editGroupForm)(adminOnly { form =>
|
||||
defining(params("groupName"), form.members.split(",").map {
|
||||
_.split(":") match {
|
||||
case Array(userName, isManager) => (userName, isManager.toBoolean)
|
||||
}
|
||||
}.toList){ case (groupName, members) =>
|
||||
getAccountByUserName(groupName, true).map { account =>
|
||||
updateGroup(groupName, form.description, form.url, form.isRemoved)
|
||||
defining(
|
||||
params("groupName"),
|
||||
form.members
|
||||
.split(",")
|
||||
.map {
|
||||
_.split(":") match {
|
||||
case Array(userName, isManager) => (userName, isManager.toBoolean)
|
||||
}
|
||||
}
|
||||
.toList
|
||||
) {
|
||||
case (groupName, members) =>
|
||||
getAccountByUserName(groupName, true).map {
|
||||
account =>
|
||||
updateGroup(groupName, form.description, form.url, form.isRemoved)
|
||||
|
||||
if(form.isRemoved){
|
||||
// Remove from GROUP_MEMBER
|
||||
updateGroupMembers(form.groupName, Nil)
|
||||
if (form.isRemoved) {
|
||||
// Remove from GROUP_MEMBER
|
||||
updateGroupMembers(form.groupName, Nil)
|
||||
// // Remove repositories
|
||||
// getRepositoryNamesOfUser(form.groupName).foreach { repositoryName =>
|
||||
// deleteRepository(groupName, repositoryName)
|
||||
@@ -361,9 +529,9 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
||||
// FileUtils.deleteDirectory(getWikiRepositoryDir(groupName, repositoryName))
|
||||
// FileUtils.deleteDirectory(getTemporaryDir(groupName, repositoryName))
|
||||
// }
|
||||
} else {
|
||||
// Update GROUP_MEMBER
|
||||
updateGroupMembers(form.groupName, members)
|
||||
} else {
|
||||
// Update GROUP_MEMBER
|
||||
updateGroupMembers(form.groupName, members)
|
||||
// // Update COLLABORATOR for group repositories
|
||||
// getRepositoryNamesOfUser(form.groupName).foreach { repositoryName =>
|
||||
// removeCollaborators(form.groupName, repositoryName)
|
||||
@@ -371,12 +539,12 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
||||
// addCollaborator(form.groupName, repositoryName, userName)
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
updateImage(form.groupName, form.fileId, form.clearImage)
|
||||
redirect("/admin/users")
|
||||
updateImage(form.groupName, form.fileId, form.clearImage)
|
||||
redirect("/admin/users")
|
||||
|
||||
} getOrElse NotFound()
|
||||
} getOrElse NotFound()
|
||||
}
|
||||
})
|
||||
|
||||
@@ -394,25 +562,26 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
||||
response.setHeader("Content-Disposition", "attachment; filename=" + file.getName)
|
||||
response.setContentLength(file.length.toInt)
|
||||
|
||||
using(new FileInputStream(file)){ in =>
|
||||
using(new FileInputStream(file)) { in =>
|
||||
IOUtils.copy(in, response.outputStream)
|
||||
}
|
||||
|
||||
()
|
||||
})
|
||||
|
||||
private def members: Constraint = new Constraint(){
|
||||
private def members: Constraint = new Constraint() {
|
||||
override def validate(name: String, value: String, messages: Messages): Option[String] = {
|
||||
if(value.split(",").exists {
|
||||
_.split(":") match { case Array(userName, isManager) => isManager.toBoolean }
|
||||
}) None else Some("Must select one manager at least.")
|
||||
if (value.split(",").exists {
|
||||
_.split(":") match { case Array(userName, isManager) => isManager.toBoolean }
|
||||
}) None
|
||||
else Some("Must select one manager at least.")
|
||||
}
|
||||
}
|
||||
|
||||
protected def disableByNotYourself(paramName: String): Constraint = new Constraint() {
|
||||
override def validate(name: String, value: String, messages: Messages): Option[String] = {
|
||||
params.get(paramName).flatMap { userName =>
|
||||
if(userName == context.loginAccount.get.userName && params.get("removed") == Some("true"))
|
||||
if (userName == context.loginAccount.get.userName && params.get("removed") == Some("true"))
|
||||
Some("You can't disable your account yourself")
|
||||
else
|
||||
None
|
||||
|
||||
@@ -14,58 +14,58 @@ trait ValidationSupport extends FormSupport { self: ServletBase with JacksonJson
|
||||
|
||||
def get[T](path: String, form: ValueType[T])(action: T => Any): Route = {
|
||||
registerValidate(path, form)
|
||||
get(path){
|
||||
get(path) {
|
||||
validate(form)(errors => BadRequest(), form => action(form))
|
||||
}
|
||||
}
|
||||
|
||||
def post[T](path: String, form: ValueType[T])(action: T => Any): Route = {
|
||||
registerValidate(path, form)
|
||||
post(path){
|
||||
post(path) {
|
||||
validate(form)(errors => BadRequest(), form => action(form))
|
||||
}
|
||||
}
|
||||
|
||||
def put[T](path: String, form: ValueType[T])(action: T => Any): Route = {
|
||||
registerValidate(path, form)
|
||||
put(path){
|
||||
put(path) {
|
||||
validate(form)(errors => BadRequest(), form => action(form))
|
||||
}
|
||||
}
|
||||
|
||||
def delete[T](path: String, form: ValueType[T])(action: T => Any): Route = {
|
||||
registerValidate(path, form)
|
||||
delete(path){
|
||||
delete(path) {
|
||||
validate(form)(errors => BadRequest(), form => action(form))
|
||||
}
|
||||
}
|
||||
|
||||
def ajaxGet[T](path: String, form: ValueType[T])(action: T => Any): Route = {
|
||||
get(path){
|
||||
get(path) {
|
||||
validate(form)(errors => ajaxError(errors), form => action(form))
|
||||
}
|
||||
}
|
||||
|
||||
def ajaxPost[T](path: String, form: ValueType[T])(action: T => Any): Route = {
|
||||
post(path){
|
||||
post(path) {
|
||||
validate(form)(errors => ajaxError(errors), form => action(form))
|
||||
}
|
||||
}
|
||||
|
||||
def ajaxDelete[T](path: String, form: ValueType[T])(action: T => Any): Route = {
|
||||
delete(path){
|
||||
delete(path) {
|
||||
validate(form)(errors => ajaxError(errors), form => action(form))
|
||||
}
|
||||
}
|
||||
|
||||
def ajaxPut[T](path: String, form: ValueType[T])(action: T => Any): Route = {
|
||||
put(path){
|
||||
put(path) {
|
||||
validate(form)(errors => ajaxError(errors), form => action(form))
|
||||
}
|
||||
}
|
||||
|
||||
private def registerValidate[T](path: String, form: ValueType[T]) = {
|
||||
post(path.replaceFirst("/$", "") + "/validate"){
|
||||
post(path.replaceFirst("/$", "") + "/validate") {
|
||||
contentType = "application/json"
|
||||
toJson(form.validate("", multiParams, messages))
|
||||
}
|
||||
@@ -84,8 +84,9 @@ trait ValidationSupport extends FormSupport { self: ServletBase with JacksonJson
|
||||
* Converts errors to JSON.
|
||||
*/
|
||||
private def toJson(errors: Seq[(String, String)]): JObject =
|
||||
JObject(errors.map { case (key, value) =>
|
||||
JField(key, JString(value))
|
||||
JObject(errors.map {
|
||||
case (key, value) =>
|
||||
JField(key, JString(value))
|
||||
}.toList)
|
||||
|
||||
}
|
||||
|
||||
@@ -14,38 +14,60 @@ import org.scalatra.forms._
|
||||
import org.eclipse.jgit.api.Git
|
||||
import org.scalatra.i18n.Messages
|
||||
|
||||
class WikiController extends WikiControllerBase
|
||||
with WikiService with RepositoryService with AccountService with ActivityService with WebHookService
|
||||
with ReadableUsersAuthenticator with ReferrerAuthenticator
|
||||
class WikiController
|
||||
extends WikiControllerBase
|
||||
with WikiService
|
||||
with RepositoryService
|
||||
with AccountService
|
||||
with ActivityService
|
||||
with WebHookService
|
||||
with ReadableUsersAuthenticator
|
||||
with ReferrerAuthenticator
|
||||
|
||||
trait WikiControllerBase extends ControllerBase {
|
||||
self: WikiService with RepositoryService with AccountService with ActivityService with WebHookService
|
||||
with ReadableUsersAuthenticator with ReferrerAuthenticator =>
|
||||
self: WikiService
|
||||
with RepositoryService
|
||||
with AccountService
|
||||
with ActivityService
|
||||
with WebHookService
|
||||
with ReadableUsersAuthenticator
|
||||
with ReferrerAuthenticator =>
|
||||
|
||||
case class WikiPageEditForm(pageName: String, content: String, message: Option[String], currentPageName: String, id: String)
|
||||
case class WikiPageEditForm(
|
||||
pageName: String,
|
||||
content: String,
|
||||
message: Option[String],
|
||||
currentPageName: String,
|
||||
id: String
|
||||
)
|
||||
|
||||
val newForm = mapping(
|
||||
"pageName" -> trim(label("Page name" , text(required, maxlength(40), pagename, unique))),
|
||||
"content" -> trim(label("Content" , text(required, conflictForNew))),
|
||||
"message" -> trim(label("Message" , optional(text()))),
|
||||
"currentPageName" -> trim(label("Current page name" , text())),
|
||||
"id" -> trim(label("Latest commit id" , text()))
|
||||
"pageName" -> trim(label("Page name", text(required, maxlength(40), pagename, unique))),
|
||||
"content" -> trim(label("Content", text(required, conflictForNew))),
|
||||
"message" -> trim(label("Message", optional(text()))),
|
||||
"currentPageName" -> trim(label("Current page name", text())),
|
||||
"id" -> trim(label("Latest commit id", text()))
|
||||
)(WikiPageEditForm.apply)
|
||||
|
||||
val editForm = mapping(
|
||||
"pageName" -> trim(label("Page name" , text(required, maxlength(40), pagename))),
|
||||
"content" -> trim(label("Content" , text(required, conflictForEdit))),
|
||||
"message" -> trim(label("Message" , optional(text()))),
|
||||
"currentPageName" -> trim(label("Current page name" , text(required))),
|
||||
"id" -> trim(label("Latest commit id" , text(required)))
|
||||
"pageName" -> trim(label("Page name", text(required, maxlength(40), pagename))),
|
||||
"content" -> trim(label("Content", text(required, conflictForEdit))),
|
||||
"message" -> trim(label("Message", optional(text()))),
|
||||
"currentPageName" -> trim(label("Current page name", text(required))),
|
||||
"id" -> trim(label("Latest commit id", text(required)))
|
||||
)(WikiPageEditForm.apply)
|
||||
|
||||
get("/:owner/:repository/wiki")(referrersOnly { repository =>
|
||||
getWikiPage(repository.owner, repository.name, "Home").map { page =>
|
||||
html.page("Home", page, getWikiPageList(repository.owner, repository.name),
|
||||
repository, isEditable(repository),
|
||||
html.page(
|
||||
"Home",
|
||||
page,
|
||||
getWikiPageList(repository.owner, repository.name),
|
||||
repository,
|
||||
isEditable(repository),
|
||||
getWikiPage(repository.owner, repository.name, "_Sidebar"),
|
||||
getWikiPage(repository.owner, repository.name, "_Footer"))
|
||||
getWikiPage(repository.owner, repository.name, "_Footer")
|
||||
)
|
||||
} getOrElse redirect(s"/${repository.owner}/${repository.name}/wiki/Home/_edit")
|
||||
})
|
||||
|
||||
@@ -53,20 +75,25 @@ trait WikiControllerBase extends ControllerBase {
|
||||
val pageName = StringUtil.urlDecode(params("page"))
|
||||
|
||||
getWikiPage(repository.owner, repository.name, pageName).map { page =>
|
||||
html.page(pageName, page, getWikiPageList(repository.owner, repository.name),
|
||||
repository, isEditable(repository),
|
||||
html.page(
|
||||
pageName,
|
||||
page,
|
||||
getWikiPageList(repository.owner, repository.name),
|
||||
repository,
|
||||
isEditable(repository),
|
||||
getWikiPage(repository.owner, repository.name, "_Sidebar"),
|
||||
getWikiPage(repository.owner, repository.name, "_Footer"))
|
||||
getWikiPage(repository.owner, repository.name, "_Footer")
|
||||
)
|
||||
} getOrElse redirect(s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(pageName)}/_edit")
|
||||
})
|
||||
|
||||
get("/:owner/:repository/wiki/:page/_history")(referrersOnly { repository =>
|
||||
val pageName = StringUtil.urlDecode(params("page"))
|
||||
|
||||
using(Git.open(getWikiRepositoryDir(repository.owner, repository.name))){ git =>
|
||||
using(Git.open(getWikiRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
JGitUtil.getCommitLog(git, "master", path = pageName + ".md") match {
|
||||
case Right((logs, hasNext)) => html.history(Some(pageName), logs, repository, isEditable(repository))
|
||||
case Left(_) => NotFound()
|
||||
case Left(_) => NotFound()
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -75,41 +102,57 @@ trait WikiControllerBase extends ControllerBase {
|
||||
val pageName = StringUtil.urlDecode(params("page"))
|
||||
val Array(from, to) = params("commitId").split("\\.\\.\\.")
|
||||
|
||||
using(Git.open(getWikiRepositoryDir(repository.owner, repository.name))){ git =>
|
||||
html.compare(Some(pageName), from, to, JGitUtil.getDiffs(git, from, to, true, false).filter(_.newPath == pageName + ".md"), repository,
|
||||
isEditable(repository), flash.get("info"))
|
||||
using(Git.open(getWikiRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
html.compare(
|
||||
Some(pageName),
|
||||
from,
|
||||
to,
|
||||
JGitUtil.getDiffs(git, Some(from), to, true, false).filter(_.newPath == pageName + ".md"),
|
||||
repository,
|
||||
isEditable(repository),
|
||||
flash.get("info")
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
get("/:owner/:repository/wiki/_compare/:commitId")(referrersOnly { repository =>
|
||||
val Array(from, to) = params("commitId").split("\\.\\.\\.")
|
||||
|
||||
using(Git.open(getWikiRepositoryDir(repository.owner, repository.name))){ git =>
|
||||
html.compare(None, from, to, JGitUtil.getDiffs(git, from, to, true, false), repository,
|
||||
isEditable(repository), flash.get("info"))
|
||||
using(Git.open(getWikiRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
html.compare(
|
||||
None,
|
||||
from,
|
||||
to,
|
||||
JGitUtil.getDiffs(git, Some(from), to, true, false),
|
||||
repository,
|
||||
isEditable(repository),
|
||||
flash.get("info")
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
get("/:owner/:repository/wiki/:page/_revert/:commitId")(readableUsersOnly { repository =>
|
||||
if(isEditable(repository)){
|
||||
if (isEditable(repository)) {
|
||||
val pageName = StringUtil.urlDecode(params("page"))
|
||||
val Array(from, to) = params("commitId").split("\\.\\.\\.")
|
||||
|
||||
if(revertWikiPage(repository.owner, repository.name, from, to, context.loginAccount.get, Some(pageName))){
|
||||
if (revertWikiPage(repository.owner, repository.name, from, to, context.loginAccount.get, Some(pageName))) {
|
||||
redirect(s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(pageName)}")
|
||||
} else {
|
||||
flash += "info" -> "This patch was not able to be reversed."
|
||||
redirect(s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(pageName)}/_compare/${from}...${to}")
|
||||
redirect(
|
||||
s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(pageName)}/_compare/${from}...${to}"
|
||||
)
|
||||
}
|
||||
} else Unauthorized()
|
||||
})
|
||||
|
||||
get("/:owner/:repository/wiki/_revert/:commitId")(readableUsersOnly { repository =>
|
||||
if(isEditable(repository)){
|
||||
if (isEditable(repository)) {
|
||||
val Array(from, to) = params("commitId").split("\\.\\.\\.")
|
||||
|
||||
if(revertWikiPage(repository.owner, repository.name, from, to, context.loginAccount.get, None)){
|
||||
redirect(s"/${repository.owner}/${repository.name}/wiki/")
|
||||
if (revertWikiPage(repository.owner, repository.name, from, to, context.loginAccount.get, None)) {
|
||||
redirect(s"/${repository.owner}/${repository.name}/wiki")
|
||||
} else {
|
||||
flash += "info" -> "This patch was not able to be reversed."
|
||||
redirect(s"/${repository.owner}/${repository.name}/wiki/_compare/${from}...${to}")
|
||||
@@ -118,85 +161,102 @@ trait WikiControllerBase extends ControllerBase {
|
||||
})
|
||||
|
||||
get("/:owner/:repository/wiki/:page/_edit")(readableUsersOnly { repository =>
|
||||
if(isEditable(repository)){
|
||||
if (isEditable(repository)) {
|
||||
val pageName = StringUtil.urlDecode(params("page"))
|
||||
html.edit(pageName, getWikiPage(repository.owner, repository.name, pageName), repository)
|
||||
} else Unauthorized()
|
||||
})
|
||||
|
||||
post("/:owner/:repository/wiki/_edit", editForm)(readableUsersOnly { (form, repository) =>
|
||||
if(isEditable(repository)){
|
||||
defining(context.loginAccount.get){ loginAccount =>
|
||||
saveWikiPage(
|
||||
repository.owner,
|
||||
repository.name,
|
||||
form.currentPageName,
|
||||
form.pageName,
|
||||
appendNewLine(convertLineSeparator(form.content, "LF"), "LF"),
|
||||
loginAccount,
|
||||
form.message.getOrElse(""),
|
||||
Some(form.id)
|
||||
).map { commitId =>
|
||||
updateLastActivityDate(repository.owner, repository.name)
|
||||
recordEditWikiPageActivity(repository.owner, repository.name, loginAccount.userName, form.pageName, commitId)
|
||||
callWebHookOf(repository.owner, repository.name, WebHook.Gollum){
|
||||
getAccountByUserName(repository.owner).map { repositoryUser =>
|
||||
WebHookGollumPayload("edited", form.pageName, commitId, repository, repositoryUser, loginAccount)
|
||||
}
|
||||
if (isEditable(repository)) {
|
||||
defining(context.loginAccount.get) {
|
||||
loginAccount =>
|
||||
saveWikiPage(
|
||||
repository.owner,
|
||||
repository.name,
|
||||
form.currentPageName,
|
||||
form.pageName,
|
||||
appendNewLine(convertLineSeparator(form.content, "LF"), "LF"),
|
||||
loginAccount,
|
||||
form.message.getOrElse(""),
|
||||
Some(form.id)
|
||||
).map {
|
||||
commitId =>
|
||||
updateLastActivityDate(repository.owner, repository.name)
|
||||
recordEditWikiPageActivity(
|
||||
repository.owner,
|
||||
repository.name,
|
||||
loginAccount.userName,
|
||||
form.pageName,
|
||||
commitId
|
||||
)
|
||||
callWebHookOf(repository.owner, repository.name, WebHook.Gollum) {
|
||||
getAccountByUserName(repository.owner).map { repositoryUser =>
|
||||
WebHookGollumPayload("edited", form.pageName, commitId, repository, repositoryUser, loginAccount)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (notReservedPageName(form.pageName)) {
|
||||
redirect(s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(form.pageName)}")
|
||||
} else {
|
||||
redirect(s"/${repository.owner}/${repository.name}/wiki")
|
||||
}
|
||||
}
|
||||
if(notReservedPageName(form.pageName)) {
|
||||
redirect(s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(form.pageName)}")
|
||||
} else {
|
||||
redirect(s"/${repository.owner}/${repository.name}/wiki")
|
||||
}
|
||||
}
|
||||
} else Unauthorized()
|
||||
})
|
||||
|
||||
get("/:owner/:repository/wiki/_new")(readableUsersOnly { repository =>
|
||||
if(isEditable(repository)){
|
||||
if (isEditable(repository)) {
|
||||
html.edit("", None, repository)
|
||||
} else Unauthorized()
|
||||
})
|
||||
|
||||
post("/:owner/:repository/wiki/_new", newForm)(readableUsersOnly { (form, repository) =>
|
||||
if(isEditable(repository)){
|
||||
defining(context.loginAccount.get){ loginAccount =>
|
||||
saveWikiPage(
|
||||
repository.owner,
|
||||
repository.name,
|
||||
form.currentPageName,
|
||||
form.pageName,
|
||||
form.content,
|
||||
loginAccount,
|
||||
form.message.getOrElse(""),
|
||||
None
|
||||
).map { commitId =>
|
||||
updateLastActivityDate(repository.owner, repository.name)
|
||||
recordCreateWikiPageActivity(repository.owner, repository.name, loginAccount.userName, form.pageName)
|
||||
callWebHookOf(repository.owner, repository.name, WebHook.Gollum){
|
||||
getAccountByUserName(repository.owner).map { repositoryUser =>
|
||||
WebHookGollumPayload("created", form.pageName, commitId, repository, repositoryUser, loginAccount)
|
||||
}
|
||||
if (isEditable(repository)) {
|
||||
defining(context.loginAccount.get) {
|
||||
loginAccount =>
|
||||
saveWikiPage(
|
||||
repository.owner,
|
||||
repository.name,
|
||||
form.currentPageName,
|
||||
form.pageName,
|
||||
form.content,
|
||||
loginAccount,
|
||||
form.message.getOrElse(""),
|
||||
None
|
||||
).map {
|
||||
commitId =>
|
||||
updateLastActivityDate(repository.owner, repository.name)
|
||||
recordCreateWikiPageActivity(repository.owner, repository.name, loginAccount.userName, form.pageName)
|
||||
callWebHookOf(repository.owner, repository.name, WebHook.Gollum) {
|
||||
getAccountByUserName(repository.owner).map { repositoryUser =>
|
||||
WebHookGollumPayload("created", form.pageName, commitId, repository, repositoryUser, loginAccount)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(notReservedPageName(form.pageName)) {
|
||||
redirect(s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(form.pageName)}")
|
||||
} else {
|
||||
redirect(s"/${repository.owner}/${repository.name}/wiki")
|
||||
}
|
||||
if (notReservedPageName(form.pageName)) {
|
||||
redirect(s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(form.pageName)}")
|
||||
} else {
|
||||
redirect(s"/${repository.owner}/${repository.name}/wiki")
|
||||
}
|
||||
}
|
||||
} else Unauthorized()
|
||||
})
|
||||
|
||||
get("/:owner/:repository/wiki/:page/_delete")(readableUsersOnly { repository =>
|
||||
if(isEditable(repository)){
|
||||
if (isEditable(repository)) {
|
||||
val pageName = StringUtil.urlDecode(params("page"))
|
||||
|
||||
defining(context.loginAccount.get){ loginAccount =>
|
||||
deleteWikiPage(repository.owner, repository.name, pageName, loginAccount.fullName, loginAccount.mailAddress, s"Destroyed ${pageName}")
|
||||
defining(context.loginAccount.get) { loginAccount =>
|
||||
deleteWikiPage(
|
||||
repository.owner,
|
||||
repository.name,
|
||||
pageName,
|
||||
loginAccount.fullName,
|
||||
loginAccount.mailAddress,
|
||||
s"Destroyed ${pageName}"
|
||||
)
|
||||
updateLastActivityDate(repository.owner, repository.name)
|
||||
|
||||
redirect(s"/${repository.owner}/${repository.name}/wiki")
|
||||
@@ -209,41 +269,51 @@ trait WikiControllerBase extends ControllerBase {
|
||||
})
|
||||
|
||||
get("/:owner/:repository/wiki/_history")(referrersOnly { repository =>
|
||||
using(Git.open(getWikiRepositoryDir(repository.owner, repository.name))){ git =>
|
||||
using(Git.open(getWikiRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
JGitUtil.getCommitLog(git, "master") match {
|
||||
case Right((logs, hasNext)) => html.history(None, logs, repository, isEditable(repository))
|
||||
case Left(_) => NotFound()
|
||||
case Left(_) => NotFound()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
get("/:owner/:repository/wiki/_blob/*")(referrersOnly { repository =>
|
||||
val path = multiParams("splat").head
|
||||
using(Git.open(getWikiRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve("master"))
|
||||
|
||||
getFileContent(repository.owner, repository.name, path).map { bytes =>
|
||||
RawData(FileUtil.getContentType(path, bytes), bytes)
|
||||
} getOrElse NotFound()
|
||||
getPathObjectId(git, path, revCommit).map { objectId =>
|
||||
responseRawFile(git, objectId, path, repository)
|
||||
} getOrElse NotFound()
|
||||
}
|
||||
})
|
||||
|
||||
private def unique: Constraint = new Constraint(){
|
||||
override def validate(name: String, value: String, params: Map[String, Seq[String]], messages: Messages): Option[String] =
|
||||
getWikiPageList(params.value("owner"), params.value("repository")).find(_ == value).map(_ => "Page already exists.")
|
||||
private def unique: Constraint = new Constraint() {
|
||||
override def validate(
|
||||
name: String,
|
||||
value: String,
|
||||
params: Map[String, Seq[String]],
|
||||
messages: Messages
|
||||
): Option[String] =
|
||||
getWikiPageList(params.value("owner"), params.value("repository"))
|
||||
.find(_ == value)
|
||||
.map(_ => "Page already exists.")
|
||||
}
|
||||
|
||||
private def pagename: Constraint = new Constraint(){
|
||||
private def pagename: Constraint = new Constraint() {
|
||||
override def validate(name: String, value: String, messages: Messages): Option[String] =
|
||||
if(value.exists("\\/:*?\"<>|".contains(_))){
|
||||
if (value.exists("\\/:*?\"<>|".contains(_))) {
|
||||
Some(s"${name} contains invalid character.")
|
||||
} else if(notReservedPageName(value) && (value.startsWith("_") || value.startsWith("-"))){
|
||||
} else if (notReservedPageName(value) && (value.startsWith("_") || value.startsWith("-"))) {
|
||||
Some(s"${name} starts with invalid character.")
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
private def notReservedPageName(value: String) = ! (Array[String]("_Sidebar","_Footer") contains value)
|
||||
private def notReservedPageName(value: String) = !(Array[String]("_Sidebar", "_Footer") contains value)
|
||||
|
||||
private def conflictForNew: Constraint = new Constraint(){
|
||||
private def conflictForNew: Constraint = new Constraint() {
|
||||
override def validate(name: String, value: String, messages: Messages): Option[String] = {
|
||||
targetWikiPage.map { _ =>
|
||||
"Someone has created the wiki since you started. Please reload this page and re-apply your changes."
|
||||
@@ -251,9 +321,9 @@ trait WikiControllerBase extends ControllerBase {
|
||||
}
|
||||
}
|
||||
|
||||
private def conflictForEdit: Constraint = new Constraint(){
|
||||
private def conflictForEdit: Constraint = new Constraint() {
|
||||
override def validate(name: String, value: String, messages: Messages): Option[String] = {
|
||||
targetWikiPage.filter(_.id != params("id")).map{ _ =>
|
||||
targetWikiPage.filter(_.id != params("id")).map { _ =>
|
||||
"Someone has edited the wiki since you started. Please reload this page and re-apply your changes."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package gitbucket.core.model
|
||||
|
||||
|
||||
trait AccessTokenComponent { self: Profile =>
|
||||
import profile.api._
|
||||
|
||||
|
||||
@@ -20,7 +20,22 @@ trait AccountComponent { self: Profile =>
|
||||
val groupAccount = column[Boolean]("GROUP_ACCOUNT")
|
||||
val removed = column[Boolean]("REMOVED")
|
||||
val description = column[String]("DESCRIPTION")
|
||||
def * = (userName, fullName, mailAddress, password, isAdmin, url.?, registeredDate, updatedDate, lastLoginDate.?, image.?, groupAccount, removed, description.?) <> (Account.tupled, Account.unapply)
|
||||
def * =
|
||||
(
|
||||
userName,
|
||||
fullName,
|
||||
mailAddress,
|
||||
password,
|
||||
isAdmin,
|
||||
url.?,
|
||||
registeredDate,
|
||||
updatedDate,
|
||||
lastLoginDate.?,
|
||||
image.?,
|
||||
groupAccount,
|
||||
removed,
|
||||
description.?
|
||||
) <> (Account.tupled, Account.unapply)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package gitbucket.core.model
|
||||
|
||||
trait AccountExtraMailAddressComponent { self: Profile =>
|
||||
import profile.api._
|
||||
|
||||
lazy val AccountExtraMailAddresses = TableQuery[AccountExtraMailAddresses]
|
||||
|
||||
class AccountExtraMailAddresses(tag: Tag) extends Table[AccountExtraMailAddress](tag, "ACCOUNT_EXTRA_MAIL_ADDRESS") {
|
||||
val userName = column[String]("USER_NAME", O PrimaryKey)
|
||||
val extraMailAddress = column[String]("EXTRA_MAIL_ADDRESS", O PrimaryKey)
|
||||
def * =
|
||||
(userName, extraMailAddress) <> (AccountExtraMailAddress.tupled, AccountExtraMailAddress.unapply)
|
||||
}
|
||||
}
|
||||
|
||||
case class AccountExtraMailAddress(
|
||||
userName: String,
|
||||
extraMailAddress: String
|
||||
)
|
||||
19
src/main/scala/gitbucket/core/model/AccountFederation.scala
Normal file
19
src/main/scala/gitbucket/core/model/AccountFederation.scala
Normal file
@@ -0,0 +1,19 @@
|
||||
package gitbucket.core.model
|
||||
|
||||
trait AccountFederationComponent { self: Profile =>
|
||||
import profile.api._
|
||||
|
||||
lazy val AccountFederations = TableQuery[AccountFederations]
|
||||
|
||||
class AccountFederations(tag: Tag) extends Table[AccountFederation](tag, "ACCOUNT_FEDERATION") {
|
||||
val issuer = column[String]("ISSUER")
|
||||
val subject = column[String]("SUBJECT")
|
||||
val userName = column[String]("USER_NAME")
|
||||
def * = (issuer, subject, userName) <> (AccountFederation.tupled, AccountFederation.unapply)
|
||||
|
||||
def byPrimaryKey(issuer: String, subject: String): Rep[Boolean] =
|
||||
(this.issuer === issuer.bind) && (this.subject === subject.bind)
|
||||
}
|
||||
}
|
||||
|
||||
case class AccountFederation(issuer: String, subject: String, userName: String)
|
||||
@@ -3,7 +3,8 @@ package gitbucket.core.model
|
||||
trait AccountWebHookComponent extends TemplateComponent { self: Profile =>
|
||||
import profile.api._
|
||||
|
||||
private implicit val whContentTypeColumnType = MappedColumnType.base[WebHookContentType, String](whct => whct.code , code => WebHookContentType.valueOf(code))
|
||||
private implicit val whContentTypeColumnType =
|
||||
MappedColumnType.base[WebHookContentType, String](whct => whct.code, code => WebHookContentType.valueOf(code))
|
||||
|
||||
lazy val AccountWebHooks = TableQuery[AccountWebHooks]
|
||||
|
||||
|
||||
@@ -8,11 +8,13 @@ trait AccountWebHookEventComponent extends TemplateComponent {
|
||||
|
||||
lazy val AccountWebHookEvents = TableQuery[AccountWebHookEvents]
|
||||
|
||||
class AccountWebHookEvents(tag: Tag) extends Table[AccountWebHookEvent](tag, "ACCOUNT_WEB_HOOK_EVENT") with BasicTemplate {
|
||||
class AccountWebHookEvents(tag: Tag)
|
||||
extends Table[AccountWebHookEvent](tag, "ACCOUNT_WEB_HOOK_EVENT")
|
||||
with BasicTemplate {
|
||||
val url = column[String]("URL")
|
||||
val event = column[WebHook.Event]("EVENT")
|
||||
|
||||
def * = (userName, url, event) <> ((AccountWebHookEvent.apply _).tupled, AccountWebHookEvent.unapply)
|
||||
def * = (userName, url, event) <> ((AccountWebHookEvent.apply _).tupled, AccountWebHookEvent.unapply)
|
||||
|
||||
def byAccountWebHook(userName: String, url: String) = (this.userName === userName.bind) && (this.url === url.bind)
|
||||
|
||||
@@ -28,7 +30,7 @@ trait AccountWebHookEventComponent extends TemplateComponent {
|
||||
}
|
||||
|
||||
case class AccountWebHookEvent(
|
||||
userName: String,
|
||||
url: String,
|
||||
event: WebHook.Event
|
||||
)
|
||||
userName: String,
|
||||
url: String,
|
||||
event: WebHook.Event
|
||||
)
|
||||
|
||||
@@ -13,7 +13,8 @@ trait ActivityComponent extends TemplateComponent { self: Profile =>
|
||||
val message = column[String]("MESSAGE")
|
||||
val additionalInfo = column[String]("ADDITIONAL_INFO")
|
||||
val activityDate = column[java.util.Date]("ACTIVITY_DATE")
|
||||
def * = (userName, repositoryName, activityUserName, activityType, message, additionalInfo.?, activityDate, activityId) <> (Activity.tupled, Activity.unapply)
|
||||
def * =
|
||||
(userName, repositoryName, activityUserName, activityType, message, additionalInfo.?, activityDate, activityId) <> (Activity.tupled, Activity.unapply)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -76,9 +76,11 @@ protected[model] trait TemplateComponent { self: Profile =>
|
||||
byRepository(userName, repositoryName) && (this.commitId === commitId)
|
||||
}
|
||||
|
||||
trait BranchTemplate extends BasicTemplate{ self: Table[_] =>
|
||||
trait BranchTemplate extends BasicTemplate { self: Table[_] =>
|
||||
val branch = column[String]("BRANCH")
|
||||
def byBranch(owner: String, repository: String, branchName: String) = byRepository(owner, repository) && (branch === branchName.bind)
|
||||
def byBranch(owner: Rep[String], repository: Rep[String], branchName: Rep[String]) = byRepository(owner, repository) && (this.branch === branchName)
|
||||
def byBranch(owner: String, repository: String, branchName: String) =
|
||||
byRepository(owner, repository) && (branch === branchName.bind)
|
||||
def byBranch(owner: Rep[String], repository: Rep[String], branchName: Rep[String]) =
|
||||
byRepository(owner, repository) && (this.branch === branchName)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package gitbucket.core.model
|
||||
import java.util.Date
|
||||
|
||||
trait Comment {
|
||||
sealed trait Comment {
|
||||
val commentedUserName: String
|
||||
val registeredDate: java.util.Date
|
||||
}
|
||||
@@ -18,13 +19,14 @@ trait IssueCommentComponent extends TemplateComponent { self: Profile =>
|
||||
val content = column[String]("CONTENT")
|
||||
val registeredDate = column[java.util.Date]("REGISTERED_DATE")
|
||||
val updatedDate = column[java.util.Date]("UPDATED_DATE")
|
||||
def * = (userName, repositoryName, issueId, commentId, action, commentedUserName, content, registeredDate, updatedDate) <> (IssueComment.tupled, IssueComment.unapply)
|
||||
def * =
|
||||
(userName, repositoryName, issueId, commentId, action, commentedUserName, content, registeredDate, updatedDate) <> (IssueComment.tupled, IssueComment.unapply)
|
||||
|
||||
def byPrimaryKey(commentId: Int) = this.commentId === commentId.bind
|
||||
}
|
||||
}
|
||||
|
||||
case class IssueComment (
|
||||
case class IssueComment(
|
||||
userName: String,
|
||||
repositoryName: String,
|
||||
issueId: Int,
|
||||
@@ -52,7 +54,21 @@ trait CommitCommentComponent extends TemplateComponent { self: Profile =>
|
||||
val registeredDate = column[java.util.Date]("REGISTERED_DATE")
|
||||
val updatedDate = column[java.util.Date]("UPDATED_DATE")
|
||||
val issueId = column[Option[Int]]("ISSUE_ID")
|
||||
def * = (userName, repositoryName, commitId, commentId, commentedUserName, content, fileName, oldLine, newLine, registeredDate, updatedDate, issueId) <> (CommitComment.tupled, CommitComment.unapply)
|
||||
def * =
|
||||
(
|
||||
userName,
|
||||
repositoryName,
|
||||
commitId,
|
||||
commentId,
|
||||
commentedUserName,
|
||||
content,
|
||||
fileName,
|
||||
oldLine,
|
||||
newLine,
|
||||
registeredDate,
|
||||
updatedDate,
|
||||
issueId
|
||||
) <> (CommitComment.tupled, CommitComment.unapply)
|
||||
|
||||
def byPrimaryKey(commentId: Int) = this.commentId === commentId.bind
|
||||
}
|
||||
@@ -71,4 +87,12 @@ case class CommitComment(
|
||||
registeredDate: java.util.Date,
|
||||
updatedDate: java.util.Date,
|
||||
issueId: Option[Int]
|
||||
) extends Comment
|
||||
) extends Comment
|
||||
|
||||
case class CommitComments(
|
||||
fileName: String,
|
||||
commentedUserName: String,
|
||||
registeredDate: Date,
|
||||
comments: Seq[CommitComment],
|
||||
diff: Option[String]
|
||||
) extends Comment
|
||||
|
||||
@@ -4,7 +4,7 @@ trait CommitStatusComponent extends TemplateComponent { self: Profile =>
|
||||
import profile.api._
|
||||
import self._
|
||||
|
||||
implicit val commitStateColumnType = MappedColumnType.base[CommitState, String](b => b.name , i => CommitState(i))
|
||||
implicit val commitStateColumnType = MappedColumnType.base[CommitState, String](b => b.name, i => CommitState(i))
|
||||
|
||||
lazy val CommitStatuses = TableQuery[CommitStatuses]
|
||||
class CommitStatuses(tag: Tag) extends Table[CommitStatus](tag, "COMMIT_STATUS") with CommitTemplate {
|
||||
@@ -16,12 +16,24 @@ trait CommitStatusComponent extends TemplateComponent { self: Profile =>
|
||||
val creator = column[String]("CREATOR")
|
||||
val registeredDate = column[java.util.Date]("REGISTERED_DATE")
|
||||
val updatedDate = column[java.util.Date]("UPDATED_DATE")
|
||||
def * = (commitStatusId, userName, repositoryName, commitId, context, state, targetUrl, description, creator, registeredDate, updatedDate) <> ((CommitStatus.apply _).tupled, CommitStatus.unapply)
|
||||
def * =
|
||||
(
|
||||
commitStatusId,
|
||||
userName,
|
||||
repositoryName,
|
||||
commitId,
|
||||
context,
|
||||
state,
|
||||
targetUrl,
|
||||
description,
|
||||
creator,
|
||||
registeredDate,
|
||||
updatedDate
|
||||
) <> ((CommitStatus.apply _).tupled, CommitStatus.unapply)
|
||||
def byPrimaryKey(id: Int) = commitStatusId === id.bind
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
case class CommitStatus(
|
||||
commitStatusId: Int = 0,
|
||||
userName: String,
|
||||
@@ -36,23 +48,24 @@ case class CommitStatus(
|
||||
updatedDate: java.util.Date
|
||||
)
|
||||
object CommitStatus {
|
||||
def pending(owner: String, repository: String, context: String) = CommitStatus(
|
||||
commitStatusId = 0,
|
||||
userName = owner,
|
||||
repositoryName = repository,
|
||||
commitId = "",
|
||||
context = context,
|
||||
state = CommitState.PENDING,
|
||||
targetUrl = None,
|
||||
description = Some("Waiting for status to be reported"),
|
||||
creator = "",
|
||||
registeredDate = new java.util.Date(),
|
||||
updatedDate = new java.util.Date())
|
||||
def pending(owner: String, repository: String, context: String) =
|
||||
CommitStatus(
|
||||
commitStatusId = 0,
|
||||
userName = owner,
|
||||
repositoryName = repository,
|
||||
commitId = "",
|
||||
context = context,
|
||||
state = CommitState.PENDING,
|
||||
targetUrl = None,
|
||||
description = Some("Waiting for status to be reported"),
|
||||
creator = "",
|
||||
registeredDate = new java.util.Date(),
|
||||
updatedDate = new java.util.Date()
|
||||
)
|
||||
}
|
||||
|
||||
sealed abstract class CommitState(val name: String)
|
||||
|
||||
|
||||
object CommitState {
|
||||
object ERROR extends CommitState("error")
|
||||
|
||||
@@ -76,11 +89,11 @@ object CommitState {
|
||||
* success if the latest status for all contexts is success
|
||||
*/
|
||||
def combine(statuses: Set[CommitState]): CommitState = {
|
||||
if(statuses.isEmpty){
|
||||
if (statuses.isEmpty) {
|
||||
PENDING
|
||||
} else if(statuses.contains(CommitState.ERROR) || statuses.contains(CommitState.FAILURE)) {
|
||||
} else if (statuses.contains(CommitState.ERROR) || statuses.contains(CommitState.FAILURE)) {
|
||||
FAILURE
|
||||
} else if(statuses.contains(CommitState.PENDING)) {
|
||||
} else if (statuses.contains(CommitState.PENDING)) {
|
||||
PENDING
|
||||
} else {
|
||||
SUCCESS
|
||||
@@ -88,4 +101,3 @@ object CommitState {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,8 @@ trait DeployKeyComponent extends TemplateComponent { self: Profile =>
|
||||
val title = column[String]("TITLE")
|
||||
val publicKey = column[String]("PUBLIC_KEY")
|
||||
val allowWrite = column[Boolean]("ALLOW_WRITE")
|
||||
def * = (userName, repositoryName, deployKeyId, title, publicKey, allowWrite) <> (DeployKey.tupled, DeployKey.unapply)
|
||||
def * =
|
||||
(userName, repositoryName, deployKeyId, title, publicKey, allowWrite) <> (DeployKey.tupled, DeployKey.unapply)
|
||||
|
||||
def byPrimaryKey(userName: String, repositoryName: String, deployKeyId: Int) =
|
||||
(this.userName === userName.bind) && (this.repositoryName === repositoryName.bind) && (this.deployKeyId === deployKeyId.bind)
|
||||
|
||||
@@ -13,13 +13,19 @@ trait IssueComponent extends TemplateComponent { self: Profile =>
|
||||
def byPrimaryKey(owner: String, repository: String) = byRepository(owner, repository)
|
||||
}
|
||||
|
||||
class IssueOutline(tag: Tag) extends Table[(String, String, Int, Int, Int)](tag, "ISSUE_OUTLINE_VIEW") with IssueTemplate {
|
||||
class IssueOutline(tag: Tag)
|
||||
extends Table[(String, String, Int, Int, Int)](tag, "ISSUE_OUTLINE_VIEW")
|
||||
with IssueTemplate {
|
||||
val commentCount = column[Int]("COMMENT_COUNT")
|
||||
val priority = column[Int]("PRIORITY")
|
||||
def * = (userName, repositoryName, issueId, commentCount, priority)
|
||||
}
|
||||
|
||||
class Issues(tag: Tag) extends Table[Issue](tag, "ISSUE") with IssueTemplate with MilestoneTemplate with PriorityTemplate {
|
||||
class Issues(tag: Tag)
|
||||
extends Table[Issue](tag, "ISSUE")
|
||||
with IssueTemplate
|
||||
with MilestoneTemplate
|
||||
with PriorityTemplate {
|
||||
val openedUserName = column[String]("OPENED_USER_NAME")
|
||||
val assignedUserName = column[String]("ASSIGNED_USER_NAME")
|
||||
val title = column[String]("TITLE")
|
||||
@@ -28,7 +34,22 @@ trait IssueComponent extends TemplateComponent { self: Profile =>
|
||||
val registeredDate = column[java.util.Date]("REGISTERED_DATE")
|
||||
val updatedDate = column[java.util.Date]("UPDATED_DATE")
|
||||
val pullRequest = column[Boolean]("PULL_REQUEST")
|
||||
def * = (userName, repositoryName, issueId, openedUserName, milestoneId.?, priorityId.?, assignedUserName.?, title, content.?, closed, registeredDate, updatedDate, pullRequest) <> (Issue.tupled, Issue.unapply)
|
||||
def * =
|
||||
(
|
||||
userName,
|
||||
repositoryName,
|
||||
issueId,
|
||||
openedUserName,
|
||||
milestoneId.?,
|
||||
priorityId.?,
|
||||
assignedUserName.?,
|
||||
title,
|
||||
content.?,
|
||||
closed,
|
||||
registeredDate,
|
||||
updatedDate,
|
||||
pullRequest
|
||||
) <> (Issue.tupled, Issue.unapply)
|
||||
|
||||
def byPrimaryKey(owner: String, repository: String, issueId: Int) = byIssue(owner, repository, issueId)
|
||||
}
|
||||
|
||||
@@ -12,23 +12,19 @@ trait LabelComponent extends TemplateComponent { self: Profile =>
|
||||
def * = (userName, repositoryName, labelId, labelName, color) <> (Label.tupled, Label.unapply)
|
||||
|
||||
def byPrimaryKey(owner: String, repository: String, labelId: Int) = byLabel(owner, repository, labelId)
|
||||
def byPrimaryKey(userName: Rep[String], repositoryName: Rep[String], labelId: Rep[Int]) = byLabel(userName, repositoryName, labelId)
|
||||
def byPrimaryKey(userName: Rep[String], repositoryName: Rep[String], labelId: Rep[Int]) =
|
||||
byLabel(userName, repositoryName, labelId)
|
||||
}
|
||||
}
|
||||
|
||||
case class Label(
|
||||
userName: String,
|
||||
repositoryName: String,
|
||||
labelId: Int = 0,
|
||||
labelName: String,
|
||||
color: String){
|
||||
case class Label(userName: String, repositoryName: String, labelId: Int = 0, labelName: String, color: String) {
|
||||
|
||||
val fontColor = {
|
||||
val r = color.substring(0, 2)
|
||||
val g = color.substring(2, 4)
|
||||
val b = color.substring(4, 6)
|
||||
|
||||
if(Integer.parseInt(r, 16) + Integer.parseInt(g, 16) + Integer.parseInt(b, 16) > 408){
|
||||
if (Integer.parseInt(r, 16) + Integer.parseInt(g, 16) + Integer.parseInt(b, 16) > 408) {
|
||||
"000000"
|
||||
} else {
|
||||
"ffffff"
|
||||
|
||||
@@ -12,10 +12,12 @@ trait MilestoneComponent extends TemplateComponent { self: Profile =>
|
||||
val description = column[Option[String]]("DESCRIPTION")
|
||||
val dueDate = column[Option[java.util.Date]]("DUE_DATE")
|
||||
val closedDate = column[Option[java.util.Date]]("CLOSED_DATE")
|
||||
def * = (userName, repositoryName, milestoneId, title, description, dueDate, closedDate) <> (Milestone.tupled, Milestone.unapply)
|
||||
def * =
|
||||
(userName, repositoryName, milestoneId, title, description, dueDate, closedDate) <> (Milestone.tupled, Milestone.unapply)
|
||||
|
||||
def byPrimaryKey(owner: String, repository: String, milestoneId: Int) = byMilestone(owner, repository, milestoneId)
|
||||
def byPrimaryKey(userName: Rep[String], repositoryName: Rep[String], milestoneId: Rep[Int]) = byMilestone(userName, repositoryName, milestoneId)
|
||||
def byPrimaryKey(userName: Rep[String], repositoryName: Rep[String], milestoneId: Rep[Int]) =
|
||||
byMilestone(userName, repositoryName, milestoneId)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,14 +12,16 @@ trait PriorityComponent extends TemplateComponent { self: Profile =>
|
||||
val ordering = column[Int]("ORDERING")
|
||||
val isDefault = column[Boolean]("IS_DEFAULT")
|
||||
val color = column[String]("COLOR")
|
||||
def * = (userName, repositoryName, priorityId, priorityName, description.?, isDefault, ordering, color) <> (Priority.tupled, Priority.unapply)
|
||||
def * =
|
||||
(userName, repositoryName, priorityId, priorityName, description.?, isDefault, ordering, color) <> (Priority.tupled, Priority.unapply)
|
||||
|
||||
def byPrimaryKey(owner: String, repository: String, priorityId: Int) = byPriority(owner, repository, priorityId)
|
||||
def byPrimaryKey(userName: Rep[String], repositoryName: Rep[String], priorityId: Rep[Int]) = byPriority(userName, repositoryName, priorityId)
|
||||
def byPrimaryKey(userName: Rep[String], repositoryName: Rep[String], priorityId: Rep[Int]) =
|
||||
byPriority(userName, repositoryName, priorityId)
|
||||
}
|
||||
}
|
||||
|
||||
case class Priority (
|
||||
case class Priority(
|
||||
userName: String,
|
||||
repositoryName: String,
|
||||
priorityId: Int = 0,
|
||||
@@ -27,14 +29,15 @@ case class Priority (
|
||||
description: Option[String],
|
||||
isDefault: Boolean,
|
||||
ordering: Int = 0,
|
||||
color: String){
|
||||
color: String
|
||||
) {
|
||||
|
||||
val fontColor = {
|
||||
val r = color.substring(0, 2)
|
||||
val g = color.substring(2, 4)
|
||||
val b = color.substring(4, 6)
|
||||
|
||||
if(Integer.parseInt(r, 16) + Integer.parseInt(g, 16) + Integer.parseInt(b, 16) > 408){
|
||||
if (Integer.parseInt(r, 16) + Integer.parseInt(g, 16) + Integer.parseInt(b, 16) > 408) {
|
||||
"000000"
|
||||
} else {
|
||||
"ffffff"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package gitbucket.core.model
|
||||
|
||||
import gitbucket.core.util.DatabaseConfig
|
||||
import com.github.takezoe.slick.blocking.BlockingJdbcProfile
|
||||
import gitbucket.core.util.DatabaseConfig
|
||||
|
||||
trait Profile {
|
||||
val profile: BlockingJdbcProfile
|
||||
@@ -16,15 +16,15 @@ trait Profile {
|
||||
)
|
||||
|
||||
/**
|
||||
* WebHookBase.Event Column Types
|
||||
*/
|
||||
* WebHookBase.Event Column Types
|
||||
*/
|
||||
implicit val eventColumnType = MappedColumnType.base[WebHook.Event, String](_.name, WebHook.Event.valueOf(_))
|
||||
|
||||
/**
|
||||
* Extends Column to add conditional condition
|
||||
*/
|
||||
implicit class RichColumn(c1: Rep[Boolean]){
|
||||
def &&(c2: => Rep[Boolean], guard: => Boolean): Rep[Boolean] = if(guard) c1 && c2 else c1
|
||||
implicit class RichColumn(c1: Rep[Boolean]) {
|
||||
def &&(c2: => Rep[Boolean], guard: => Boolean): Rep[Boolean] = if (guard) c1 && c2 else c1
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -40,28 +40,34 @@ trait ProfileProvider { self: Profile =>
|
||||
|
||||
}
|
||||
|
||||
trait CoreProfile extends ProfileProvider with Profile
|
||||
with AccessTokenComponent
|
||||
with AccountComponent
|
||||
with ActivityComponent
|
||||
with CollaboratorComponent
|
||||
with CommitCommentComponent
|
||||
with CommitStatusComponent
|
||||
with GroupMemberComponent
|
||||
with IssueComponent
|
||||
with IssueCommentComponent
|
||||
with IssueLabelComponent
|
||||
with LabelComponent
|
||||
with PriorityComponent
|
||||
with MilestoneComponent
|
||||
with PullRequestComponent
|
||||
with RepositoryComponent
|
||||
with SshKeyComponent
|
||||
with RepositoryWebHookComponent
|
||||
with RepositoryWebHookEventComponent
|
||||
with AccountWebHookComponent
|
||||
with AccountWebHookEventComponent
|
||||
with ProtectedBranchComponent
|
||||
with DeployKeyComponent
|
||||
trait CoreProfile
|
||||
extends ProfileProvider
|
||||
with Profile
|
||||
with AccessTokenComponent
|
||||
with AccountComponent
|
||||
with ActivityComponent
|
||||
with CollaboratorComponent
|
||||
with CommitCommentComponent
|
||||
with CommitStatusComponent
|
||||
with GroupMemberComponent
|
||||
with IssueComponent
|
||||
with IssueCommentComponent
|
||||
with IssueLabelComponent
|
||||
with LabelComponent
|
||||
with PriorityComponent
|
||||
with MilestoneComponent
|
||||
with PullRequestComponent
|
||||
with RepositoryComponent
|
||||
with SshKeyComponent
|
||||
with RepositoryWebHookComponent
|
||||
with RepositoryWebHookEventComponent
|
||||
with AccountWebHookComponent
|
||||
with AccountWebHookEventComponent
|
||||
with AccountFederationComponent
|
||||
with ProtectedBranchComponent
|
||||
with DeployKeyComponent
|
||||
with ReleaseTagComponent
|
||||
with ReleaseAssetComponent
|
||||
with AccountExtraMailAddressComponent
|
||||
|
||||
object Profile extends CoreProfile
|
||||
|
||||
@@ -8,27 +8,22 @@ trait ProtectedBranchComponent extends TemplateComponent { self: Profile =>
|
||||
class ProtectedBranches(tag: Tag) extends Table[ProtectedBranch](tag, "PROTECTED_BRANCH") with BranchTemplate {
|
||||
val statusCheckAdmin = column[Boolean]("STATUS_CHECK_ADMIN")
|
||||
def * = (userName, repositoryName, branch, statusCheckAdmin) <> (ProtectedBranch.tupled, ProtectedBranch.unapply)
|
||||
def byPrimaryKey(userName: String, repositoryName: String, branch: String) = byBranch(userName, repositoryName, branch)
|
||||
def byPrimaryKey(userName: Rep[String], repositoryName: Rep[String], branch: Rep[String]) = byBranch(userName, repositoryName, branch)
|
||||
def byPrimaryKey(userName: String, repositoryName: String, branch: String) =
|
||||
byBranch(userName, repositoryName, branch)
|
||||
def byPrimaryKey(userName: Rep[String], repositoryName: Rep[String], branch: Rep[String]) =
|
||||
byBranch(userName, repositoryName, branch)
|
||||
}
|
||||
|
||||
lazy val ProtectedBranchContexts = TableQuery[ProtectedBranchContexts]
|
||||
class ProtectedBranchContexts(tag: Tag) extends Table[ProtectedBranchContext](tag, "PROTECTED_BRANCH_REQUIRE_CONTEXT") with BranchTemplate {
|
||||
class ProtectedBranchContexts(tag: Tag)
|
||||
extends Table[ProtectedBranchContext](tag, "PROTECTED_BRANCH_REQUIRE_CONTEXT")
|
||||
with BranchTemplate {
|
||||
val context = column[String]("CONTEXT")
|
||||
def * = (userName, repositoryName, branch, context) <> (ProtectedBranchContext.tupled, ProtectedBranchContext.unapply)
|
||||
def * =
|
||||
(userName, repositoryName, branch, context) <> (ProtectedBranchContext.tupled, ProtectedBranchContext.unapply)
|
||||
}
|
||||
}
|
||||
|
||||
case class ProtectedBranch(userName: String, repositoryName: String, branch: String, statusCheckAdmin: Boolean)
|
||||
|
||||
case class ProtectedBranch(
|
||||
userName: String,
|
||||
repositoryName: String,
|
||||
branch: String,
|
||||
statusCheckAdmin: Boolean)
|
||||
|
||||
|
||||
case class ProtectedBranchContext(
|
||||
userName: String,
|
||||
repositoryName: String,
|
||||
branch: String,
|
||||
context: String)
|
||||
case class ProtectedBranchContext(userName: String, repositoryName: String, branch: String, context: String)
|
||||
|
||||
@@ -12,10 +12,23 @@ trait PullRequestComponent extends TemplateComponent { self: Profile =>
|
||||
val requestBranch = column[String]("REQUEST_BRANCH")
|
||||
val commitIdFrom = column[String]("COMMIT_ID_FROM")
|
||||
val commitIdTo = column[String]("COMMIT_ID_TO")
|
||||
def * = (userName, repositoryName, issueId, branch, requestUserName, requestRepositoryName, requestBranch, commitIdFrom, commitIdTo) <> (PullRequest.tupled, PullRequest.unapply)
|
||||
def * =
|
||||
(
|
||||
userName,
|
||||
repositoryName,
|
||||
issueId,
|
||||
branch,
|
||||
requestUserName,
|
||||
requestRepositoryName,
|
||||
requestBranch,
|
||||
commitIdFrom,
|
||||
commitIdTo
|
||||
) <> (PullRequest.tupled, PullRequest.unapply)
|
||||
|
||||
def byPrimaryKey(userName: String, repositoryName: String, issueId: Int) = byIssue(userName, repositoryName, issueId)
|
||||
def byPrimaryKey(userName: Rep[String], repositoryName: Rep[String], issueId: Rep[Int]) = byIssue(userName, repositoryName, issueId)
|
||||
def byPrimaryKey(userName: String, repositoryName: String, issueId: Int) =
|
||||
byIssue(userName, repositoryName, issueId)
|
||||
def byPrimaryKey(userName: Rep[String], repositoryName: Rep[String], issueId: Rep[Int]) =
|
||||
byIssue(userName, repositoryName, issueId)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
43
src/main/scala/gitbucket/core/model/ReleaseAsset.scala
Normal file
43
src/main/scala/gitbucket/core/model/ReleaseAsset.scala
Normal file
@@ -0,0 +1,43 @@
|
||||
package gitbucket.core.model
|
||||
|
||||
import java.util.Date
|
||||
|
||||
trait ReleaseAssetComponent extends TemplateComponent {
|
||||
self: Profile =>
|
||||
|
||||
import profile.api._
|
||||
import self._
|
||||
|
||||
lazy val ReleaseAssets = TableQuery[ReleaseAssets]
|
||||
|
||||
class ReleaseAssets(tag_ : Tag) extends Table[ReleaseAsset](tag_, "RELEASE_ASSET") with BasicTemplate {
|
||||
val tag = column[String]("TAG")
|
||||
val releaseAssetId = column[Int]("RELEASE_ASSET_ID", O AutoInc)
|
||||
val fileName = column[String]("FILE_NAME")
|
||||
val label = column[String]("LABEL")
|
||||
val size = column[Long]("SIZE")
|
||||
val uploader = column[String]("UPLOADER")
|
||||
val registeredDate = column[Date]("REGISTERED_DATE")
|
||||
val updatedDate = column[Date]("UPDATED_DATE")
|
||||
|
||||
def * =
|
||||
(userName, repositoryName, tag, releaseAssetId, fileName, label, size, uploader, registeredDate, updatedDate) <> (ReleaseAsset.tupled, ReleaseAsset.unapply)
|
||||
def byPrimaryKey(owner: String, repository: String, tag: String, fileName: String) =
|
||||
byTag(owner, repository, tag) && (this.fileName === fileName.bind)
|
||||
def byTag(owner: String, repository: String, tag: String) =
|
||||
byRepository(owner, repository) && (this.tag === tag.bind)
|
||||
}
|
||||
}
|
||||
|
||||
case class ReleaseAsset(
|
||||
userName: String,
|
||||
repositoryName: String,
|
||||
tag: String,
|
||||
releaseAssetId: Int = 0,
|
||||
fileName: String,
|
||||
label: String,
|
||||
size: Long,
|
||||
uploader: String,
|
||||
registeredDate: Date,
|
||||
updatedDate: Date
|
||||
)
|
||||
36
src/main/scala/gitbucket/core/model/ReleaseTag.scala
Normal file
36
src/main/scala/gitbucket/core/model/ReleaseTag.scala
Normal file
@@ -0,0 +1,36 @@
|
||||
package gitbucket.core.model
|
||||
|
||||
trait ReleaseTagComponent extends TemplateComponent {
|
||||
self: Profile =>
|
||||
|
||||
import profile.api._
|
||||
import self._
|
||||
|
||||
lazy val ReleaseTags = TableQuery[ReleaseTags]
|
||||
|
||||
class ReleaseTags(tag_ : Tag) extends Table[ReleaseTag](tag_, "RELEASE_TAG") with BasicTemplate {
|
||||
val name = column[String]("NAME")
|
||||
val tag = column[String]("TAG")
|
||||
val author = column[String]("AUTHOR")
|
||||
val content = column[Option[String]]("CONTENT")
|
||||
val registeredDate = column[java.util.Date]("REGISTERED_DATE")
|
||||
val updatedDate = column[java.util.Date]("UPDATED_DATE")
|
||||
|
||||
def * =
|
||||
(userName, repositoryName, name, tag, author, content, registeredDate, updatedDate) <> (ReleaseTag.tupled, ReleaseTag.unapply)
|
||||
def byPrimaryKey(owner: String, repository: String, tag: String) = byTag(owner, repository, tag)
|
||||
def byTag(owner: String, repository: String, tag: String) =
|
||||
byRepository(owner, repository) && (this.tag === tag.bind)
|
||||
}
|
||||
}
|
||||
|
||||
case class ReleaseTag(
|
||||
userName: String,
|
||||
repositoryName: String,
|
||||
name: String,
|
||||
tag: String,
|
||||
author: String,
|
||||
content: Option[String],
|
||||
registeredDate: java.util.Date,
|
||||
updatedDate: java.util.Date
|
||||
)
|
||||
@@ -7,60 +7,80 @@ trait RepositoryComponent extends TemplateComponent { self: Profile =>
|
||||
lazy val Repositories = TableQuery[Repositories]
|
||||
|
||||
class Repositories(tag: Tag) extends Table[Repository](tag, "REPOSITORY") with BasicTemplate {
|
||||
val isPrivate = column[Boolean]("PRIVATE")
|
||||
val description = column[String]("DESCRIPTION")
|
||||
val defaultBranch = column[String]("DEFAULT_BRANCH")
|
||||
val registeredDate = column[java.util.Date]("REGISTERED_DATE")
|
||||
val updatedDate = column[java.util.Date]("UPDATED_DATE")
|
||||
val lastActivityDate = column[java.util.Date]("LAST_ACTIVITY_DATE")
|
||||
val originUserName = column[String]("ORIGIN_USER_NAME")
|
||||
val isPrivate = column[Boolean]("PRIVATE")
|
||||
val description = column[String]("DESCRIPTION")
|
||||
val defaultBranch = column[String]("DEFAULT_BRANCH")
|
||||
val registeredDate = column[java.util.Date]("REGISTERED_DATE")
|
||||
val updatedDate = column[java.util.Date]("UPDATED_DATE")
|
||||
val lastActivityDate = column[java.util.Date]("LAST_ACTIVITY_DATE")
|
||||
val originUserName = column[String]("ORIGIN_USER_NAME")
|
||||
val originRepositoryName = column[String]("ORIGIN_REPOSITORY_NAME")
|
||||
val parentUserName = column[String]("PARENT_USER_NAME")
|
||||
val parentUserName = column[String]("PARENT_USER_NAME")
|
||||
val parentRepositoryName = column[String]("PARENT_REPOSITORY_NAME")
|
||||
val issuesOption = column[String]("ISSUES_OPTION")
|
||||
val externalIssuesUrl = column[String]("EXTERNAL_ISSUES_URL")
|
||||
val wikiOption = column[String]("WIKI_OPTION")
|
||||
val externalWikiUrl = column[String]("EXTERNAL_WIKI_URL")
|
||||
val allowFork = column[Boolean]("ALLOW_FORK")
|
||||
val issuesOption = column[String]("ISSUES_OPTION")
|
||||
val externalIssuesUrl = column[String]("EXTERNAL_ISSUES_URL")
|
||||
val wikiOption = column[String]("WIKI_OPTION")
|
||||
val externalWikiUrl = column[String]("EXTERNAL_WIKI_URL")
|
||||
val allowFork = column[Boolean]("ALLOW_FORK")
|
||||
val mergeOptions = column[String]("MERGE_OPTIONS")
|
||||
val defaultMergeOption = column[String]("DEFAULT_MERGE_OPTION")
|
||||
|
||||
def * = (
|
||||
(userName, repositoryName, isPrivate, description.?, defaultBranch,
|
||||
registeredDate, updatedDate, lastActivityDate, originUserName.?, originRepositoryName.?, parentUserName.?, parentRepositoryName.?),
|
||||
(issuesOption, externalIssuesUrl.?, wikiOption, externalWikiUrl.?, allowFork)
|
||||
).shaped <> (
|
||||
{ case (repository, options) =>
|
||||
Repository(
|
||||
repository._1,
|
||||
repository._2,
|
||||
repository._3,
|
||||
repository._4,
|
||||
repository._5,
|
||||
repository._6,
|
||||
repository._7,
|
||||
repository._8,
|
||||
repository._9,
|
||||
repository._10,
|
||||
repository._11,
|
||||
repository._12,
|
||||
RepositoryOptions.tupled.apply(options)
|
||||
)
|
||||
def * =
|
||||
(
|
||||
(
|
||||
userName,
|
||||
repositoryName,
|
||||
isPrivate,
|
||||
description.?,
|
||||
defaultBranch,
|
||||
registeredDate,
|
||||
updatedDate,
|
||||
lastActivityDate,
|
||||
originUserName.?,
|
||||
originRepositoryName.?,
|
||||
parentUserName.?,
|
||||
parentRepositoryName.?
|
||||
),
|
||||
(issuesOption, externalIssuesUrl.?, wikiOption, externalWikiUrl.?, allowFork, mergeOptions, defaultMergeOption)
|
||||
).shaped <> ({
|
||||
case (repository, options) =>
|
||||
Repository(
|
||||
repository._1,
|
||||
repository._2,
|
||||
repository._3,
|
||||
repository._4,
|
||||
repository._5,
|
||||
repository._6,
|
||||
repository._7,
|
||||
repository._8,
|
||||
repository._9,
|
||||
repository._10,
|
||||
repository._11,
|
||||
repository._12,
|
||||
RepositoryOptions.tupled.apply(options)
|
||||
)
|
||||
}, { (r: Repository) =>
|
||||
Some(((
|
||||
r.userName,
|
||||
r.repositoryName,
|
||||
r.isPrivate,
|
||||
r.description,
|
||||
r.defaultBranch,
|
||||
r.registeredDate,
|
||||
r.updatedDate,
|
||||
r.lastActivityDate,
|
||||
r.originUserName,
|
||||
r.originRepositoryName,
|
||||
r.parentUserName,
|
||||
r.parentRepositoryName
|
||||
),(
|
||||
RepositoryOptions.unapply(r.options).get
|
||||
)))
|
||||
Some(
|
||||
(
|
||||
(
|
||||
r.userName,
|
||||
r.repositoryName,
|
||||
r.isPrivate,
|
||||
r.description,
|
||||
r.defaultBranch,
|
||||
r.registeredDate,
|
||||
r.updatedDate,
|
||||
r.lastActivityDate,
|
||||
r.originUserName,
|
||||
r.originRepositoryName,
|
||||
r.parentUserName,
|
||||
r.parentRepositoryName
|
||||
),
|
||||
(
|
||||
RepositoryOptions.unapply(r.options).get
|
||||
)
|
||||
)
|
||||
)
|
||||
})
|
||||
|
||||
def byPrimaryKey(owner: String, repository: String) = byRepository(owner, repository)
|
||||
@@ -88,5 +108,7 @@ case class RepositoryOptions(
|
||||
externalIssuesUrl: Option[String],
|
||||
wikiOption: String,
|
||||
externalWikiUrl: Option[String],
|
||||
allowFork: Boolean
|
||||
allowFork: Boolean,
|
||||
mergeOptions: String,
|
||||
defaultMergeOption: String
|
||||
)
|
||||
|
||||
@@ -3,7 +3,8 @@ package gitbucket.core.model
|
||||
trait RepositoryWebHookComponent extends TemplateComponent { self: Profile =>
|
||||
import profile.api._
|
||||
|
||||
implicit val whContentTypeColumnType = MappedColumnType.base[WebHookContentType, String](whct => whct.code , code => WebHookContentType.valueOf(code))
|
||||
implicit val whContentTypeColumnType =
|
||||
MappedColumnType.base[WebHookContentType, String](whct => whct.code, code => WebHookContentType.valueOf(code))
|
||||
|
||||
lazy val RepositoryWebHooks = TableQuery[RepositoryWebHooks]
|
||||
|
||||
@@ -11,13 +12,14 @@ trait RepositoryWebHookComponent extends TemplateComponent { self: Profile =>
|
||||
val url = column[String]("URL")
|
||||
val token = column[Option[String]]("TOKEN")
|
||||
val ctype = column[WebHookContentType]("CTYPE")
|
||||
def * = (userName, repositoryName, url, ctype, token) <> ((RepositoryWebHook.apply _).tupled, RepositoryWebHook.unapply)
|
||||
def * =
|
||||
(userName, repositoryName, url, ctype, token) <> ((RepositoryWebHook.apply _).tupled, RepositoryWebHook.unapply)
|
||||
|
||||
def byPrimaryKey(owner: String, repository: String, url: String) = byRepository(owner, repository) && (this.url === url.bind)
|
||||
def byPrimaryKey(owner: String, repository: String, url: String) =
|
||||
byRepository(owner, repository) && (this.url === url.bind)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
case class RepositoryWebHook(
|
||||
userName: String,
|
||||
repositoryName: String,
|
||||
|
||||
@@ -6,17 +6,22 @@ trait RepositoryWebHookEventComponent extends TemplateComponent { self: Profile
|
||||
|
||||
lazy val RepositoryWebHookEvents = TableQuery[RepositoryWebHookEvents]
|
||||
|
||||
class RepositoryWebHookEvents(tag: Tag) extends Table[RepositoryWebHookEvent](tag, "WEB_HOOK_EVENT") with BasicTemplate {
|
||||
class RepositoryWebHookEvents(tag: Tag)
|
||||
extends Table[RepositoryWebHookEvent](tag, "WEB_HOOK_EVENT")
|
||||
with BasicTemplate {
|
||||
val url = column[String]("URL")
|
||||
val event = column[WebHook.Event]("EVENT")
|
||||
def * = (userName, repositoryName, url, event) <> ((RepositoryWebHookEvent.apply _).tupled, RepositoryWebHookEvent.unapply)
|
||||
def * =
|
||||
(userName, repositoryName, url, event) <> ((RepositoryWebHookEvent.apply _).tupled, RepositoryWebHookEvent.unapply)
|
||||
|
||||
def byRepositoryWebHook(owner: String, repository: String, url: String) = byRepository(owner, repository) && (this.url === url.bind)
|
||||
def byRepositoryWebHook(owner: String, repository: String, url: String) =
|
||||
byRepository(owner, repository) && (this.url === url.bind)
|
||||
def byRepositoryWebHook(owner: Rep[String], repository: Rep[String], url: Rep[String]) =
|
||||
byRepository(userName, repositoryName) && (this.url === url)
|
||||
def byRepositoryWebHook(webhook: RepositoryWebHooks) =
|
||||
byRepository(webhook.userName, webhook.repositoryName) && (this.url === webhook.url)
|
||||
def byPrimaryKey(owner: String, repository: String, url: String, event: WebHook.Event) = byRepositoryWebHook(owner, repository, url) && (this.event === event.bind)
|
||||
def byPrimaryKey(owner: String, repository: String, url: String, event: WebHook.Event) =
|
||||
byRepositoryWebHook(owner, repository, url) && (this.event === event.bind)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,8 @@ trait SshKeyComponent { self: Profile =>
|
||||
val publicKey = column[String]("PUBLIC_KEY")
|
||||
def * = (userName, sshKeyId, title, publicKey) <> (SshKey.tupled, SshKey.unapply)
|
||||
|
||||
def byPrimaryKey(userName: String, sshKeyId: Int) = (this.userName === userName.bind) && (this.sshKeyId === sshKeyId.bind)
|
||||
def byPrimaryKey(userName: String, sshKeyId: Int) =
|
||||
(this.userName === userName.bind) && (this.sshKeyId === sshKeyId.bind)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ object WebHookContentType {
|
||||
def valueOpt(code: String): Option[WebHookContentType] = map.get(code)
|
||||
}
|
||||
|
||||
trait WebHook{
|
||||
trait WebHook {
|
||||
val url: String
|
||||
val ctype: WebHookContentType
|
||||
val token: Option[String]
|
||||
@@ -45,7 +45,7 @@ object WebHook {
|
||||
case object TeamAdd extends Event("team_add")
|
||||
case object Watch extends Event("watch")
|
||||
|
||||
object Event{
|
||||
object Event {
|
||||
val values = List(
|
||||
CommitComment,
|
||||
Create,
|
||||
@@ -68,7 +68,7 @@ object WebHook {
|
||||
Watch
|
||||
)
|
||||
|
||||
private val map: Map[String,Event] = values.map(e => e.name -> e).toMap
|
||||
private val map: Map[String, Event] = values.map(e => e.name -> e).toMap
|
||||
def valueOf(name: String): Event = map(name)
|
||||
def valueOpt(name: String): Option[Event] = map.get(name)
|
||||
}
|
||||
|
||||
@@ -10,13 +10,18 @@ import gitbucket.core.service.SystemSettingsService.SystemSettings
|
||||
* @param localPath the string to assemble local file path of repository (e.g. "gist/$1/$2")
|
||||
* @param filter the filter for request to the Git repository which is defined by this routing
|
||||
*/
|
||||
case class GitRepositoryRouting(urlPattern: String, localPath: String, filter: GitRepositoryFilter){
|
||||
case class GitRepositoryRouting(urlPattern: String, localPath: String, filter: GitRepositoryFilter) {
|
||||
|
||||
def this(urlPattern: String, localPath: String) = {
|
||||
this(urlPattern, localPath, new GitRepositoryFilter(){
|
||||
def filter(repositoryName: String, userName: Option[String], settings: SystemSettings, isUpdating: Boolean)
|
||||
(implicit session: Session): Boolean = true
|
||||
})
|
||||
this(
|
||||
urlPattern,
|
||||
localPath,
|
||||
new GitRepositoryFilter() {
|
||||
def filter(repositoryName: String, userName: Option[String], settings: SystemSettings, isUpdating: Boolean)(
|
||||
implicit session: Session
|
||||
): Boolean = true
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -36,7 +41,8 @@ trait GitRepositoryFilter {
|
||||
* @param session the database session
|
||||
* @return true if allow accessing to repository, otherwise false.
|
||||
*/
|
||||
def filter(path: String, userName: Option[String], settings: SystemSettings, isUpdating: Boolean)
|
||||
(implicit session: Session): Boolean
|
||||
def filter(path: String, userName: Option[String], settings: SystemSettings, isUpdating: Boolean)(
|
||||
implicit session: Session
|
||||
): Boolean
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,10 @@ import profile.api._
|
||||
trait IssueHook {
|
||||
|
||||
def created(issue: Issue, repository: RepositoryInfo)(implicit session: Session, context: Context): Unit = ()
|
||||
def addedComment(commentId: Int, content: String, issue: Issue, repository: RepositoryInfo)(implicit session: Session, context: Context): Unit = ()
|
||||
def addedComment(commentId: Int, content: String, issue: Issue, repository: RepositoryInfo)(
|
||||
implicit session: Session,
|
||||
context: Context
|
||||
): Unit = ()
|
||||
def closed(issue: Issue, repository: RepositoryInfo)(implicit session: Session, context: Context): Unit = ()
|
||||
def reopened(issue: Issue, repository: RepositoryInfo)(implicit session: Session, context: Context): Unit = ()
|
||||
|
||||
|
||||
@@ -30,7 +30,8 @@ abstract class Plugin {
|
||||
/**
|
||||
* Override to declare this plug-in provides images.
|
||||
*/
|
||||
def images(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[(String, Array[Byte])] = Nil
|
||||
def images(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[(String, Array[Byte])] =
|
||||
Nil
|
||||
|
||||
/**
|
||||
* Override to declare this plug-in provides controllers.
|
||||
@@ -40,7 +41,11 @@ abstract class Plugin {
|
||||
/**
|
||||
* Override to declare this plug-in provides controllers.
|
||||
*/
|
||||
def controllers(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[(String, ControllerBase)] = Nil
|
||||
def controllers(
|
||||
registry: PluginRegistry,
|
||||
context: ServletContext,
|
||||
settings: SystemSettings
|
||||
): Seq[(String, ControllerBase)] = Nil
|
||||
|
||||
/**
|
||||
* Override to declare this plug-in provides JavaScript.
|
||||
@@ -50,7 +55,8 @@ abstract class Plugin {
|
||||
/**
|
||||
* Override to declare this plug-in provides JavaScript.
|
||||
*/
|
||||
def javaScripts(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[(String, String)] = Nil
|
||||
def javaScripts(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[(String, String)] =
|
||||
Nil
|
||||
|
||||
/**
|
||||
* Override to declare this plug-in provides renderers.
|
||||
@@ -60,7 +66,8 @@ abstract class Plugin {
|
||||
/**
|
||||
* Override to declare this plug-in provides renderers.
|
||||
*/
|
||||
def renderers(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[(String, Renderer)] = Nil
|
||||
def renderers(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[(String, Renderer)] =
|
||||
Nil
|
||||
|
||||
/**
|
||||
* Override to add git repository routings.
|
||||
@@ -70,7 +77,11 @@ abstract class Plugin {
|
||||
/**
|
||||
* Override to add git repository routings.
|
||||
*/
|
||||
def repositoryRoutings(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[GitRepositoryRouting] = Nil
|
||||
def repositoryRoutings(
|
||||
registry: PluginRegistry,
|
||||
context: ServletContext,
|
||||
settings: SystemSettings
|
||||
): Seq[GitRepositoryRouting] = Nil
|
||||
|
||||
/**
|
||||
* Override to add account hooks.
|
||||
@@ -100,7 +111,11 @@ abstract class Plugin {
|
||||
/**
|
||||
* Override to add repository hooks.
|
||||
*/
|
||||
def repositoryHooks(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[RepositoryHook] = Nil
|
||||
def repositoryHooks(
|
||||
registry: PluginRegistry,
|
||||
context: ServletContext,
|
||||
settings: SystemSettings
|
||||
): Seq[RepositoryHook] = Nil
|
||||
|
||||
/**
|
||||
* Override to add issue hooks.
|
||||
@@ -120,7 +135,11 @@ abstract class Plugin {
|
||||
/**
|
||||
* Override to add pull request hooks.
|
||||
*/
|
||||
def pullRequestHooks(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[PullRequestHook] = Nil
|
||||
def pullRequestHooks(
|
||||
registry: PluginRegistry,
|
||||
context: ServletContext,
|
||||
settings: SystemSettings
|
||||
): Seq[PullRequestHook] = Nil
|
||||
|
||||
/**
|
||||
* Override to add repository headers.
|
||||
@@ -130,7 +149,11 @@ abstract class Plugin {
|
||||
/**
|
||||
* Override to add repository headers.
|
||||
*/
|
||||
def repositoryHeaders(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[(RepositoryInfo, Context) => Option[Html]] = Nil
|
||||
def repositoryHeaders(
|
||||
registry: PluginRegistry,
|
||||
context: ServletContext,
|
||||
settings: SystemSettings
|
||||
): Seq[(RepositoryInfo, Context) => Option[Html]] = Nil
|
||||
|
||||
/**
|
||||
* Override to add global menus.
|
||||
@@ -140,7 +163,11 @@ abstract class Plugin {
|
||||
/**
|
||||
* Override to add global menus.
|
||||
*/
|
||||
def globalMenus(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[(Context) => Option[Link]] = Nil
|
||||
def globalMenus(
|
||||
registry: PluginRegistry,
|
||||
context: ServletContext,
|
||||
settings: SystemSettings
|
||||
): Seq[(Context) => Option[Link]] = Nil
|
||||
|
||||
/**
|
||||
* Override to add repository menus.
|
||||
@@ -150,7 +177,11 @@ abstract class Plugin {
|
||||
/**
|
||||
* Override to add repository menus.
|
||||
*/
|
||||
def repositoryMenus(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[(RepositoryInfo, Context) => Option[Link]] = Nil
|
||||
def repositoryMenus(
|
||||
registry: PluginRegistry,
|
||||
context: ServletContext,
|
||||
settings: SystemSettings
|
||||
): Seq[(RepositoryInfo, Context) => Option[Link]] = Nil
|
||||
|
||||
/**
|
||||
* Override to add repository setting tabs.
|
||||
@@ -160,7 +191,11 @@ abstract class Plugin {
|
||||
/**
|
||||
* Override to add repository setting tabs.
|
||||
*/
|
||||
def repositorySettingTabs(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[(RepositoryInfo, Context) => Option[Link]] = Nil
|
||||
def repositorySettingTabs(
|
||||
registry: PluginRegistry,
|
||||
context: ServletContext,
|
||||
settings: SystemSettings
|
||||
): Seq[(RepositoryInfo, Context) => Option[Link]] = Nil
|
||||
|
||||
/**
|
||||
* Override to add profile tabs.
|
||||
@@ -170,7 +205,11 @@ abstract class Plugin {
|
||||
/**
|
||||
* Override to add profile tabs.
|
||||
*/
|
||||
def profileTabs(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[(Account, Context) => Option[Link]] = Nil
|
||||
def profileTabs(
|
||||
registry: PluginRegistry,
|
||||
context: ServletContext,
|
||||
settings: SystemSettings
|
||||
): Seq[(Account, Context) => Option[Link]] = Nil
|
||||
|
||||
/**
|
||||
* Override to add system setting menus.
|
||||
@@ -180,7 +219,11 @@ abstract class Plugin {
|
||||
/**
|
||||
* Override to add system setting menus.
|
||||
*/
|
||||
def systemSettingMenus(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[(Context) => Option[Link]] = Nil
|
||||
def systemSettingMenus(
|
||||
registry: PluginRegistry,
|
||||
context: ServletContext,
|
||||
settings: SystemSettings
|
||||
): Seq[(Context) => Option[Link]] = Nil
|
||||
|
||||
/**
|
||||
* Override to add account setting menus.
|
||||
@@ -190,7 +233,11 @@ abstract class Plugin {
|
||||
/**
|
||||
* Override to add account setting menus.
|
||||
*/
|
||||
def accountSettingMenus(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[(Context) => Option[Link]] = Nil
|
||||
def accountSettingMenus(
|
||||
registry: PluginRegistry,
|
||||
context: ServletContext,
|
||||
settings: SystemSettings
|
||||
): Seq[(Context) => Option[Link]] = Nil
|
||||
|
||||
/**
|
||||
* Override to add dashboard tabs.
|
||||
@@ -200,7 +247,11 @@ abstract class Plugin {
|
||||
/**
|
||||
* Override to add dashboard tabs.
|
||||
*/
|
||||
def dashboardTabs(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[(Context) => Option[Link]] = Nil
|
||||
def dashboardTabs(
|
||||
registry: PluginRegistry,
|
||||
context: ServletContext,
|
||||
settings: SystemSettings
|
||||
): Seq[(Context) => Option[Link]] = Nil
|
||||
|
||||
/**
|
||||
* Override to add issue sidebars.
|
||||
@@ -210,7 +261,11 @@ abstract class Plugin {
|
||||
/**
|
||||
* Override to add issue sidebars.
|
||||
*/
|
||||
def issueSidebars(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[(Issue, RepositoryInfo, Context) => Option[Html]] = Nil
|
||||
def issueSidebars(
|
||||
registry: PluginRegistry,
|
||||
context: ServletContext,
|
||||
settings: SystemSettings
|
||||
): Seq[(Issue, RepositoryInfo, Context) => Option[Html]] = Nil
|
||||
|
||||
/**
|
||||
* Override to add assets mappings.
|
||||
@@ -220,7 +275,11 @@ abstract class Plugin {
|
||||
/**
|
||||
* Override to add assets mappings.
|
||||
*/
|
||||
def assetsMappings(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[(String, String)] = Nil
|
||||
def assetsMappings(
|
||||
registry: PluginRegistry,
|
||||
context: ServletContext,
|
||||
settings: SystemSettings
|
||||
): Seq[(String, String)] = Nil
|
||||
|
||||
/**
|
||||
* Override to add text decorators.
|
||||
@@ -230,7 +289,8 @@ abstract class Plugin {
|
||||
/**
|
||||
* Override to add text decorators.
|
||||
*/
|
||||
def textDecorators(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[TextDecorator] = Nil
|
||||
def textDecorators(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[TextDecorator] =
|
||||
Nil
|
||||
|
||||
/**
|
||||
* Override to add suggestion provider.
|
||||
@@ -240,7 +300,11 @@ abstract class Plugin {
|
||||
/**
|
||||
* Override to add suggestion provider.
|
||||
*/
|
||||
def suggestionProviders(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[SuggestionProvider] = Nil
|
||||
def suggestionProviders(
|
||||
registry: PluginRegistry,
|
||||
context: ServletContext,
|
||||
settings: SystemSettings
|
||||
): Seq[SuggestionProvider] = Nil
|
||||
|
||||
/**
|
||||
* Override to add ssh command providers.
|
||||
@@ -250,25 +314,32 @@ abstract class Plugin {
|
||||
/**
|
||||
* Override to add ssh command providers.
|
||||
*/
|
||||
def sshCommandProviders(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[PartialFunction[String, Command]] = Nil
|
||||
|
||||
def sshCommandProviders(
|
||||
registry: PluginRegistry,
|
||||
context: ServletContext,
|
||||
settings: SystemSettings
|
||||
): Seq[PartialFunction[String, Command]] = Nil
|
||||
|
||||
/**
|
||||
* This method is invoked in initialization of plugin system.
|
||||
* Register plugin functionality to PluginRegistry.
|
||||
*/
|
||||
def initialize(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Unit = {
|
||||
(images ++ images(registry, context, settings)).foreach { case (id, in) =>
|
||||
registry.addImage(id, in)
|
||||
(images ++ images(registry, context, settings)).foreach {
|
||||
case (id, in) =>
|
||||
registry.addImage(id, in)
|
||||
}
|
||||
(controllers ++ controllers(registry, context, settings)).foreach { case (path, controller) =>
|
||||
registry.addController(path, controller)
|
||||
(controllers ++ controllers(registry, context, settings)).foreach {
|
||||
case (path, controller) =>
|
||||
registry.addController(path, controller)
|
||||
}
|
||||
(javaScripts ++ javaScripts(registry, context, settings)).foreach { case (path, script) =>
|
||||
registry.addJavaScript(path, script)
|
||||
(javaScripts ++ javaScripts(registry, context, settings)).foreach {
|
||||
case (path, script) =>
|
||||
registry.addJavaScript(path, script)
|
||||
}
|
||||
(renderers ++ renderers(registry, context, settings)).foreach { case (extension, renderer) =>
|
||||
registry.addRenderer(extension, renderer)
|
||||
(renderers ++ renderers(registry, context, settings)).foreach {
|
||||
case (extension, renderer) =>
|
||||
registry.addRenderer(extension, renderer)
|
||||
}
|
||||
(repositoryRoutings ++ repositoryRoutings(registry, context, settings)).foreach { routing =>
|
||||
registry.addRepositoryRouting(routing)
|
||||
@@ -345,7 +416,7 @@ abstract class Plugin {
|
||||
* Helper method to get a resource from classpath.
|
||||
*/
|
||||
protected def fromClassPath(path: String): Array[Byte] =
|
||||
using(getClass.getClassLoader.getResourceAsStream(path)){ in =>
|
||||
using(getClass.getClassLoader.getResourceAsStream(path)) { in =>
|
||||
val bytes = new Array[Byte](in.available)
|
||||
in.read(bytes)
|
||||
bytes
|
||||
|
||||
@@ -70,7 +70,7 @@ class PluginRegistry {
|
||||
|
||||
@deprecated("Use addImage(id: String, bytes: Array[Byte]) instead", "3.4.0")
|
||||
def addImage(id: String, in: InputStream): Unit = {
|
||||
val bytes = using(in){ in =>
|
||||
val bytes = using(in) { in =>
|
||||
val bytes = new Array[Byte](in.available)
|
||||
in.read(bytes)
|
||||
bytes
|
||||
@@ -87,9 +87,11 @@ class PluginRegistry {
|
||||
|
||||
def getControllers(): Seq[(ControllerBase, String)] = controllers.asScala.toSeq
|
||||
|
||||
def addJavaScript(path: String, script: String): Unit = javaScripts.add((path, script)) //javaScripts += ((path, script))
|
||||
def addJavaScript(path: String, script: String): Unit =
|
||||
javaScripts.add((path, script)) //javaScripts += ((path, script))
|
||||
|
||||
def getJavaScript(currentPath: String): List[String] = javaScripts.asScala.filter(x => currentPath.matches(x._1)).toList.map(_._2)
|
||||
def getJavaScript(currentPath: String): List[String] =
|
||||
javaScripts.asScala.filter(x => currentPath.matches(x._1)).toList.map(_._2)
|
||||
|
||||
def addRenderer(extension: String, renderer: Renderer): Unit = renderers.put(extension, renderer)
|
||||
|
||||
@@ -129,7 +131,8 @@ class PluginRegistry {
|
||||
|
||||
def getPullRequestHooks: Seq[PullRequestHook] = pullRequestHooks.asScala.toSeq
|
||||
|
||||
def addRepositoryHeader(repositoryHeader: (RepositoryInfo, Context) => Option[Html]): Unit = repositoryHeaders.add(repositoryHeader)
|
||||
def addRepositoryHeader(repositoryHeader: (RepositoryInfo, Context) => Option[Html]): Unit =
|
||||
repositoryHeaders.add(repositoryHeader)
|
||||
|
||||
def getRepositoryHeaders: Seq[(RepositoryInfo, Context) => Option[Html]] = repositoryHeaders.asScala.toSeq
|
||||
|
||||
@@ -137,11 +140,13 @@ class PluginRegistry {
|
||||
|
||||
def getGlobalMenus: Seq[(Context) => Option[Link]] = globalMenus.asScala.toSeq
|
||||
|
||||
def addRepositoryMenu(repositoryMenu: (RepositoryInfo, Context) => Option[Link]): Unit = repositoryMenus.add(repositoryMenu)
|
||||
def addRepositoryMenu(repositoryMenu: (RepositoryInfo, Context) => Option[Link]): Unit =
|
||||
repositoryMenus.add(repositoryMenu)
|
||||
|
||||
def getRepositoryMenus: Seq[(RepositoryInfo, Context) => Option[Link]] = repositoryMenus.asScala.toSeq
|
||||
|
||||
def addRepositorySettingTab(repositorySettingTab: (RepositoryInfo, Context) => Option[Link]): Unit = repositorySettingTabs.add(repositorySettingTab)
|
||||
def addRepositorySettingTab(repositorySettingTab: (RepositoryInfo, Context) => Option[Link]): Unit =
|
||||
repositorySettingTabs.add(repositorySettingTab)
|
||||
|
||||
def getRepositorySettingTabs: Seq[(RepositoryInfo, Context) => Option[Link]] = repositorySettingTabs.asScala.toSeq
|
||||
|
||||
@@ -149,11 +154,13 @@ class PluginRegistry {
|
||||
|
||||
def getProfileTabs: Seq[(Account, Context) => Option[Link]] = profileTabs.asScala.toSeq
|
||||
|
||||
def addSystemSettingMenu(systemSettingMenu: (Context) => Option[Link]): Unit = systemSettingMenus.add(systemSettingMenu)
|
||||
def addSystemSettingMenu(systemSettingMenu: (Context) => Option[Link]): Unit =
|
||||
systemSettingMenus.add(systemSettingMenu)
|
||||
|
||||
def getSystemSettingMenus: Seq[(Context) => Option[Link]] = systemSettingMenus.asScala.toSeq
|
||||
|
||||
def addAccountSettingMenu(accountSettingMenu: (Context) => Option[Link]): Unit = accountSettingMenus.add(accountSettingMenu)
|
||||
def addAccountSettingMenu(accountSettingMenu: (Context) => Option[Link]): Unit =
|
||||
accountSettingMenus.add(accountSettingMenu)
|
||||
|
||||
def getAccountSettingMenus: Seq[(Context) => Option[Link]] = accountSettingMenus.asScala.toSeq
|
||||
|
||||
@@ -161,7 +168,8 @@ class PluginRegistry {
|
||||
|
||||
def getDashboardTabs: Seq[(Context) => Option[Link]] = dashboardTabs.asScala.toSeq
|
||||
|
||||
def addIssueSidebar(issueSidebar: (Issue, RepositoryInfo, Context) => Option[Html]): Unit = issueSidebars.add(issueSidebar)
|
||||
def addIssueSidebar(issueSidebar: (Issue, RepositoryInfo, Context) => Option[Html]): Unit =
|
||||
issueSidebars.add(issueSidebar)
|
||||
|
||||
def getIssueSidebars: Seq[(Issue, RepositoryInfo, Context) => Option[Html]] = issueSidebars.asScala.toSeq
|
||||
|
||||
@@ -177,7 +185,8 @@ class PluginRegistry {
|
||||
|
||||
def getSuggestionProviders: Seq[SuggestionProvider] = suggestionProviders.asScala.toSeq
|
||||
|
||||
def addSshCommandProvider(sshCommandProvider: PartialFunction[String, Command]): Unit = sshCommandProviders.add(sshCommandProvider)
|
||||
def addSshCommandProvider(sshCommandProvider: PartialFunction[String, Command]): Unit =
|
||||
sshCommandProviders.add(sshCommandProvider)
|
||||
|
||||
def getSshCommandProviders: Seq[PartialFunction[String, Command]] = sshCommandProviders.asScala.toSeq
|
||||
}
|
||||
@@ -212,37 +221,44 @@ object PluginRegistry {
|
||||
/**
|
||||
* Uninstall a specified plugin.
|
||||
*/
|
||||
def uninstall(pluginId: String, context: ServletContext, settings: SystemSettings, conn: java.sql.Connection): Unit = synchronized {
|
||||
instance.getPlugins()
|
||||
.collect { case plugin if plugin.pluginId == pluginId => plugin }
|
||||
.foreach { plugin =>
|
||||
def uninstall(pluginId: String, context: ServletContext, settings: SystemSettings, conn: java.sql.Connection): Unit =
|
||||
synchronized {
|
||||
instance
|
||||
.getPlugins()
|
||||
.collect { case plugin if plugin.pluginId == pluginId => plugin }
|
||||
.foreach { plugin =>
|
||||
// try {
|
||||
// plugin.pluginClass.uninstall(instance, context, settings)
|
||||
// } catch {
|
||||
// case e: Exception =>
|
||||
// logger.error(s"Error during uninstalling plugin: ${plugin.pluginJar.getName}", e)
|
||||
// }
|
||||
shutdown(context, settings)
|
||||
plugin.pluginJar.delete()
|
||||
instance = new PluginRegistry()
|
||||
initialize(context, settings, conn)
|
||||
}
|
||||
}
|
||||
shutdown(context, settings)
|
||||
plugin.pluginJar.delete()
|
||||
instance = new PluginRegistry()
|
||||
initialize(context, settings, conn)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Install a plugin from a specified jar file.
|
||||
*/
|
||||
def install(file: File, context: ServletContext, settings: SystemSettings, conn: java.sql.Connection): Unit = synchronized {
|
||||
shutdown(context, settings)
|
||||
FileUtils.copyFile(file, new File(PluginHome, file.getName))
|
||||
instance = new PluginRegistry()
|
||||
initialize(context, settings, conn)
|
||||
}
|
||||
def install(file: File, context: ServletContext, settings: SystemSettings, conn: java.sql.Connection): Unit =
|
||||
synchronized {
|
||||
shutdown(context, settings)
|
||||
FileUtils.copyFile(file, new File(PluginHome, file.getName))
|
||||
instance = new PluginRegistry()
|
||||
initialize(context, settings, conn)
|
||||
}
|
||||
|
||||
private def listPluginJars(dir: File): Seq[File] = {
|
||||
dir.listFiles(new FilenameFilter {
|
||||
override def accept(dir: File, name: String): Boolean = name.endsWith(".jar")
|
||||
}).toSeq.sortBy(_.getName).reverse
|
||||
dir
|
||||
.listFiles(new FilenameFilter {
|
||||
override def accept(dir: File, name: String): Boolean = name.endsWith(".jar")
|
||||
})
|
||||
.toSeq
|
||||
.sortBy(_.getName)
|
||||
.reverse
|
||||
}
|
||||
|
||||
lazy val extraPluginDir: Option[String] = Option(System.getProperty("gitbucket.pluginDir"))
|
||||
@@ -256,13 +272,17 @@ object PluginRegistry {
|
||||
|
||||
// Clean installed directory
|
||||
val installedDir = new File(PluginHome, ".installed")
|
||||
if(installedDir.exists){
|
||||
if (installedDir.exists) {
|
||||
FileUtils.deleteDirectory(installedDir)
|
||||
}
|
||||
installedDir.mkdirs()
|
||||
|
||||
val pluginJars = listPluginJars(pluginDir)
|
||||
val extraJars = extraPluginDir.map { extraDir => listPluginJars(new File(extraDir)) }.getOrElse(Nil)
|
||||
val extraJars = extraPluginDir
|
||||
.map { extraDir =>
|
||||
listPluginJars(new File(extraDir))
|
||||
}
|
||||
.getOrElse(Nil)
|
||||
|
||||
(extraJars ++ pluginJars).foreach { pluginJar =>
|
||||
val installedJar = new File(installedDir, pluginJar.getName)
|
||||
@@ -283,26 +303,32 @@ object PluginRegistry {
|
||||
case None => {
|
||||
// Migration
|
||||
val solidbase = new Solidbase()
|
||||
solidbase.migrate(conn, classLoader, DatabaseConfig.liquiDriver, new Module(plugin.pluginId, plugin.versions: _*))
|
||||
solidbase
|
||||
.migrate(conn, classLoader, DatabaseConfig.liquiDriver, new Module(plugin.pluginId, plugin.versions: _*))
|
||||
conn.commit()
|
||||
|
||||
// Check database version
|
||||
val databaseVersion = manager.getCurrentVersion(plugin.pluginId)
|
||||
val pluginVersion = plugin.versions.last.getVersion
|
||||
if (databaseVersion != pluginVersion) {
|
||||
throw new IllegalStateException(s"Plugin version is ${pluginVersion}, but database version is ${databaseVersion}")
|
||||
throw new IllegalStateException(
|
||||
s"Plugin version is ${pluginVersion}, but database version is ${databaseVersion}"
|
||||
)
|
||||
}
|
||||
|
||||
// Initialize
|
||||
plugin.initialize(instance, context, settings)
|
||||
instance.addPlugin(PluginInfo(
|
||||
pluginId = plugin.pluginId,
|
||||
pluginName = plugin.pluginName,
|
||||
pluginVersion = plugin.versions.last.getVersion,
|
||||
description = plugin.description,
|
||||
pluginClass = plugin,
|
||||
pluginJar = pluginJar,
|
||||
classLoader = classLoader
|
||||
))
|
||||
instance.addPlugin(
|
||||
PluginInfo(
|
||||
pluginId = plugin.pluginId,
|
||||
pluginName = plugin.pluginName,
|
||||
pluginVersion = plugin.versions.last.getVersion,
|
||||
description = plugin.description,
|
||||
pluginClass = plugin,
|
||||
pluginJar = pluginJar,
|
||||
classLoader = classLoader
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
@@ -310,13 +336,13 @@ object PluginRegistry {
|
||||
}
|
||||
}
|
||||
|
||||
if(watcher == null){
|
||||
if (watcher == null) {
|
||||
watcher = new PluginWatchThread(context, PluginHome)
|
||||
watcher.start()
|
||||
}
|
||||
|
||||
extraPluginDir.foreach { extraDir =>
|
||||
if(extraWatcher == null){
|
||||
if (extraWatcher == null) {
|
||||
extraWatcher = new PluginWatchThread(context, extraDir)
|
||||
extraWatcher.start()
|
||||
}
|
||||
@@ -327,11 +353,11 @@ object PluginRegistry {
|
||||
instance.getPlugins().foreach { plugin =>
|
||||
try {
|
||||
plugin.pluginClass.shutdown(instance, context, settings)
|
||||
if(watcher != null){
|
||||
if (watcher != null) {
|
||||
watcher.interrupt()
|
||||
watcher = null
|
||||
}
|
||||
if(extraWatcher != null){
|
||||
if (extraWatcher != null) {
|
||||
extraWatcher.interrupt()
|
||||
extraWatcher = null
|
||||
}
|
||||
@@ -379,17 +405,19 @@ class PluginWatchThread(context: ServletContext, dir: String) extends Thread wit
|
||||
|
||||
override def run(): Unit = {
|
||||
val path = Paths.get(dir)
|
||||
if(!Files.exists(path)){
|
||||
if (!Files.exists(path)) {
|
||||
Files.createDirectories(path)
|
||||
}
|
||||
val fs = path.getFileSystem
|
||||
val watcher = fs.newWatchService
|
||||
|
||||
val watchKey = path.register(watcher,
|
||||
val watchKey = path.register(
|
||||
watcher,
|
||||
StandardWatchEventKinds.ENTRY_CREATE,
|
||||
StandardWatchEventKinds.ENTRY_MODIFY,
|
||||
StandardWatchEventKinds.ENTRY_DELETE,
|
||||
StandardWatchEventKinds.OVERFLOW)
|
||||
StandardWatchEventKinds.OVERFLOW
|
||||
)
|
||||
|
||||
logger.info("Start PluginWatchThread: " + path)
|
||||
|
||||
@@ -399,13 +427,13 @@ class PluginWatchThread(context: ServletContext, dir: String) extends Thread wit
|
||||
val events = detectedWatchKey.pollEvents.asScala.filter { e =>
|
||||
e.context.toString != ".installed" && !e.context.toString.endsWith(".bak")
|
||||
}
|
||||
if(events.nonEmpty){
|
||||
if (events.nonEmpty) {
|
||||
events.foreach { event =>
|
||||
logger.info(event.kind + ": " + event.context)
|
||||
}
|
||||
new Thread {
|
||||
override def run(): Unit = {
|
||||
gitbucket.core.servlet.Database() withTransaction { session =>
|
||||
gitbucket.core.servlet.Database() withTransaction { session =>
|
||||
logger.info("Reloading plugins...")
|
||||
PluginRegistry.reload(context, loadSystemSettings(), session.conn)
|
||||
logger.info("Reloading finished.")
|
||||
|
||||
@@ -15,7 +15,7 @@ object PluginRepository {
|
||||
lazy val LocalRepositoryIndexFile = new java.io.File(LocalRepositoryDir, "plugins.json")
|
||||
|
||||
def getPlugins(): Seq[PluginMetadata] = {
|
||||
if(LocalRepositoryIndexFile.exists){
|
||||
if (LocalRepositoryIndexFile.exists) {
|
||||
parsePluginJson(FileUtils.readFileToString(LocalRepositoryIndexFile, "UTF-8"))
|
||||
} else Nil
|
||||
}
|
||||
@@ -29,13 +29,14 @@ case class PluginMetadata(
|
||||
description: String,
|
||||
versions: Seq[VersionDef],
|
||||
default: Boolean = false
|
||||
){
|
||||
) {
|
||||
lazy val latestVersion: VersionDef = versions.last
|
||||
}
|
||||
|
||||
case class VersionDef(
|
||||
version: String,
|
||||
file: String,
|
||||
url: String,
|
||||
range: String
|
||||
)
|
||||
|
||||
) {
|
||||
lazy val file = url.substring(url.lastIndexOf("/") + 1)
|
||||
}
|
||||
|
||||
@@ -6,10 +6,12 @@ import profile.api._
|
||||
|
||||
trait ReceiveHook {
|
||||
|
||||
def preReceive(owner: String, repository: String, receivePack: ReceivePack, command: ReceiveCommand, pusher: String)
|
||||
(implicit session: Session): Option[String] = None
|
||||
def preReceive(owner: String, repository: String, receivePack: ReceivePack, command: ReceiveCommand, pusher: String)(
|
||||
implicit session: Session
|
||||
): Option[String] = None
|
||||
|
||||
def postReceive(owner: String, repository: String, receivePack: ReceivePack, command: ReceiveCommand, pusher: String)
|
||||
(implicit session: Session): Unit = ()
|
||||
def postReceive(owner: String, repository: String, receivePack: ReceivePack, command: ReceiveCommand, pusher: String)(
|
||||
implicit session: Session
|
||||
): Unit = ()
|
||||
|
||||
}
|
||||
|
||||
@@ -21,14 +21,16 @@ trait Renderer {
|
||||
object MarkdownRenderer extends Renderer {
|
||||
override def render(request: RenderRequest): Html = {
|
||||
import request._
|
||||
Html(Markdown.toHtml(
|
||||
markdown = fileContent,
|
||||
repository = repository,
|
||||
enableWikiLink = enableWikiLink,
|
||||
enableRefsLink = enableRefsLink,
|
||||
enableAnchor = enableAnchor,
|
||||
enableLineBreaks = false
|
||||
)(context))
|
||||
Html(
|
||||
Markdown.toHtml(
|
||||
markdown = fileContent,
|
||||
repository = repository,
|
||||
enableWikiLink = enableWikiLink,
|
||||
enableRefsLink = enableRefsLink,
|
||||
enableAnchor = enableAnchor,
|
||||
enableLineBreaks = false
|
||||
)(context)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -73,7 +73,9 @@ trait SuggestionProvider {
|
||||
*
|
||||
* Each element can be accessed as `option` in `template()` or `replace()` method.
|
||||
*/
|
||||
def options(repository: RepositoryInfo): Seq[(String, String)] = values(repository).map { value => (value, value) }
|
||||
def options(repository: RepositoryInfo): Seq[(String, String)] = values(repository).map { value =>
|
||||
(value, value)
|
||||
}
|
||||
|
||||
/**
|
||||
* JavaScript fragment to generate a label of completion proposal. The default is: `option.label`.
|
||||
|
||||
@@ -2,12 +2,11 @@ package gitbucket.core.service
|
||||
|
||||
import gitbucket.core.model.Profile._
|
||||
import gitbucket.core.model.Profile.profile.blockingApi._
|
||||
import gitbucket.core.model.{Account, AccessToken}
|
||||
import gitbucket.core.model.{AccessToken, Account}
|
||||
import gitbucket.core.util.StringUtil
|
||||
|
||||
import scala.util.Random
|
||||
|
||||
|
||||
trait AccessTokenService {
|
||||
|
||||
def makeAccessTokenString: String = {
|
||||
@@ -27,13 +26,10 @@ trait AccessTokenService {
|
||||
|
||||
do {
|
||||
token = makeAccessTokenString
|
||||
hash = tokenToHash(token)
|
||||
hash = tokenToHash(token)
|
||||
} while (AccessTokens.filter(_.tokenHash === hash.bind).exists.run)
|
||||
|
||||
val newToken = AccessToken(
|
||||
userName = userName,
|
||||
note = note,
|
||||
tokenHash = hash)
|
||||
val newToken = AccessToken(userName = userName, note = note, tokenHash = hash)
|
||||
val tokenId = (AccessTokens returning AccessTokens.map(_.accessTokenId)) insert newToken
|
||||
(tokenId, token)
|
||||
}
|
||||
@@ -41,13 +37,19 @@ trait AccessTokenService {
|
||||
def getAccountByAccessToken(token: String)(implicit s: Session): Option[Account] =
|
||||
Accounts
|
||||
.join(AccessTokens)
|
||||
.filter { case (ac, t) => (ac.userName === t.userName) && (t.tokenHash === tokenToHash(token).bind) && (ac.removed === false.bind) }
|
||||
.map { case (ac, t) => ac }
|
||||
.filter {
|
||||
case (ac, t) =>
|
||||
(ac.userName === t.userName) && (t.tokenHash === tokenToHash(token).bind) && (ac.removed === false.bind)
|
||||
}
|
||||
.map { case (ac, t) => ac }
|
||||
.firstOption
|
||||
|
||||
def getAccessTokens(userName: String)(implicit s: Session): List[AccessToken] =
|
||||
AccessTokens.filter(_.userName === userName.bind).sortBy(_.accessTokenId.desc).list
|
||||
|
||||
def hasAccessToken(userName: String)(implicit s: Session): Boolean =
|
||||
AccessTokens.filter(_.userName === userName.bind).exists.run
|
||||
|
||||
def deleteAccessToken(userName: String, accessTokenId: Int)(implicit s: Session): Unit =
|
||||
AccessTokens filter (t => t.userName === userName.bind && t.accessTokenId === accessTokenId) delete
|
||||
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
package gitbucket.core.service
|
||||
|
||||
import gitbucket.core.model.Profile.profile.blockingApi._
|
||||
import gitbucket.core.model.Profile.{AccountFederations, Accounts}
|
||||
import gitbucket.core.model.{Account, AccountFederation}
|
||||
import gitbucket.core.util.SyntaxSugars.~
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
trait AccountFederationService {
|
||||
self: AccountService =>
|
||||
|
||||
private val logger = LoggerFactory.getLogger(classOf[AccountFederationService])
|
||||
|
||||
/**
|
||||
* Get or create a user account federated with OIDC or SAML IdP.
|
||||
*
|
||||
* @param issuer Issuer
|
||||
* @param subject Subject
|
||||
* @param mailAddress Mail address
|
||||
* @param preferredUserName Username (if this is none, username will be generated from the mail address)
|
||||
* @param fullName Fullname (defaults to username)
|
||||
* @return Account
|
||||
*/
|
||||
def getOrCreateFederatedUser(
|
||||
issuer: String,
|
||||
subject: String,
|
||||
mailAddress: String,
|
||||
preferredUserName: Option[String],
|
||||
fullName: Option[String]
|
||||
)(implicit s: Session): Option[Account] =
|
||||
getAccountByFederation(issuer, subject) match {
|
||||
case Some(account) if !account.isRemoved =>
|
||||
Some(account)
|
||||
case Some(account) =>
|
||||
logger.info(s"Federated user found but disabled: userName=${account.userName}, isRemoved=${account.isRemoved}")
|
||||
None
|
||||
case None =>
|
||||
findAvailableUserName(preferredUserName, mailAddress) flatMap { userName =>
|
||||
createAccount(userName, "[DUMMY]", fullName.getOrElse(userName), mailAddress, isAdmin = false, None, None)
|
||||
createAccountFederation(issuer, subject, userName)
|
||||
getAccountByUserName(userName)
|
||||
}
|
||||
}
|
||||
|
||||
private def extractSafeStringForUserName(s: String) = """^[a-zA-Z0-9][a-zA-Z0-9\-_.]*""".r.findPrefixOf(s)
|
||||
|
||||
/**
|
||||
* Find an available username from the preferred username or mail address.
|
||||
*
|
||||
* @param mailAddress Mail address
|
||||
* @param preferredUserName Username
|
||||
* @return Available username
|
||||
*/
|
||||
def findAvailableUserName(preferredUserName: Option[String], mailAddress: String)(
|
||||
implicit s: Session
|
||||
): Option[String] = {
|
||||
preferredUserName
|
||||
.flatMap(n => extractSafeStringForUserName(n))
|
||||
.orElse(extractSafeStringForUserName(mailAddress)) match {
|
||||
case Some(safeUserName) =>
|
||||
getAccountByUserName(safeUserName, includeRemoved = true) match {
|
||||
case None => Some(safeUserName)
|
||||
case Some(_) =>
|
||||
logger.info(
|
||||
s"User ($safeUserName) already exists. preferredUserName=$preferredUserName, mailAddress=$mailAddress"
|
||||
)
|
||||
None
|
||||
}
|
||||
case None =>
|
||||
logger.info(s"Could not extract username from preferredUserName=$preferredUserName, mailAddress=$mailAddress")
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
def getAccountByFederation(issuer: String, subject: String)(implicit s: Session): Option[Account] =
|
||||
AccountFederations
|
||||
.filter(_.byPrimaryKey(issuer, subject))
|
||||
.join(Accounts)
|
||||
.on { case af ~ ac => af.userName === ac.userName }
|
||||
.map { case _ ~ ac => ac }
|
||||
.firstOption
|
||||
|
||||
def hasAccountFederation(userName: String)(implicit s: Session): Boolean =
|
||||
AccountFederations.filter(_.userName === userName.bind).exists.run
|
||||
|
||||
def createAccountFederation(issuer: String, subject: String, userName: String)(implicit s: Session): Unit =
|
||||
AccountFederations insert AccountFederation(issuer, subject, userName)
|
||||
}
|
||||
|
||||
object AccountFederationService extends AccountFederationService with AccountService
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user