aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--dockerfiles/README.md42
-rwxr-xr-xdockerfiles/build.sh57
-rw-r--r--dockerfiles/images/gogits/Dockerfile11
-rw-r--r--dockerfiles/images/gogits/deploy.sh39
-rw-r--r--dockerfiles/images/memcache/.gitkeep (renamed from dockerfiles/images/memcached/.gitkeep)0
-rw-r--r--dockerfiles/images/memcache/Dockerfile26
-rw-r--r--dockerfiles/images/mysql/Dockerfile2
-rw-r--r--dockerfiles/images/postgres/Dockerfile2
-rw-r--r--dockerfiles/images/redis/Dockerfile14
-rw-r--r--models/git.go464
-rw-r--r--modules/oauth2/oauth2.go233
-rw-r--r--modules/oauth2/oauth2_test.go162
-rw-r--r--templates/mail/auth/reset_password.html25
-rw-r--r--templates/user/active.tmpl36
14 files changed, 1060 insertions, 53 deletions
diff --git a/dockerfiles/README.md b/dockerfiles/README.md
index 814d36f6..cbf582d0 100644
--- a/dockerfiles/README.md
+++ b/dockerfiles/README.md
@@ -1,16 +1,14 @@
-### Gogs Install With Docker
+### Install Gogs With Docker
-
-
-#### Gogs With MySQL
-
-Deploying gogs in [Docker](http://www.docker.io/) is just as easy as eating a pie, what you do is just open the `dockerfiles/build.sh` file, replace the configs:
+Deplying gogs in [Docker](http://www.docker.io/) is just as easy as eating a pie, what you do is just open the `dockerfiles/build.sh` file, replace the confis:
```
-DB_TYPE="mysql"
-DB_PASSWORD="YOUR_MYSQL_PASSWORD"
-DB_RUN_NAME="YOUR_MYSQL_RUN_NAME"
-HOST_PORT="YOUR_HOST_PORT"
+DB_TYPE="YOUR_DB_TYPE" # type of database, support 'mysql' and 'postgres'
+MEM_TYPE="YOUR_MEM_TYPE" # type of memory database, support 'redis' and 'memcache'
+DB_PASSWORD="YOUR_DB_PASSWORD" # The database password.
+DB_RUN_NAME="YOUR_DB_RUN_NAME" # The --name option value when run the database image.
+MEM_RUN_NAME="YOUR_MEM_RUN_NAME" # The --name option value when run the mem database image.
+HOST_PORT="YOUR_HOST_PORT" # The port on host, which will be redirected to the port 3000 inside gogs container.
```
And run:
@@ -23,13 +21,13 @@ The build might take some time, just be paient. After it finishes, you will rece
```
Now we have the MySQL image(running) and gogs image, use the follow command to start gogs service( the content might be different, according to your own configs):
- docker run -i -t --link gogs_mysql:db -p 3333:3000 gogs/gogits
+ docker run -i -t --link YOUR_DB_RUN_NAME:db --link YOUR_MEM_RUN_NAME:mem -p YOUR_HOST_PORT:3000 gogits/gogs
```
Just follow the message, run:
```
- docker run -i -t --link gogs_mysql:db -p 3333:3000 gogs/gogits
+ docker run -i -t --link YOUR_DB_RUN_NAME:db --link YOUR_MEM_RUN_NAME:mem -p YOUR_HOST_PORT:3000 gogits/gogs
```
Now we have gogs running! Open the browser and navigate to:
@@ -39,22 +37,4 @@ http://YOUR_HOST_IP:YOUR_HOST_PORT
```
Let's 'gogs'!
-
-#### Gogs With PostgreSQL
-
-Installing Gogs with PostgreSQL is nearly the same with installing it with MySQL. What you do is just change the DB_TYPE in build.sh to 'postgres'.
-
-#### Gogs, MySQL With Redis
-
-
-#### Gogs, MySQL With Memcached
-
-
-#### Gogs, PostgreSQL With Redis
-
-
-#### Gogs, PostgreSQL With Memcached
-
-
-
-
+Ouya~ \ No newline at end of file
diff --git a/dockerfiles/build.sh b/dockerfiles/build.sh
index 272424c8..83f7e9a5 100755
--- a/dockerfiles/build.sh
+++ b/dockerfiles/build.sh
@@ -1,29 +1,68 @@
# Configs of the docker images, you might have specify your own configs here.
-# type of database, support 'mysql' and 'postgres'
-DB_TYPE="postgres"
-DB_PASSWORD="YOUR_DB_PASSWORD"
-DB_RUN_NAME="YOUR_DB_RUN_NAME"
-HOST_PORT="YOUR_HOST_PORT"
+
+DB_TYPE="YOUR_DB_TYPE" # type of database, support 'mysql' and 'postgres'
+MEM_TYPE="YOUR_MEM_TYPE" # type of memory database, support 'redis' and 'memcache'
+DB_PASSWORD="YOUR_DB_PASSWORD" # The database password.
+DB_RUN_NAME="YOUR_DB_RUN_NAME" # The --name option value when run the database image.
+MEM_RUN_NAME="YOUR_MEM_RUN_NAME" # The --name option value when run the mem database image.
+HOST_PORT="YOUR_HOST_PORT" # The port on host, which will be redirected to the port 3000 inside gogs container.
+
+# apt source, you can select 'nchc'(mirror in Taiwan) or 'aliyun'(best for mainlance China users) according to your network, if you could connect to the official unbunt mirror in a fast speed, just leave it to "".
+APT_SOURCE=""
# Replace the database root password in database image Dockerfile.
sed -i "s/THE_DB_PASSWORD/$DB_PASSWORD/g" images/$DB_TYPE/Dockerfile
# Replace the database root password in gogits image deploy.sh file.
sed -i "s/THE_DB_PASSWORD/$DB_PASSWORD/g" images/gogits/deploy.sh
+# Replace the apt source in gogits image Dockerfile.
+sed -i "s/#$APT_SOURCE#//" images/gogits/Dockerfile
+# Uncomment the installation of database lib in gogs Dockerfile
+sed -i "s/#$DB_TYPE#//" images/gogits/Dockerfile
# Replace the database type in gogits image deploy.sh file.
sed -i "s/THE_DB_TYPE/$DB_TYPE/g" images/gogits/deploy.sh
+if [ $MEM_TYPE != "" ]
+ then
+ # Replace the mem configs in deploy.sh
+ sed -i "s/THE_MEM_TYPE/$MEM_TYPE/g" images/gogits/deploy.sh
+ # Uncomment the installation of go mem lib
+ sed -i "s/#$MEM_TYPE#//" images/gogits/Dockerfile
+
+ # Add the tags when get gogs
+ sed -i "s#RUN go get -u -d github.com/gogits/gogs#RUN go get -u -d -tags $MEM_TYPE github.com/gogits/gogs#g" images/gogits/Dockerfile
+ # Append the tag in gogs build
+ GOGS_BUILD_LINE=`awk '$0 ~ str{print NR}' str="go build" images/gogits/Dockerfile`
+ # Append the build tags
+ sed -i "${GOGS_BUILD_LINE}s/$/ -tags $MEM_TYPE/" images/gogits/Dockerfile
+
+ cd images/$MEM_TYPE
+ docker build -t gogits/$MEM_TYPE .
+ docker run -d --name $MEM_RUN_NAME gogits/$MEM_TYPE
+ MEM_LINK=" --link $MEM_RUN_NAME:mem "
+ cd ../../
+fi
+
# Build the database image
cd images/$DB_TYPE
-docker build -t gogs/$DB_TYPE .
+docker build -t gogits/$DB_TYPE .
#
+
+
## Build the gogits image
cd ../gogits
-docker build -t gogs/gogits .
+
+docker build -t gogits/gogs .
+
+#sed -i "s#RUN go get -u -tags $MEM_TYPE github.com/gogits/gogs#RUN go get -u github.com/gogits/gogs#g" Dockerfile
+
+# Remove the appended tags in go build line(if there is any)
+sed -i "s/ -tags $MEM_TYPE//" Dockerfile
+
#
## Run MySQL image with name
-docker run -d --name $DB_RUN_NAME gogs/$DB_TYPE
+docker run -d --name $DB_RUN_NAME gogits/$DB_TYPE
#
## Run gogits image and link it to the database image
echo "Now we have the $DB_TYPE image(running) and gogs image, use the follow command to start gogs service:"
-echo -e "\033[33m docker run -i -t --link $DB_RUN_NAME:db -p $HOST_PORT:3000 gogs/gogits \033[0m"
+echo -e "\033[33m docker run -i -t --link $DB_RUN_NAME:db $MEM_LINK -p $HOST_PORT:3000 gogits/gogs \033[0m"
diff --git a/dockerfiles/images/gogits/Dockerfile b/dockerfiles/images/gogits/Dockerfile
index 410bb9cb..25d1dd5e 100644
--- a/dockerfiles/images/gogits/Dockerfile
+++ b/dockerfiles/images/gogits/Dockerfile
@@ -3,7 +3,9 @@ MAINTAINER Meaglith Ma <genedna@gmail.com> (@genedna)
ENV DEBIAN_FRONTEND noninteractive
-RUN echo "deb http://mirrors.aliyun.com/ubuntu/ saucy main restricted" > /etc/apt/sources.list && echo "deb http://mirrors.aliyun.com/ubuntu/ saucy-updates main restricted" >> /etc/apt/sources.list && echo "deb http://mirrors.aliyun.com/ubuntu/ saucy universe" >> /etc/apt/sources.list && echo "deb http://mirrors.aliyun.com/ubuntu/ saucy-updates universe" >> /etc/apt/sources.list && echo "deb http://mirrors.aliyun.com/ubuntu/ saucy multiverse" >> /etc/apt/sources.list && echo "deb http://mirrors.aliyun.com/ubuntu/ saucy-updates multiverse" >> /etc/apt/sources.list && echo "deb http://mirrors.aliyun.com/ubuntu/ saucy-backports main restricted universe multiverse" >> /etc/apt/sources.list && echo "deb http://mirrors.aliyun.com/ubuntu/ saucy-security main restricted" >> /etc/apt/sources.list && echo "deb http://mirrors.aliyun.com/ubuntu/ saucy-security universe" >> /etc/apt/sources.list && echo "deb http://mirrors.aliyun.com/ubuntu/ saucy-security multiverse" >> /etc/apt/sources.list
+#aliyun#RUN echo "deb http://mirrors.aliyun.com/ubuntu/ saucy main restricted" > /etc/apt/sources.list && echo "deb http://mirrors.aliyun.com/ubuntu/ saucy-updates main restricted" >> /etc/apt/sources.list && echo "deb http://mirrors.aliyun.com/ubuntu/ saucy universe" >> /etc/apt/sources.list && echo "deb http://mirrors.aliyun.com/ubuntu/ saucy-updates universe" >> /etc/apt/sources.list && echo "deb http://mirrors.aliyun.com/ubuntu/ saucy multiverse" >> /etc/apt/sources.list && echo "deb http://mirrors.aliyun.com/ubuntu/ saucy-updates multiverse" >> /etc/apt/sources.list && echo "deb http://mirrors.aliyun.com/ubuntu/ saucy-backports main restricted universe multiverse" >> /etc/apt/sources.list && echo "deb http://mirrors.aliyun.com/ubuntu/ saucy-security main restricted" >> /etc/apt/sources.list && echo "deb http://mirrors.aliyun.com/ubuntu/ saucy-security universe" >> /etc/apt/sources.list && echo "deb http://mirrors.aliyun.com/ubuntu/ saucy-security multiverse" >> /etc/apt/sources.list
+
+#nchc#RUN echo "deb http://free.nchc.org.tw/ubuntu/ saucy main restricted" >> /etc/apt/source.list && echo "deb-src http://free.nchc.org.tw/ubuntu/ saucy main restricted" >> /etc/apt/source.list && echo "deb http://free.nchc.org.tw/ubuntu/ saucy-updates main restricted" >> /etc/apt/source.list && echo "deb-src http://free.nchc.org.tw/ubuntu/ saucy-updates main restricted" >> /etc/apt/source.list && echo "deb http://free.nchc.org.tw/ubuntu/ saucy universe" >> /etc/apt/source.list && echo "deb-src http://free.nchc.org.tw/ubuntu/ saucy universe" >> /etc/apt/source.list && echo "deb http://free.nchc.org.tw/ubuntu/ saucy-updates universe" >> /etc/apt/source.list && echo "deb-src http://free.nchc.org.tw/ubuntu/ saucy-updates universe" >> /etc/apt/source.list && echo "deb http://free.nchc.org.tw/ubuntu/ saucy multiverse" >> /etc/apt/source.list && echo "deb-src http://free.nchc.org.tw/ubuntu/ saucy multiverse" >> /etc/apt/source.list && echo "deb http://free.nchc.org.tw/ubuntu/ saucy-updates multiverse" >> /etc/apt/source.list && echo "deb-src http://free.nchc.org.tw/ubuntu/ saucy-updates multiverse" >> /etc/apt/source.list && echo "deb http://free.nchc.org.tw/ubuntu/ saucy-backports main restricted universe multiverse" >> /etc/apt/source.list && echo "deb-src http://free.nchc.org.tw/ubuntu/ saucy-backports main restricted universe multiverse" >> /etc/apt/source.list && echo "deb http://free.nchc.org.tw/ubuntu/ saucy-security main restricted" >> /etc/apt/source.list && echo "deb-src http://free.nchc.org.tw/ubuntu/ saucy-security main restricted" >> /etc/apt/source.list && echo "deb http://free.nchc.org.tw/ubuntu/ saucy-security universe" >> /etc/apt/source.list && echo "deb-src http://free.nchc.org.tw/ubuntu/ saucy-security universe" >> /etc/apt/source.list && echo "deb http://free.nchc.org.tw/ubuntu/ saucy-security multiverse" >> /etc/apt/source.list && echo "deb-src http://free.nchc.org.tw/ubuntu/ saucy-security multiverse" >> /etc/apt/source.list && echo "deb http://extras.ubuntu.com/ubuntu saucy main" >> /etc/apt/source.list && echo "deb-src http://extras.ubuntu.com/ubuntu saucy main" >> /etc/apt/source.list
RUN mkdir -p /go
ENV PATH /usr/local/go/bin:/go/bin:$PATH
@@ -11,14 +13,13 @@ ENV GOROOT /usr/local/go
ENV GOPATH /go
RUN apt-get update && apt-get install --yes --force-yes curl git mercurial zip wget ca-certificates build-essential
+RUN apt-get install -yq vim
RUN curl -s http://docker.u.qiniudn.com/go1.2.1.src.tar.gz | tar -v -C /usr/local -xz
RUN cd /usr/local/go/src && ./make.bash --no-clean 2>&1
-# You may need a proxy, if github is very slow.
-#RUN http_proxy=106.187.38.45:3128 go get -u github.com/gogits/gogs
-RUN go get -u github.com/gogits/gogs
-RUN cd $GOPATH/src/github.com/gogits/gogs && go build
+RUN go get -u -d github.com/gogits/gogs
+RUN cd $GOPATH/src/github.com/gogits/gogs && git checkout dev && git pull origin dev && go install && go build -tags redis
# Clean all the unused packages
RUN apt-get autoremove -y
diff --git a/dockerfiles/images/gogits/deploy.sh b/dockerfiles/images/gogits/deploy.sh
index 47e13260..94085248 100644
--- a/dockerfiles/images/gogits/deploy.sh
+++ b/dockerfiles/images/gogits/deploy.sh
@@ -4,22 +4,47 @@
DB_TYPE=THE_DB_TYPE
DB_PASSWORD=THE_DB_PASSWORD
DB_ALIAS=DB
+MEM_TYPE=THE_MEM_TYPE
+
DB_TYPE_LINE=`awk '$0 ~ str{print NR}' str="DB_TYPE = mysql" $GOPATH/src/github.com/gogits/gogs/conf/app.ini`
DB_PASSWORD_LINE=`awk '$0 ~ str{print NR+1}' str="USER = root" $GOPATH/src/github.com/gogits/gogs/conf/app.ini`
sed -i "${DB_TYPE_LINE}s/.*$/DB_TYPE = $DB_TYPE/g" $GOPATH/src/github.com/gogits/gogs/conf/app.ini
sed -i "${DB_PASSWORD_LINE}s/.*$/PASSWD = $DB_PASSWORD/g" $GOPATH/src/github.com/gogits/gogs/conf/app.ini
-if [ $DB_TYPE = "postgres" ]
+
+
+if [ $MEM_TYPE != "" ]
then
- # Add the postgres in gogs image.
- apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys B97B0AFCAA1A47F044F244A07FCC7D46ACCC4CF8
- echo "deb http://apt.postgresql.org/pub/repos/apt/ precise-pgdg main" > /etc/apt/sources.list.d/pgdg.list
- apt-get update
- apt-get -y -q install python-software-properties software-properties-common
- apt-get -y -q install postgresql-9.3 postgresql-client-9.3 postgresql-contrib-9.3
+ MEM_HOST_LINE=`awk '$0 ~ str{print NR+6}' str="ADAPTER = memory" $GOPATH/src/github.com/gogits/gogs/conf/app.ini`
+
+ _MEM_ADDR=`echo $MEM_PORT | cut -d '/' -f 3 | cut -d ':' -f 1`
+ _MEM_PORT=`echo $MEM_PORT | cut -d '/' -f 3 | cut -d ':' -f 2`
+
+ # take advantage of memory db for adapter and provider
+ sed -i "s/ADAPTER = memory/ADAPTER = $MEM_TYPE/g" $GOPATH/src/github.com/gogits/gogs/conf/app.ini
+ # Comment the memory interval since we don't use 'memory' as adapter
+ sed -i "s/INTERVAL = 60/;INTERVAL = 60/g" $GOPATH/src/github.com/gogits/gogs/conf/app.ini
+
+
+ case $MEM_TYPE in
+ "redis")
+ # Modify the adapter host
+ sed -i "${MEM_HOST_LINE}s/.*/HOST = $_MEM_ADDR:$_MEM_PORT/" $GOPATH/src/github.com/gogits/gogs/conf/app.ini
+ sed -i "s/PROVIDER = file/PROVIDER = $MEM_TYPE/g" $GOPATH/src/github.com/gogits/gogs/conf/app.ini
+ # Modify the provider config
+ sed -i "s#PROVIDER_CONFIG = data/sessions#PROVIDER_CONFIG = $_MEM_ADDR:$_MEM_PORT#g" $GOPATH/src/github.com/gogits/gogs/conf/app.ini
+ ;;
+
+ "memcache")
+ # Modify the adapter host
+ sed -i "${MEM_HOST_LINE}s/.*/HOST = $_MEM_ADDR:$_MEM_PORT/" $GOPATH/src/github.com/gogits/gogs/conf/app.ini
+ ;;
+ esac
+
fi
+
## Replace the database address and port
# When using --link in docker run, the database image's info looks like this:
# DB_PORT=tcp://172.17.0.2:3306
diff --git a/dockerfiles/images/memcached/.gitkeep b/dockerfiles/images/memcache/.gitkeep
index e69de29b..e69de29b 100644
--- a/dockerfiles/images/memcached/.gitkeep
+++ b/dockerfiles/images/memcache/.gitkeep
diff --git a/dockerfiles/images/memcache/Dockerfile b/dockerfiles/images/memcache/Dockerfile
new file mode 100644
index 00000000..2466c1f5
--- /dev/null
+++ b/dockerfiles/images/memcache/Dockerfile
@@ -0,0 +1,26 @@
+FROM ubuntu
+
+# Set the file maintainer (your name - the file's author)
+MAINTAINER Borja Burgos <borja@tutum.co>
+
+ENV DEBIAN_FRONTEND noninteractive
+
+# Update the default application repository sources list
+RUN apt-get update
+
+# Install Memcached
+RUN apt-get install -y memcached
+
+# Port to expose (default: 11211)
+EXPOSE 11211
+
+# Default Memcached run command arguments
+# Change to limit memory when creating container in Tutum
+CMD ["-m", "64"]
+
+# Set the user to run Memcached daemon
+USER daemon
+
+# Set the entrypoint to memcached binary
+ENTRYPOINT memcached
+
diff --git a/dockerfiles/images/mysql/Dockerfile b/dockerfiles/images/mysql/Dockerfile
index 9b163e32..8fff5a19 100644
--- a/dockerfiles/images/mysql/Dockerfile
+++ b/dockerfiles/images/mysql/Dockerfile
@@ -10,7 +10,7 @@ RUN add-apt-repository -y "deb http://archive.ubuntu.com/ubuntu $(lsb_release -s
RUN apt-get --yes --force-yes update
RUN apt-get --yes --force-yes upgrade
-ENV MYSQL_PASSWORD THE_MYSQL_PASSWORD
+ENV MYSQL_PASSWORD THE_DB_PASSWORD
RUN echo "mysql-server mysql-server/root_password password $MYSQL_PASSWORD" | debconf-set-selections
RUN echo "mysql-server mysql-server/root_password_again password $MYSQL_PASSWORD" | debconf-set-selections
diff --git a/dockerfiles/images/postgres/Dockerfile b/dockerfiles/images/postgres/Dockerfile
index 9f026600..44e82b7d 100644
--- a/dockerfiles/images/postgres/Dockerfile
+++ b/dockerfiles/images/postgres/Dockerfile
@@ -1,6 +1,8 @@
FROM ubuntu
MAINTAINER SvenDowideit@docker.com
+ENV DEBIAN_FRONTEND noninteractive
+
# Add the PostgreSQL PGP key to verify their Debian packages.
# It should be the same key as https://www.postgresql.org/media/keys/ACCC4CF8.asc
RUN apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys B97B0AFCAA1A47F044F244A07FCC7D46ACCC4CF8
diff --git a/dockerfiles/images/redis/Dockerfile b/dockerfiles/images/redis/Dockerfile
new file mode 100644
index 00000000..d8b24d5a
--- /dev/null
+++ b/dockerfiles/images/redis/Dockerfile
@@ -0,0 +1,14 @@
+FROM stackbrew/ubuntu:saucy
+MAINTAINER Meaglith Ma <genedna@gmail.com> (@genedna), Lance Ju <juzhenatpku@gmail.com> (@crystaldust)
+
+ENV DEBIAN_FRONTEND noninteractive
+
+ENV DEBIAN_FRONTEND noninteractive
+
+RUN apt-get update && apt-get install -y redis-server
+# Usually redis doesn't need a password
+#RUN sed -i "s/# requirepass foobared/requirepass THE_REDIS_PASSWORD/g" /etc/redis/redis.conf
+EXPOSE 6379
+ENTRYPOINT ["/usr/bin/redis-server"]
+CMD ["--bind", "0.0.0.0"]
+
diff --git a/models/git.go b/models/git.go
new file mode 100644
index 00000000..46345d0f
--- /dev/null
+++ b/models/git.go
@@ -0,0 +1,464 @@
+// Copyright 2014 The Gogs Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package models
+
+import (
+ "bufio"
+ "container/list"
+ "fmt"
+ "io"
+ "os"
+ "os/exec"
+ "path"
+ "strings"
+
+ "github.com/gogits/git"
+
+ "github.com/gogits/gogs/modules/base"
+)
+
+// RepoFile represents a file object in git repository.
+type RepoFile struct {
+ *git.TreeEntry
+ Path string
+ Size int64
+ Repo *git.Repository
+ Commit *git.Commit
+}
+
+// LookupBlob returns the content of an object.
+func (file *RepoFile) LookupBlob() (*git.Blob, error) {
+ if file.Repo == nil {
+ return nil, ErrRepoFileNotLoaded
+ }
+
+ return file.Repo.LookupBlob(file.Id)
+}
+
+// GetBranches returns all branches of given repository.
+func GetBranches(userName, repoName string) ([]string, error) {
+ repo, err := git.OpenRepository(RepoPath(userName, repoName))
+ if err != nil {
+ return nil, err
+ }
+
+ refs, err := repo.AllReferences()
+ if err != nil {
+ return nil, err
+ }
+
+ brs := make([]string, len(refs))
+ for i, ref := range refs {
+ brs[i] = ref.BranchName()
+ }
+ return brs, nil
+}
+
+// GetTags returns all tags of given repository.
+func GetTags(userName, repoName string) ([]string, error) {
+ repo, err := git.OpenRepository(RepoPath(userName, repoName))
+ if err != nil {
+ return nil, err
+ }
+
+ refs, err := repo.AllTags()
+ if err != nil {
+ return nil, err
+ }
+
+ tags := make([]string, len(refs))
+ for i, ref := range refs {
+ tags[i] = ref.Name
+ }
+ return tags, nil
+}
+
+func IsBranchExist(userName, repoName, branchName string) bool {
+ repo, err := git.OpenRepository(RepoPath(userName, repoName))
+ if err != nil {
+ return false
+ }
+ return repo.IsBranchExist(branchName)
+}
+
+func GetTargetFile(userName, repoName, branchName, commitId, rpath string) (*RepoFile, error) {
+ repo, err := git.OpenRepository(RepoPath(userName, repoName))
+ if err != nil {
+ return nil, err
+ }
+
+ commit, err := repo.GetCommitOfBranch(branchName)
+ if err != nil {
+ commit, err = repo.GetCommit(commitId)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ parts := strings.Split(path.Clean(rpath), "/")
+
+ var entry *git.TreeEntry
+ tree := commit.Tree
+ for i, part := range parts {
+ if i == len(parts)-1 {
+ entry = tree.EntryByName(part)
+ if entry == nil {
+ return nil, ErrRepoFileNotExist
+ }
+ } else {
+ tree, err = repo.SubTree(tree, part)
+ if err != nil {
+ return nil, err
+ }
+ }
+ }
+
+ size, err := repo.ObjectSize(entry.Id)
+ if err != nil {
+ return nil, err
+ }
+
+ repoFile := &RepoFile{
+ entry,
+ rpath,
+ size,
+ repo,
+ commit,
+ }
+
+ return repoFile, nil
+}
+
+// GetReposFiles returns a list of file object in given directory of repository.
+// func GetReposFilesOfBranch(userName, repoName, branchName, rpath string) ([]*RepoFile, error) {
+// return getReposFiles(userName, repoName, commitId, rpath)
+// }
+
+// GetReposFiles returns a list of file object in given directory of repository.
+func GetReposFiles(userName, repoName, commitId, rpath string) ([]*RepoFile, error) {
+ return getReposFiles(userName, repoName, commitId, rpath)
+}
+
+func getReposFiles(userName, repoName, commitId string, rpath string) ([]*RepoFile, error) {
+ repo, err := git.OpenRepository(RepoPath(userName, repoName))
+ if err != nil {
+ return nil, err
+ }
+
+ commit, err := repo.GetCommit(commitId)
+ if err != nil {
+ return nil, err
+ }
+
+ var repodirs []*RepoFile
+ var repofiles []*RepoFile
+ commit.Tree.Walk(func(dirname string, entry *git.TreeEntry) int {
+ if dirname == rpath {
+ // TODO: size get method shoule be improved
+ size, err := repo.ObjectSize(entry.Id)
+ if err != nil {
+ return 0
+ }
+
+ var cm = commit
+ var i int
+ for {
+ i = i + 1
+ //fmt.Println(".....", i, cm.Id(), cm.ParentCount())
+ if cm.ParentCount() == 0 {
+ break
+ } else if cm.ParentCount() == 1 {
+ pt, _ := repo.SubTree(cm.Parent(0).Tree, dirname)
+ if pt == nil {
+ break
+ }
+ pEntry := pt.EntryByName(entry.Name)
+ if pEntry == nil || !pEntry.Id.Equal(entry.Id) {
+ break
+ } else {
+ cm = cm.Parent(0)
+ }
+ } else {
+ var emptyCnt = 0
+ var sameIdcnt = 0
+ var lastSameCm *git.Commit
+ //fmt.Println(".....", cm.ParentCount())
+ for i := 0; i < cm.ParentCount(); i++ {
+ //fmt.Println("parent", i, cm.Parent(i).Id())
+ p := cm.Parent(i)
+ pt, _ := repo.SubTree(p.Tree, dirname)
+ var pEntry *git.TreeEntry
+ if pt != nil {
+ pEntry = pt.EntryByName(entry.Name)
+ }
+
+ //fmt.Println("pEntry", pEntry)
+
+ if pEntry == nil {
+ emptyCnt = emptyCnt + 1
+ if emptyCnt+sameIdcnt == cm.ParentCount() {
+ if lastSameCm == nil {
+ goto loop
+ } else {
+ cm = lastSameCm
+ break
+ }
+ }
+ } else {
+ //fmt.Println(i, "pEntry", pEntry.Id, "entry", entry.Id)
+ if !pEntry.Id.Equal(entry.Id) {
+ goto loop
+ } else {
+ lastSameCm = cm.Parent(i)
+ sameIdcnt = sameIdcnt + 1
+ if emptyCnt+sameIdcnt == cm.ParentCount() {
+ // TODO: now follow the first parent commit?
+ cm = lastSameCm
+ //fmt.Println("sameId...")
+ break
+ }
+ }
+ }
+ }
+ }
+ }
+
+ loop:
+
+ rp := &RepoFile{
+ entry,
+ path.Join(dirname, entry.Name),
+ size,
+ repo,
+ cm,
+ }
+
+ if entry.IsFile() {
+ repofiles = append(repofiles, rp)
+ } else if entry.IsDir() {
+ repodirs = append(repodirs, rp)
+ }
+ }
+ return 0
+ })
+
+ return append(repodirs, repofiles...), nil
+}
+
+func GetCommit(userName, repoName, commitId string) (*git.Commit, error) {
+ repo, err := git.OpenRepository(RepoPath(userName, repoName))
+ if err != nil {
+ return nil, err
+ }
+
+ return repo.GetCommit(commitId)
+}
+
+// GetCommitsByBranch returns all commits of given branch of repository.
+func GetCommitsByBranch(userName, repoName, branchName string) (*list.List, error) {
+ repo, err := git.OpenRepository(RepoPath(userName, repoName))
+ if err != nil {
+ return nil, err
+ }
+ r, err := repo.LookupReference(fmt.Sprintf("refs/heads/%s", branchName))
+ if err != nil {
+ return nil, err
+ }
+ return r.AllCommits()
+}
+
+// GetCommitsByCommitId returns all commits of given commitId of repository.
+func GetCommitsByCommitId(userName, repoName, commitId string) (*list.List, error) {
+ repo, err := git.OpenRepository(RepoPath(userName, repoName))
+ if err != nil {
+ return nil, err
+ }
+ oid, err := git.NewOidFromString(commitId)
+ if err != nil {
+ return nil, err
+ }
+ return repo.CommitsBefore(oid)
+}
+
+// Diff line types.
+const (
+ DIFF_LINE_PLAIN = iota + 1
+ DIFF_LINE_ADD
+ DIFF_LINE_DEL
+ DIFF_LINE_SECTION
+)
+
+const (
+ DIFF_FILE_ADD = iota + 1
+ DIFF_FILE_CHANGE
+ DIFF_FILE_DEL
+)
+
+type DiffLine struct {
+ LeftIdx int
+ RightIdx int
+ Type int
+ Content string
+}
+
+func (d DiffLine) GetType() int {
+ return d.Type
+}
+
+type DiffSection struct {
+ Name string
+ Lines []*DiffLine
+}
+
+type DiffFile struct {
+ Name string
+ Addition, Deletion int
+ Type int
+ Sections []*DiffSection
+}
+
+type Diff struct {
+ TotalAddition, TotalDeletion int
+ Files []*DiffFile
+}
+
+func (diff *Diff) NumFiles() int {
+ return len(diff.Files)
+}
+
+const DIFF_HEAD = "diff --git "
+
+func ParsePatch(reader io.Reader) (*Diff, error) {
+ scanner := bufio.NewScanner(reader)
+ var (
+ curFile *DiffFile
+ curSection = &DiffSection{
+ Lines: make([]*DiffLine, 0, 10),
+ }
+
+ leftLine, rightLine int
+ )
+
+ diff := &Diff{Files: make([]*DiffFile, 0)}
+ var i int
+ for scanner.Scan() {
+ line := scanner.Text()
+ // fmt.Println(i, line)
+ if strings.HasPrefix(line, "+++ ") || strings.HasPrefix(line, "--- ") {
+ continue
+ }
+
+ i = i + 1
+ if line == "" {
+ continue
+ }
+ if line[0] == ' ' {
+ diffLine := &DiffLine{Type: DIFF_LINE_PLAIN, Content: line, LeftIdx: leftLine, RightIdx: rightLine}
+ leftLine++
+ rightLine++
+ curSection.Lines = append(curSection.Lines, diffLine)
+ continue
+ } else if line[0] == '@' {
+ curSection = &DiffSection{}
+ curFile.Sections = append(curFile.Sections, curSection)
+ ss := strings.Split(line, "@@")
+ diffLine := &DiffLine{Type: DIFF_LINE_SECTION, Content: line}
+ curSection.Lines = append(curSection.Lines, diffLine)
+
+ // Parse line number.
+ ranges := strings.Split(ss[len(ss)-2][1:], " ")
+ leftLine, _ = base.StrTo(strings.Split(ranges[0], ",")[0][1:]).Int()
+ rightLine, _ = base.StrTo(strings.Split(ranges[1], ",")[0]).Int()
+ continue
+ } else if line[0] == '+' {
+ curFile.Addition++
+ diff.TotalAddition++
+ diffLine := &DiffLine{Type: DIFF_LINE_ADD, Content: line, RightIdx: rightLine}
+ rightLine++
+ curSection.Lines = append(curSection.Lines, diffLine)
+ continue
+ } else if line[0] == '-' {
+ curFile.Deletion++
+ diff.TotalDeletion++
+ diffLine := &DiffLine{Type: DIFF_LINE_DEL, Content: line, LeftIdx: leftLine}
+ if leftLine > 0 {
+ leftLine++
+ }
+ curSection.Lines = append(curSection.Lines, diffLine)
+ continue
+ }
+
+ // Get new file.
+ if strings.HasPrefix(line, DIFF_HEAD) {
+ fs := strings.Split(line[len(DIFF_HEAD):], " ")
+ a := fs[0]
+
+ curFile = &DiffFile{
+ Name: a[strings.Index(a, "/")+1:],
+ Type: DIFF_FILE_CHANGE,
+ Sections: make([]*DiffSection, 0, 10),
+ }
+ diff.Files = append(diff.Files, curFile)
+
+ // Check file diff type.
+ for scanner.Scan() {
+ switch {
+ case strings.HasPrefix(scanner.Text(), "new file"):
+ curFile.Type = DIFF_FILE_ADD
+ case strings.HasPrefix(scanner.Text(), "deleted"):
+ curFile.Type = DIFF_FILE_DEL
+ case strings.HasPrefix(scanner.Text(), "index"):
+ curFile.Type = DIFF_FILE_CHANGE
+ }
+ if curFile.Type > 0 {
+ break
+ }
+ }
+ }
+ }
+
+ return diff, nil
+}
+
+func GetDiff(repoPath, commitid string) (*Diff, error) {
+ repo, err := git.OpenRepository(repoPath)
+ if err != nil {
+ return nil, err
+ }
+
+ commit, err := repo.GetCommit(commitid)
+ if err != nil {
+ return nil, err
+ }
+
+ // First commit of repository.
+ if commit.ParentCount() == 0 {
+ rd, wr := io.Pipe()
+ go func() {
+ cmd := exec.Command("git", "show", commitid)
+ cmd.Dir = repoPath
+ cmd.Stdout = wr
+ cmd.Stdin = os.Stdin
+ cmd.Stderr = os.Stderr
+ cmd.Run()
+ wr.Close()
+ }()
+ defer rd.Close()
+ return ParsePatch(rd)
+ }
+
+ rd, wr := io.Pipe()
+ go func() {
+ cmd := exec.Command("git", "diff", commit.Parent(0).Oid.String(), commitid)
+ cmd.Dir = repoPath
+ cmd.Stdout = wr
+ cmd.Stdin = os.Stdin
+ cmd.Stderr = os.Stderr
+ cmd.Run()
+ wr.Close()
+ }()
+ defer rd.Close()
+ return ParsePatch(rd)
+}
diff --git a/modules/oauth2/oauth2.go b/modules/oauth2/oauth2.go
new file mode 100644
index 00000000..088d65dd
--- /dev/null
+++ b/modules/oauth2/oauth2.go
@@ -0,0 +1,233 @@
+// Copyright 2014 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Package oauth2 contains Martini handlers to provide
+// user login via an OAuth 2.0 backend.
+package oauth2
+
+import (
+ "encoding/json"
+ "fmt"
+ "net/http"
+ "net/url"
+ "strings"
+ "time"
+
+ "code.google.com/p/goauth2/oauth"
+ "github.com/go-martini/martini"
+ "github.com/martini-contrib/sessions"
+)
+
+const (
+ codeRedirect = 302
+ keyToken = "oauth2_token"
+ keyNextPage = "next"
+)
+
+var (
+ // Path to handle OAuth 2.0 logins.
+ PathLogin = "/login"
+ // Path to handle OAuth 2.0 logouts.
+ PathLogout = "/logout"
+ // Path to handle callback from OAuth 2.0 backend
+ // to exchange credentials.
+ PathCallback = "/oauth2callback"
+ // Path to handle error cases.
+ PathError = "/oauth2error"
+)
+
+// Represents OAuth2 backend options.
+type Options struct {
+ ClientId string
+ ClientSecret string
+ RedirectURL string
+ Scopes []string
+
+ AuthUrl string
+ TokenUrl string
+}
+
+// Represents a container that contains
+// user's OAuth 2.0 access and refresh tokens.
+type Tokens interface {
+ Access() string
+ Refresh() string
+ IsExpired() bool
+ ExpiryTime() time.Time
+ ExtraData() map[string]string
+}
+
+type token struct {
+ oauth.Token
+}
+
+func (t *token) ExtraData() map[string]string {
+ return t.Extra
+}
+
+// Returns the access token.
+func (t *token) Access() string {
+ return t.AccessToken
+}
+
+// Returns the refresh token.
+func (t *token) Refresh() string {
+ return t.RefreshToken
+}
+
+// Returns whether the access token is
+// expired or not.
+func (t *token) IsExpired() bool {
+ if t == nil {
+ return true
+ }
+ return t.Expired()
+}
+
+// Returns the expiry time of the user's
+// access token.
+func (t *token) ExpiryTime() time.Time {
+ return t.Expiry
+}
+
+// Formats tokens into string.
+func (t *token) String() string {
+ return fmt.Sprintf("tokens: %v", t)
+}
+
+// Returns a new Google OAuth 2.0 backend endpoint.
+func Google(opts *Options) martini.Handler {
+ opts.AuthUrl = "https://accounts.google.com/o/oauth2/auth"
+ opts.TokenUrl = "https://accounts.google.com/o/oauth2/token"
+ return NewOAuth2Provider(opts)
+}
+
+// Returns a new Github OAuth 2.0 backend endpoint.
+func Github(opts *Options) martini.Handler {
+ opts.AuthUrl = "https://github.com/login/oauth/authorize"
+ opts.TokenUrl = "https://github.com/login/oauth/access_token"
+ return NewOAuth2Provider(opts)
+}
+
+func Facebook(opts *Options) martini.Handler {
+ opts.AuthUrl = "https://www.facebook.com/dialog/oauth"
+ opts.TokenUrl = "https://graph.facebook.com/oauth/access_token"
+ return NewOAuth2Provider(opts)
+}
+
+// Returns a generic OAuth 2.0 backend endpoint.
+func NewOAuth2Provider(opts *Options) martini.Handler {
+ config := &oauth.Config{
+ ClientId: opts.ClientId,
+ ClientSecret: opts.ClientSecret,
+ RedirectURL: opts.RedirectURL,
+ Scope: strings.Join(opts.Scopes, " "),
+ AuthURL: opts.AuthUrl,
+ TokenURL: opts.TokenUrl,
+ }
+
+ transport := &oauth.Transport{
+ Config: config,
+ Transport: http.DefaultTransport,
+ }
+
+ return func(s sessions.Session, c martini.Context, w http.ResponseWriter, r *http.Request) {
+ if r.Method == "GET" {
+ switch r.URL.Path {
+ case PathLogin:
+ login(transport, s, w, r)
+ case PathLogout:
+ logout(transport, s, w, r)
+ case PathCallback:
+ handleOAuth2Callback(transport, s, w, r)
+ }
+ }
+
+ tk := unmarshallToken(s)
+ if tk != nil {
+ // check if the access token is expired
+ if tk.IsExpired() && tk.Refresh() == "" {
+ s.Delete(keyToken)
+ tk = nil
+ }
+ }
+ // Inject tokens.
+ c.MapTo(tk, (*Tokens)(nil))
+ }
+}
+
+// Handler that redirects user to the login page
+// if user is not logged in.
+// Sample usage:
+// m.Get("/login-required", oauth2.LoginRequired, func() ... {})
+var LoginRequired martini.Handler = func() martini.Handler {
+ return func(s sessions.Session, c martini.Context, w http.ResponseWriter, r *http.Request) {
+ token := unmarshallToken(s)
+ if token == nil || token.IsExpired() {
+ next := url.QueryEscape(r.URL.RequestURI())
+ http.Redirect(w, r, PathLogin+"?next="+next, codeRedirect)
+ }
+ }
+}()
+
+func login(t *oauth.Transport, s sessions.Session, w http.ResponseWriter, r *http.Request) {
+ next := extractPath(r.URL.Query().Get(keyNextPage))
+ if s.Get(keyToken) == nil {
+ // User is not logged in.
+ http.Redirect(w, r, t.Config.AuthCodeURL(next), codeRedirect)
+ return
+ }
+ // No need to login, redirect to the next page.
+ http.Redirect(w, r, next, codeRedirect)
+}
+
+func logout(t *oauth.Transport, s sessions.Session, w http.ResponseWriter, r *http.Request) {
+ next := extractPath(r.URL.Query().Get(keyNextPage))
+ s.Delete(keyToken)
+ http.Redirect(w, r, next, codeRedirect)
+}
+
+func handleOAuth2Callback(t *oauth.Transport, s sessions.Session, w http.ResponseWriter, r *http.Request) {
+ next := extractPath(r.URL.Query().Get("state"))
+ code := r.URL.Query().Get("code")
+ tk, err := t.Exchange(code)
+ if err != nil {
+ // Pass the error message, or allow dev to provide its own
+ // error handler.
+ http.Redirect(w, r, PathError, codeRedirect)
+ return
+ }
+ // Store the credentials in the session.
+ val, _ := json.Marshal(tk)
+ s.Set(keyToken, val)
+ http.Redirect(w, r, next, codeRedirect)
+}
+
+func unmarshallToken(s sessions.Session) (t *token) {
+ if s.Get(keyToken) == nil {
+ return
+ }
+ data := s.Get(keyToken).([]byte)
+ var tk oauth.Token
+ json.Unmarshal(data, &tk)
+ return &token{tk}
+}
+
+func extractPath(next string) string {
+ n, err := url.Parse(next)
+ if err != nil {
+ return "/"
+ }
+ return n.Path
+}
diff --git a/modules/oauth2/oauth2_test.go b/modules/oauth2/oauth2_test.go
new file mode 100644
index 00000000..71443030
--- /dev/null
+++ b/modules/oauth2/oauth2_test.go
@@ -0,0 +1,162 @@
+// Copyright 2014 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package oauth2
+
+import (
+ "net/http"
+ "net/http/httptest"
+ "testing"
+
+ "github.com/go-martini/martini"
+ "github.com/martini-contrib/sessions"
+)
+
+func Test_LoginRedirect(t *testing.T) {
+ recorder := httptest.NewRecorder()
+ m := martini.New()
+ m.Use(sessions.Sessions("my_session", sessions.NewCookieStore([]byte("secret123"))))
+ m.Use(Google(&Options{
+ ClientId: "client_id",
+ ClientSecret: "client_secret",
+ RedirectURL: "refresh_url",
+ Scopes: []string{"x", "y"},
+ }))
+
+ r, _ := http.NewRequest("GET", "/login", nil)
+ m.ServeHTTP(recorder, r)
+
+ location := recorder.HeaderMap["Location"][0]
+ if recorder.Code != 302 {
+ t.Errorf("Not being redirected to the auth page.")
+ }
+ if location != "https://accounts.google.com/o/oauth2/auth?access_type=&approval_prompt=&client_id=client_id&redirect_uri=refresh_url&response_type=code&scope=x+y&state=" {
+ t.Errorf("Not being redirected to the right page, %v found", location)
+ }
+}
+
+func Test_LoginRedirectAfterLoginRequired(t *testing.T) {
+ recorder := httptest.NewRecorder()
+ m := martini.Classic()
+ m.Use(sessions.Sessions("my_session", sessions.NewCookieStore([]byte("secret123"))))
+ m.Use(Google(&Options{
+ ClientId: "client_id",
+ ClientSecret: "client_secret",
+ RedirectURL: "refresh_url",
+ Scopes: []string{"x", "y"},
+ }))
+
+ m.Get("/login-required", LoginRequired, func(tokens Tokens) (int, string) {
+ return 200, tokens.Access()
+ })
+
+ r, _ := http.NewRequest("GET", "/login-required?key=value", nil)
+ m.ServeHTTP(recorder, r)
+
+ location := recorder.HeaderMap["Location"][0]
+ if recorder.Code != 302 {
+ t.Errorf("Not being redirected to the auth page.")
+ }
+ if location != "/login?next=%2Flogin-required%3Fkey%3Dvalue" {
+ t.Errorf("Not being redirected to the right page, %v found", location)
+ }
+}
+
+func Test_Logout(t *testing.T) {
+ recorder := httptest.NewRecorder()
+ s := sessions.NewCookieStore([]byte("secret123"))
+
+ m := martini.Classic()
+ m.Use(sessions.Sessions("my_session", s))
+ m.Use(Google(&Options{
+ // no need to configure
+ }))
+
+ m.Get("/", func(s sessions.Session) {
+ s.Set(keyToken, "dummy token")
+ })
+
+ m.Get("/get", func(s sessions.Session) {
+ if s.Get(keyToken) != nil {
+ t.Errorf("User credentials are still kept in the session.")
+ }
+ })
+
+ logout, _ := http.NewRequest("GET", "/logout", nil)
+ index, _ := http.NewRequest("GET", "/", nil)
+
+ m.ServeHTTP(httptest.NewRecorder(), index)
+ m.ServeHTTP(recorder, logout)
+
+ if recorder.Code != 302 {
+ t.Errorf("Not being redirected to the next page.")
+ }
+}
+
+func Test_LogoutOnAccessTokenExpiration(t *testing.T) {
+ recorder := httptest.NewRecorder()
+ s := sessions.NewCookieStore([]byte("secret123"))
+
+ m := martini.Classic()
+ m.Use(sessions.Sessions("my_session", s))
+ m.Use(Google(&Options{
+ // no need to configure
+ }))
+
+ m.Get("/addtoken", func(s sessions.Session) {
+ s.Set(keyToken, "dummy token")
+ })
+
+ m.Get("/", func(s sessions.Session) {
+ if s.Get(keyToken) != nil {
+ t.Errorf("User not logged out although access token is expired.")
+ }
+ })
+
+ addtoken, _ := http.NewRequest("GET", "/addtoken", nil)
+ index, _ := http.NewRequest("GET", "/", nil)
+ m.ServeHTTP(recorder, addtoken)
+ m.ServeHTTP(recorder, index)
+}
+
+func Test_InjectedTokens(t *testing.T) {
+ recorder := httptest.NewRecorder()
+ m := martini.Classic()
+ m.Use(sessions.Sessions("my_session", sessions.NewCookieStore([]byte("secret123"))))
+ m.Use(Google(&Options{
+ // no need to configure
+ }))
+ m.Get("/", func(tokens Tokens) string {
+ return "Hello world!"
+ })
+ r, _ := http.NewRequest("GET", "/", nil)
+ m.ServeHTTP(recorder, r)
+}
+
+func Test_LoginRequired(t *testing.T) {
+ recorder := httptest.NewRecorder()
+ m := martini.Classic()
+ m.Use(sessions.Sessions("my_session", sessions.NewCookieStore([]byte("secret123"))))
+ m.Use(Google(&Options{
+ // no need to configure
+ }))
+ m.Get("/", LoginRequired, func(tokens Tokens) string {
+ return "Hello world!"
+ })
+ r, _ := http.NewRequest("GET", "/", nil)
+ m.ServeHTTP(recorder, r)
+ if recorder.Code != 302 {
+ t.Errorf("Not being redirected to the auth page although user is not logged in.")
+ }
+}
diff --git a/templates/mail/auth/reset_password.html b/templates/mail/auth/reset_password.html
new file mode 100644
index 00000000..40a9efa8
--- /dev/null
+++ b/templates/mail/auth/reset_password.html
@@ -0,0 +1,25 @@
+{{template "mail/base.html" .}}
+{{define "title"}}
+ {{if eq .Lang "zh-CN"}}
+ {{.User.NickName}},重置账户密码
+ {{end}}
+ {{if eq .Lang "en-US"}}
+ {{.User.NickName}}, reset your password
+ {{end}}
+{{end}}
+{{define "body"}}
+ {{if eq .Lang "zh-CN"}}
+ <p style="margin:0;padding:0 0 9px 0;">点击链接重置密码,{{.ResetPwdCodeLives}} 分钟内有效</p>
+ <p style="margin:0;padding:0 0 9px 0;">
+ <a href="{{.AppUrl}}reset/{{.Code}}">{{.AppUrl}}reset/{{.Code}}</a>
+ </p>
+ <p style="margin:0;padding:0 0 9px 0;">如果链接点击无反应,请复制到浏览器打开。</p>
+ {{end}}
+ {{if eq .Lang "en-US"}}
+ <p style="margin:0;padding:0 0 9px 0;">Please click following link to reset your password in {{.ResetPwdCodeLives}} hours</p>
+ <p style="margin:0;padding:0 0 9px 0;">
+ <a href="{{.AppUrl}}reset/{{.Code}}">{{.AppUrl}}reset/{{.Code}}</a>
+ </p>
+ <p style="margin:0;padding:0 0 9px 0;">Copy and paste it to your browser if it's not working.</p>
+ {{end}}
+{{end}} \ No newline at end of file
diff --git a/templates/user/active.tmpl b/templates/user/active.tmpl
new file mode 100644
index 00000000..9cac069d
--- /dev/null
+++ b/templates/user/active.tmpl
@@ -0,0 +1,36 @@
+{{template "base/head" .}}
+{{template "base/navbar" .}}
+<div id="body" class="container">
+ <form action="/user/activate" method="post" class="form-horizontal card" id="login-card">
+ {{.CsrfTokenHtml}}
+ <h3>Activate Your Account</h3>
+ {{if .IsActivatePage}}
+ {{if .ServiceNotEnabled}}
+ <p>Sorry, Register Mail Confirmation has been disabled.</p>
+ {{else if .ResendLimited}}
+ <p>Sorry, you are sending activation e-mail too frequently, please wait 3 minutes.</p>
+ {{else}}
+ <p>New confirmation e-mail has been sent to <b>{{.SignedUser.Email}}</b>, please check your inbox within {{.Hours}} hours to complete your registeration.</p>
+ <hr/>
+ <a href="http://{{Mail2Domain .SignedUser.Email}}" class="btn btn-lg btn-success">Sign in to your e-mail</a>
+ {{end}}
+ {{else}}
+ {{if .IsSendRegisterMail}}
+ <p>A confirmation e-mail has been sent to <b>{{.Email}}</b>, please check your inbox within {{.Hours}} hours to complete your registeration.</p>
+ <hr/>
+ <a href="http://{{Mail2Domain .Email}}" class="btn btn-lg btn-success">Sign in to your e-mail</a>
+ {{else if .IsActivateFailed}}
+ <p>Sorry, your confirmation code has been exipired or not valid.</p>
+ {{else}}
+ <p>Hi, {{.SignedUser.Name}}, you have an unconfirmed email address(<b>{{.SignedUser.Email}}</b>). If you haven't received a confirmation e-mail or need to resend a new one, please click botton below.</p>
+ <hr/>
+ <div class="form-group">
+ <div class="col-md-offset-4 col-md-6">
+ <button type="submit" class="btn btn-lg btn-primary">Click here to resend your active e-mail</button>
+ </div>
+ </div>
+ {{end}}
+ {{end}}
+ </form>
+</div>
+{{template "base/footer" .}} \ No newline at end of file