From c58708d3eedb352bb277feb2c12bb1652a8a58b7 Mon Sep 17 00:00:00 2001 From: Ethan Koenig Date: Tue, 25 Apr 2017 03:24:51 -0400 Subject: [PATCH] Integration test framework (#1290) * Integration test framework * udpate drone sign * Formatting fixes and move router.go to routers/ * update sign for drone --- .drone.yml | 15 +- .drone.yml.sig | 2 +- .gitignore | 1 + Makefile | 22 +- cmd/web.go | 632 +------------------------- integrations/install_test.go | 91 ---- integrations/integration_test.go | 92 ++++ integrations/internal/utils/utils.go | 156 ------- integrations/mysql.ini | 56 +++ integrations/pgsql.ini | 56 +++ integrations/signup_test.go | 50 ++- integrations/sqlite.ini | 58 +++ integrations/version_test.go | 80 +--- integrations/view_test.go | 32 ++ models/setup_for_test.go | 8 +- models/test_fixtures.go | 23 + routers/routes/routes.go | 645 +++++++++++++++++++++++++++ 17 files changed, 1044 insertions(+), 975 deletions(-) delete mode 100644 integrations/install_test.go create mode 100644 integrations/integration_test.go delete mode 100644 integrations/internal/utils/utils.go create mode 100644 integrations/mysql.ini create mode 100644 integrations/pgsql.ini create mode 100644 integrations/sqlite.ini create mode 100644 integrations/view_test.go create mode 100644 models/test_fixtures.go create mode 100644 routers/routes/routes.go diff --git a/.drone.yml b/.drone.yml index 34cafd357..eb37dc8ac 100644 --- a/.drone.yml +++ b/.drone.yml @@ -25,6 +25,17 @@ pipeline: when: event: [ push, tag, pull_request ] + test-sqlite: + image: webhippie/golang:edge + pull: true + environment: + TAGS: bindata + GOPATH: /srv/app + commands: + - make test-sqlite + when: + event: [ push, tag, pull_request ] + test-mysql: image: webhippie/golang:edge pull: true @@ -32,7 +43,7 @@ pipeline: TAGS: bindata GOPATH: /srv/app commands: - - make test-mysql + - echo make test-mysql # Not ready yet when: event: [ push, tag, pull_request ] @@ -43,7 +54,7 @@ pipeline: TAGS: bindata GOPATH: /srv/app commands: - - make test-pgsql + - echo make test-pqsql # Not ready yet when: event: [ push, tag, pull_request ] diff --git a/.drone.yml.sig b/.drone.yml.sig index b9a71b82f..1ea768660 100644 --- a/.drone.yml.sig +++ b/.drone.yml.sig @@ -1 +1 @@ -eyJhbGciOiJIUzI1NiJ9.d29ya3NwYWNlOgogIGJhc2U6IC9zcnYvYXBwCiAgcGF0aDogc3JjL2NvZGUuZ2l0ZWEuaW8vZ2l0ZWEKCnBpcGVsaW5lOgogIGNsb25lOgogICAgaW1hZ2U6IHBsdWdpbnMvZ2l0CiAgICB0YWdzOiB0cnVlCgogIHRlc3Q6CiAgICBpbWFnZTogd2ViaGlwcGllL2dvbGFuZzplZGdlCiAgICBwdWxsOiB0cnVlCiAgICBlbnZpcm9ubWVudDoKICAgICAgVEFHUzogYmluZGF0YSBzcWxpdGUKICAgICAgR09QQVRIOiAvc3J2L2FwcAogICAgY29tbWFuZHM6CiAgICAgIC0gYXBrIC1VIGFkZCBvcGVuc3NoLWNsaWVudAogICAgICAtIG1ha2UgY2xlYW4KICAgICAgLSBtYWtlIGdlbmVyYXRlCiAgICAgIC0gbWFrZSB2ZXQKICAgICAgLSBtYWtlIGxpbnQKICAgICAgLSBtYWtlIHRlc3QtdmVuZG9yCiAgICAgIC0gbWFrZSB0ZXN0CiAgICAgIC0gbWFrZSBidWlsZAogICAgd2hlbjoKICAgICAgZXZlbnQ6IFsgcHVzaCwgdGFnLCBwdWxsX3JlcXVlc3QgXQoKICB0ZXN0LW15c3FsOgogICAgaW1hZ2U6IHdlYmhpcHBpZS9nb2xhbmc6ZWRnZQogICAgcHVsbDogdHJ1ZQogICAgZW52aXJvbm1lbnQ6CiAgICAgIFRBR1M6IGJpbmRhdGEKICAgICAgR09QQVRIOiAvc3J2L2FwcAogICAgY29tbWFuZHM6CiAgICAgIC0gbWFrZSB0ZXN0LW15c3FsCiAgICB3aGVuOgogICAgICBldmVudDogWyBwdXNoLCB0YWcsIHB1bGxfcmVxdWVzdCBdCgogIHRlc3QtcGdzcWw6CiAgICBpbWFnZTogd2ViaGlwcGllL2dvbGFuZzplZGdlCiAgICBwdWxsOiB0cnVlCiAgICBlbnZpcm9ubWVudDoKICAgICAgVEFHUzogYmluZGF0YQogICAgICBHT1BBVEg6IC9zcnYvYXBwCiAgICBjb21tYW5kczoKICAgICAgLSBtYWtlIHRlc3QtcGdzcWwKICAgIHdoZW46CiAgICAgIGV2ZW50OiBbIHB1c2gsIHRhZywgcHVsbF9yZXF1ZXN0IF0KCiAgc3RhdGljOgogICAgaW1hZ2U6IGthcmFsYWJlL3hnby1sYXRlc3Q6bGF0ZXN0CiAgICBwdWxsOiB0cnVlCiAgICBlbnZpcm9ubWVudDoKICAgICAgVEFHUzogYmluZGF0YSBzcWxpdGUKICAgICAgR09QQVRIOiAvc3J2L2FwcAogICAgY29tbWFuZHM6CiAgICAgIC0gbWFrZSByZWxlYXNlCiAgICB3aGVuOgogICAgICBldmVudDogWyBwdXNoLCB0YWcsIHB1bGxfcmVxdWVzdCBdCgogIGNvdmVyYWdlOgogICAgaW1hZ2U6IHBsdWdpbnMvY292ZXJhZ2UKICAgIHNlcnZlcjogaHR0cHM6Ly9jb3ZlcmFnZS5naXRlYS5pbwogICAgd2hlbjoKICAgICAgZXZlbnQ6IFsgcHVzaCwgdGFnLCBwdWxsX3JlcXVlc3QgXQoKICBkb2NrZXI6CiAgICBpbWFnZTogcGx1Z2lucy9kb2NrZXIKICAgIHJlcG86IGdpdGVhL2dpdGVhCiAgICB0YWdzOiBbICcke0RST05FX1RBRyMjdn0nIF0KICAgIHdoZW46CiAgICAgIGV2ZW50OiBbIHRhZyBdCiAgICAgIGJyYW5jaDogWyByZWZzL3RhZ3MvKiBdCgogIGRvY2tlcjoKICAgIGltYWdlOiBwbHVnaW5zL2RvY2tlcgogICAgcmVwbzogZ2l0ZWEvZ2l0ZWEKICAgIHRhZ3M6IFsgJyR7RFJPTkVfQlJBTkNIIyNyZWxlYXNlL3Z9JyBdCiAgICB3aGVuOgogICAgICBldmVudDogWyBwdXNoIF0KICAgICAgYnJhbmNoOiBbIHJlbGVhc2UvKiBdCgogIGRvY2tlcjoKICAgIGltYWdlOiBwbHVnaW5zL2RvY2tlcgogICAgcmVwbzogZ2l0ZWEvZ2l0ZWEKICAgIHRhZ3M6IFsgJ2xhdGVzdCcgXQogICAgd2hlbjoKICAgICAgZXZlbnQ6IFsgcHVzaCBdCiAgICAgIGJyYW5jaDogWyBtYXN0ZXIgXQoKICByZWxlYXNlOgogICAgaW1hZ2U6IHBsdWdpbnMvczMKICAgIHBhdGhfc3R5bGU6IHRydWUKICAgIHN0cmlwX3ByZWZpeDogZGlzdC9yZWxlYXNlLwogICAgc291cmNlOiBkaXN0L3JlbGVhc2UvKgogICAgdGFyZ2V0OiAvZ2l0ZWEvJHtEUk9ORV9UQUcjI3Z9CiAgICB3aGVuOgogICAgICBldmVudDogWyB0YWcgXQogICAgICBicmFuY2g6IFsgcmVmcy90YWdzLyogXQoKICByZWxlYXNlOgogICAgaW1hZ2U6IHBsdWdpbnMvczMKICAgIHBhdGhfc3R5bGU6IHRydWUKICAgIHN0cmlwX3ByZWZpeDogZGlzdC9yZWxlYXNlLwogICAgc291cmNlOiBkaXN0L3JlbGVhc2UvKgogICAgdGFyZ2V0OiAvZ2l0ZWEvJHtEUk9ORV9CUkFOQ0gjI3JlbGVhc2Uvdn0KICAgIHdoZW46CiAgICAgIGV2ZW50OiBbIHB1c2ggXQogICAgICBicmFuY2g6IFsgcmVsZWFzZS8qIF0KCiAgcmVsZWFzZToKICAgIGltYWdlOiBwbHVnaW5zL3MzCiAgICBwYXRoX3N0eWxlOiB0cnVlCiAgICBzdHJpcF9wcmVmaXg6IGRpc3QvcmVsZWFzZS8KICAgIHNvdXJjZTogZGlzdC9yZWxlYXNlLyoKICAgIHRhcmdldDogL2dpdGVhL21hc3RlcgogICAgd2hlbjoKICAgICAgZXZlbnQ6IFsgcHVzaCBdCiAgICAgIGJyYW5jaDogWyBtYXN0ZXIgXQoKICBnaXRodWI6CiAgICBpbWFnZTogcGx1Z2lucy9naXRodWItcmVsZWFzZQogICAgZmlsZXM6CiAgICAgIC0gZGlzdC9yZWxlYXNlLyoKICAgIHdoZW46CiAgICAgIGV2ZW50OiBbIHRhZyBdCiAgICAgIGJyYW5jaDogWyByZWZzL3RhZ3MvKiBdCgogIGdpdHRlcjoKICAgIGltYWdlOiBwbHVnaW5zL2dpdHRlcgoKc2VydmljZXM6CiAgbXlzcWw6CiAgICBpbWFnZTogbXlzcWw6NS43CiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBNWVNRTF9EQVRBQkFTRT10ZXN0CiAgICAgIC0gTVlTUUxfQUxMT1dfRU1QVFlfUEFTU1dPUkQ9eWVzCiAgICB3aGVuOgogICAgICBldmVudDogWyBwdXNoLCB0YWcsIHB1bGxfcmVxdWVzdCBdCgogIHBnc3FsOgogICAgaW1hZ2U6IHBvc3RncmVzOjkuNQogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gUE9TVEdSRVNfREI9dGVzdAogICAgd2hlbjoKICAgICAgZXZlbnQ6IFsgcHVzaCwgdGFnLCBwdWxsX3JlcXVlc3QgXQo.C4Zwhff5mceN64UpDsVF263WmpDTnMMY0pcbB6KKdd0 \ No newline at end of file +eyJhbGciOiJIUzI1NiJ9.d29ya3NwYWNlOgogIGJhc2U6IC9zcnYvYXBwCiAgcGF0aDogc3JjL2NvZGUuZ2l0ZWEuaW8vZ2l0ZWEKCnBpcGVsaW5lOgogIGNsb25lOgogICAgaW1hZ2U6IHBsdWdpbnMvZ2l0CiAgICB0YWdzOiB0cnVlCgogIHRlc3Q6CiAgICBpbWFnZTogd2ViaGlwcGllL2dvbGFuZzplZGdlCiAgICBwdWxsOiB0cnVlCiAgICBlbnZpcm9ubWVudDoKICAgICAgVEFHUzogYmluZGF0YSBzcWxpdGUKICAgICAgR09QQVRIOiAvc3J2L2FwcAogICAgY29tbWFuZHM6CiAgICAgIC0gYXBrIC1VIGFkZCBvcGVuc3NoLWNsaWVudAogICAgICAtIG1ha2UgY2xlYW4KICAgICAgLSBtYWtlIGdlbmVyYXRlCiAgICAgIC0gbWFrZSB2ZXQKICAgICAgLSBtYWtlIGxpbnQKICAgICAgLSBtYWtlIHRlc3QtdmVuZG9yCiAgICAgIC0gbWFrZSB0ZXN0CiAgICAgIC0gbWFrZSBidWlsZAogICAgd2hlbjoKICAgICAgZXZlbnQ6IFsgcHVzaCwgdGFnLCBwdWxsX3JlcXVlc3QgXQoKICB0ZXN0LXNxbGl0ZToKICAgIGltYWdlOiB3ZWJoaXBwaWUvZ29sYW5nOmVkZ2UKICAgIHB1bGw6IHRydWUKICAgIGVudmlyb25tZW50OgogICAgICBUQUdTOiBiaW5kYXRhCiAgICAgIEdPUEFUSDogL3Nydi9hcHAKICAgIGNvbW1hbmRzOgogICAgICAtIG1ha2UgdGVzdC1zcWxpdGUKICAgIHdoZW46CiAgICAgIGV2ZW50OiBbIHB1c2gsIHRhZywgcHVsbF9yZXF1ZXN0IF0KCiAgdGVzdC1teXNxbDoKICAgIGltYWdlOiB3ZWJoaXBwaWUvZ29sYW5nOmVkZ2UKICAgIHB1bGw6IHRydWUKICAgIGVudmlyb25tZW50OgogICAgICBUQUdTOiBiaW5kYXRhCiAgICAgIEdPUEFUSDogL3Nydi9hcHAKICAgIGNvbW1hbmRzOgogICAgICAtIGVjaG8gbWFrZSB0ZXN0LW15c3FsICMgTm90IHJlYWR5IHlldAogICAgd2hlbjoKICAgICAgZXZlbnQ6IFsgcHVzaCwgdGFnLCBwdWxsX3JlcXVlc3QgXQoKICB0ZXN0LXBnc3FsOgogICAgaW1hZ2U6IHdlYmhpcHBpZS9nb2xhbmc6ZWRnZQogICAgcHVsbDogdHJ1ZQogICAgZW52aXJvbm1lbnQ6CiAgICAgIFRBR1M6IGJpbmRhdGEKICAgICAgR09QQVRIOiAvc3J2L2FwcAogICAgY29tbWFuZHM6CiAgICAgIC0gZWNobyBtYWtlIHRlc3QtcHFzcWwgIyBOb3QgcmVhZHkgeWV0CiAgICB3aGVuOgogICAgICBldmVudDogWyBwdXNoLCB0YWcsIHB1bGxfcmVxdWVzdCBdCgogIHN0YXRpYzoKICAgIGltYWdlOiBrYXJhbGFiZS94Z28tbGF0ZXN0OmxhdGVzdAogICAgcHVsbDogdHJ1ZQogICAgZW52aXJvbm1lbnQ6CiAgICAgIFRBR1M6IGJpbmRhdGEgc3FsaXRlCiAgICAgIEdPUEFUSDogL3Nydi9hcHAKICAgIGNvbW1hbmRzOgogICAgICAtIG1ha2UgcmVsZWFzZQogICAgd2hlbjoKICAgICAgZXZlbnQ6IFsgcHVzaCwgdGFnLCBwdWxsX3JlcXVlc3QgXQoKICBjb3ZlcmFnZToKICAgIGltYWdlOiBwbHVnaW5zL2NvdmVyYWdlCiAgICBzZXJ2ZXI6IGh0dHBzOi8vY292ZXJhZ2UuZ2l0ZWEuaW8KICAgIHdoZW46CiAgICAgIGV2ZW50OiBbIHB1c2gsIHRhZywgcHVsbF9yZXF1ZXN0IF0KCiAgZG9ja2VyOgogICAgaW1hZ2U6IHBsdWdpbnMvZG9ja2VyCiAgICByZXBvOiBnaXRlYS9naXRlYQogICAgdGFnczogWyAnJHtEUk9ORV9UQUcjI3Z9JyBdCiAgICB3aGVuOgogICAgICBldmVudDogWyB0YWcgXQogICAgICBicmFuY2g6IFsgcmVmcy90YWdzLyogXQoKICBkb2NrZXI6CiAgICBpbWFnZTogcGx1Z2lucy9kb2NrZXIKICAgIHJlcG86IGdpdGVhL2dpdGVhCiAgICB0YWdzOiBbICcke0RST05FX0JSQU5DSCMjcmVsZWFzZS92fScgXQogICAgd2hlbjoKICAgICAgZXZlbnQ6IFsgcHVzaCBdCiAgICAgIGJyYW5jaDogWyByZWxlYXNlLyogXQoKICBkb2NrZXI6CiAgICBpbWFnZTogcGx1Z2lucy9kb2NrZXIKICAgIHJlcG86IGdpdGVhL2dpdGVhCiAgICB0YWdzOiBbICdsYXRlc3QnIF0KICAgIHdoZW46CiAgICAgIGV2ZW50OiBbIHB1c2ggXQogICAgICBicmFuY2g6IFsgbWFzdGVyIF0KCiAgcmVsZWFzZToKICAgIGltYWdlOiBwbHVnaW5zL3MzCiAgICBwYXRoX3N0eWxlOiB0cnVlCiAgICBzdHJpcF9wcmVmaXg6IGRpc3QvcmVsZWFzZS8KICAgIHNvdXJjZTogZGlzdC9yZWxlYXNlLyoKICAgIHRhcmdldDogL2dpdGVhLyR7RFJPTkVfVEFHIyN2fQogICAgd2hlbjoKICAgICAgZXZlbnQ6IFsgdGFnIF0KICAgICAgYnJhbmNoOiBbIHJlZnMvdGFncy8qIF0KCiAgcmVsZWFzZToKICAgIGltYWdlOiBwbHVnaW5zL3MzCiAgICBwYXRoX3N0eWxlOiB0cnVlCiAgICBzdHJpcF9wcmVmaXg6IGRpc3QvcmVsZWFzZS8KICAgIHNvdXJjZTogZGlzdC9yZWxlYXNlLyoKICAgIHRhcmdldDogL2dpdGVhLyR7RFJPTkVfQlJBTkNIIyNyZWxlYXNlL3Z9CiAgICB3aGVuOgogICAgICBldmVudDogWyBwdXNoIF0KICAgICAgYnJhbmNoOiBbIHJlbGVhc2UvKiBdCgogIHJlbGVhc2U6CiAgICBpbWFnZTogcGx1Z2lucy9zMwogICAgcGF0aF9zdHlsZTogdHJ1ZQogICAgc3RyaXBfcHJlZml4OiBkaXN0L3JlbGVhc2UvCiAgICBzb3VyY2U6IGRpc3QvcmVsZWFzZS8qCiAgICB0YXJnZXQ6IC9naXRlYS9tYXN0ZXIKICAgIHdoZW46CiAgICAgIGV2ZW50OiBbIHB1c2ggXQogICAgICBicmFuY2g6IFsgbWFzdGVyIF0KCiAgZ2l0aHViOgogICAgaW1hZ2U6IHBsdWdpbnMvZ2l0aHViLXJlbGVhc2UKICAgIGZpbGVzOgogICAgICAtIGRpc3QvcmVsZWFzZS8qCiAgICB3aGVuOgogICAgICBldmVudDogWyB0YWcgXQogICAgICBicmFuY2g6IFsgcmVmcy90YWdzLyogXQoKICBnaXR0ZXI6CiAgICBpbWFnZTogcGx1Z2lucy9naXR0ZXIKCnNlcnZpY2VzOgogIG15c3FsOgogICAgaW1hZ2U6IG15c3FsOjUuNwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gTVlTUUxfREFUQUJBU0U9dGVzdAogICAgICAtIE1ZU1FMX0FMTE9XX0VNUFRZX1BBU1NXT1JEPXllcwogICAgd2hlbjoKICAgICAgZXZlbnQ6IFsgcHVzaCwgdGFnLCBwdWxsX3JlcXVlc3QgXQoKICBwZ3NxbDoKICAgIGltYWdlOiBwb3N0Z3Jlczo5LjUKICAgIGVudmlyb25tZW50OgogICAgICAtIFBPU1RHUkVTX0RCPXRlc3QKICAgIHdoZW46CiAgICAgIGV2ZW50OiBbIHB1c2gsIHRhZywgcHVsbF9yZXF1ZXN0IF0K.dA2VK6LdoPXvBTYAUywWervhOZmgOjU32uiiPrBbVdQ \ No newline at end of file diff --git a/.gitignore b/.gitignore index 79198ffcf..8e1689e65 100644 --- a/.gitignore +++ b/.gitignore @@ -44,3 +44,4 @@ coverage.out /indexers /log /public/img/avatar +/integrations/gitea-integration* diff --git a/Makefile b/Makefile index ee8d5eb6d..4796eb305 100644 --- a/Makefile +++ b/Makefile @@ -86,13 +86,27 @@ test-vendor: fi govendor status +outside +unused || exit 1 +.PHONY: test-sqlite +test-sqlite: integrations.test integrations/gitea-integration + GITEA_CONF=integrations/sqlite.ini ./integrations.test + .PHONY: test-mysql -test-mysql: - @echo "Not integrated yet!" +test-mysql: integrations.test integrations/gitea-integration + echo "CREATE DATABASE IF NOT EXISTS testgitea" | mysql -u root + GITEA_CONF=integrations/mysql.ini ./integrations.test .PHONY: test-pgsql -test-pgsql: - @echo "Not integrated yet!" +test-pgsql: integrations.test integrations/gitea-integration + GITEA_CONF=integrations/pgsql.ini ./integrations.test + +integrations.test: $(SOURCES) + go test -c code.gitea.io/gitea/integrations -tags 'sqlite' + +integrations/gitea-integration: + curl -L https://github.com/ethantkoenig/gitea-integration/archive/v2.tar.gz > integrations/gitea-integration.tar.gz + mkdir -p integrations/gitea-integration + tar -xf integrations/gitea-integration.tar.gz -C integrations/gitea-integration --strip-components 1 + .PHONY: check check: test diff --git a/cmd/web.go b/cmd/web.go index 5346bd5e5..21f12ee16 100644 --- a/cmd/web.go +++ b/cmd/web.go @@ -11,39 +11,15 @@ import ( "net/http/fcgi" _ "net/http/pprof" // Used for debugging if enabled and a web server is running "os" - "path" "strings" - "code.gitea.io/gitea/models" - "code.gitea.io/gitea/modules/auth" - "code.gitea.io/gitea/modules/context" - "code.gitea.io/gitea/modules/lfs" "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/options" - "code.gitea.io/gitea/modules/public" "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/templates" - "code.gitea.io/gitea/modules/validation" "code.gitea.io/gitea/routers" - "code.gitea.io/gitea/routers/admin" - apiv1 "code.gitea.io/gitea/routers/api/v1" - "code.gitea.io/gitea/routers/dev" - "code.gitea.io/gitea/routers/org" - "code.gitea.io/gitea/routers/private" - "code.gitea.io/gitea/routers/repo" - "code.gitea.io/gitea/routers/user" + "code.gitea.io/gitea/routers/routes" - "github.com/go-macaron/binding" - "github.com/go-macaron/cache" - "github.com/go-macaron/captcha" - "github.com/go-macaron/csrf" - "github.com/go-macaron/gzip" - "github.com/go-macaron/i18n" - "github.com/go-macaron/session" - "github.com/go-macaron/toolbox" context2 "github.com/gorilla/context" "github.com/urfave/cli" - macaron "gopkg.in/macaron.v1" ) // CmdWeb represents the available web sub-command. @@ -72,94 +48,6 @@ and it takes care of all the other things for you`, }, } -// newMacaron initializes Macaron instance. -func newMacaron() *macaron.Macaron { - m := macaron.New() - if !setting.DisableRouterLog { - m.Use(macaron.Logger()) - } - m.Use(macaron.Recovery()) - if setting.EnableGzip { - m.Use(gzip.Gziper()) - } - if setting.Protocol == setting.FCGI { - m.SetURLPrefix(setting.AppSubURL) - } - m.Use(public.Custom( - &public.Options{ - SkipLogging: setting.DisableRouterLog, - }, - )) - m.Use(public.Static( - &public.Options{ - Directory: path.Join(setting.StaticRootPath, "public"), - SkipLogging: setting.DisableRouterLog, - }, - )) - m.Use(macaron.Static( - setting.AvatarUploadPath, - macaron.StaticOptions{ - Prefix: "avatars", - SkipLogging: setting.DisableRouterLog, - ETag: true, - }, - )) - - m.Use(templates.Renderer()) - models.InitMailRender(templates.Mailer()) - - localeNames, err := options.Dir("locale") - - if err != nil { - log.Fatal(4, "Failed to list locale files: %v", err) - } - - localFiles := make(map[string][]byte) - - for _, name := range localeNames { - localFiles[name], err = options.Locale(name) - - if err != nil { - log.Fatal(4, "Failed to load %s locale file. %v", name, err) - } - } - - m.Use(i18n.I18n(i18n.Options{ - SubURL: setting.AppSubURL, - Files: localFiles, - Langs: setting.Langs, - Names: setting.Names, - DefaultLang: "en-US", - Redirect: true, - })) - m.Use(cache.Cacher(cache.Options{ - Adapter: setting.CacheAdapter, - AdapterConfig: setting.CacheConn, - Interval: setting.CacheInterval, - })) - m.Use(captcha.Captchaer(captcha.Options{ - SubURL: setting.AppSubURL, - })) - m.Use(session.Sessioner(setting.SessionConfig)) - m.Use(csrf.Csrfer(csrf.Options{ - Secret: setting.SecretKey, - Cookie: setting.CSRFCookieName, - SetCookie: true, - Header: "X-Csrf-Token", - CookiePath: setting.AppSubURL, - })) - m.Use(toolbox.Toolboxer(m, toolbox.Options{ - HealthCheckFuncs: []*toolbox.HealthCheckFuncDesc{ - { - Desc: "Database connection", - Func: models.Ping, - }, - }, - })) - m.Use(context.Contexter()) - return m -} - func runWeb(ctx *cli.Context) error { if ctx.IsSet("config") { setting.CustomConf = ctx.String("config") @@ -171,522 +59,8 @@ func runWeb(ctx *cli.Context) error { routers.GlobalInit() - m := newMacaron() - - reqSignIn := context.Toggle(&context.ToggleOptions{SignInRequired: true}) - ignSignIn := context.Toggle(&context.ToggleOptions{SignInRequired: setting.Service.RequireSignInView}) - ignSignInAndCsrf := context.Toggle(&context.ToggleOptions{DisableCSRF: true}) - reqSignOut := context.Toggle(&context.ToggleOptions{SignOutRequired: true}) - - bindIgnErr := binding.BindIgnErr - validation.AddBindingRules() - - m.Use(user.GetNotificationCount) - - // FIXME: not all routes need go through same middlewares. - // Especially some AJAX requests, we can reduce middleware number to improve performance. - // Routers. - // for health check - m.Head("/", func() string { - return "" - }) - m.Get("/", ignSignIn, routers.Home) - m.Group("/explore", func() { - m.Get("", func(ctx *context.Context) { - ctx.Redirect(setting.AppSubURL + "/explore/repos") - }) - m.Get("/repos", routers.ExploreRepos) - m.Get("/users", routers.ExploreUsers) - m.Get("/organizations", routers.ExploreOrganizations) - }, ignSignIn) - m.Combo("/install", routers.InstallInit).Get(routers.Install). - Post(bindIgnErr(auth.InstallForm{}), routers.InstallPost) - m.Get("/^:type(issues|pulls)$", reqSignIn, user.Issues) - - // ***** START: User ***** - m.Group("/user", func() { - m.Get("/login", user.SignIn) - m.Post("/login", bindIgnErr(auth.SignInForm{}), user.SignInPost) - if setting.Service.EnableOpenIDSignIn { - m.Combo("/login/openid"). - Get(user.SignInOpenID). - Post(bindIgnErr(auth.SignInOpenIDForm{}), user.SignInOpenIDPost) - m.Group("/openid", func() { - m.Combo("/connect"). - Get(user.ConnectOpenID). - Post(bindIgnErr(auth.ConnectOpenIDForm{}), user.ConnectOpenIDPost) - m.Combo("/register"). - Get(user.RegisterOpenID). - Post(bindIgnErr(auth.SignUpOpenIDForm{}), user.RegisterOpenIDPost) - }) - } - m.Get("/sign_up", user.SignUp) - m.Post("/sign_up", bindIgnErr(auth.RegisterForm{}), user.SignUpPost) - m.Get("/reset_password", user.ResetPasswd) - m.Post("/reset_password", user.ResetPasswdPost) - m.Group("/oauth2", func() { - m.Get("/:provider", user.SignInOAuth) - m.Get("/:provider/callback", user.SignInOAuthCallback) - }) - m.Get("/link_account", user.LinkAccount) - m.Post("/link_account_signin", bindIgnErr(auth.SignInForm{}), user.LinkAccountPostSignIn) - m.Post("/link_account_signup", bindIgnErr(auth.RegisterForm{}), user.LinkAccountPostRegister) - m.Group("/two_factor", func() { - m.Get("", user.TwoFactor) - m.Post("", bindIgnErr(auth.TwoFactorAuthForm{}), user.TwoFactorPost) - m.Get("/scratch", user.TwoFactorScratch) - m.Post("/scratch", bindIgnErr(auth.TwoFactorScratchAuthForm{}), user.TwoFactorScratchPost) - }) - }, reqSignOut) - - m.Group("/user/settings", func() { - m.Get("", user.Settings) - m.Post("", bindIgnErr(auth.UpdateProfileForm{}), user.SettingsPost) - m.Combo("/avatar").Get(user.SettingsAvatar). - Post(binding.MultipartForm(auth.AvatarForm{}), user.SettingsAvatarPost) - m.Post("/avatar/delete", user.SettingsDeleteAvatar) - m.Combo("/email").Get(user.SettingsEmails). - Post(bindIgnErr(auth.AddEmailForm{}), user.SettingsEmailPost) - m.Post("/email/delete", user.DeleteEmail) - m.Get("/password", user.SettingsPassword) - m.Post("/password", bindIgnErr(auth.ChangePasswordForm{}), user.SettingsPasswordPost) - if setting.Service.EnableOpenIDSignIn { - m.Group("/openid", func() { - m.Combo("").Get(user.SettingsOpenID). - Post(bindIgnErr(auth.AddOpenIDForm{}), user.SettingsOpenIDPost) - m.Post("/delete", user.DeleteOpenID) - m.Post("/toggle_visibility", user.ToggleOpenIDVisibility) - }) - } - - m.Combo("/ssh").Get(user.SettingsSSHKeys). - Post(bindIgnErr(auth.AddSSHKeyForm{}), user.SettingsSSHKeysPost) - m.Post("/ssh/delete", user.DeleteSSHKey) - m.Combo("/applications").Get(user.SettingsApplications). - Post(bindIgnErr(auth.NewAccessTokenForm{}), user.SettingsApplicationsPost) - m.Post("/applications/delete", user.SettingsDeleteApplication) - m.Route("/delete", "GET,POST", user.SettingsDelete) - m.Combo("/account_link").Get(user.SettingsAccountLinks).Post(user.SettingsDeleteAccountLink) - m.Group("/two_factor", func() { - m.Get("", user.SettingsTwoFactor) - m.Post("/regenerate_scratch", user.SettingsTwoFactorRegenerateScratch) - m.Post("/disable", user.SettingsTwoFactorDisable) - m.Get("/enroll", user.SettingsTwoFactorEnroll) - m.Post("/enroll", bindIgnErr(auth.TwoFactorAuthForm{}), user.SettingsTwoFactorEnrollPost) - }) - }, reqSignIn, func(ctx *context.Context) { - ctx.Data["PageIsUserSettings"] = true - }) - - m.Group("/user", func() { - // r.Get("/feeds", binding.Bind(auth.FeedsForm{}), user.Feeds) - m.Any("/activate", user.Activate) - m.Any("/activate_email", user.ActivateEmail) - m.Get("/email2user", user.Email2User) - m.Get("/forgot_password", user.ForgotPasswd) - m.Post("/forgot_password", user.ForgotPasswdPost) - m.Get("/logout", user.SignOut) - }) - // ***** END: User ***** - - adminReq := context.Toggle(&context.ToggleOptions{SignInRequired: true, AdminRequired: true}) - - // ***** START: Admin ***** - m.Group("/admin", func() { - m.Get("", adminReq, admin.Dashboard) - m.Get("/config", admin.Config) - m.Post("/config/test_mail", admin.SendTestMail) - m.Get("/monitor", admin.Monitor) - - m.Group("/users", func() { - m.Get("", admin.Users) - m.Combo("/new").Get(admin.NewUser).Post(bindIgnErr(auth.AdminCreateUserForm{}), admin.NewUserPost) - m.Combo("/:userid").Get(admin.EditUser).Post(bindIgnErr(auth.AdminEditUserForm{}), admin.EditUserPost) - m.Post("/:userid/delete", admin.DeleteUser) - }) - - m.Group("/orgs", func() { - m.Get("", admin.Organizations) - }) - - m.Group("/repos", func() { - m.Get("", admin.Repos) - m.Post("/delete", admin.DeleteRepo) - }) - - m.Group("/auths", func() { - m.Get("", admin.Authentications) - m.Combo("/new").Get(admin.NewAuthSource).Post(bindIgnErr(auth.AuthenticationForm{}), admin.NewAuthSourcePost) - m.Combo("/:authid").Get(admin.EditAuthSource). - Post(bindIgnErr(auth.AuthenticationForm{}), admin.EditAuthSourcePost) - m.Post("/:authid/delete", admin.DeleteAuthSource) - }) - - m.Group("/notices", func() { - m.Get("", admin.Notices) - m.Post("/delete", admin.DeleteNotices) - m.Get("/empty", admin.EmptyNotices) - }) - }, adminReq) - // ***** END: Admin ***** - - m.Group("", func() { - m.Group("/:username", func() { - m.Get("", user.Profile) - m.Get("/followers", user.Followers) - m.Get("/following", user.Following) - }) - - m.Get("/attachments/:uuid", func(ctx *context.Context) { - attach, err := models.GetAttachmentByUUID(ctx.Params(":uuid")) - if err != nil { - if models.IsErrAttachmentNotExist(err) { - ctx.Error(404) - } else { - ctx.Handle(500, "GetAttachmentByUUID", err) - } - return - } - - fr, err := os.Open(attach.LocalPath()) - if err != nil { - ctx.Handle(500, "Open", err) - return - } - defer fr.Close() - - if err := attach.IncreaseDownloadCount(); err != nil { - ctx.Handle(500, "Update", err) - return - } - - if err = repo.ServeData(ctx, attach.Name, fr); err != nil { - ctx.Handle(500, "ServeData", err) - return - } - }) - m.Post("/attachments", repo.UploadAttachment) - }, ignSignIn) - - m.Group("/:username", func() { - m.Get("/action/:action", user.Action) - }, reqSignIn) - - if macaron.Env == macaron.DEV { - m.Get("/template/*", dev.TemplatePreview) - } - - reqRepoAdmin := context.RequireRepoAdmin() - reqRepoWriter := context.RequireRepoWriter() - - // ***** START: Organization ***** - m.Group("/org", func() { - m.Group("", func() { - m.Get("/create", org.Create) - m.Post("/create", bindIgnErr(auth.CreateOrgForm{}), org.CreatePost) - }, func(ctx *context.Context) { - if !ctx.User.CanCreateOrganization() { - ctx.NotFound() - } - }) - - m.Group("/:org", func() { - m.Get("/dashboard", user.Dashboard) - m.Get("/^:type(issues|pulls)$", user.Issues) - m.Get("/members", org.Members) - m.Get("/members/action/:action", org.MembersAction) - - m.Get("/teams", org.Teams) - }, context.OrgAssignment(true)) - - m.Group("/:org", func() { - m.Get("/teams/:team", org.TeamMembers) - m.Get("/teams/:team/repositories", org.TeamRepositories) - m.Route("/teams/:team/action/:action", "GET,POST", org.TeamsAction) - m.Route("/teams/:team/action/repo/:action", "GET,POST", org.TeamsRepoAction) - }, context.OrgAssignment(true, false, true)) - - m.Group("/:org", func() { - m.Get("/teams/new", org.NewTeam) - m.Post("/teams/new", bindIgnErr(auth.CreateTeamForm{}), org.NewTeamPost) - m.Get("/teams/:team/edit", org.EditTeam) - m.Post("/teams/:team/edit", bindIgnErr(auth.CreateTeamForm{}), org.EditTeamPost) - m.Post("/teams/:team/delete", org.DeleteTeam) - - m.Group("/settings", func() { - m.Combo("").Get(org.Settings). - Post(bindIgnErr(auth.UpdateOrgSettingForm{}), org.SettingsPost) - m.Post("/avatar", binding.MultipartForm(auth.AvatarForm{}), org.SettingsAvatar) - m.Post("/avatar/delete", org.SettingsDeleteAvatar) - - m.Group("/hooks", func() { - m.Get("", org.Webhooks) - m.Post("/delete", org.DeleteWebhook) - m.Get("/:type/new", repo.WebhooksNew) - m.Post("/gogs/new", bindIgnErr(auth.NewWebhookForm{}), repo.WebHooksNewPost) - m.Post("/slack/new", bindIgnErr(auth.NewSlackHookForm{}), repo.SlackHooksNewPost) - m.Get("/:id", repo.WebHooksEdit) - m.Post("/gogs/:id", bindIgnErr(auth.NewWebhookForm{}), repo.WebHooksEditPost) - m.Post("/slack/:id", bindIgnErr(auth.NewSlackHookForm{}), repo.SlackHooksEditPost) - }) - - m.Route("/delete", "GET,POST", org.SettingsDelete) - }) - - m.Route("/invitations/new", "GET,POST", org.Invitation) - }, context.OrgAssignment(true, true)) - }, reqSignIn) - // ***** END: Organization ***** - - // ***** START: Repository ***** - m.Group("/repo", func() { - m.Get("/create", repo.Create) - m.Post("/create", bindIgnErr(auth.CreateRepoForm{}), repo.CreatePost) - m.Get("/migrate", repo.Migrate) - m.Post("/migrate", bindIgnErr(auth.MigrateRepoForm{}), repo.MigratePost) - m.Combo("/fork/:repoid").Get(repo.Fork). - Post(bindIgnErr(auth.CreateRepoForm{}), repo.ForkPost) - }, reqSignIn) - - m.Group("/:username/:reponame", func() { - m.Group("/settings", func() { - m.Combo("").Get(repo.Settings). - Post(bindIgnErr(auth.RepoSettingForm{}), repo.SettingsPost) - m.Group("/collaboration", func() { - m.Combo("").Get(repo.Collaboration).Post(repo.CollaborationPost) - m.Post("/access_mode", repo.ChangeCollaborationAccessMode) - m.Post("/delete", repo.DeleteCollaboration) - }) - m.Group("/branches", func() { - m.Combo("").Get(repo.ProtectedBranch).Post(repo.ProtectedBranchPost) - m.Post("/can_push", repo.ChangeProtectedBranch) - m.Post("/delete", repo.DeleteProtectedBranch) - }, repo.MustBeNotBare) - - m.Group("/hooks", func() { - m.Get("", repo.Webhooks) - m.Post("/delete", repo.DeleteWebhook) - m.Get("/:type/new", repo.WebhooksNew) - m.Post("/gogs/new", bindIgnErr(auth.NewWebhookForm{}), repo.WebHooksNewPost) - m.Post("/slack/new", bindIgnErr(auth.NewSlackHookForm{}), repo.SlackHooksNewPost) - m.Get("/:id", repo.WebHooksEdit) - m.Post("/:id/test", repo.TestWebhook) - m.Post("/gogs/:id", bindIgnErr(auth.NewWebhookForm{}), repo.WebHooksEditPost) - m.Post("/slack/:id", bindIgnErr(auth.NewSlackHookForm{}), repo.SlackHooksEditPost) - - m.Group("/git", func() { - m.Get("", repo.GitHooks) - m.Combo("/:name").Get(repo.GitHooksEdit). - Post(repo.GitHooksEditPost) - }, context.GitHookService()) - }) - - m.Group("/keys", func() { - m.Combo("").Get(repo.DeployKeys). - Post(bindIgnErr(auth.AddSSHKeyForm{}), repo.DeployKeysPost) - m.Post("/delete", repo.DeleteDeployKey) - }) - - }, func(ctx *context.Context) { - ctx.Data["PageIsSettings"] = true - }, context.UnitTypes()) - }, reqSignIn, context.RepoAssignment(), reqRepoAdmin, context.RepoRef()) - - m.Get("/:username/:reponame/action/:action", reqSignIn, context.RepoAssignment(), repo.Action) - m.Group("/:username/:reponame", func() { - // FIXME: should use different URLs but mostly same logic for comments of issue and pull reuqest. - // So they can apply their own enable/disable logic on routers. - m.Group("/issues", func() { - m.Combo("/new", repo.MustEnableIssues).Get(context.RepoRef(), repo.NewIssue). - Post(bindIgnErr(auth.CreateIssueForm{}), repo.NewIssuePost) - - m.Group("/:index", func() { - m.Post("/title", repo.UpdateIssueTitle) - m.Post("/content", repo.UpdateIssueContent) - m.Post("/watch", repo.IssueWatch) - m.Combo("/comments").Post(bindIgnErr(auth.CreateCommentForm{}), repo.NewComment) - }) - - m.Post("/labels", repo.UpdateIssueLabel, reqRepoWriter) - m.Post("/milestone", repo.UpdateIssueMilestone, reqRepoWriter) - m.Post("/assignee", repo.UpdateIssueAssignee, reqRepoWriter) - m.Post("/status", repo.UpdateIssueStatus, reqRepoWriter) - }) - m.Group("/comments/:id", func() { - m.Post("", repo.UpdateCommentContent) - m.Post("/delete", repo.DeleteComment) - }) - m.Group("/labels", func() { - m.Post("/new", bindIgnErr(auth.CreateLabelForm{}), repo.NewLabel) - m.Post("/edit", bindIgnErr(auth.CreateLabelForm{}), repo.UpdateLabel) - m.Post("/delete", repo.DeleteLabel) - m.Post("/initialize", bindIgnErr(auth.InitializeLabelsForm{}), repo.InitializeLabels) - }, reqRepoWriter, context.RepoRef()) - m.Group("/milestones", func() { - m.Combo("/new").Get(repo.NewMilestone). - Post(bindIgnErr(auth.CreateMilestoneForm{}), repo.NewMilestonePost) - m.Get("/:id/edit", repo.EditMilestone) - m.Post("/:id/edit", bindIgnErr(auth.CreateMilestoneForm{}), repo.EditMilestonePost) - m.Get("/:id/:action", repo.ChangeMilestonStatus) - m.Post("/delete", repo.DeleteMilestone) - }, reqRepoWriter, context.RepoRef()) - m.Group("/releases", func() { - m.Get("/new", repo.NewRelease) - m.Post("/new", bindIgnErr(auth.NewReleaseForm{}), repo.NewReleasePost) - m.Post("/delete", repo.DeleteRelease) - }, repo.MustBeNotBare, reqRepoWriter, context.RepoRef()) - m.Group("/releases", func() { - m.Get("/edit/*", repo.EditRelease) - m.Post("/edit/*", bindIgnErr(auth.EditReleaseForm{}), repo.EditReleasePost) - }, repo.MustBeNotBare, reqRepoWriter, func(ctx *context.Context) { - var err error - ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetBranchCommit(ctx.Repo.Repository.DefaultBranch) - if err != nil { - ctx.Handle(500, "GetBranchCommit", err) - return - } - ctx.Repo.CommitsCount, err = ctx.Repo.Commit.CommitsCount() - if err != nil { - ctx.Handle(500, "CommitsCount", err) - return - } - ctx.Data["CommitsCount"] = ctx.Repo.CommitsCount - }) - - m.Combo("/compare/*", repo.MustAllowPulls, repo.SetEditorconfigIfExists). - Get(repo.CompareAndPullRequest). - Post(bindIgnErr(auth.CreateIssueForm{}), repo.CompareAndPullRequestPost) - - m.Group("", func() { - m.Combo("/_edit/*").Get(repo.EditFile). - Post(bindIgnErr(auth.EditRepoFileForm{}), repo.EditFilePost) - m.Combo("/_new/*").Get(repo.NewFile). - Post(bindIgnErr(auth.EditRepoFileForm{}), repo.NewFilePost) - m.Post("/_preview/*", bindIgnErr(auth.EditPreviewDiffForm{}), repo.DiffPreviewPost) - m.Combo("/_delete/*").Get(repo.DeleteFile). - Post(bindIgnErr(auth.DeleteRepoFileForm{}), repo.DeleteFilePost) - - m.Group("", func() { - m.Combo("/_upload/*").Get(repo.UploadFile). - Post(bindIgnErr(auth.UploadRepoFileForm{}), repo.UploadFilePost) - m.Post("/upload-file", repo.UploadFileToServer) - m.Post("/upload-remove", bindIgnErr(auth.RemoveUploadFileForm{}), repo.RemoveUploadFileFromServer) - }, func(ctx *context.Context) { - if !setting.Repository.Upload.Enabled { - ctx.Handle(404, "", nil) - return - } - }) - }, repo.MustBeNotBare, reqRepoWriter, context.RepoRef(), func(ctx *context.Context) { - if !ctx.Repo.Repository.CanEnableEditor() || ctx.Repo.IsViewCommit { - ctx.Handle(404, "", nil) - return - } - }) - }, reqSignIn, context.RepoAssignment(), context.UnitTypes()) - - m.Group("/:username/:reponame", func() { - m.Group("", func() { - m.Get("/releases", repo.MustBeNotBare, repo.Releases) - m.Get("/^:type(issues|pulls)$", repo.RetrieveLabels, repo.Issues) - m.Get("/^:type(issues|pulls)$/:index", repo.ViewIssue) - m.Get("/labels/", repo.RetrieveLabels, repo.Labels) - m.Get("/milestones", repo.Milestones) - }, context.RepoRef()) - - // m.Get("/branches", repo.Branches) - m.Post("/branches/:name/delete", reqSignIn, reqRepoWriter, repo.MustBeNotBare, repo.DeleteBranchPost) - - m.Group("/wiki", func() { - m.Get("/?:page", repo.Wiki) - m.Get("/_pages", repo.WikiPages) - - m.Group("", func() { - m.Combo("/_new").Get(repo.NewWiki). - Post(bindIgnErr(auth.NewWikiForm{}), repo.NewWikiPost) - m.Combo("/:page/_edit").Get(repo.EditWiki). - Post(bindIgnErr(auth.NewWikiForm{}), repo.EditWikiPost) - m.Post("/:page/delete", repo.DeleteWikiPagePost) - }, reqSignIn, reqRepoWriter) - }, repo.MustEnableWiki, context.RepoRef()) - - m.Group("/wiki", func() { - m.Get("/raw/*", repo.WikiRaw) - m.Get("/*", repo.WikiRaw) - }, repo.MustEnableWiki) - - m.Get("/archive/*", repo.MustBeNotBare, repo.Download) - - m.Group("/pulls/:index", func() { - m.Get("/commits", context.RepoRef(), repo.ViewPullCommits) - m.Get("/files", context.RepoRef(), repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.ViewPullFiles) - m.Post("/merge", reqRepoWriter, repo.MergePullRequest) - }, repo.MustAllowPulls) - - m.Group("", func() { - m.Get("/src/*", repo.SetEditorconfigIfExists, repo.Home) - m.Get("/raw/*", repo.SingleDownload) - m.Get("/commits/*", repo.RefCommits) - m.Get("/graph", repo.Graph) - m.Get("/commit/:sha([a-f0-9]{7,40})$", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.Diff) - m.Get("/forks", repo.Forks) - }, context.RepoRef()) - m.Get("/commit/:sha([a-f0-9]{7,40})\\.:ext(patch|diff)", repo.MustBeNotBare, repo.RawDiff) - - m.Get("/compare/:before([a-z0-9]{40})\\.\\.\\.:after([a-z0-9]{40})", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.MustBeNotBare, repo.CompareDiff) - }, ignSignIn, context.RepoAssignment(), context.UnitTypes()) - m.Group("/:username/:reponame", func() { - m.Get("/stars", repo.Stars) - m.Get("/watchers", repo.Watchers) - }, ignSignIn, context.RepoAssignment(), context.RepoRef(), context.UnitTypes()) - - m.Group("/:username", func() { - m.Group("/:reponame", func() { - m.Get("", repo.SetEditorconfigIfExists, repo.Home) - m.Get("\\.git$", repo.SetEditorconfigIfExists, repo.Home) - }, ignSignIn, context.RepoAssignment(), context.RepoRef(), context.UnitTypes()) - - m.Group("/:reponame", func() { - m.Group("/info/lfs", func() { - m.Post("/objects/batch", lfs.BatchHandler) - m.Get("/objects/:oid/:filename", lfs.ObjectOidHandler) - m.Any("/objects/:oid", lfs.ObjectOidHandler) - m.Post("/objects", lfs.PostHandler) - m.Any("/*", func(ctx *context.Context) { - ctx.Handle(404, "", nil) - }) - }, ignSignInAndCsrf) - m.Any("/*", ignSignInAndCsrf, repo.HTTP) - m.Head("/tasks/trigger", repo.TriggerTask) - }) - }) - // ***** END: Repository ***** - - m.Group("/notifications", func() { - m.Get("", user.Notifications) - m.Post("/status", user.NotificationStatusPost) - }, reqSignIn) - - m.Group("/api", func() { - apiv1.RegisterRoutes(m) - }, ignSignIn) - - m.Group("/api/internal", func() { - // package name internal is ideal but Golang is not allowed, so we use private as package name. - private.RegisterRoutes(m) - }) - - // robots.txt - m.Get("/robots.txt", func(ctx *context.Context) { - if setting.HasRobotsTxt { - ctx.ServeFileContent(path.Join(setting.CustomPath, "robots.txt")) - } else { - ctx.Error(404) - } - }) - - // Not found handler. - m.NotFound(routers.NotFound) + m := routes.NewMacaron() + routes.RegisterRoutes(m) // Flag for port number in case first time run conflict. if ctx.IsSet("port") { diff --git a/integrations/install_test.go b/integrations/install_test.go deleted file mode 100644 index 9912bfa90..000000000 --- a/integrations/install_test.go +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright 2017 The Gitea 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 integration - -import ( - "fmt" - "net/http" - "os" - "os/user" - "testing" - "time" - - "code.gitea.io/gitea/integrations/internal/utils" -) - -// The HTTP port listened by the Gitea server. -const ServerHTTPPort = "3001" - -const _RetryLimit = 10 - -func makeSimpleSettings(user, port string) map[string][]string { - return map[string][]string{ - "db_type": {"SQLite3"}, - "db_host": {"localhost"}, - "db_path": {"data/gitea.db"}, - "app_name": {"Gitea: Git with a cup of tea"}, - "repo_root_path": {"repositories"}, - "run_user": {user}, - "domain": {"localhost"}, - "ssh_port": {"22"}, - "http_port": {port}, - "app_url": {"http://localhost:" + port}, - "log_root_path": {"log"}, - } -} - -func install(t *utils.T) error { - var r *http.Response - var err error - - for i := 1; i <= _RetryLimit; i++ { - - r, err = http.Get("http://:" + ServerHTTPPort + "/") - if err == nil { - fmt.Fprintln(os.Stderr) - break - } - - // Give the server some amount of time to warm up. - time.Sleep(100 * time.Millisecond) - fmt.Fprint(os.Stderr, ".") - } - - if err != nil { - return err - } - - defer r.Body.Close() - - _user, err := user.Current() - if err != nil { - return err - } - - settings := makeSimpleSettings(_user.Username, ServerHTTPPort) - r, err = http.PostForm("http://:"+ServerHTTPPort+"/install", settings) - if err != nil { - return err - } - defer r.Body.Close() - - if r.StatusCode != http.StatusOK { - return fmt.Errorf("'/install': %s", r.Status) - } - return nil -} - -func TestInstall(t *testing.T) { - conf := utils.Config{ - Program: "../gitea", - WorkDir: "", - Args: []string{"web", "--port", ServerHTTPPort}, - LogFile: os.Stderr, - } - - if err := utils.New(t, &conf).RunTest(install); err != nil { - t.Fatal(err) - } -} diff --git a/integrations/integration_test.go b/integrations/integration_test.go new file mode 100644 index 000000000..db78eead8 --- /dev/null +++ b/integrations/integration_test.go @@ -0,0 +1,92 @@ +// Copyright 2017 The Gitea 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 integrations + +import ( + "bytes" + "fmt" + "io" + "net/http" + "os" + "testing" + + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/routers" + "code.gitea.io/gitea/routers/routes" + + "gopkg.in/macaron.v1" + "gopkg.in/testfixtures.v2" +) + +var mac *macaron.Macaron + +func TestMain(m *testing.M) { + appIniPath := os.Getenv("GITEA_CONF") + if appIniPath == "" { + fmt.Println("Environment variable $GITEA_CONF not set") + os.Exit(1) + } + setting.CustomConf = appIniPath + routers.GlobalInit() + mac = routes.NewMacaron() + routes.RegisterRoutes(mac) + + var helper testfixtures.Helper + if setting.UseMySQL { + helper = &testfixtures.MySQL{} + } else if setting.UsePostgreSQL { + helper = &testfixtures.PostgreSQL{} + } else if setting.UseSQLite3 { + helper = &testfixtures.SQLite{} + } else { + fmt.Println("Unsupported RDBMS for integration tests") + os.Exit(1) + } + + err := models.InitFixtures( + helper, + "integrations/gitea-integration/fixtures/", + ) + if err != nil { + fmt.Printf("Error initializing test database: %v\n", err) + os.Exit(1) + } + os.Exit(m.Run()) +} + +type TestResponseWriter struct { + HeaderCode int + Writer io.Writer +} + +func (w *TestResponseWriter) Header() http.Header { + return make(map[string][]string) +} + +func (w *TestResponseWriter) Write(b []byte) (int, error) { + return w.Writer.Write(b) +} + +func (w *TestResponseWriter) WriteHeader(n int) { + w.HeaderCode = n +} + +type TestResponse struct { + HeaderCode int + Body []byte +} + +func MakeRequest(req *http.Request) *TestResponse { + buffer := bytes.NewBuffer(nil) + respWriter := &TestResponseWriter{ + Writer: buffer, + } + mac.ServeHTTP(respWriter, req) + return &TestResponse{ + HeaderCode: respWriter.HeaderCode, + Body: buffer.Bytes(), + } +} diff --git a/integrations/internal/utils/utils.go b/integrations/internal/utils/utils.go deleted file mode 100644 index e8380df39..000000000 --- a/integrations/internal/utils/utils.go +++ /dev/null @@ -1,156 +0,0 @@ -// Copyright 2017 The Gitea 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 utils - -import ( - "errors" - "fmt" - "io" - "io/ioutil" - "log" - "net/http" - "os" - "os/exec" - "path/filepath" - "syscall" - "testing" -) - -// T wraps testing.T and the configurations of the testing instance. -type T struct { - *testing.T - Config *Config -} - -// New create an instance of T -func New(t *testing.T, c *Config) *T { - return &T{T: t, Config: c} -} - -// Config Settings of the testing program -type Config struct { - // The executable path of the tested program. - Program string - // Working directory prepared for the tested program. - // If empty, a directory named with random suffixes is picked, and created under the platform-dependent default temporary directory. - // The directory will be removed when the test finishes. - WorkDir string - // Command-line arguments passed to the tested program. - Args []string - - // Where to redirect the stdout/stderr to. For debugging purposes. - LogFile *os.File -} - -func redirect(cmd *exec.Cmd, f *os.File) error { - stdout, err := cmd.StdoutPipe() - if err != nil { - return err - } - - stderr, err := cmd.StderrPipe() - if err != nil { - return err - } - - go io.Copy(f, stdout) - go io.Copy(f, stderr) - return nil -} - -// RunTest Helper function for setting up a running Gitea server for functional testing and then gracefully terminating it. -func (t *T) RunTest(tests ...func(*T) error) (err error) { - if t.Config.Program == "" { - return errors.New("Need input file") - } - - path, err := filepath.Abs(t.Config.Program) - if err != nil { - return err - } - - workdir := t.Config.WorkDir - if workdir == "" { - workdir, err = ioutil.TempDir(os.TempDir(), "gitea_tests-") - if err != nil { - return err - } - defer os.RemoveAll(workdir) - } - - newpath := filepath.Join(workdir, filepath.Base(path)) - if err := os.Symlink(path, newpath); err != nil { - return err - } - - log.Printf("Starting the server: %s args:%s workdir:%s", newpath, t.Config.Args, workdir) - - cmd := exec.Command(newpath, t.Config.Args...) - cmd.Dir = workdir - - if t.Config.LogFile != nil && testing.Verbose() { - if err := redirect(cmd, t.Config.LogFile); err != nil { - return err - } - } - - if err := cmd.Start(); err != nil { - return err - } - - log.Println("Server started.") - - defer func() { - // Do not early return. We have to call Wait anyway. - _ = cmd.Process.Signal(syscall.SIGTERM) - - if _err := cmd.Wait(); _err != nil { - if _err.Error() != "signal: terminated" { - err = _err - return - } - } - - log.Println("Server exited") - }() - - for _, fn := range tests { - if err := fn(t); err != nil { - return err - } - } - - // Note that the return value 'err' may be updated by the 'defer' statement before despite it's returning nil here. - return nil -} - -// GetAndPost provides a convenient helper function for testing an HTTP endpoint with GET and POST method. -// The function sends GET first and then POST with the given form. -func GetAndPost(url string, form map[string][]string) error { - var err error - var r *http.Response - - r, err = http.Get(url) - if err != nil { - return err - } - defer r.Body.Close() - - if r.StatusCode != http.StatusOK { - return fmt.Errorf("GET '%s': %s", url, r.Status) - } - - r, err = http.PostForm(url, form) - if err != nil { - return err - } - defer r.Body.Close() - - if r.StatusCode != http.StatusOK { - return fmt.Errorf("POST '%s': %s", url, r.Status) - } - - return nil -} diff --git a/integrations/mysql.ini b/integrations/mysql.ini new file mode 100644 index 000000000..9e121ac1e --- /dev/null +++ b/integrations/mysql.ini @@ -0,0 +1,56 @@ +APP_NAME = Gitea: Git with a cup of tea +RUN_MODE = prod + +[database] +DB_TYPE = mysql +HOST = 127.0.0.1:3306 +NAME = testgitea +USER = root +PASSWD = +SSL_MODE = disable +PATH = data/gitea.db + +[repository] +ROOT = integrations/gitea-integration/gitea-repositories + +[server] +SSH_DOMAIN = localhost +HTTP_PORT = 3000 +ROOT_URL = http://localhost:3000/ +DISABLE_SSH = false +SSH_PORT = 22 +LFS_START_SERVER = false +OFFLINE_MODE = false + +[mailer] +ENABLED = false + +[service] +REGISTER_EMAIL_CONFIRM = false +ENABLE_NOTIFY_MAIL = false +DISABLE_REGISTRATION = false +ENABLE_CAPTCHA = false +REQUIRE_SIGNIN_VIEW = false +DEFAULT_KEEP_EMAIL_PRIVATE = false +NO_REPLY_ADDRESS = noreply.example.org + +[picture] +DISABLE_GRAVATAR = false +ENABLE_FEDERATED_AVATAR = false + +[session] +PROVIDER = file + +[log] +MODE = console,file + +[log.console] +LEVEL = Warn + +[log.file] +LEVEL = Info +ROOT_PATH = log + +[security] +INSTALL_LOCK = true +SECRET_KEY = 9pCviYTWSb diff --git a/integrations/pgsql.ini b/integrations/pgsql.ini new file mode 100644 index 000000000..42e29e826 --- /dev/null +++ b/integrations/pgsql.ini @@ -0,0 +1,56 @@ +APP_NAME = Gitea: Git with a cup of tea +RUN_MODE = prod + +[database] +DB_TYPE = postgres +HOST = 127.0.0.1:5432 +NAME = testgitea +USER = postgres +PASSWD = postgres +SSL_MODE = disable +PATH = data/gitea.db + +[repository] +ROOT = integrations/gitea-integration/gitea-repositories + +[server] +SSH_DOMAIN = localhost +HTTP_PORT = 3000 +ROOT_URL = http://localhost:3000/ +DISABLE_SSH = false +SSH_PORT = 22 +LFS_START_SERVER = false +OFFLINE_MODE = false + +[mailer] +ENABLED = false + +[service] +REGISTER_EMAIL_CONFIRM = false +ENABLE_NOTIFY_MAIL = false +DISABLE_REGISTRATION = false +ENABLE_CAPTCHA = false +REQUIRE_SIGNIN_VIEW = false +DEFAULT_KEEP_EMAIL_PRIVATE = false +NO_REPLY_ADDRESS = noreply.example.org + +[picture] +DISABLE_GRAVATAR = false +ENABLE_FEDERATED_AVATAR = false + +[session] +PROVIDER = file + +[log] +MODE = console,file + +[log.console] +LEVEL = Warn + +[log.file] +LEVEL = Info +ROOT_PATH = log + +[security] +INSTALL_LOCK = true +SECRET_KEY = 9pCviYTWSb diff --git a/integrations/signup_test.go b/integrations/signup_test.go index 55ae64c37..7d8f27332 100644 --- a/integrations/signup_test.go +++ b/integrations/signup_test.go @@ -2,34 +2,40 @@ // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. -package integration +package integrations import ( - "os" + "bytes" + "net/http" + "net/url" "testing" - "code.gitea.io/gitea/integrations/internal/utils" + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/setting" + + "github.com/stretchr/testify/assert" ) -var signupFormSample map[string][]string = map[string][]string{ - "Name": {"tester"}, - "Email": {"user1@example.com"}, - "Passwd": {"12345678"}, -} - -func signup(t *utils.T) error { - return utils.GetAndPost("http://:"+ServerHTTPPort+"/user/sign_up", signupFormSample) -} - func TestSignup(t *testing.T) { - conf := utils.Config{ - Program: "../gitea", - WorkDir: "", - Args: []string{"web", "--port", ServerHTTPPort}, - LogFile: os.Stderr, - } + assert.NoError(t, models.LoadFixtures()) + setting.Service.EnableCaptcha = false - if err := utils.New(t, &conf).RunTest(install, signup); err != nil { - t.Fatal(err) - } + req, err := http.NewRequest("POST", "/user/sign_up", + bytes.NewBufferString(url.Values{ + "user_name": []string{"exampleUser"}, + "email": []string{"exampleUser@example.com"}, + "password": []string{"examplePassword"}, + "retype": []string{"examplePassword"}, + }.Encode()), + ) + assert.NoError(t, err) + req.Header.Add("Content-Type", "application/x-www-form-urlencoded") + resp := MakeRequest(req) + assert.EqualValues(t, http.StatusFound, resp.HeaderCode) + + // should be able to view new user's page + req, err = http.NewRequest("GET", "/exampleUser", nil) + assert.NoError(t, err) + resp = MakeRequest(req) + assert.EqualValues(t, http.StatusOK, resp.HeaderCode) } diff --git a/integrations/sqlite.ini b/integrations/sqlite.ini new file mode 100644 index 000000000..dc17b23ec --- /dev/null +++ b/integrations/sqlite.ini @@ -0,0 +1,58 @@ +APP_NAME = Gitea: Git with a cup of tea +RUN_MODE = prod + +[database] +DB_TYPE = sqlite3 +HOST = 127.0.0.1:3306 +NAME = testgitea +USER = gitea +PASSWD = +SSL_MODE = disable +PATH = :memory: + +[repository] +ROOT = integrations/gitea-integration/gitea-repositories + +[server] +SSH_DOMAIN = localhost +HTTP_PORT = 3000 +ROOT_URL = http://localhost:3000/ +DISABLE_SSH = false +SSH_PORT = 22 +LFS_START_SERVER = false +OFFLINE_MODE = false + +[mailer] +ENABLED = false + +[service] +REGISTER_EMAIL_CONFIRM = false +ENABLE_NOTIFY_MAIL = false +DISABLE_REGISTRATION = false +ENABLE_CAPTCHA = false +REQUIRE_SIGNIN_VIEW = false +DEFAULT_KEEP_EMAIL_PRIVATE = false +NO_REPLY_ADDRESS = noreply.example.org + +[picture] +DISABLE_GRAVATAR = false +ENABLE_FEDERATED_AVATAR = false + +[session] +PROVIDER = file + +[log] +MODE = console,file + +[log.console] +LEVEL = Warn + +[log.file] +LEVEL = Info +ROOT_PATH = log + +[security] +INSTALL_LOCK = true +SECRET_KEY = 9pCviYTWSb +INTERNAL_TOKEN = eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOjE0OTI3OTU5ODN9.OQkH5UmzID2XBdwQ9TAI6Jj2t1X-wElVTjbE7aoN4I8 + diff --git a/integrations/version_test.go b/integrations/version_test.go index beda5c3ab..58568e22f 100644 --- a/integrations/version_test.go +++ b/integrations/version_test.go @@ -2,81 +2,33 @@ // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. -package integration +package integrations import ( + "bytes" "encoding/json" - "fmt" - "log" "net/http" - "os" - "os/exec" - "path/filepath" - "strings" "testing" - "code.gitea.io/gitea/integrations/internal/utils" + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/setting" "code.gitea.io/sdk/gitea" "github.com/stretchr/testify/assert" ) -func version(t *utils.T) error { - var err error - - path, err := filepath.Abs(t.Config.Program) - if err != nil { - return err - } - - cmd := exec.Command(path, "--version") - out, err := cmd.Output() - if err != nil { - return err - } - - fields := strings.Fields(string(out)) - if !strings.HasPrefix(string(out), "Gitea version") { - return fmt.Errorf("unexpected version string '%s' of the gitea executable", out) - } - - expected := fields[2] - - var r *http.Response - r, err = http.Get("http://:" + ServerHTTPPort + "/api/v1/version") - if err != nil { - return err - } - defer r.Body.Close() - - if r.StatusCode != http.StatusOK { - return fmt.Errorf("'/api/v1/version': %s\n", r.Status) - } - - var v gitea.ServerVersion - - dec := json.NewDecoder(r.Body) - if err := dec.Decode(&v); err != nil { - return err - } - - actual := v.Version - - log.Printf("Actual: \"%s\" Expected: \"%s\"\n", actual, expected) - assert.Equal(t, expected, actual) - - return nil -} - func TestVersion(t *testing.T) { - conf := utils.Config{ - Program: "../gitea", - WorkDir: "", - Args: []string{"web", "--port", ServerHTTPPort}, - LogFile: os.Stderr, - } + assert.NoError(t, models.LoadFixtures()) - if err := utils.New(t, &conf).RunTest(install, version); err != nil { - t.Fatal(err) - } + setting.AppVer = "1.1.0+dev" + req, err := http.NewRequest("GET", "/api/v1/version", nil) + assert.NoError(t, err) + resp := MakeRequest(req) + + var version gitea.ServerVersion + decoder := json.NewDecoder(bytes.NewBuffer(resp.Body)) + assert.NoError(t, decoder.Decode(&version)) + + assert.EqualValues(t, http.StatusOK, resp.HeaderCode) + assert.Equal(t, setting.AppVer, string(version.Version)) } diff --git a/integrations/view_test.go b/integrations/view_test.go new file mode 100644 index 000000000..1314fe383 --- /dev/null +++ b/integrations/view_test.go @@ -0,0 +1,32 @@ +// Copyright 2017 The Gitea 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 integrations + +import ( + "net/http" + "testing" + + "code.gitea.io/gitea/models" + + "github.com/stretchr/testify/assert" +) + +func TestViewRepo(t *testing.T) { + assert.NoError(t, models.LoadFixtures()) + + req, err := http.NewRequest("GET", "/user1/repo1", nil) + assert.NoError(t, err) + resp := MakeRequest(req) + assert.EqualValues(t, http.StatusOK, resp.HeaderCode) +} + +func TestViewUser(t *testing.T) { + assert.NoError(t, models.LoadFixtures()) + + req, err := http.NewRequest("GET", "/user1", nil) + assert.NoError(t, err) + resp := MakeRequest(req) + assert.EqualValues(t, http.StatusOK, resp.HeaderCode) +} diff --git a/models/setup_for_test.go b/models/setup_for_test.go index 5a17eac78..9fb83f15c 100644 --- a/models/setup_for_test.go +++ b/models/setup_for_test.go @@ -37,11 +37,8 @@ func TestMain(m *testing.M) { os.Exit(m.Run()) } -var fixtures *testfixtures.Context - // CreateTestEngine create an xorm engine for testing func CreateTestEngine() error { - testfixtures.SkipDatabaseNameCheck(true) var err error x, err = xorm.NewEngine("sqlite3", "file::memory:?cache=shared") if err != nil { @@ -52,8 +49,7 @@ func CreateTestEngine() error { return err } - fixtures, err = testfixtures.NewFolder(x.DB().DB, &testfixtures.SQLite{}, "fixtures/") - return err + return InitFixtures(&testfixtures.SQLite{}, "fixtures/") } func TestFixturesAreConsistent(t *testing.T) { @@ -63,7 +59,7 @@ func TestFixturesAreConsistent(t *testing.T) { // PrepareTestDatabase load test fixtures into test database func PrepareTestDatabase() error { - return fixtures.Load() + return LoadFixtures() } func loadBeanIfExists(bean interface{}, conditions ...interface{}) (bool, error) { diff --git a/models/test_fixtures.go b/models/test_fixtures.go new file mode 100644 index 000000000..d7f59ec3b --- /dev/null +++ b/models/test_fixtures.go @@ -0,0 +1,23 @@ +// Copyright 2017 The Gitea 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 ( + "gopkg.in/testfixtures.v2" +) + +var fixtures *testfixtures.Context + +// InitFixtures initialize test fixtures for a test database +func InitFixtures(helper testfixtures.Helper, dir string) (err error) { + testfixtures.SkipDatabaseNameCheck(true) + fixtures, err = testfixtures.NewFolder(x.DB().DB, helper, dir) + return err +} + +// LoadFixtures load fixtures for a test database +func LoadFixtures() error { + return fixtures.Load() +} diff --git a/routers/routes/routes.go b/routers/routes/routes.go new file mode 100644 index 000000000..a28473c0e --- /dev/null +++ b/routers/routes/routes.go @@ -0,0 +1,645 @@ +// Copyright 2017 The Gitea 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 routes + +import ( + "os" + "path" + + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/auth" + "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/lfs" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/options" + "code.gitea.io/gitea/modules/public" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/templates" + "code.gitea.io/gitea/modules/validation" + "code.gitea.io/gitea/routers" + "code.gitea.io/gitea/routers/admin" + apiv1 "code.gitea.io/gitea/routers/api/v1" + "code.gitea.io/gitea/routers/dev" + "code.gitea.io/gitea/routers/org" + "code.gitea.io/gitea/routers/private" + "code.gitea.io/gitea/routers/repo" + "code.gitea.io/gitea/routers/user" + + "github.com/go-macaron/binding" + "github.com/go-macaron/cache" + "github.com/go-macaron/captcha" + "github.com/go-macaron/csrf" + "github.com/go-macaron/gzip" + "github.com/go-macaron/i18n" + "github.com/go-macaron/session" + "github.com/go-macaron/toolbox" + "gopkg.in/macaron.v1" +) + +// NewMacaron initializes Macaron instance. +func NewMacaron() *macaron.Macaron { + m := macaron.New() + if !setting.DisableRouterLog { + m.Use(macaron.Logger()) + } + m.Use(macaron.Recovery()) + if setting.EnableGzip { + m.Use(gzip.Gziper()) + } + if setting.Protocol == setting.FCGI { + m.SetURLPrefix(setting.AppSubURL) + } + m.Use(public.Custom( + &public.Options{ + SkipLogging: setting.DisableRouterLog, + }, + )) + m.Use(public.Static( + &public.Options{ + Directory: path.Join(setting.StaticRootPath, "public"), + SkipLogging: setting.DisableRouterLog, + }, + )) + m.Use(macaron.Static( + setting.AvatarUploadPath, + macaron.StaticOptions{ + Prefix: "avatars", + SkipLogging: setting.DisableRouterLog, + ETag: true, + }, + )) + + m.Use(templates.Renderer()) + models.InitMailRender(templates.Mailer()) + + localeNames, err := options.Dir("locale") + + if err != nil { + log.Fatal(4, "Failed to list locale files: %v", err) + } + + localFiles := make(map[string][]byte) + + for _, name := range localeNames { + localFiles[name], err = options.Locale(name) + + if err != nil { + log.Fatal(4, "Failed to load %s locale file. %v", name, err) + } + } + + m.Use(i18n.I18n(i18n.Options{ + SubURL: setting.AppSubURL, + Files: localFiles, + Langs: setting.Langs, + Names: setting.Names, + DefaultLang: "en-US", + Redirect: true, + })) + m.Use(cache.Cacher(cache.Options{ + Adapter: setting.CacheAdapter, + AdapterConfig: setting.CacheConn, + Interval: setting.CacheInterval, + })) + m.Use(captcha.Captchaer(captcha.Options{ + SubURL: setting.AppSubURL, + })) + m.Use(session.Sessioner(setting.SessionConfig)) + m.Use(csrf.Csrfer(csrf.Options{ + Secret: setting.SecretKey, + Cookie: setting.CSRFCookieName, + SetCookie: true, + Header: "X-Csrf-Token", + CookiePath: setting.AppSubURL, + })) + m.Use(toolbox.Toolboxer(m, toolbox.Options{ + HealthCheckFuncs: []*toolbox.HealthCheckFuncDesc{ + { + Desc: "Database connection", + Func: models.Ping, + }, + }, + })) + m.Use(context.Contexter()) + return m +} + +// RegisterRoutes routes routes to Macaron +func RegisterRoutes(m *macaron.Macaron) { + reqSignIn := context.Toggle(&context.ToggleOptions{SignInRequired: true}) + ignSignIn := context.Toggle(&context.ToggleOptions{SignInRequired: setting.Service.RequireSignInView}) + ignSignInAndCsrf := context.Toggle(&context.ToggleOptions{DisableCSRF: true}) + reqSignOut := context.Toggle(&context.ToggleOptions{SignOutRequired: true}) + + bindIgnErr := binding.BindIgnErr + validation.AddBindingRules() + + m.Use(user.GetNotificationCount) + + // FIXME: not all routes need go through same middlewares. + // Especially some AJAX requests, we can reduce middleware number to improve performance. + // Routers. + // for health check + m.Head("/", func() string { + return "" + }) + m.Get("/", ignSignIn, routers.Home) + m.Group("/explore", func() { + m.Get("", func(ctx *context.Context) { + ctx.Redirect(setting.AppSubURL + "/explore/repos") + }) + m.Get("/repos", routers.ExploreRepos) + m.Get("/users", routers.ExploreUsers) + m.Get("/organizations", routers.ExploreOrganizations) + }, ignSignIn) + m.Combo("/install", routers.InstallInit).Get(routers.Install). + Post(bindIgnErr(auth.InstallForm{}), routers.InstallPost) + m.Get("/^:type(issues|pulls)$", reqSignIn, user.Issues) + + // ***** START: User ***** + m.Group("/user", func() { + m.Get("/login", user.SignIn) + m.Post("/login", bindIgnErr(auth.SignInForm{}), user.SignInPost) + if setting.Service.EnableOpenIDSignIn { + m.Combo("/login/openid"). + Get(user.SignInOpenID). + Post(bindIgnErr(auth.SignInOpenIDForm{}), user.SignInOpenIDPost) + m.Group("/openid", func() { + m.Combo("/connect"). + Get(user.ConnectOpenID). + Post(bindIgnErr(auth.ConnectOpenIDForm{}), user.ConnectOpenIDPost) + m.Combo("/routes"). + Get(user.RegisterOpenID). + Post(bindIgnErr(auth.SignUpOpenIDForm{}), user.RegisterOpenIDPost) + }) + } + m.Get("/sign_up", user.SignUp) + m.Post("/sign_up", bindIgnErr(auth.RegisterForm{}), user.SignUpPost) + m.Get("/reset_password", user.ResetPasswd) + m.Post("/reset_password", user.ResetPasswdPost) + m.Group("/oauth2", func() { + m.Get("/:provider", user.SignInOAuth) + m.Get("/:provider/callback", user.SignInOAuthCallback) + }) + m.Get("/link_account", user.LinkAccount) + m.Post("/link_account_signin", bindIgnErr(auth.SignInForm{}), user.LinkAccountPostSignIn) + m.Post("/link_account_signup", bindIgnErr(auth.RegisterForm{}), user.LinkAccountPostRegister) + m.Group("/two_factor", func() { + m.Get("", user.TwoFactor) + m.Post("", bindIgnErr(auth.TwoFactorAuthForm{}), user.TwoFactorPost) + m.Get("/scratch", user.TwoFactorScratch) + m.Post("/scratch", bindIgnErr(auth.TwoFactorScratchAuthForm{}), user.TwoFactorScratchPost) + }) + }, reqSignOut) + + m.Group("/user/settings", func() { + m.Get("", user.Settings) + m.Post("", bindIgnErr(auth.UpdateProfileForm{}), user.SettingsPost) + m.Combo("/avatar").Get(user.SettingsAvatar). + Post(binding.MultipartForm(auth.AvatarForm{}), user.SettingsAvatarPost) + m.Post("/avatar/delete", user.SettingsDeleteAvatar) + m.Combo("/email").Get(user.SettingsEmails). + Post(bindIgnErr(auth.AddEmailForm{}), user.SettingsEmailPost) + m.Post("/email/delete", user.DeleteEmail) + m.Get("/password", user.SettingsPassword) + m.Post("/password", bindIgnErr(auth.ChangePasswordForm{}), user.SettingsPasswordPost) + if setting.Service.EnableOpenIDSignIn { + m.Group("/openid", func() { + m.Combo("").Get(user.SettingsOpenID). + Post(bindIgnErr(auth.AddOpenIDForm{}), user.SettingsOpenIDPost) + m.Post("/delete", user.DeleteOpenID) + m.Post("/toggle_visibility", user.ToggleOpenIDVisibility) + }) + } + + m.Combo("/ssh").Get(user.SettingsSSHKeys). + Post(bindIgnErr(auth.AddSSHKeyForm{}), user.SettingsSSHKeysPost) + m.Post("/ssh/delete", user.DeleteSSHKey) + m.Combo("/applications").Get(user.SettingsApplications). + Post(bindIgnErr(auth.NewAccessTokenForm{}), user.SettingsApplicationsPost) + m.Post("/applications/delete", user.SettingsDeleteApplication) + m.Route("/delete", "GET,POST", user.SettingsDelete) + m.Combo("/account_link").Get(user.SettingsAccountLinks).Post(user.SettingsDeleteAccountLink) + m.Group("/two_factor", func() { + m.Get("", user.SettingsTwoFactor) + m.Post("/regenerate_scratch", user.SettingsTwoFactorRegenerateScratch) + m.Post("/disable", user.SettingsTwoFactorDisable) + m.Get("/enroll", user.SettingsTwoFactorEnroll) + m.Post("/enroll", bindIgnErr(auth.TwoFactorAuthForm{}), user.SettingsTwoFactorEnrollPost) + }) + }, reqSignIn, func(ctx *context.Context) { + ctx.Data["PageIsUserSettings"] = true + }) + + m.Group("/user", func() { + // r.Get("/feeds", binding.Bind(auth.FeedsForm{}), user.Feeds) + m.Any("/activate", user.Activate) + m.Any("/activate_email", user.ActivateEmail) + m.Get("/email2user", user.Email2User) + m.Get("/forgot_password", user.ForgotPasswd) + m.Post("/forgot_password", user.ForgotPasswdPost) + m.Get("/logout", user.SignOut) + }) + // ***** END: User ***** + + adminReq := context.Toggle(&context.ToggleOptions{SignInRequired: true, AdminRequired: true}) + + // ***** START: Admin ***** + m.Group("/admin", func() { + m.Get("", adminReq, admin.Dashboard) + m.Get("/config", admin.Config) + m.Post("/config/test_mail", admin.SendTestMail) + m.Get("/monitor", admin.Monitor) + + m.Group("/users", func() { + m.Get("", admin.Users) + m.Combo("/new").Get(admin.NewUser).Post(bindIgnErr(auth.AdminCreateUserForm{}), admin.NewUserPost) + m.Combo("/:userid").Get(admin.EditUser).Post(bindIgnErr(auth.AdminEditUserForm{}), admin.EditUserPost) + m.Post("/:userid/delete", admin.DeleteUser) + }) + + m.Group("/orgs", func() { + m.Get("", admin.Organizations) + }) + + m.Group("/repos", func() { + m.Get("", admin.Repos) + m.Post("/delete", admin.DeleteRepo) + }) + + m.Group("/auths", func() { + m.Get("", admin.Authentications) + m.Combo("/new").Get(admin.NewAuthSource).Post(bindIgnErr(auth.AuthenticationForm{}), admin.NewAuthSourcePost) + m.Combo("/:authid").Get(admin.EditAuthSource). + Post(bindIgnErr(auth.AuthenticationForm{}), admin.EditAuthSourcePost) + m.Post("/:authid/delete", admin.DeleteAuthSource) + }) + + m.Group("/notices", func() { + m.Get("", admin.Notices) + m.Post("/delete", admin.DeleteNotices) + m.Get("/empty", admin.EmptyNotices) + }) + }, adminReq) + // ***** END: Admin ***** + + m.Group("", func() { + m.Group("/:username", func() { + m.Get("", user.Profile) + m.Get("/followers", user.Followers) + m.Get("/following", user.Following) + }) + + m.Get("/attachments/:uuid", func(ctx *context.Context) { + attach, err := models.GetAttachmentByUUID(ctx.Params(":uuid")) + if err != nil { + if models.IsErrAttachmentNotExist(err) { + ctx.Error(404) + } else { + ctx.Handle(500, "GetAttachmentByUUID", err) + } + return + } + + fr, err := os.Open(attach.LocalPath()) + if err != nil { + ctx.Handle(500, "Open", err) + return + } + defer fr.Close() + + if err := attach.IncreaseDownloadCount(); err != nil { + ctx.Handle(500, "Update", err) + return + } + + if err = repo.ServeData(ctx, attach.Name, fr); err != nil { + ctx.Handle(500, "ServeData", err) + return + } + }) + m.Post("/attachments", repo.UploadAttachment) + }, ignSignIn) + + m.Group("/:username", func() { + m.Get("/action/:action", user.Action) + }, reqSignIn) + + if macaron.Env == macaron.DEV { + m.Get("/template/*", dev.TemplatePreview) + } + + reqRepoAdmin := context.RequireRepoAdmin() + reqRepoWriter := context.RequireRepoWriter() + + // ***** START: Organization ***** + m.Group("/org", func() { + m.Group("", func() { + m.Get("/create", org.Create) + m.Post("/create", bindIgnErr(auth.CreateOrgForm{}), org.CreatePost) + }, func(ctx *context.Context) { + if !ctx.User.CanCreateOrganization() { + ctx.NotFound() + } + }) + + m.Group("/:org", func() { + m.Get("/dashboard", user.Dashboard) + m.Get("/^:type(issues|pulls)$", user.Issues) + m.Get("/members", org.Members) + m.Get("/members/action/:action", org.MembersAction) + + m.Get("/teams", org.Teams) + }, context.OrgAssignment(true)) + + m.Group("/:org", func() { + m.Get("/teams/:team", org.TeamMembers) + m.Get("/teams/:team/repositories", org.TeamRepositories) + m.Route("/teams/:team/action/:action", "GET,POST", org.TeamsAction) + m.Route("/teams/:team/action/repo/:action", "GET,POST", org.TeamsRepoAction) + }, context.OrgAssignment(true, false, true)) + + m.Group("/:org", func() { + m.Get("/teams/new", org.NewTeam) + m.Post("/teams/new", bindIgnErr(auth.CreateTeamForm{}), org.NewTeamPost) + m.Get("/teams/:team/edit", org.EditTeam) + m.Post("/teams/:team/edit", bindIgnErr(auth.CreateTeamForm{}), org.EditTeamPost) + m.Post("/teams/:team/delete", org.DeleteTeam) + + m.Group("/settings", func() { + m.Combo("").Get(org.Settings). + Post(bindIgnErr(auth.UpdateOrgSettingForm{}), org.SettingsPost) + m.Post("/avatar", binding.MultipartForm(auth.AvatarForm{}), org.SettingsAvatar) + m.Post("/avatar/delete", org.SettingsDeleteAvatar) + + m.Group("/hooks", func() { + m.Get("", org.Webhooks) + m.Post("/delete", org.DeleteWebhook) + m.Get("/:type/new", repo.WebhooksNew) + m.Post("/gogs/new", bindIgnErr(auth.NewWebhookForm{}), repo.WebHooksNewPost) + m.Post("/slack/new", bindIgnErr(auth.NewSlackHookForm{}), repo.SlackHooksNewPost) + m.Get("/:id", repo.WebHooksEdit) + m.Post("/gogs/:id", bindIgnErr(auth.NewWebhookForm{}), repo.WebHooksEditPost) + m.Post("/slack/:id", bindIgnErr(auth.NewSlackHookForm{}), repo.SlackHooksEditPost) + }) + + m.Route("/delete", "GET,POST", org.SettingsDelete) + }) + + m.Route("/invitations/new", "GET,POST", org.Invitation) + }, context.OrgAssignment(true, true)) + }, reqSignIn) + // ***** END: Organization ***** + + // ***** START: Repository ***** + m.Group("/repo", func() { + m.Get("/create", repo.Create) + m.Post("/create", bindIgnErr(auth.CreateRepoForm{}), repo.CreatePost) + m.Get("/migrate", repo.Migrate) + m.Post("/migrate", bindIgnErr(auth.MigrateRepoForm{}), repo.MigratePost) + m.Combo("/fork/:repoid").Get(repo.Fork). + Post(bindIgnErr(auth.CreateRepoForm{}), repo.ForkPost) + }, reqSignIn) + + m.Group("/:username/:reponame", func() { + m.Group("/settings", func() { + m.Combo("").Get(repo.Settings). + Post(bindIgnErr(auth.RepoSettingForm{}), repo.SettingsPost) + m.Group("/collaboration", func() { + m.Combo("").Get(repo.Collaboration).Post(repo.CollaborationPost) + m.Post("/access_mode", repo.ChangeCollaborationAccessMode) + m.Post("/delete", repo.DeleteCollaboration) + }) + m.Group("/branches", func() { + m.Combo("").Get(repo.ProtectedBranch).Post(repo.ProtectedBranchPost) + m.Post("/can_push", repo.ChangeProtectedBranch) + m.Post("/delete", repo.DeleteProtectedBranch) + }, repo.MustBeNotBare) + + m.Group("/hooks", func() { + m.Get("", repo.Webhooks) + m.Post("/delete", repo.DeleteWebhook) + m.Get("/:type/new", repo.WebhooksNew) + m.Post("/gogs/new", bindIgnErr(auth.NewWebhookForm{}), repo.WebHooksNewPost) + m.Post("/slack/new", bindIgnErr(auth.NewSlackHookForm{}), repo.SlackHooksNewPost) + m.Get("/:id", repo.WebHooksEdit) + m.Post("/:id/test", repo.TestWebhook) + m.Post("/gogs/:id", bindIgnErr(auth.NewWebhookForm{}), repo.WebHooksEditPost) + m.Post("/slack/:id", bindIgnErr(auth.NewSlackHookForm{}), repo.SlackHooksEditPost) + + m.Group("/git", func() { + m.Get("", repo.GitHooks) + m.Combo("/:name").Get(repo.GitHooksEdit). + Post(repo.GitHooksEditPost) + }, context.GitHookService()) + }) + + m.Group("/keys", func() { + m.Combo("").Get(repo.DeployKeys). + Post(bindIgnErr(auth.AddSSHKeyForm{}), repo.DeployKeysPost) + m.Post("/delete", repo.DeleteDeployKey) + }) + + }, func(ctx *context.Context) { + ctx.Data["PageIsSettings"] = true + }, context.UnitTypes()) + }, reqSignIn, context.RepoAssignment(), reqRepoAdmin, context.RepoRef()) + + m.Get("/:username/:reponame/action/:action", reqSignIn, context.RepoAssignment(), repo.Action) + m.Group("/:username/:reponame", func() { + // FIXME: should use different URLs but mostly same logic for comments of issue and pull reuqest. + // So they can apply their own enable/disable logic on routers. + m.Group("/issues", func() { + m.Combo("/new", repo.MustEnableIssues).Get(context.RepoRef(), repo.NewIssue). + Post(bindIgnErr(auth.CreateIssueForm{}), repo.NewIssuePost) + + m.Group("/:index", func() { + m.Post("/title", repo.UpdateIssueTitle) + m.Post("/content", repo.UpdateIssueContent) + m.Post("/watch", repo.IssueWatch) + m.Combo("/comments").Post(bindIgnErr(auth.CreateCommentForm{}), repo.NewComment) + }) + + m.Post("/labels", repo.UpdateIssueLabel, reqRepoWriter) + m.Post("/milestone", repo.UpdateIssueMilestone, reqRepoWriter) + m.Post("/assignee", repo.UpdateIssueAssignee, reqRepoWriter) + m.Post("/status", repo.UpdateIssueStatus, reqRepoWriter) + }) + m.Group("/comments/:id", func() { + m.Post("", repo.UpdateCommentContent) + m.Post("/delete", repo.DeleteComment) + }) + m.Group("/labels", func() { + m.Post("/new", bindIgnErr(auth.CreateLabelForm{}), repo.NewLabel) + m.Post("/edit", bindIgnErr(auth.CreateLabelForm{}), repo.UpdateLabel) + m.Post("/delete", repo.DeleteLabel) + m.Post("/initialize", bindIgnErr(auth.InitializeLabelsForm{}), repo.InitializeLabels) + }, reqRepoWriter, context.RepoRef()) + m.Group("/milestones", func() { + m.Combo("/new").Get(repo.NewMilestone). + Post(bindIgnErr(auth.CreateMilestoneForm{}), repo.NewMilestonePost) + m.Get("/:id/edit", repo.EditMilestone) + m.Post("/:id/edit", bindIgnErr(auth.CreateMilestoneForm{}), repo.EditMilestonePost) + m.Get("/:id/:action", repo.ChangeMilestonStatus) + m.Post("/delete", repo.DeleteMilestone) + }, reqRepoWriter, context.RepoRef()) + m.Group("/releases", func() { + m.Get("/new", repo.NewRelease) + m.Post("/new", bindIgnErr(auth.NewReleaseForm{}), repo.NewReleasePost) + m.Post("/delete", repo.DeleteRelease) + }, repo.MustBeNotBare, reqRepoWriter, context.RepoRef()) + m.Group("/releases", func() { + m.Get("/edit/*", repo.EditRelease) + m.Post("/edit/*", bindIgnErr(auth.EditReleaseForm{}), repo.EditReleasePost) + }, repo.MustBeNotBare, reqRepoWriter, func(ctx *context.Context) { + var err error + ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetBranchCommit(ctx.Repo.Repository.DefaultBranch) + if err != nil { + ctx.Handle(500, "GetBranchCommit", err) + return + } + ctx.Repo.CommitsCount, err = ctx.Repo.Commit.CommitsCount() + if err != nil { + ctx.Handle(500, "CommitsCount", err) + return + } + ctx.Data["CommitsCount"] = ctx.Repo.CommitsCount + }) + + m.Combo("/compare/*", repo.MustAllowPulls, repo.SetEditorconfigIfExists). + Get(repo.CompareAndPullRequest). + Post(bindIgnErr(auth.CreateIssueForm{}), repo.CompareAndPullRequestPost) + + m.Group("", func() { + m.Combo("/_edit/*").Get(repo.EditFile). + Post(bindIgnErr(auth.EditRepoFileForm{}), repo.EditFilePost) + m.Combo("/_new/*").Get(repo.NewFile). + Post(bindIgnErr(auth.EditRepoFileForm{}), repo.NewFilePost) + m.Post("/_preview/*", bindIgnErr(auth.EditPreviewDiffForm{}), repo.DiffPreviewPost) + m.Combo("/_delete/*").Get(repo.DeleteFile). + Post(bindIgnErr(auth.DeleteRepoFileForm{}), repo.DeleteFilePost) + + m.Group("", func() { + m.Combo("/_upload/*").Get(repo.UploadFile). + Post(bindIgnErr(auth.UploadRepoFileForm{}), repo.UploadFilePost) + m.Post("/upload-file", repo.UploadFileToServer) + m.Post("/upload-remove", bindIgnErr(auth.RemoveUploadFileForm{}), repo.RemoveUploadFileFromServer) + }, func(ctx *context.Context) { + if !setting.Repository.Upload.Enabled { + ctx.Handle(404, "", nil) + return + } + }) + }, repo.MustBeNotBare, reqRepoWriter, context.RepoRef(), func(ctx *context.Context) { + if !ctx.Repo.Repository.CanEnableEditor() || ctx.Repo.IsViewCommit { + ctx.Handle(404, "", nil) + return + } + }) + }, reqSignIn, context.RepoAssignment(), context.UnitTypes()) + + m.Group("/:username/:reponame", func() { + m.Group("", func() { + m.Get("/releases", repo.MustBeNotBare, repo.Releases) + m.Get("/^:type(issues|pulls)$", repo.RetrieveLabels, repo.Issues) + m.Get("/^:type(issues|pulls)$/:index", repo.ViewIssue) + m.Get("/labels/", repo.RetrieveLabels, repo.Labels) + m.Get("/milestones", repo.Milestones) + }, context.RepoRef()) + + // m.Get("/branches", repo.Branches) + m.Post("/branches/:name/delete", reqSignIn, reqRepoWriter, repo.MustBeNotBare, repo.DeleteBranchPost) + + m.Group("/wiki", func() { + m.Get("/?:page", repo.Wiki) + m.Get("/_pages", repo.WikiPages) + + m.Group("", func() { + m.Combo("/_new").Get(repo.NewWiki). + Post(bindIgnErr(auth.NewWikiForm{}), repo.NewWikiPost) + m.Combo("/:page/_edit").Get(repo.EditWiki). + Post(bindIgnErr(auth.NewWikiForm{}), repo.EditWikiPost) + m.Post("/:page/delete", repo.DeleteWikiPagePost) + }, reqSignIn, reqRepoWriter) + }, repo.MustEnableWiki, context.RepoRef()) + + m.Group("/wiki", func() { + m.Get("/raw/*", repo.WikiRaw) + m.Get("/*", repo.WikiRaw) + }, repo.MustEnableWiki) + + m.Get("/archive/*", repo.MustBeNotBare, repo.Download) + + m.Group("/pulls/:index", func() { + m.Get("/commits", context.RepoRef(), repo.ViewPullCommits) + m.Get("/files", context.RepoRef(), repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.ViewPullFiles) + m.Post("/merge", reqRepoWriter, repo.MergePullRequest) + }, repo.MustAllowPulls) + + m.Group("", func() { + m.Get("/src/*", repo.SetEditorconfigIfExists, repo.Home) + m.Get("/raw/*", repo.SingleDownload) + m.Get("/commits/*", repo.RefCommits) + m.Get("/graph", repo.Graph) + m.Get("/commit/:sha([a-f0-9]{7,40})$", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.Diff) + m.Get("/forks", repo.Forks) + }, context.RepoRef()) + m.Get("/commit/:sha([a-f0-9]{7,40})\\.:ext(patch|diff)", repo.MustBeNotBare, repo.RawDiff) + + m.Get("/compare/:before([a-z0-9]{40})\\.\\.\\.:after([a-z0-9]{40})", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.MustBeNotBare, repo.CompareDiff) + }, ignSignIn, context.RepoAssignment(), context.UnitTypes()) + m.Group("/:username/:reponame", func() { + m.Get("/stars", repo.Stars) + m.Get("/watchers", repo.Watchers) + }, ignSignIn, context.RepoAssignment(), context.RepoRef(), context.UnitTypes()) + + m.Group("/:username", func() { + m.Group("/:reponame", func() { + m.Get("", repo.SetEditorconfigIfExists, repo.Home) + m.Get("\\.git$", repo.SetEditorconfigIfExists, repo.Home) + }, ignSignIn, context.RepoAssignment(), context.RepoRef(), context.UnitTypes()) + + m.Group("/:reponame", func() { + m.Group("/info/lfs", func() { + m.Post("/objects/batch", lfs.BatchHandler) + m.Get("/objects/:oid/:filename", lfs.ObjectOidHandler) + m.Any("/objects/:oid", lfs.ObjectOidHandler) + m.Post("/objects", lfs.PostHandler) + m.Any("/*", func(ctx *context.Context) { + ctx.Handle(404, "", nil) + }) + }, ignSignInAndCsrf) + m.Any("/*", ignSignInAndCsrf, repo.HTTP) + m.Head("/tasks/trigger", repo.TriggerTask) + }) + }) + // ***** END: Repository ***** + + m.Group("/notifications", func() { + m.Get("", user.Notifications) + m.Post("/status", user.NotificationStatusPost) + }, reqSignIn) + + m.Group("/api", func() { + apiv1.RegisterRoutes(m) + }, ignSignIn) + + m.Group("/api/internal", func() { + // package name internal is ideal but Golang is not allowed, so we use private as package name. + private.RegisterRoutes(m) + }) + + // robots.txt + m.Get("/robots.txt", func(ctx *context.Context) { + if setting.HasRobotsTxt { + ctx.ServeFileContent(path.Join(setting.CustomPath, "robots.txt")) + } else { + ctx.Error(404) + } + }) + + // Not found handler. + m.NotFound(routers.NotFound) +}