Browse Source

Initial base app import

Dashie der otter 1 year ago
commit
869c626e09
Signed by: Dashie <dashie@sigpipe.me> GPG Key ID: C2D57B325840B755
100 changed files with 14673 additions and 0 deletions
  1. 21
    0
      .bra.toml
  2. 21
    0
      .gitignore
  3. 99
    0
      Jenkinsfile
  4. 95
    0
      Jenkinsfile.gdsl
  5. 8
    0
      LICENSE
  6. 42
    0
      Makefile
  7. 12
    0
      README.md
  8. 37
    0
      cmd/cmd.go
  9. 228
    0
      cmd/web.go
  10. 184
    0
      conf/app.ini
  11. 145
    0
      conf/locale/locale_en-US.ini
  12. 62
    0
      context/auth.go
  13. 207
    0
      context/context.go
  14. 28
    0
      context/user.go
  15. 122
    0
      glide.lock
  16. 51
    0
      glide.yaml
  17. 88
    0
      i18n.py
  18. 217
    0
      models/error.go
  19. 12
    0
      models/errors/errors.go
  20. 18
    0
      models/errors/repo.go
  21. 45
    0
      models/errors/user.go
  22. 223
    0
      models/models.go
  23. 11
    0
      models/models_sqlite.go
  24. 327
    0
      models/user.go
  25. 34
    0
      myapp.go
  26. 62
    0
      pkg/auth/auth.go
  27. 24
    0
      pkg/cron/cron.go
  28. 29
    0
      pkg/form/auth.go
  29. 159
    0
      pkg/form/form.go
  30. 50
    0
      pkg/form/gitxt.go
  31. 15
    0
      pkg/form/user.go
  32. 104
    0
      pkg/mailer/mail.go
  33. 229
    0
      pkg/mailer/mailer.go
  34. 124
    0
      pkg/markup/markdown.go
  35. 39
    0
      pkg/markup/markdown_test.go
  36. 284
    0
      pkg/markup/markup.go
  37. 56
    0
      pkg/markup/markup_test.go
  38. 46
    0
      pkg/markup/sanitizer.go
  39. 37
    0
      pkg/markup/sanitizer_test.go
  40. 147
    0
      pkg/sanitize/sanitize.go
  41. 49
    0
      pkg/sanitize/sanitize_test.go
  42. 70
    0
      pkg/sync/exclusive_pool.go
  43. 49
    0
      pkg/sync/status_pool.go
  44. 70
    0
      pkg/sync/unique_queue.go
  45. 113
    0
      pkg/template/template.go
  46. 55
    0
      pkg/tool/file.go
  47. 31
    0
      pkg/tool/file_test.go
  48. 235
    0
      pkg/tool/tool.go
  49. 88
    0
      pkg/tool/tool_test.go
  50. 109
    0
      routers/admin/admin.go
  51. 14
    0
      routers/hub.go
  52. 350
    0
      routers/user/auth.go
  53. 38
    0
      routers/user/setting.go
  54. 457
    0
      setting/setting.go
  55. 6
    0
      static/css/bootstrap.min.css
  56. 1
    0
      static/css/bootstrap.min.css.map
  57. 354
    0
      static/css/custom.css
  58. 7
    0
      static/font-awesome-4.7.0/HELP-US-OUT.txt
  59. 2337
    0
      static/font-awesome-4.7.0/css/font-awesome.css
  60. 4
    0
      static/font-awesome-4.7.0/css/font-awesome.min.css
  61. BIN
      static/font-awesome-4.7.0/fonts/FontAwesome.otf
  62. BIN
      static/font-awesome-4.7.0/fonts/fontawesome-webfont.eot
  63. 2671
    0
      static/font-awesome-4.7.0/fonts/fontawesome-webfont.svg
  64. BIN
      static/font-awesome-4.7.0/fonts/fontawesome-webfont.ttf
  65. BIN
      static/font-awesome-4.7.0/fonts/fontawesome-webfont.woff
  66. BIN
      static/font-awesome-4.7.0/fonts/fontawesome-webfont.woff2
  67. 34
    0
      static/font-awesome-4.7.0/less/animated.less
  68. 25
    0
      static/font-awesome-4.7.0/less/bordered-pulled.less
  69. 12
    0
      static/font-awesome-4.7.0/less/core.less
  70. 6
    0
      static/font-awesome-4.7.0/less/fixed-width.less
  71. 18
    0
      static/font-awesome-4.7.0/less/font-awesome.less
  72. 789
    0
      static/font-awesome-4.7.0/less/icons.less
  73. 13
    0
      static/font-awesome-4.7.0/less/larger.less
  74. 19
    0
      static/font-awesome-4.7.0/less/list.less
  75. 60
    0
      static/font-awesome-4.7.0/less/mixins.less
  76. 15
    0
      static/font-awesome-4.7.0/less/path.less
  77. 20
    0
      static/font-awesome-4.7.0/less/rotated-flipped.less
  78. 5
    0
      static/font-awesome-4.7.0/less/screen-reader.less
  79. 20
    0
      static/font-awesome-4.7.0/less/stacked.less
  80. 800
    0
      static/font-awesome-4.7.0/less/variables.less
  81. 34
    0
      static/font-awesome-4.7.0/scss/_animated.scss
  82. 25
    0
      static/font-awesome-4.7.0/scss/_bordered-pulled.scss
  83. 12
    0
      static/font-awesome-4.7.0/scss/_core.scss
  84. 6
    0
      static/font-awesome-4.7.0/scss/_fixed-width.scss
  85. 789
    0
      static/font-awesome-4.7.0/scss/_icons.scss
  86. 13
    0
      static/font-awesome-4.7.0/scss/_larger.scss
  87. 19
    0
      static/font-awesome-4.7.0/scss/_list.scss
  88. 60
    0
      static/font-awesome-4.7.0/scss/_mixins.scss
  89. 15
    0
      static/font-awesome-4.7.0/scss/_path.scss
  90. 20
    0
      static/font-awesome-4.7.0/scss/_rotated-flipped.scss
  91. 5
    0
      static/font-awesome-4.7.0/scss/_screen-reader.scss
  92. 20
    0
      static/font-awesome-4.7.0/scss/_stacked.scss
  93. 800
    0
      static/font-awesome-4.7.0/scss/_variables.scss
  94. 18
    0
      static/font-awesome-4.7.0/scss/font-awesome.scss
  95. 7
    0
      static/js/bootstrap.min.js
  96. 4
    0
      static/js/jquery-3.2.1.min.js
  97. 1
    0
      static/js/jquery-3.2.1.min.map
  98. 115
    0
      templates/admin/dashboard.tmpl
  99. 24
    0
      templates/base/alert.tmpl
  100. 0
    0
      templates/base/footer.tmpl

+ 21
- 0
.bra.toml View File

@@ -0,0 +1,21 @@
1
+[run]
2
+init_cmds = [
3
+	["make", "build-dev"],
4
+	["./myapp", "web"]
5
+]
6
+watch_all = true
7
+watch_dirs = [
8
+	"$WORKDIR/cmd",
9
+	"$WORKDIR/routers",
10
+    "$WORKDIR/context",
11
+    "$WORKDIR/stuff",
12
+    "$WORKDIR/models",
13
+    "$WORKDIR/conf/locale",
14
+]
15
+watch_exts = [".go"]
16
+ignore_files = [".+_test.go"]
17
+build_delay = 1500
18
+cmds = [
19
+	["make", "build-dev"], # TAGS=sqlite cert pam tidb
20
+	["./myapp", "web"]
21
+]

+ 21
- 0
.gitignore View File

@@ -0,0 +1,21 @@
1
+.DS_Store
2
+*.db
3
+*.log
4
+log/
5
+custom/
6
+data/
7
+.vendor/
8
+.idea/
9
+*.iml
10
+public/img/avatar/
11
+*.exe
12
+*.exe~
13
+profile/
14
+*.pem
15
+output*
16
+*.sublime-project
17
+*.sublime-workspace
18
+/release
19
+myapp
20
+myapp
21
+myapp.db

+ 99
- 0
Jenkinsfile View File

@@ -0,0 +1,99 @@
1
+#!/usr/bin/env groovy
2
+
3
+// http://www.asciiarmor.com/post/99010893761/jenkins-now-with-more-gopher
4
+// https://medium.com/@reynn/automate-cross-platform-golang-builds-with-jenkins-ef7b07f1366e
5
+// http://grugrut.hatenablog.jp/entry/2017/04/10/201607
6
+// https://gist.github.com/wavded/5e6b0d5016c2a3c05237
7
+
8
+node('linux && x86_64 && go') {
9
+    // Install the desired Go version
10
+    def root = tool name: 'Go 1.8.3', type: 'go'
11
+
12
+    ws("${JENKINS_HOME}/jobs/${JOB_NAME}/builds/${BUILD_ID}/src/dev.sigpipe.me/dashie/myapp") {
13
+        // Export environment variables pointing to the directory where Go was installed
14
+        env.GOROOT="${root}"
15
+        env.GOPATH="${JENKINS_HOME}/jobs/${JOB_NAME}/builds/${BUILD_ID}/"
16
+        env.PATH="${GOPATH}/bin:$PATH"
17
+
18
+        stage('Requirements') {
19
+            sh 'go version'
20
+
21
+            sh 'go get -u github.com/golang/lint/golint'
22
+            sh 'go get -u github.com/tebeka/go2xunit'
23
+            sh 'go get github.com/Masterminds/glide'
24
+        }
25
+
26
+        stage('Checkout') {
27
+        //    git url: 'https://dev.sigpipe.me/dashie/myapp.git'
28
+            checkout scm
29
+        }
30
+
31
+        String applicationName = "myapp"
32
+        String appVersion = sh (
33
+            script: "cat myapp.go | awk -F'\"' '/const APP_VER/ { print \$2 }'",
34
+            returnStdout: true
35
+            ).trim()
36
+        String buildNumber = "${appVersion}-${env.BUILD_NUMBER}"
37
+
38
+        stage('Install dependencies') {
39
+            sh 'glide install'
40
+        }
41
+
42
+        stage('Test') {
43
+            // Static check and publish warnings
44
+            sh 'golint $(go list ./... | grep -v /vendor/) > lint.txt'
45
+            warnings canComputeNew: false, canResolveRelativePaths: false, defaultEncoding: '', excludePattern: '', healthy: '', includePattern: '', messagesPattern: '', parserConfigurations: [[parserName: 'Go Lint', pattern: 'lint.txt']], unHealthy: ''
46
+
47
+            // The real tests then publish the results
48
+            try {
49
+                // broken due to some go /vendor directory crap
50
+                sh 'go test -v $(go list ./... | grep -v /vendor/) > tests.txt'
51
+            } catch (err) {
52
+                if (currentBuild.result == 'UNSTABLE')
53
+                    currentBuild.result = 'FAILURE'
54
+                throw err
55
+            } finally {
56
+                sh 'cat tests.txt | go2xunit -output tests.xml'
57
+                step([$class: 'JUnitResultArchiver', testResults: 'tests.xml', healthScaleFactor: 1.0])
58
+                //No such DSL method 'publishHTML'
59
+                //publishHTML (target: [
60
+                //    allowMissing: false,
61
+                //    alwaysLinkToLastBuild: false,
62
+                //    keepAll: true,
63
+                //    reportDir: 'coverage',
64
+                //    reportFiles: 'index.html',
65
+                //    reportName: "Junit Report"
66
+                //])
67
+            }
68
+        }
69
+
70
+        stage('Build') {
71
+            // Darwin/amd64
72
+            //sh "make build GOOS=darwin GOARCH=amd64 BUILD_FLAGS='-o binaries/amd64/${buildNumber}/darwin/${applicationName}-${buildNumber}.darwin.amd6'"
73
+            // Windows/amd64
74
+            //sh "make build GOOS=windows GOARCH=amd64 BUILD_FLAGS='-o binaries/amd64/${buildNumber}/windows/${applicationName}-${buildNumber}.windows.amd64.exe'"
75
+            // Linux/amd64
76
+            sh "make build GOOS=linux GOARCH=amd64 BUILD_FLAGS='-o binaries/amd64/${buildNumber}/linux/${applicationName}-${buildNumber}.linux.amd64'"
77
+        }
78
+
79
+        stage('Archivate Artifacts') {
80
+            // this doesn't works
81
+            //zip dir: '${env.WORKSPACE}/', zipFile: "${env.WORKSPACE}/myapp.linux-${buildNumber}.zip", glob: 'binaries/**,conf,LICENSE*,README*,lint.txt,tests.txt', archive: true
82
+            sh 'ls'
83
+            sh """
84
+            mkdir myapp.linux-${buildNumber}
85
+            cp binaries/amd64/${buildNumber}/linux/${applicationName}-${buildNumber}.linux.amd64 myapp.linux-${buildNumber}
86
+            cp -r conf myapp.linux-${buildNumber}
87
+            cp LICENSE* myapp.linux-${buildNumber}
88
+            cp README.md myapp.linux-${buildNumber}
89
+            cp lint.txt tests.txt myapp.linux-${buildNumber}
90
+            zip -r myapp.linux-${buildNumber}.zip myapp.linux-${buildNumber}
91
+            rm -rf myapp.linux-${buildNumber}
92
+            """
93
+
94
+            archiveArtifacts artifacts: 'binaries/**,conf,LICENSE*,README*', fingerprint: true
95
+            archiveArtifacts artifacts: 'lint.txt,tests.txt', fingerprint: true
96
+            archiveArtifacts artifacts: "myapp.linux-${buildNumber}.zip", fingerprint: true
97
+        }
98
+    } // ws
99
+} // node

+ 95
- 0
Jenkinsfile.gdsl
File diff suppressed because it is too large
View File


+ 8
- 0
LICENSE View File

@@ -0,0 +1,8 @@
1
+MIT License
2
+Copyright (c) 2017 <dashie@sigpipe.me>
3
+
4
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
5
+
6
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
7
+
8
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

+ 42
- 0
Makefile View File

@@ -0,0 +1,42 @@
1
+# LDFLAGS += -X "/setting.BuildTime=$(shell date -u '+%Y-%m-%d %I:%M:%S %Z')"
2
+# LDFLAGS += -X "/setting.BuildGitHash=$(shell git rev-parse HEAD)"
3
+
4
+OS := $(shell uname)
5
+
6
+DATA_FILES := $(shell find conf | sed 's/ /\\ /g')
7
+
8
+BUILD_FLAGS:=-o myapp -v
9
+TAGS=sqlite
10
+NOW=$(shell date -u '+%Y%m%d%I%M%S')
11
+GOVET=go tool vet -composites=false -methods=false -structtags=false
12
+
13
+.PHONY: build clean
14
+
15
+all: build
16
+
17
+check: test
18
+
19
+web: build
20
+	./myapp web
21
+
22
+govet:
23
+	$(GOVET) myapp.go
24
+
25
+build:
26
+	go build $(BUILD_FLAGS) -ldflags '$(LDFLAGS)' -tags '$(TAGS)'
27
+
28
+build-dev: govet
29
+	go build $(BUILD_FLAGS) -tags '$(TAGS)'
30
+
31
+build-dev-race: govet
32
+	go build $(BUILD_FLAGS) -race -tags '$(TAGS)'
33
+
34
+clean:
35
+	go clean -i ./...
36
+
37
+clean-mac: clean
38
+	find . -name ".DS_Store" -delete
39
+
40
+test:
41
+	go test -cover -race ./...
42
+

+ 12
- 0
README.md View File

@@ -0,0 +1,12 @@
1
+myapp
2
+=====
3
+
4
+Basic / Base app template of dashie.
5
+
6
+- Extracted from git.txt
7
+- User auth/register/login/logout/profile edit
8
+- Basic admin page with Go stats
9
+- ini settings
10
+- i18n support
11
+- Base template from git.txt (bootstrap + jQuery)
12
+

+ 37
- 0
cmd/cmd.go View File

@@ -0,0 +1,37 @@
1
+package cmd
2
+
3
+import (
4
+	"github.com/urfave/cli"
5
+	"time"
6
+)
7
+
8
+func stringFlag(name, value, usage string) cli.StringFlag {
9
+	return cli.StringFlag{
10
+		Name:  name,
11
+		Value: value,
12
+		Usage: usage,
13
+	}
14
+}
15
+
16
+func boolFlag(name, usage string) cli.BoolFlag {
17
+	return cli.BoolFlag{
18
+		Name:  name,
19
+		Usage: usage,
20
+	}
21
+}
22
+
23
+func intFlag(name string, value int, usage string) cli.IntFlag {
24
+	return cli.IntFlag{
25
+		Name:  name,
26
+		Value: value,
27
+		Usage: usage,
28
+	}
29
+}
30
+
31
+func durationFlag(name string, value time.Duration, usage string) cli.DurationFlag {
32
+	return cli.DurationFlag{
33
+		Name:  name,
34
+		Value: value,
35
+		Usage: usage,
36
+	}
37
+}

+ 228
- 0
cmd/web.go View File

@@ -0,0 +1,228 @@
1
+package cmd
2
+
3
+import (
4
+	"dev.sigpipe.me/dashie/myapp/context"
5
+	"dev.sigpipe.me/dashie/myapp/models"
6
+	"dev.sigpipe.me/dashie/myapp/pkg/cron"
7
+	"dev.sigpipe.me/dashie/myapp/pkg/form"
8
+	"dev.sigpipe.me/dashie/myapp/pkg/mailer"
9
+	"dev.sigpipe.me/dashie/myapp/pkg/template"
10
+	"dev.sigpipe.me/dashie/myapp/routers"
11
+	"dev.sigpipe.me/dashie/myapp/routers/admin"
12
+	"dev.sigpipe.me/dashie/myapp/routers/user"
13
+	"dev.sigpipe.me/dashie/myapp/setting"
14
+	"fmt"
15
+	"github.com/go-macaron/binding"
16
+	"github.com/go-macaron/cache"
17
+	"github.com/go-macaron/csrf"
18
+	"github.com/go-macaron/i18n"
19
+	"github.com/go-macaron/session"
20
+	"github.com/go-macaron/toolbox"
21
+	"github.com/urfave/cli"
22
+	log "gopkg.in/clog.v1"
23
+	"gopkg.in/macaron.v1"
24
+	"net"
25
+	"net/http"
26
+	"net/http/fcgi"
27
+	"os"
28
+	"path"
29
+	"strings"
30
+)
31
+
32
+var Web = cli.Command{
33
+	Name:        "web",
34
+	Usage:       "Start web server",
35
+	Description: "It starts a web server, great no ?",
36
+	Action:      runWeb,
37
+	Flags: []cli.Flag{
38
+		stringFlag("port, p", "3000", "Server port"),
39
+		stringFlag("config, c", "config/app.ini", "Custom config file path"),
40
+	},
41
+}
42
+
43
+func newMacaron() *macaron.Macaron {
44
+	m := macaron.New()
45
+	if !setting.DisableRouterLog {
46
+		m.Use(macaron.Logger())
47
+	}
48
+
49
+	m.Use(macaron.Recovery())
50
+
51
+	if setting.Protocol == setting.SCHEME_FCGI {
52
+		m.SetURLPrefix(setting.AppSubURL)
53
+	}
54
+
55
+	m.Use(macaron.Static(
56
+		path.Join(setting.StaticRootPath, "static"),
57
+		macaron.StaticOptions{
58
+			SkipLogging: setting.DisableRouterLog,
59
+		},
60
+	))
61
+
62
+	funcMap := template.NewFuncMap()
63
+	m.Use(macaron.Renderer(macaron.RenderOptions{
64
+		Directory:  path.Join(setting.StaticRootPath, "templates"),
65
+		Funcs:      funcMap,
66
+		IndentJSON: macaron.Env != macaron.PROD,
67
+	}))
68
+	mailer.InitMailRender(path.Join(setting.StaticRootPath, "templates/mail"), funcMap)
69
+
70
+	m.Use(i18n.I18n(i18n.Options{
71
+		SubURL: setting.AppSubURL,
72
+		//Files:           localFiles,
73
+		Langs:       setting.Langs,
74
+		Names:       setting.Names,
75
+		DefaultLang: "en-US",
76
+		Redirect:    true,
77
+	}))
78
+
79
+	m.Use(cache.Cacher(cache.Options{
80
+		Adapter:       setting.CacheAdapter,
81
+		AdapterConfig: setting.CacheConn,
82
+		Interval:      setting.CacheInterval,
83
+	}))
84
+
85
+	m.Use(session.Sessioner(setting.SessionConfig))
86
+
87
+	m.Use(csrf.Csrfer(csrf.Options{
88
+		Secret:     setting.SecretKey,
89
+		Cookie:     setting.CSRFCookieName,
90
+		SetCookie:  true,
91
+		Header:     "X-Csrf-Token",
92
+		CookiePath: setting.AppSubURL,
93
+	}))
94
+
95
+	m.Use(toolbox.Toolboxer(m, toolbox.Options{
96
+		HealthCheckFuncs: []*toolbox.HealthCheckFuncDesc{
97
+			&toolbox.HealthCheckFuncDesc{
98
+				Desc: "Database connection",
99
+				Func: models.Ping,
100
+			},
101
+		},
102
+	}))
103
+
104
+	m.Use(context.Contexter())
105
+
106
+	return m
107
+
108
+}
109
+
110
+func runWeb(ctx *cli.Context) error {
111
+	if ctx.IsSet("config") {
112
+		setting.CustomConf = ctx.String("config")
113
+	}
114
+
115
+	setting.InitConfig()
116
+	models.InitDb()
117
+	cron.NewContext()
118
+	mailer.NewContext()
119
+
120
+	m := newMacaron()
121
+
122
+	if setting.ProdMode {
123
+		macaron.Env = macaron.PROD
124
+		macaron.ColorLog = false
125
+	} else {
126
+		macaron.Env = macaron.DEV
127
+	}
128
+	log.Info("Run Mode: %s", strings.Title(macaron.Env))
129
+
130
+	reqSignIn := context.Toggle(&context.ToggleOptions{SignInRequired: true})
131
+	reqSignOut := context.Toggle(&context.ToggleOptions{SignOutRequired: true})
132
+	adminReq := context.Toggle(&context.ToggleOptions{SignInRequired: true, AdminRequired: true})
133
+	bindIgnErr := binding.BindIgnErr
134
+
135
+	// HOME PAGE
136
+	m.Get("/", routers.Home)
137
+
138
+	// START USER
139
+	m.Group("/user", func() {
140
+		m.Group("/login", func() {
141
+			m.Combo("").Get(user.Login).Post(bindIgnErr(form.Login{}), user.LoginPost)
142
+		})
143
+		m.Get("/register", user.Register)
144
+		m.Post("/register", csrf.Validate, bindIgnErr(form.Register{}), user.RegisterPost)
145
+		m.Get("/reset_password", user.ResetPasswd)
146
+		m.Post("/reset_password", user.ResetPasswdPost)
147
+	}, reqSignOut)
148
+
149
+	m.Group("/user/settings", func() {
150
+		m.Get("", user.Settings)
151
+		m.Post("", csrf.Validate, bindIgnErr(form.UpdateSettingsProfile{}), user.SettingsPost)
152
+	}, reqSignIn, func(ctx *context.Context) {
153
+		ctx.Data["PageIsUserSettings"] = true
154
+	})
155
+
156
+	m.Group("/user", func() {
157
+		m.Get("/logout", user.Logout)
158
+	}, reqSignIn)
159
+
160
+	m.Group("/user", func() {
161
+		m.Get("/forget_password", user.ForgotPasswd)
162
+		m.Post("/forget_password", user.ForgotPasswdPost)
163
+	})
164
+	// END USER
165
+
166
+	/* Admin part */
167
+
168
+	m.Group("/admin", func() {
169
+		m.Get("", adminReq, admin.Dashboard)
170
+	}, adminReq)
171
+
172
+	// robots.txt
173
+	m.Get("/robots.txt", func(ctx *context.Context) {
174
+		if setting.HasRobotsTxt {
175
+			ctx.ServeFileContent(setting.RobotsTxtPath)
176
+		} else {
177
+			ctx.Error(404)
178
+		}
179
+	})
180
+
181
+	m.NotFound(routers.NotFound)
182
+
183
+	if ctx.IsSet("port") {
184
+		setting.AppURL = strings.Replace(setting.AppURL, setting.HTTPPort, ctx.String("port"), 1)
185
+		setting.HTTPPort = ctx.String("port")
186
+	}
187
+
188
+	var listenAddr string
189
+	if setting.Protocol == setting.SCHEME_UNIX_SOCKET {
190
+		listenAddr = fmt.Sprintf("%s", setting.HTTPAddr)
191
+	} else {
192
+		listenAddr = fmt.Sprintf("%s:%s", setting.HTTPAddr, setting.HTTPPort)
193
+	}
194
+	log.Info("Listen: %v://%s%s", setting.Protocol, listenAddr, setting.AppSubURL)
195
+
196
+	var err error
197
+	switch setting.Protocol {
198
+	case setting.SCHEME_HTTP:
199
+		err = http.ListenAndServe(listenAddr, m)
200
+	case setting.SCHEME_HTTPS:
201
+		log.Fatal(2, "https not supported")
202
+	case setting.SCHEME_FCGI:
203
+		err = fcgi.Serve(nil, m)
204
+	case setting.SCHEME_UNIX_SOCKET:
205
+		os.Remove(listenAddr)
206
+
207
+		var listener *net.UnixListener
208
+		listener, err = net.ListenUnix("unix", &net.UnixAddr{listenAddr, "unix"})
209
+		if err != nil {
210
+			break // Handle error after switch
211
+		}
212
+
213
+		// FIXME: add proper implementation of signal capture on all protocols
214
+		// execute this on SIGTERM or SIGINT: listener.Close()
215
+		if err = os.Chmod(listenAddr, os.FileMode(setting.UnixSocketPermission)); err != nil {
216
+			log.Fatal(4, "Failed to set permission of unix socket: %v", err)
217
+		}
218
+		err = http.Serve(listener, m)
219
+	default:
220
+		log.Fatal(4, "Invalid protocol: %s", setting.Protocol)
221
+	}
222
+
223
+	if err != nil {
224
+		log.Fatal(4, "Fail to start server: %v", err)
225
+	}
226
+
227
+	return nil
228
+}

+ 184
- 0
conf/app.ini View File

@@ -0,0 +1,184 @@
1
+APP_NAME = myapp
2
+CAN_REGISTER = true
3
+ANONYMOUS_CREATE = true
4
+; Either "dev", "prod" or "test"
5
+RUN_MODE = dev
6
+
7
+[server]
8
+PROTOCOL = http
9
+DOMAIN = localhost
10
+ROOT_URL = %(PROTOCOL)s://%(DOMAIN)s:%(HTTP_PORT)s/
11
+UNIX_SOCKET_PERMISSION = 666
12
+HTTP_ADDR = 0.0.0.0
13
+HTTP_PORT = 4000
14
+DISABLE_ROUTER_LOG = false
15
+; Upper level of template and static file path
16
+; default is the path where myapp is executed
17
+STATIC_ROOT_PATH =
18
+
19
+[database]
20
+; Either "mysql", "postgres" or "sqlite3", you can connect to TiDB with MySQL protocol
21
+DB_TYPE = sqlite3
22
+HOST = 127.0.0.1:3306
23
+NAME = gogs
24
+USER = root
25
+PASSWD =
26
+; For "postgres" only, either "disable", "require" or "verify-full"
27
+SSL_MODE = disable
28
+; For "sqlite3" and "tidb", use absolute path when you start as service
29
+PATH = myapp.db
30
+
31
+[repository]
32
+; Root path for storing repositories's data, default is "~/<username>/gitxt-repositories"
33
+ROOT =
34
+; If you want to disable cloning using HTTP
35
+DISABLE_HTTP_GIT = false
36
+; You can specify full path to git if non-standard
37
+; Git binary is only used for HTTP git upload-pack (checkout over HTTP)
38
+GIT_BINARY = git
39
+
40
+[log]
41
+ROOT_PATH =
42
+
43
+; Can be "console" and "file", default is "console"
44
+; Use comma to separate multiple modes, e.g. "console, file"
45
+MODE = console
46
+; Buffer length of channel, keep it as it is if you don't know what it is.
47
+BUFFER_LEN = 100
48
+; Either "Trace", "Info", "Warn", "Error", "Fatal", default is "Trace"
49
+LEVEL = Trace
50
+
51
+; For "console" mode only
52
+[log.console]
53
+; leave empty to inherit
54
+LEVEL =
55
+
56
+; For "file" mode only
57
+[log.file]
58
+; leave empty to inherit
59
+LEVEL =
60
+; This enables automated log rotate (switch of following options)
61
+LOG_ROTATE = true
62
+; Segment log daily
63
+DAILY_ROTATE = true
64
+; Max size shift of single file, default is 28 means 1 << 28, 256MB
65
+MAX_SIZE_SHIFT = 28
66
+; Max line number of single file
67
+MAX_LINES = 1000000
68
+; Expired days of log file (delete after max days)
69
+MAX_DAYS = 7
70
+
71
+; For "slack" mode only
72
+[log.slack]
73
+; leave empty to inherit
74
+LEVEL =
75
+; Webhook URL
76
+URL =
77
+
78
+[log.xorm]
79
+; Enable file rotation
80
+ROTATE = true
81
+; Rotate every day
82
+ROTATE_DAILY = true
83
+; Rotate once file size excesses x MB
84
+MAX_SIZE = 100
85
+; Maximum days to keep logger files
86
+MAX_DAYS = 3
87
+
88
+[session]
89
+; Either "memory", "file", or "redis", default is "memory"
90
+PROVIDER = file
91
+; Provider config options
92
+; memory: not have any config yet
93
+; file: session file path, e.g. `data/sessions`
94
+; redis: network=tcp,addr=:6379,password=macaron,db=0,pool_size=100,idle_timeout=180
95
+; mysql: go-sql-driver/mysql dsn config string, e.g. `root:password@/session_table`
96
+PROVIDER_CONFIG = data/sessions
97
+; Session cookie name
98
+COOKIE_NAME = i_like_ponies
99
+; If you use session in https only, default is false
100
+COOKIE_SECURE = false
101
+; Enable set cookie, default is true
102
+ENABLE_SET_COOKIE = true
103
+; Session GC time interval, default is 3600
104
+GC_INTERVAL_TIME = 3600
105
+; Session life time, default is 86400
106
+SESSION_LIFE_TIME = 86400
107
+; Cookie name for CSRF
108
+CSRF_COOKIE_NAME = _csrf
109
+
110
+[security]
111
+; !!CHANGE THIS TO KEEP YOUR USER DATA SAFE!!
112
+SECRET_KEY = !#@FDEWREWR&*(
113
+INSTALL_LOCK = false
114
+; Auto-login remember days
115
+LOGIN_REMEMBER_DAYS = 7
116
+COOKIE_USERNAME = gitxt_celestia
117
+COOKIE_REMEMBER_NAME = gitxt_luna
118
+COOKIE_SECURE = false
119
+; Enable to set cookie to indicate user login status
120
+ENABLE_LOGIN_STATUS_COOKIE = false
121
+LOGIN_STATUS_COOKIE_NAME = login_status
122
+
123
+[i18n]
124
+LANGS = en-US
125
+NAMES = English
126
+
127
+[cron]
128
+; Enable running cron tasks periodically.
129
+ENABLED = true
130
+; Run cron tasks when Gitxt starts.
131
+RUN_AT_START = false
132
+
133
+; Cleanup repository archives
134
+[cron.repo_archive_cleanup]
135
+RUN_AT_START = true
136
+SCHEDULE = @every 24h
137
+; Time duration to check if archive should be cleaned
138
+OLDER_THAN = 24h
139
+
140
+[markdown]
141
+; Enable hard line break extension
142
+ENABLE_HARD_LINE_BREAK = false
143
+; List of custom URL-Schemes that are allowed as links when rendering Markdown
144
+; for example git,magnet
145
+CUSTOM_URL_SCHEMES =
146
+; List of file extensions that should be rendered/edited as Markdown
147
+; Separate extensions with a comma. To render files w/o extension as markdown, just put a comma
148
+FILE_EXTENSIONS = .md,.markdown,.mdown,.mkd
149
+
150
+[smartypants]
151
+ENABLED = false
152
+FRACTIONS = true
153
+DASHES = true
154
+LATEX_DASHES = true
155
+ANGLED_QUOTES = true
156
+
157
+[mailer]
158
+ENABLED = true
159
+; Buffer length of channel, keep it as it is if you don't know what it is.
160
+SEND_BUFFER_LEN = 100
161
+; Name displayed in mail title
162
+SUBJECT = %(APP_NAME)s
163
+; Mail server
164
+; Gmail: smtp.gmail.com:587
165
+; QQ: smtp.qq.com:465
166
+; Note, if the port ends with "465", SMTPS will be used. Using STARTTLS on port 587 is recommended per RFC 6409. If the server supports STARTTLS it will always be used.
167
+HOST =
168
+; Disable HELO operation when hostname are different.
169
+DISABLE_HELO =
170
+; Custom hostname for HELO operation, default is from system.
171
+HELO_HOSTNAME =
172
+; Do not verify the certificate of the server. Only use this for self-signed certificates
173
+SKIP_VERIFY =
174
+; Use client certificate
175
+USE_CERTIFICATE = false
176
+CERT_FILE = conf/mailer/cert.pem
177
+KEY_FILE = conf/mailer/key.pem
178
+; Mail from address, RFC 5322. This can be just an email address, or the `"Name" <email@example.com>` format
179
+FROM =
180
+; Mailer user name and password
181
+USER =
182
+PASSWD =
183
+; Use text/plain as format of content
184
+USE_PLAIN_TEXT = false

+ 145
- 0
conf/locale/locale_en-US.ini View File

@@ -0,0 +1,145 @@
1
+[login]
2
+title = "Sign In"
3
+sign_in = "Sign In"
4
+username = "Username"
5
+username_placeholder = "Your username."
6
+email = "Email"
7
+email_placeholder = "Your email."
8
+password = "Password"
9
+password_placeholder = "Your password."
10
+repeat_password_placeholder = "And the same one here."
11
+remember_me = "Remember me"
12
+
13
+[register]
14
+title = "Register"
15
+register = "Register"
16
+not_allowed = "Registration not allowed"
17
+successfull = Register succesfull
18
+password = "Password"
19
+password_placeholder = "Your nice password."
20
+
21
+[form]
22
+password_not_match = "Passwords doesn't match"
23
+username_password_incorrect = "Invalid username or password"
24
+username_been_taken = "Username already taken, sorry"
25
+username_reserved = "Username reserved, please choose another username"
26
+username_pattern_not_allowed = "Invalid username pattern"
27
+save = "Save changes"
28
+Email = "Email: "
29
+Password = "Password: "
30
+Description = "Description"
31
+ExpiryHours = "Expiry"
32
+UserName = "Username"
33
+
34
+require_error = ` cannot be empty.`
35
+alpha_dash_error = ` must be valid alpha or numeric or dash(-_) characters.`
36
+alpha_dash_dot_error = ` must be valid alpha or numeric or dash(-_) or dot characters.`
37
+alpha_dash_dot_slash_error = ` must be valid alpha or numeric or dash(-_) or dot characters or slashes.`
38
+size_error  = ` must be size %s.`
39
+min_size_error = ` must contain at least %s characters.`
40
+max_size_error = ` must contain at most %s characters.`
41
+email_error = "Invalid email format"
42
+url_error = ` is not a valid URL.`
43
+include_error = ` must contain substring '%s'.`
44
+in_error = " must be one of: %s"
45
+unknown_error = Unknown error:
46
+
47
+[settings]
48
+title = "User settings"
49
+update_profile_success = "Profile saved successfully"
50
+sidebar.profile = "Profile"
51
+reset_password = "Reset Password"
52
+sidebar.reset_password = "Reset Password"
53
+
54
+[settings_profile]
55
+title = "Profile settings"
56
+you_are_admin = "You are admin."
57
+
58
+[user]
59
+unauthorized = Unauthorized
60
+
61
+[admin]
62
+dashboard.system_status = System Monitor Status
63
+dashboard.title = Admin Dashboard
64
+dashboard.server_uptime = Server Uptime
65
+dashboard.current_goroutine = Current Goroutines
66
+dashboard.current_memory_usage = Current Memory Usage
67
+dashboard.total_memory_allocated = Total Memory Allocated
68
+dashboard.memory_obtained = Memory Obtained
69
+dashboard.pointer_lookup_times = Pointer Lookup Times
70
+dashboard.memory_allocate_times = Memory Allocate Times
71
+dashboard.memory_free_times = Memory Free Times
72
+dashboard.current_heap_usage = Current Heap Usage
73
+dashboard.heap_memory_obtained = Heap Memory Obtained
74
+dashboard.heap_memory_idle = Heap Memory Idle
75
+dashboard.heap_memory_in_use = Heap Memory In Use
76
+dashboard.heap_memory_released = Heap Memory Released
77
+dashboard.heap_objects = Heap Objects
78
+dashboard.bootstrap_stack_usage = Bootstrap Stack Usage
79
+dashboard.stack_memory_obtained = Stack Memory Obtained
80
+dashboard.mspan_structures_usage = MSpan Structures Usage
81
+dashboard.mspan_structures_obtained = MSpan Structures Obtained
82
+dashboard.mcache_structures_usage = MCache Structures Usage
83
+dashboard.mcache_structures_obtained = MCache Structures Obtained
84
+dashboard.profiling_bucket_hash_table_obtained = Profiling Bucket Hash Table Obtained
85
+dashboard.gc_metadata_obtained = GC Metadata Obtained
86
+dashboard.other_system_allocation_obtained = Other System Allocation Obtained
87
+dashboard.next_gc_recycle = Next GC Recycle
88
+dashboard.last_gc_time = Since Last GC Time
89
+dashboard.total_gc_time = Total GC Pause
90
+dashboard.total_gc_pause = Total GC Pause
91
+dashboard.last_gc_pause = Last GC Pause
92
+dashboard.gc_times = GC Times
93
+
94
+monitor.cron = Cron Tasks
95
+monitor.name = Name
96
+monitor.schedule = Schedule
97
+monitor.next = Next Time
98
+monitor.previous = Previous Time
99
+monitor.execute_times = Execute Times
100
+
101
+[auth]
102
+disable_register_mail = Sorry, email services are disabled. Please contact the site administrator.
103
+forgot_password= Forgot Password
104
+forget_password = Forgot password?
105
+confirmation_mail_sent_prompt = A new confirmation email has been sent to <b>%s</b>, please check your inbox within the next %d hours to complete the registration process.
106
+resent_limit_prompt = Sorry, you already requested an activation email recently. Please wait 3 minutes then try again.
107
+send_reset_mail = Click here to (re)send your password reset email
108
+reset_password = Reset Your Password
109
+invalid_code = Sorry, your confirmation code has expired or not valid.
110
+reset_password_helper = Click here to reset your password
111
+password_too_short = Password length cannot be less then 6.
112
+needs_password_reset = "Needs a password reset ?"
113
+
114
+[error]
115
+page_not_found = Page Not Found
116
+internal_server_error = Internal Server Error
117
+error_404_not_found = "Error: 404 not found"
118
+app_version = "Application Version: %s"
119
+if_error_app = "If you think this is an error with the application, please open an issue."
120
+error_occured = "An error occured."
121
+error_msg = "An error has occurred : %s"
122
+
123
+[mail]
124
+activate_account = Please activate your account
125
+activate_email = Verify your email address
126
+reset_password = Reset your password
127
+
128
+[footer]
129
+version = version
130
+page = page
131
+template = template
132
+sources = Sources
133
+
134
+[header]
135
+toggle_nav = Toggle navigation
136
+my_whatever = My whatever
137
+settings = Settings
138
+admin = Admin
139
+logout = Logout
140
+register = Register
141
+login = Login
142
+
143
+[page]
144
+next = Next
145
+previous = Previous

+ 62
- 0
context/auth.go View File

@@ -0,0 +1,62 @@
1
+package context
2
+
3
+import (
4
+	"dev.sigpipe.me/dashie/myapp/setting"
5
+	"github.com/go-macaron/csrf"
6
+	log "gopkg.in/clog.v1"
7
+	"gopkg.in/macaron.v1"
8
+	"net/url"
9
+)
10
+
11
+type ToggleOptions struct {
12
+	SignInRequired  bool
13
+	SignOutRequired bool
14
+	AdminRequired   bool
15
+	DisableCSRF     bool
16
+}
17
+
18
+func Toggle(options *ToggleOptions) macaron.Handler {
19
+	return func(ctx *Context) {
20
+		// Redirect non-login pages from logged in user
21
+		if options.SignOutRequired && ctx.IsLogged && ctx.Req.RequestURI != "/" {
22
+			ctx.Redirect(setting.AppSubURL + "/")
23
+			return
24
+		}
25
+
26
+		log.Trace("SignOutRequired: %s, DisableCSRF: %s, Req Method: %s", options.SignOutRequired, options.DisableCSRF, ctx.Req.Method)
27
+		if !options.SignOutRequired && !options.DisableCSRF && ctx.Req.Method == "POST" {
28
+			log.Trace("Validating CSRF")
29
+			csrf.Validate(ctx.Context, ctx.csrf)
30
+			if ctx.Written() {
31
+				return
32
+			}
33
+		}
34
+
35
+		if options.SignInRequired {
36
+			if !ctx.IsLogged {
37
+				ctx.SetCookie("redirect_to", url.QueryEscape(setting.AppSubURL+ctx.Req.RequestURI), 0, setting.AppSubURL)
38
+				ctx.Redirect(setting.AppSubURL + "/user/login")
39
+				return
40
+			} else if !ctx.User.IsActive {
41
+				ctx.Title("auth.activate_your_account")
42
+				ctx.HTML(200, "user/auth/activate")
43
+				return
44
+			}
45
+		}
46
+
47
+		// Redirect to login page if auto-sign provided and not signed in
48
+		if !options.SignOutRequired && !ctx.IsLogged && len(ctx.GetCookie(setting.CookieUserName)) > 0 {
49
+			ctx.SetCookie("redirect_to", url.QueryEscape(setting.AppSubURL+ctx.Req.RequestURI), 0, setting.AppSubURL)
50
+			ctx.Redirect(setting.AppSubURL + "/user/login")
51
+			return
52
+		}
53
+
54
+		if options.AdminRequired {
55
+			if !ctx.User.IsAdmin {
56
+				ctx.Error(403)
57
+				return
58
+			}
59
+			ctx.Data["PageIsAdmin"] = true
60
+		}
61
+	}
62
+}

+ 207
- 0
context/context.go View File

@@ -0,0 +1,207 @@
1
+package context
2
+
3
+import (
4
+	"dev.sigpipe.me/dashie/myapp/models"
5
+	"dev.sigpipe.me/dashie/myapp/pkg/auth"
6
+	"dev.sigpipe.me/dashie/myapp/pkg/form"
7
+	"dev.sigpipe.me/dashie/myapp/setting"
8
+	"fmt"
9
+	"github.com/go-macaron/cache"
10
+	"github.com/go-macaron/csrf"
11
+	"github.com/go-macaron/i18n"
12
+	"github.com/go-macaron/session"
13
+	log "gopkg.in/clog.v1"
14
+	"gopkg.in/macaron.v1"
15
+	"html/template"
16
+	"io"
17
+	"net/http"
18
+	"strings"
19
+	"time"
20
+)
21
+
22
+// Context represents context of a request.
23
+type Context struct {
24
+	*macaron.Context
25
+	Cache   cache.Cache
26
+	csrf    csrf.CSRF
27
+	Flash   *session.Flash
28
+	Session session.Store
29
+
30
+	User *models.User // logged in user
31
+
32
+	IsLogged    bool
33
+	IsBasicAuth bool
34
+}
35
+
36
+// Title sets "Title" field in template data.
37
+func (c *Context) Title(locale string) {
38
+	c.Data["Title"] = c.Tr(locale) + " - " + setting.AppName
39
+}
40
+
41
+// PageIs sets "PageIsxxx" field in template data.
42
+func (c *Context) PageIs(name string) {
43
+	c.Data["PageIs"+name] = true
44
+}
45
+
46
+// HTML responses template with given status.
47
+func (ctx *Context) HTML(status int, name string) {
48
+	log.Trace("Template: %s", name)
49
+	ctx.Context.HTML(status, name)
50
+}
51
+
52
+// Success responses template with status http.StatusOK.
53
+func (c *Context) Success(name string) {
54
+	c.HTML(http.StatusOK, name)
55
+}
56
+
57
+// JSONSuccess responses JSON with status http.StatusOK.
58
+func (c *Context) JSONSuccess(data interface{}) {
59
+	c.JSON(http.StatusOK, data)
60
+}
61
+
62
+// HasError returns true if error occurs in form validation.
63
+func (ctx *Context) HasError() bool {
64
+	hasErr, ok := ctx.Data["HasError"]
65
+	if !ok {
66
+		return false
67
+	}
68
+	ctx.Flash.ErrorMsg = ctx.Data["ErrorMsg"].(string)
69
+	ctx.Data["Flash"] = ctx.Flash
70
+	return hasErr.(bool)
71
+}
72
+
73
+// RenderWithErr used for page has form validation but need to prompt error to users.
74
+func (ctx *Context) RenderWithErr(msg, tpl string, f interface{}) {
75
+	if f != nil {
76
+		form.Assign(f, ctx.Data)
77
+	}
78
+	ctx.Flash.ErrorMsg = msg
79
+	ctx.Data["Flash"] = ctx.Flash
80
+	ctx.HTML(http.StatusOK, tpl)
81
+}
82
+
83
+// Handle handles and logs error by given status.
84
+func (ctx *Context) Handle(status int, title string, err error) {
85
+	switch status {
86
+	case http.StatusNotFound:
87
+		ctx.Data["Title"] = ctx.Tr("error.page_not_found")
88
+	case http.StatusInternalServerError:
89
+		ctx.Data["Title"] = ctx.Tr("internal_server_error")
90
+		log.Error(2, "%s: %v", title, err)
91
+	}
92
+	ctx.HTML(status, fmt.Sprintf("status/%d", status))
93
+}
94
+
95
+func (ctx *Context) HandleText(status int, title string) {
96
+	ctx.PlainText(status, []byte(title))
97
+}
98
+
99
+// NotFound renders the 404 page.
100
+func (ctx *Context) NotFound() {
101
+	ctx.Handle(http.StatusNotFound, "", nil)
102
+}
103
+
104
+// ServerError renders the 500 page.
105
+func (c *Context) ServerError(title string, err error) {
106
+	c.Handle(http.StatusInternalServerError, title, err)
107
+}
108
+
109
+// SubURLRedirect responses redirection wtih given location and status.
110
+// It prepends setting.AppSubURL to the location string.
111
+func (c *Context) SubURLRedirect(location string, status ...int) {
112
+	c.Redirect(setting.AppSubURL + location)
113
+}
114
+
115
+// NotFoundOrServerError use error check function to determine if the error
116
+// is about not found. It responses with 404 status code for not found error,
117
+// or error context description for logging purpose of 500 server error.
118
+func (c *Context) NotFoundOrServerError(title string, errck func(error) bool, err error) {
119
+	if errck(err) {
120
+		c.NotFound()
121
+		return
122
+	}
123
+	c.ServerError(title, err)
124
+}
125
+
126
+func (ctx *Context) ServeContent(name string, r io.ReadSeeker, params ...interface{}) {
127
+	modtime := time.Now()
128
+	for _, p := range params {
129
+		switch v := p.(type) {
130
+		case time.Time:
131
+			modtime = v
132
+		}
133
+	}
134
+	ctx.Resp.Header().Set("Content-Description", "File Transfer")
135
+	ctx.Resp.Header().Set("Content-Type", "application/octet-stream")
136
+	ctx.Resp.Header().Set("Content-Disposition", "attachment; filename="+name)
137
+	ctx.Resp.Header().Set("Content-Transfer-Encoding", "binary")
138
+	ctx.Resp.Header().Set("Expires", "0")
139
+	ctx.Resp.Header().Set("Cache-Control", "must-revalidate")
140
+	ctx.Resp.Header().Set("Pragma", "public")
141
+	http.ServeContent(ctx.Resp, ctx.Req.Request, name, modtime, r)
142
+}
143
+
144
+func (ctx *Context) ServeContentNoDownload(name string, mime string, r io.ReadSeeker, params ...interface{}) {
145
+	modtime := time.Now()
146
+	for _, p := range params {
147
+		switch v := p.(type) {
148
+		case time.Time:
149
+			modtime = v
150
+		}
151
+	}
152
+	ctx.Resp.Header().Set("Content-Description", "File Content")
153
+	ctx.Resp.Header().Set("Content-Type", mime)
154
+	ctx.Resp.Header().Set("Expires", "0")
155
+	ctx.Resp.Header().Set("Cache-Control", "must-revalidate")
156
+	ctx.Resp.Header().Set("Pragma", "public")
157
+	http.ServeContent(ctx.Resp, ctx.Req.Request, name, modtime, r)
158
+}
159
+
160
+// Contexter initializes a classic context for a request.
161
+func Contexter() macaron.Handler {
162
+	return func(c *macaron.Context, l i18n.Locale, cache cache.Cache, sess session.Store, f *session.Flash, x csrf.CSRF) {
163
+		ctx := &Context{
164
+			Context: c,
165
+			Cache:   cache,
166
+			csrf:    x,
167
+			Flash:   f,
168
+			Session: sess,
169
+		}
170
+
171
+		if len(setting.HTTP.AccessControlAllowOrigin) > 0 {
172
+			ctx.Header().Set("Access-Control-Allow-Origin", setting.HTTP.AccessControlAllowOrigin)
173
+			ctx.Header().Set("'Access-Control-Allow-Credentials' ", "true")
174
+			ctx.Header().Set("Access-Control-Max-Age", "3600")
175
+			ctx.Header().Set("Access-Control-Allow-Headers", "Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With")
176
+		}
177
+
178
+		// Compute current URL for real-time change language.
179
+		ctx.Data["Link"] = setting.AppSubURL + strings.TrimSuffix(ctx.Req.URL.Path, "/")
180
+
181
+		ctx.Data["PageStartTime"] = time.Now()
182
+
183
+		// Get user from session if logined.
184
+		ctx.User, ctx.IsBasicAuth = auth.SignedInUser(ctx.Context, ctx.Session)
185
+
186
+		if ctx.User != nil {
187
+			ctx.IsLogged = true
188
+			ctx.Data["IsLogged"] = ctx.IsLogged
189
+			ctx.Data["UserIsAdmin"] = ctx.User.IsAdmin
190
+			ctx.Data["LoggedUser"] = ctx.User
191
+			ctx.Data["LoggedUserID"] = ctx.User.ID
192
+			ctx.Data["LoggedUserName"] = ctx.User.UserName
193
+		} else {
194
+			ctx.IsLogged = false
195
+			ctx.Data["IsLogged"] = ctx.IsLogged
196
+			ctx.Data["LoggedUserID"] = 0
197
+			ctx.Data["LoggedUserName"] = ""
198
+		}
199
+
200
+		ctx.Data["CSRFToken"] = x.GetToken()
201
+		ctx.Data["CSRFTokenHTML"] = template.HTML(`<input type="hidden" name="_csrf" value="` + x.GetToken() + `">`)
202
+		log.Trace("Session ID: %s", sess.ID())
203
+		log.Trace("CSRF Token: %v", ctx.Data["CSRFToken"])
204
+
205
+		c.Map(ctx)
206
+	}
207
+}

+ 28
- 0
context/user.go View File

@@ -0,0 +1,28 @@
1
+package context
2
+
3
+import (
4
+	"dev.sigpipe.me/dashie/myapp/models"
5
+	"dev.sigpipe.me/dashie/myapp/models/errors"
6
+	"gopkg.in/macaron.v1"
7
+)
8
+
9
+func AssignUser() macaron.Handler {
10
+	return func(ctx *Context) {
11
+		userName := ctx.Params("user")
12
+
13
+		// Anonymous user doesn't really exists, that's nil
14
+		if userName == "anonymous" {
15
+			return
16
+		}
17
+
18
+		_, err := models.GetUserByName(userName)
19
+		if err != nil {
20
+			if errors.IsUserNotExist(err) {
21
+				ctx.NotFound()
22
+			} else {
23
+				ctx.Handle(500, "GetUserByName", err)
24
+			}
25
+			return
26
+		}
27
+	}
28
+}

+ 122
- 0
glide.lock View File

@@ -0,0 +1,122 @@
1
+hash: 5679f4c213ae5ac73bd7dfc86f1f5efe8a2979e67e34d8086275d5824ef3ea8b
2
+updated: 2017-06-03T22:47:39.021162572+02:00
3
+imports:
4
+- name: github.com/certifi/gocertifi
5
+  version: a9c833d2837d3b16888d55d5aafa9ffe9afb22b0
6
+- name: github.com/denisenkom/go-mssqldb
7
+  version: f77039e4e9bc788cb0406a666f410abee0ca1580
8
+- name: github.com/fatih/color
9
+  version: 62e9147c64a1ed519147b62a56a14e83e2be02c1
10
+- name: github.com/getsentry/raven-go
11
+  version: 4fa2ac0d29f801e79a063c0da82d37b5ff2873b2
12
+- name: github.com/go-macaron/binding
13
+  version: 1513c901915731e941815f60ede6a9c21c69a550
14
+- name: github.com/go-macaron/cache
15
+  version: 56173531277692bc2925924d51fda1cd0a6b8178
16
+- name: github.com/go-macaron/csrf
17
+  version: 428b7c62d7d0034b048b1cd43ba60d87857f5253
18
+- name: github.com/go-macaron/i18n
19
+  version: ef57533c3b0fc2d8581deda14937e52f11a203ab
20
+- name: github.com/go-macaron/inject
21
+  version: d8a0b8677191f4380287cfebd08e462217bac7ad
22
+- name: github.com/go-macaron/session
23
+  version: b8e286a0dba8f4999042d6b258daf51b31d08938
24
+- name: github.com/go-macaron/toolbox
25
+  version: 6766b8f16d1b135b250f09ba4dc4e24ab65b1107
26
+- name: github.com/go-sql-driver/mysql
27
+  version: a0583e0143b1624142adab07e0e97fe106d99561
28
+- name: github.com/go-xorm/builder
29
+  version: c8871c857d2555fbfbd8524f895be5386d3d8836
30
+- name: github.com/go-xorm/core
31
+  version: 5bf745d7d163f4380e6c2bba8c4afa60534dd087
32
+- name: github.com/go-xorm/xorm
33
+  version: 3c8314e91603f0bfa6a228c1a0fea8a9e298f6d9
34
+- name: github.com/gogits/cron
35
+  version: c803a08f3bd6f5d5f0281e5ef6a5e09b28488ed7
36
+- name: github.com/jaytaylor/html2text
37
+  version: 7c7a33a7a158a5ce395c803d2b6a209b2bbc14c8
38
+- name: github.com/lib/pq
39
+  version: 91f10e40ba0dd2d0bf9f93ec1d4077711a78df88
40
+  subpackages:
41
+  - oid
42
+- name: github.com/mattn/go-colorable
43
+  version: 5411d3eea5978e6cdc258b30de592b60df6aba96
44
+  repo: https://github.com/mattn/go-colorable
45
+- name: github.com/mattn/go-isatty
46
+  version: 57fdcb988a5c543893cc61bce354a6e24ab70022
47
+  repo: https://github.com/mattn/go-isatty
48
+- name: github.com/mattn/go-runewidth
49
+  version: 97311d9f7767e3d6f422ea06661bc2c7a19e8a5d
50
+- name: github.com/mattn/go-sqlite3
51
+  version: ca5e3819723d8eeaf170ad510e7da1d6d2e94a08
52
+- name: github.com/microcosm-cc/bluemonday
53
+  version: e79763773ab6222ca1d5a7cbd9d62d83c1f77081
54
+- name: github.com/olekukonko/tablewriter
55
+  version: febf2d34b54a69ce7530036c7503b1c9fbfdf0bb
56
+- name: github.com/rakyll/magicmime
57
+  version: 9b99294d6b2216897632b9734769d8ce4568681e
58
+- name: github.com/russross/blackfriday
59
+  version: 0b647d0506a698cca42caca173e55559b12a69f2
60
+- name: github.com/shurcooL/sanitized_anchor_name
61
+  version: 541ff5ee47f1dddf6a5281af78307d921524bcb5
62
+- name: github.com/ssor/bom
63
+  version: 6ed919a936d5ab554e4b40bc51f7c522488122c6
64
+- name: github.com/Unknwon/com
65
+  version: 0db4a625e949e956314d7d1adea9bf82384cc10c
66
+- name: github.com/Unknwon/i18n
67
+  version: 8372b908b5876d26cfa46a85fc4851b981dad102
68
+- name: github.com/Unknwon/paginater
69
+  version: 45e5d631308ea359946e761484147982c978d0df
70
+- name: github.com/urfave/cli
71
+  version: 0bdeddeeb0f650497d603c4ad7b20cfe685682f6
72
+- name: golang.org/x/crypto
73
+  version: e1a4589e7d3ea14a3352255d04b6f1a418845e5e
74
+  subpackages:
75
+  - md4
76
+  - pbkdf2
77
+- name: golang.org/x/net
78
+  version: 3da985ce5951d99de868be4385f21ea6c2b22f24
79
+  subpackages:
80
+  - context
81
+  - html
82
+  - html/atom
83
+- name: golang.org/x/sys
84
+  version: e24f485414aeafb646f6fca458b0bf869c0880a1
85
+  repo: https://go.googlesource.com/sys
86
+  subpackages:
87
+  - unix
88
+- name: golang.org/x/text
89
+  version: 4ee4af566555f5fbe026368b75596286a312663a
90
+  subpackages:
91
+  - internal/tag
92
+  - language
93
+- name: gopkg.in/alexcesaro/quotedprintable.v3
94
+  version: 2caba252f4dc53eaf6b553000885530023f54623
95
+- name: gopkg.in/clog.v1
96
+  version: 8492a6faa632c31ba82f562b53b4a6e5eacf2732
97
+- name: gopkg.in/gomail.v2
98
+  version: 81ebce5c23dfd25c6c67194b37d3dd3f338c98b1
99
+- name: gopkg.in/ini.v1
100
+  version: d3de07a94d22b4a0972deb4b96d790c2c0ce8333
101
+- name: gopkg.in/libgit2/git2go.v25
102
+  version: 334260d743d713a55ff3c097ec6707f2bb39e9d5
103
+- name: gopkg.in/macaron.v1
104
+  version: a325110f8b392bce3e5cdeb8c44bf98078ada3be
105
+testImports:
106
+- name: github.com/gopherjs/gopherjs
107
+  version: 7cde3d36aea3e1b3be7827c4dcbe6bbae73d012e
108
+  subpackages:
109
+  - js
110
+- name: github.com/jtolds/gls
111
+  version: 77f18212c9c7edc9bd6a33d383a7b545ce62f064
112
+- name: github.com/smartystreets/assertions
113
+  version: 8d53f0381cdc56c7e0f06f4995d21cd04c5a53a8
114
+  subpackages:
115
+  - internal/go-render/render
116
+  - internal/oglematchers
117
+- name: github.com/smartystreets/goconvey
118
+  version: d4c757aa9afd1e2fc1832aaab209b5794eb336e1
119
+  subpackages:
120
+  - convey
121
+  - convey/gotest
122
+  - convey/reporting

+ 51
- 0
glide.yaml View File

@@ -0,0 +1,51 @@
1
+package: dev.sigpipe.me/dashie/myapp
2
+import:
3
+- package: github.com/Unknwon/com
4
+  version: master
5
+- package: github.com/Unknwon/paginater
6
+- package: github.com/denisenkom/go-mssqldb
7
+- package: github.com/getsentry/raven-go
8
+- package: github.com/go-macaron/binding
9
+- package: github.com/go-macaron/cache
10
+- package: github.com/go-macaron/csrf
11
+- package: github.com/go-macaron/i18n
12
+- package: github.com/go-macaron/session
13
+- package: github.com/go-macaron/toolbox
14
+- package: github.com/go-sql-driver/mysql
15
+  version: ^1.3.0
16
+- package: github.com/go-xorm/core
17
+  version: ^0.5.6
18
+- package: github.com/go-xorm/xorm
19
+  version: ^0.6.2
20
+- package: github.com/gogits/cron
21
+  version: master
22
+- package: github.com/jaytaylor/html2text
23
+- package: github.com/lib/pq
24
+- package: github.com/mattn/go-sqlite3
25
+  version: ^1.2.0
26
+- package: github.com/microcosm-cc/bluemonday
27
+- package: github.com/rakyll/magicmime
28
+- package: github.com/russross/blackfriday
29
+  version: ^1.4.0
30
+- package: github.com/urfave/cli
31
+  version: ^1.19.1
32
+- package: golang.org/x/crypto
33
+  subpackages:
34
+  - pbkdf2
35
+- package: golang.org/x/net
36
+  subpackages:
37
+  - html
38
+- package: gopkg.in/clog.v1
39
+  version: master
40
+- package: gopkg.in/gomail.v2
41
+  version: master
42
+- package: gopkg.in/ini.v1
43
+  version: master
44
+- package: gopkg.in/libgit2/git2go.v25
45
+- package: gopkg.in/macaron.v1
46
+  version: master
47
+testImport:
48
+- package: github.com/smartystreets/goconvey
49
+  version: ^1.6.2
50
+  subpackages:
51
+  - convey

+ 88
- 0
i18n.py View File

@@ -0,0 +1,88 @@
1
+#!/usr/bin/env python
2
+import re
3
+import configparser
4
+import pprint
5
+import os
6
+
7
+# START OF CONFIG
8
+"""
9
+Examples for each regexp match:
10
+1.
11
+
12
+2.
13
+"""
14
+RES = {
15
+    ".tmpl": re.compile(r"{{\s*\.i18n\.Tr\s+\"(\w*)\.([a-zA-Z_-]*)\"\s*}}"),
16
+    ".go": re.compile(r"ctx\.Tr\(\"(\w*)\.(.*)\"")
17
+}
18
+INI_PARSE = [
19
+    "conf/locale/locale_en-US.ini",
20
+    "conf/locale/locale_fr-FR.ini",
21
+]
22
+DIRS_SEARCH = [
23
+    "cmd",
24
+    "models",
25
+    "routers",
26
+    "setting",
27
+    "stuff",
28
+    "templates",
29
+]
30
+EXT_ALLOWED = [".go", ".tmpl"]
31
+# END OF CONFIG
32
+
33
+# Global variables
34
+"""
35
+[{filename, obj},]
36
+"""
37
+ini_parsed = []
38
+
39
+"""
40
+[{ini, filename, section, option, kind},]
41
+"""
42
+unknown = []
43
+
44
+# 1. Parse ini file
45
+print("== Parsing {num} INI local files".format(num=len(INI_PARSE)))
46
+for i in INI_PARSE:
47
+    a = configparser.ConfigParser()
48
+    a.read(i)
49
+    ini_parsed.append({'filename': i, 'obj': a})
50
+    print("= {filename}".format(filename=i))
51
+
52
+print("")
53
+
54
+def compare_inis(match, fname):
55
+    for i in ini_parsed:
56
+        try:
57
+            kv = i['obj'].get(match[0], match[1])
58
+        except configparser.NoOptionError:
59
+            unknown.append({'ini': i['filename'], 'filename': fname, 'section': match[0], 'option': match[1], 'kind': 'option'})
60
+        except configparser.NoSectionError:
61
+            unknown.append({'ini': i['filename'], 'filename': fname, 'section': match[0], 'option': match[1], 'kind': 'section'})
62
+
63
+def parse_file(path, fname, ext):
64
+    filename = os.path.join(path, fname)
65
+    print("= Parsing {filename}".format(filename=filename))
66
+    with open(filename, 'r') as f:
67
+        for line in f:
68
+            match = RES[ext].findall(line)
69
+            if match and len(match) >= 1:
70
+                for m in match:
71
+                    compare_inis(m, filename)
72
+
73
+# 2. Parse each dirs - walking sub
74
+for directory in DIRS_SEARCH:
75
+    for root, dirs, files in os.walk(directory):
76
+        for fname in files:
77
+            ext = os.path.splitext(fname)[1]
78
+            if ext in EXT_ALLOWED:
79
+                parse_file(root, fname, ext)
80
+
81
+# 3. Show non-matched
82
+print("")
83
+if len(unknown) <= 0:
84
+    print("= No unmatched i18n strings detected")
85
+else:
86
+    print("= Unmatched strings:")
87
+    for u in unknown:
88
+        print("From file '{filename}' with ini locale '{ini}', missing [{section}] {option}".format(filename=u['filename'], ini=u['ini'], section=u['section'], option=u['option']))

+ 217
- 0
models/error.go View File

@@ -0,0 +1,217 @@
1
+// Copyright 2015 The Gogs Authors. All rights reserved.
2
+// Use of this source code is governed by a MIT-style
3
+// license that can be found in the LICENSE file.
4
+
5
+package models
6
+
7
+import (
8
+	"fmt"
9
+)
10
+
11
+type ErrNameReserved struct {
12
+	Name string
13
+}
14
+
15
+func IsErrNameReserved(err error) bool {
16
+	_, ok := err.(ErrNameReserved)
17
+	return ok
18
+}
19
+
20
+func (err ErrNameReserved) Error() string {
21
+	return fmt.Sprintf("name is reserved [name: %s]", err.Name)
22
+}
23
+
24
+type ErrNamePatternNotAllowed struct {
25
+	Pattern string
26
+}
27
+
28
+func IsErrNamePatternNotAllowed(err error) bool {
29
+	_, ok := err.(ErrNamePatternNotAllowed)
30
+	return ok
31
+}
32
+
33
+func (err ErrNamePatternNotAllowed) Error() string {
34
+	return fmt.Sprintf("name pattern is not allowed [pattern: %s]", err.Pattern)
35
+}
36
+
37
+//  ____ ___
38
+// |    |   \______ ___________
39
+// |    |   /  ___// __ \_  __ \
40
+// |    |  /\___ \\  ___/|  | \/
41
+// |______//____  >\___  >__|
42
+//              \/     \/
43
+
44
+type ErrUserAlreadyExist struct {
45
+	Name string
46
+}
47
+
48
+func IsErrUserAlreadyExist(err error) bool {
49
+	_, ok := err.(ErrUserAlreadyExist)
50
+	return ok
51
+}
52
+
53
+func (err ErrUserAlreadyExist) Error() string {
54
+	return fmt.Sprintf("user already exists [name: %s]", err.Name)
55
+}
56
+
57
+type ErrEmailAlreadyUsed struct {
58
+	Email string
59
+}
60
+
61
+func IsErrEmailAlreadyUsed(err error) bool {
62
+	_, ok := err.(ErrEmailAlreadyUsed)
63
+	return ok
64
+}
65
+
66
+func (err ErrEmailAlreadyUsed) Error() string {
67
+	return fmt.Sprintf("e-mail has been used [email: %s]", err.Email)
68
+}
69
+
70
+type ErrUserOwnRepos struct {
71
+	UID int64
72
+}
73
+
74
+func IsErrUserOwnRepos(err error) bool {
75
+	_, ok := err.(ErrUserOwnRepos)
76
+	return ok
77
+}
78
+
79
+func (err ErrUserOwnRepos) Error() string {
80
+	return fmt.Sprintf("user still has ownership of repositories [uid: %d]", err.UID)
81
+}
82
+
83
+// __________     ___.   .__  .__          ____  __.
84
+// \______   \__ _\_ |__ |  | |__| ____   |    |/ _|____ ___.__.
85
+//  |     ___/  |  \ __ \|  | |  |/ ___\  |      <_/ __ <   |  |
86
+//  |    |   |  |  / \_\ \  |_|  \  \___  |    |  \  ___/\___  |
87
+//  |____|   |____/|___  /____/__|\___  > |____|__ \___  > ____|
88
+//                     \/             \/          \/   \/\/
89
+
90
+type ErrKeyUnableVerify struct {
91
+	Result string
92
+}
93
+
94
+func IsErrKeyUnableVerify(err error) bool {
95
+	_, ok := err.(ErrKeyUnableVerify)
96
+	return ok
97
+}
98
+
99
+func (err ErrKeyUnableVerify) Error() string {
100
+	return fmt.Sprintf("Unable to verify key content [result: %s]", err.Result)
101
+}
102
+
103
+type ErrKeyNotExist struct {
104
+	ID int64
105
+}
106
+
107
+func IsErrKeyNotExist(err error) bool {
108
+	_, ok := err.(ErrKeyNotExist)
109
+	return ok
110
+}
111
+
112
+func (err ErrKeyNotExist) Error() string {
113
+	return fmt.Sprintf("public key does not exist [id: %d]", err.ID)
114
+}
115
+
116
+type ErrKeyAlreadyExist struct {
117
+	OwnerID int64
118
+	Content string
119
+}
120
+
121
+func IsErrKeyAlreadyExist(err error) bool {
122
+	_, ok := err.(ErrKeyAlreadyExist)
123
+	return ok
124
+}
125
+
126
+func (err ErrKeyAlreadyExist) Error() string {
127
+	return fmt.Sprintf("public key already exists [owner_id: %d, content: %s]", err.OwnerID, err.Content)
128
+}
129
+
130
+type ErrKeyNameAlreadyUsed struct {
131
+	OwnerID int64
132
+	Name    string
133
+}
134
+
135
+func IsErrKeyNameAlreadyUsed(err error) bool {
136
+	_, ok := err.(ErrKeyNameAlreadyUsed)
137
+	return ok
138
+}
139
+
140
+func (err ErrKeyNameAlreadyUsed) Error() string {
141
+	return fmt.Sprintf("public key already exists [owner_id: %d, name: %s]", err.OwnerID, err.Name)
142
+}
143
+
144
+type ErrKeyAccessDenied struct {
145
+	UserID int64
146
+	KeyID  int64
147
+	Note   string
148
+}
149
+
150
+func IsErrKeyAccessDenied(err error) bool {
151
+	_, ok := err.(ErrKeyAccessDenied)
152
+	return ok
153
+}
154
+
155
+func (err ErrKeyAccessDenied) Error() string {
156
+	return fmt.Sprintf("user does not have access to the key [user_id: %d, key_id: %d, note: %s]",
157
+		err.UserID, err.KeyID, err.Note)
158
+}
159
+
160
+type ErrDeployKeyNotExist struct {
161
+	ID     int64
162
+	KeyID  int64
163
+	RepoID int64
164
+}
165
+
166
+func IsErrDeployKeyNotExist(err error) bool {
167
+	_, ok := err.(ErrDeployKeyNotExist)
168
+	return ok
169
+}
170
+
171
+func (err ErrDeployKeyNotExist) Error() string {
172
+	return fmt.Sprintf("Deploy key does not exist [id: %d, key_id: %d, repo_id: %d]", err.ID, err.KeyID, err.RepoID)
173
+}
174
+
175
+type ErrDeployKeyAlreadyExist struct {
176
+	KeyID  int64
177
+	RepoID int64
178
+}
179
+
180
+func IsErrDeployKeyAlreadyExist(err error) bool {
181
+	_, ok := err.(ErrDeployKeyAlreadyExist)
182
+	return ok
183
+}
184
+
185
+func (err ErrDeployKeyAlreadyExist) Error() string {
186
+	return fmt.Sprintf("public key already exists [key_id: %d, repo_id: %d]", err.KeyID, err.RepoID)
187
+}
188
+
189
+type ErrDeployKeyNameAlreadyUsed struct {
190
+	RepoID int64
191
+	Name   string
192
+}
193
+
194
+func IsErrDeployKeyNameAlreadyUsed(err error) bool {
195
+	_, ok := err.(ErrDeployKeyNameAlreadyUsed)
196
+	return ok
197
+}
198
+
199
+func (err ErrDeployKeyNameAlreadyUsed) Error() string {
200
+	return fmt.Sprintf("public key already exists [repo_id: %d, name: %s]", err.RepoID, err.Name)
201
+}
202
+
203
+
204
+// Gitxt
205
+type ErrHashAlreadyExist struct {
206
+	Hash string
207
+}
208
+
209
+func IsErrHashAlreadyExist(err error) bool {
210
+	_, ok := err.(ErrHashAlreadyExist)
211
+	return ok
212
+}
213
+
214
+
215
+func (err ErrHashAlreadyExist) Error() string {
216
+	return fmt.Sprintf("hash already exists [hash: %s]", err.Hash)
217
+}

+ 12
- 0
models/errors/errors.go View File

@@ -0,0 +1,12 @@
1
+// Copyright 2017 The Gogs Authors. All rights reserved.
2
+// Use of this source code is governed by a MIT-style
3
+// license that can be found in the LICENSE file.
4
+
5
+package errors
6
+
7
+import "errors"
8
+
9
+// New is a wrapper of real errors.New function.
10
+func New(text string) error {
11
+	return errors.New(text)
12
+}

+ 18
- 0
models/errors/repo.go View File

@@ -0,0 +1,18 @@
1
+package errors
2
+
3
+import "fmt"
4
+
5
+type RepoNotExist struct {
6
+	ID     int64
7
+	UserID   int64
8
+	Name   string
9
+}
10
+
11
+func IsRepoNotExist(err error) bool {
12
+	_, ok := err.(RepoNotExist)
13
+	return ok
14
+}
15
+
16
+func (err RepoNotExist) Error() string {
17
+	return fmt.Sprintf("repository does not exist [id: %d, user_id: %d, name: %s]", err.ID, err.UserID, err.Name)
18
+}

+ 45
- 0
models/errors/user.go View File

@@ -0,0 +1,45 @@
1
+// Copyright 2017 The Gogs Authors. All rights reserved.
2
+// Use of this source code is governed by a MIT-style
3
+// license that can be found in the LICENSE file.
4
+
5
+package errors
6
+
7
+import "fmt"
8
+
9
+type EmptyName struct{}
10
+
11
+func IsEmptyName(err error) bool {
12
+	_, ok := err.(EmptyName)
13
+	return ok
14
+}
15
+
16
+func (err EmptyName) Error() string {
17
+	return "empty name"
18
+}
19
+
20
+type UserNotExist struct {
21
+	UserID int64
22
+	Name   string
23
+}
24
+
25
+func IsUserNotExist(err error) bool {
26
+	_, ok := err.(UserNotExist)
27
+	return ok
28
+}
29
+
30
+func (err UserNotExist) Error() string {
31
+	return fmt.Sprintf("user does not exist [user_id: %d, name: %s]", err.UserID, err.Name)
32
+}
33
+
34
+type UserNotKeyOwner struct {
35
+	KeyID int64
36
+}
37
+
38
+func IsUserNotKeyOwner(err error) bool {
39
+	_, ok := err.(UserNotKeyOwner)
40
+	return ok
41
+}
42
+
43
+func (err UserNotKeyOwner) Error() string {
44
+	return fmt.Sprintf("user is not the owner of public key [key_id: %d]", err.KeyID)
45
+}

+ 223
- 0
models/models.go View File

@@ -0,0 +1,223 @@
1
+package models
2
+
3
+import (
4
+	"database/sql"
5
+	"dev.sigpipe.me/dashie/myapp/setting"
6
+	"errors"
7
+	"fmt"
8
+	_ "github.com/denisenkom/go-mssqldb"
9
+	_ "github.com/go-sql-driver/mysql"
10
+	"github.com/go-xorm/core"
11
+	"github.com/go-xorm/xorm"
12
+	_ "github.com/lib/pq"
13
+	log "gopkg.in/clog.v1"
14
+	"net/url"
15
+	"os"
16
+	"path"
17
+	"strings"
18
+)
19
+
20
+// Engine represents a XORM engine or session.
21
+type Engine interface {
22
+	Delete(interface{}) (int64, error)
23
+	Exec(string, ...interface{}) (sql.Result, error)
24
+	Find(interface{}, ...interface{}) error
25
+	Get(interface{}) (bool, error)
26
+	Id(interface{}) *xorm.Session
27
+	In(string, ...interface{}) *xorm.Session
28
+	Insert(...interface{}) (int64, error)
29
+	InsertOne(interface{}) (int64, error)
30
+	Iterate(interface{}, xorm.IterFunc) error
31
+	Sql(string, ...interface{}) *xorm.Session
32
+	Table(interface{}) *xorm.Session
33
+	Where(interface{}, ...interface{}) *xorm.Session
34
+}
35
+
36
+func sessionRelease(sess *xorm.Session) {
37
+	if !sess.IsCommitedOrRollbacked {
38
+		sess.Rollback()
39
+	}
40
+	sess.Close()
41
+}
42
+
43
+var (
44
+	x         *xorm.Engine
45
+	tables    []interface{}
46
+	HasEngine bool
47
+
48
+	DbCfg struct {
49
+		Type, Host, Name, User, Passwd, Path, SSLMode string
50
+	}
51
+
52
+	EnableSQLite3 bool
53
+)
54
+
55
+func init() {
56
+	tables = append(tables, new(User))
57
+
58
+	gonicNames := []string{"SSL"}
59
+	for _, name := range gonicNames {
60
+		core.LintGonicMapper[name] = true
61
+	}
62
+}
63
+
64
+func LoadConfigs() {
65
+	sec := setting.Cfg.Section("database")
66
+	DbCfg.Type = sec.Key("DB_TYPE").String()
67
+	switch DbCfg.Type {
68
+	case "sqlite3":
69
+		setting.UseSQLite3 = true
70
+	case "mysql":
71
+		setting.UseMySQL = true
72
+	case "postgres":
73
+		setting.UsePostgreSQL = true
74
+	case "mssql":
75
+		setting.UseMSSQL = true
76
+	}
77
+	DbCfg.Host = sec.Key("HOST").String()
78
+	DbCfg.Name = sec.Key("NAME").String()
79
+	DbCfg.User = sec.Key("USER").String()
80
+	if len(DbCfg.Passwd) == 0 {
81
+		DbCfg.Passwd = sec.Key("PASSWD").String()
82
+	}
83
+	DbCfg.SSLMode = sec.Key("SSL_MODE").String()
84
+	DbCfg.Path = sec.Key("PATH").MustString("myapp.db")
85
+}
86
+
87
+// parsePostgreSQLHostPort parses given input in various forms defined in
88
+// https://www.postgresql.org/docs/current/static/libpq-connect.html#LIBPQ-CONNSTRING
89
+// and returns proper host and port number.
90
+func parsePostgreSQLHostPort(info string) (string, string) {
91
+	host, port := "127.0.0.1", "5432"
92
+	if strings.Contains(info, ":") && !strings.HasSuffix(info, "]") {
93
+		idx := strings.LastIndex(info, ":")
94
+		host = info[:idx]
95
+		port = info[idx+1:]
96
+	} else if len(info) > 0 {
97
+		host = info
98
+	}
99
+	return host, port
100
+}
101
+
102
+func parseMSSQLHostPort(info string) (string, string) {
103
+	host, port := "127.0.0.1", "1433"
104
+	if strings.Contains(info, ":") {
105
+		host = strings.Split(info, ":")[0]
106
+		port = strings.Split(info, ":")[1]
107
+	} else if strings.Contains(info, ",") {
108
+		host = strings.Split(info, ",")[0]
109
+		port = strings.TrimSpace(strings.Split(info, ",")[1])
110
+	} else if len(info) > 0 {
111
+		host = info
112
+	}
113
+	return host, port
114
+}
115
+
116
+func getEngine() (*xorm.Engine, error) {
117
+	connStr := ""
118
+	var Param string = "?"
119
+	if strings.Contains(DbCfg.Name, Param) {
120
+		Param = "&"
121
+	}
122
+	switch DbCfg.Type {
123
+	case "mysql":
124
+		if DbCfg.Host[0] == '/' { // looks like a unix socket
125
+			connStr = fmt.Sprintf("%s:%s@unix(%s)/%s%scharset=utf8&parseTime=true",
126
+				DbCfg.User, DbCfg.Passwd, DbCfg.Host, DbCfg.Name, Param)
127
+		} else {
128
+			connStr = fmt.Sprintf("%s:%s@tcp(%s)/%s%scharset=utf8&parseTime=true",
129
+				DbCfg.User, DbCfg.Passwd, DbCfg.Host, DbCfg.Name, Param)
130
+		}
131
+	case "postgres":
132
+		host, port := parsePostgreSQLHostPort(DbCfg.Host)
133
+		if host[0] == '/' { // looks like a unix socket
134
+			connStr = fmt.Sprintf("postgres://%s:%s@:%s/%s%ssslmode=%s&host=%s",
135
+				url.QueryEscape(DbCfg.User), url.QueryEscape(DbCfg.Passwd), port, DbCfg.Name, Param, DbCfg.SSLMode, host)
136
+		} else {
137
+			connStr = fmt.Sprintf("postgres://%s:%s@%s:%s/%s%ssslmode=%s",
138
+				url.QueryEscape(DbCfg.User), url.QueryEscape(DbCfg.Passwd), host, port, DbCfg.Name, Param, DbCfg.SSLMode)
139
+		}
140
+	case "mssql":
141
+		host, port := parseMSSQLHostPort(DbCfg.Host)
142
+		connStr = fmt.Sprintf("server=%s; port=%s; database=%s; user id=%s; password=%s;", host, port, DbCfg.Name, DbCfg.User, DbCfg.Passwd)
143
+	case "sqlite3":
144
+		if !EnableSQLite3 {
145
+			return nil, errors.New("This binary version does not build support for SQLite3.")
146
+		}
147
+		if err := os.MkdirAll(path.Dir(DbCfg.Path), os.ModePerm); err != nil {
148
+			return nil, fmt.Errorf("Fail to create directories: %v", err)
149
+		}
150
+		connStr = "file:" + DbCfg.Path + "?cache=shared&mode=rwc"
151
+	default:
152
+		return nil, fmt.Errorf("Unknown database type: %s", DbCfg.Type)
153
+	}
154
+	return xorm.NewEngine(DbCfg.Type, connStr)
155
+}
156
+
157
+func NewTestEngine(x *xorm.Engine) (err error) {
158
+	x, err = getEngine()
159
+	if err != nil {
160
+		return fmt.Errorf("Connect to database: %v", err)
161
+	}
162
+
163
+	x.SetMapper(core.GonicMapper{})
164
+	return x.StoreEngine("InnoDB").Sync2(tables...)
165
+}
166
+
167
+func SetEngine() (err error) {
168
+	x, err = getEngine()
169
+	if err != nil {
170
+		return fmt.Errorf("Fail to connect to database: %v", err)
171
+	}
172
+
173
+	x.SetMapper(core.GonicMapper{})
174
+
175
+	// WARNING: for serv command, MUST remove the output to os.stdout,
176
+	// so use log file to instead print to stdout.
177
+	sec := setting.Cfg.Section("log.xorm")
178
+	logger, err := log.NewFileWriter(path.Join(setting.LogRootPath, "xorm.log"),
179
+		log.FileRotationConfig{
180
+			Rotate:  sec.Key("ROTATE").MustBool(true),
181
+			Daily:   sec.Key("ROTATE_DAILY").MustBool(true),
182
+			MaxSize: sec.Key("MAX_SIZE").MustInt64(100) * 1024 * 1024,
183
+			MaxDays: sec.Key("MAX_DAYS").MustInt64(3),
184
+		})
185
+	if err != nil {
186
+		return fmt.Errorf("Fail to create 'xorm.log': %v", err)
187
+	}
188
+
189
+	x.SetLogger(xorm.NewSimpleLogger3(logger, xorm.DEFAULT_LOG_PREFIX, xorm.DEFAULT_LOG_FLAG, core.LOG_DEBUG))
190
+	x.ShowSQL(true)
191
+	return nil
192
+}
193
+
194
+func NewEngine() (err error) {
195
+	if err = SetEngine(); err != nil {
196
+		return err
197
+	}
198
+
199
+	// TODO: here do migrations if any
200
+
201
+	if err = x.StoreEngine("InnoDB").Sync2(tables...); err != nil {
202
+		return fmt.Errorf("sync database struct error: %v\n", err)
203
+	}
204
+
205
+	return nil
206
+}
207
+
208
+func Ping() error {
209
+	return x.Ping()
210
+}
211
+
212
+func InitDb() {
213
+	LoadConfigs()
214
+
215
+	if err := NewEngine(); err != nil {
216
+		log.Fatal(2, "Fail to initialize ORM engine: %v", err)
217
+	}
218
+	HasEngine = true
219
+
220
+	if EnableSQLite3 {
221
+		log.Info("SQLite3 Supported")
222
+	}
223
+}

+ 11
- 0
models/models_sqlite.go View File

@@ -0,0 +1,11 @@
1
+// +build sqlite
2
+
3
+package models
4
+
5
+import (
6
+	_ "github.com/mattn/go-sqlite3"
7
+)
8
+
9
+func init() {
10
+	EnableSQLite3 = true
11
+}

+ 327
- 0
models/user.go View File

@@ -0,0 +1,327 @@
1
+package models
2
+
3
+import (
4
+	"crypto/sha256"
5
+	"crypto/subtle"
6
+	"dev.sigpipe.me/dashie/myapp/models/errors"
7
+	"dev.sigpipe.me/dashie/myapp/pkg/mailer"
8
+	"dev.sigpipe.me/dashie/myapp/pkg/tool"
9
+	"encoding/hex"
10
+	"fmt"
11
+	"github.com/Unknwon/com"
12
+	"golang.org/x/crypto/pbkdf2"
13
+	log "gopkg.in/clog.v1"
14
+	"strings"
15
+	"time"
16
+	"unicode/utf8"
17
+)
18
+
19
+type User struct {
20
+	ID        int64  `xorm:"pk autoincr"`
21
+	UserName  string `xorm:"UNIQUE NOT NULL"`
22
+	LowerName string `xorm:"UNIQUE NOT NULL"`
23
+	Email     string `xorm:"NOT NULL"`
24
+
25
+	Password string `xorm:"NOT NULL"`
26
+	Rands    string `xorm:"VARCHAR(10)"`
27
+	Salt     string `xorm:"VARCHAR(10)"`
28
+
29
+	// Permissions
30
+	IsAdmin  bool `xorm:"DEFAULT 0"`
31
+	IsActive bool `xorm:"DEFAULT 0"`
32
+
33
+	Created     time.Time `xorm:"-"`
34
+	CreatedUnix int64
35
+	Updated     time.Time `xorm:"-"`
36
+	UpdatedUnix int64
37
+
38
+	// Relations
39
+	// 	Gitxts
40
+	// 	SshKeys
41
+}
42
+
43
+func (user *User) BeforeInsert() {
44
+	user.CreatedUnix = time.Now().Unix()
45
+	user.UpdatedUnix = user.CreatedUnix
46
+}
47
+
48
+func (user *User) BeforeUpdate() {
49
+	user.UpdatedUnix = time.Now().Unix()
50
+}
51
+
52
+func countUsers(e Engine) int64 {
53
+	count, _ := x.Count(new(User))
54
+	return count
55
+}
56
+
57
+// CountUsers returns number of users.
58
+func CountUsers() int64 {
59
+	return countUsers(x)
60
+}
61
+
62
+func getUserByID(e Engine, id int64) (*User, error) {
63
+	u := new(User)
64
+	has, err := e.Id(id).Get(u)
65
+	if err != nil {
66
+		return nil, err
67
+	} else if !has {
68
+		return nil, errors.UserNotExist{id, ""}
69
+	}
70
+	return u, nil
71
+}
72
+
73
+// GetUserByID returns the user object by given ID if exists.
74
+func GetUserByID(id int64) (*User, error) {
75
+	return getUserByID(x, id)
76
+}
77
+
78
+// GetUserByName returns user by given name.
79
+func GetUserByName(name string) (*User, error) {
80
+	if len(name) == 0 {
81
+		return nil, errors.UserNotExist{0, name}
82
+	}
83
+	u := &User{LowerName: strings.ToLower(name)}
84
+	has, err := x.Get(u)
85
+	if err != nil {
86
+		return nil, err
87
+	} else if !has {
88
+		return nil, errors.UserNotExist{0, name}
89
+	}
90
+	return u, nil
91
+}
92
+
93
+// GetUserByEmail returns the user object by given e-mail if exists.
94
+func GetUserByEmail(email string) (*User, error) {
95
+	if len(email) == 0 {
96
+		return nil, errors.UserNotExist{0, "email"}
97
+	}
98
+
99
+	email = strings.ToLower(email)
100
+	user := &User{Email: email}
101
+	has, err := x.Get(user)
102
+	if err != nil {
103
+		return nil, err
104
+	}
105
+	if has {
106
+		return user, nil
107
+	}
108
+
109
+	return nil, errors.UserNotExist{0, email}
110
+}
111
+
112
+// IsUserExist checks if given user name exist,
113
+// the user name should be noncased unique.
114
+// If uid is presented, then check will rule out that one,
115
+// it is used when update a user name in settings page.
116
+func IsUserExist(uid int64, name string) (bool, error) {
117
+	if len(name) == 0 {
118
+		return false, nil
119
+	}
120
+	return x.Where("id != ?", uid).Get(&User{LowerName: strings.ToLower(name)})
121
+}
122
+
123
+var (
124
+	reservedUsernames    = []string{"anon", "anonymous", "private", "assets", "css", "img", "js", "less", "plugins", "debug", "raw", "install", "api", "avatar", "user", "org", "help", "stars", "issues", "pulls", "commits", "repo", "template", "admin", "new", ".", ".."}
125
+	reservedUserPatterns = []string{"*.keys"}
126
+)
127
+
128
+// isUsableName checks if name is reserved or pattern of name is not allowed
129
+// based on given reserved names and patterns.
130
+// Names are exact match, patterns can be prefix or suffix match with placeholder '*'.
131
+func isUsableName(names, patterns []string, name string) error {
132
+	name = strings.TrimSpace(strings.ToLower(name))
133
+	if utf8.RuneCountInString(name) == 0 {
134
+		return errors.EmptyName{}
135
+	}
136
+
137
+	for i := range names {
138
+		if name == names[i] {
139
+			return ErrNameReserved{name}
140
+		}
141
+	}
142
+
143
+	for _, pat := range patterns {
144
+		if pat[0] == '*' && strings.HasSuffix(name, pat[1:]) ||
145
+			(pat[len(pat)-1] == '*' && strings.HasPrefix(name, pat[:len(pat)-1])) {
146
+			return ErrNamePatternNotAllowed{pat}
147
+		}
148
+	}
149
+
150
+	return nil
151
+}
152
+
153
+func IsUsableUsername(name string) error {
154
+	return isUsableName(reservedUsernames, reservedUserPatterns, name)
155
+}
156
+
157
+// EncodePasswd encodes password to safe format.
158
+func (u *User) EncodePasswd() {
159
+	newPasswd := pbkdf2.Key([]byte(u.Password), []byte(u.Salt), 10000, 50, sha256.New)
160
+	u.Password = fmt.Sprintf("%x", newPasswd)
161
+}
162
+
163
+// ValidatePassword checks if given password matches the one belongs to the user.
164
+func (u *User) ValidatePassword(passwd string) bool {
165
+	newUser := &User{Password: passwd, Salt: u.Salt}
166
+	newUser.EncodePasswd()
167
+	return subtle.ConstantTimeCompare([]byte(u.Password), []byte(newUser.Password)) == 1
168
+}
169
+
170
+// GetUserSalt returns a ramdom user salt token.
171
+func GetUserSalt() (string, error) {
172
+	return tool.RandomString(10)
173
+}
174
+
175
+// Create a new user and do some validation
176
+func CreateUser(u *User) (err error) {
177
+	if err = IsUsableUsername(u.UserName); err != nil {
178
+		return err
179
+	}
180
+
181
+	isExist, err := IsUserExist(0, u.UserName)
182
+	if err != nil {
183
+		return err
184
+	} else if isExist {
185
+		return ErrUserAlreadyExist{u.UserName}
186
+	}
187
+
188
+	u.Email = strings.ToLower(u.Email)
189
+	u.LowerName = strings.ToLower(u.UserName)
190
+
191
+	if u.Rands, err = GetUserSalt(); err != nil {
192
+		return err
193
+	}
194
+	if u.Salt, err = GetUserSalt(); err != nil {
195
+		return err
196
+	}
197
+	u.EncodePasswd()
198
+
199
+	sess := x.NewSession()
200
+	defer sessionRelease(sess)
201
+	if err = sess.Begin(); err != nil {
202
+		return err
203
+	}
204
+
205
+	if _, err = sess.Insert(u); err != nil {
206
+		return err
207
+	}
208
+
209
+	return sess.Commit()
210
+}
211
+
212
+// Update an user
213
+func updateUser(e Engine, u *User) error {
214
+	u.LowerName = strings.ToLower(u.UserName)
215
+	u.Email = strings.ToLower(u.Email)
216
+	_, err := e.Id(u.ID).AllCols().Update(u)
217
+	return err
218
+}
219
+
220
+func UpdateUser(u *User) error {
221
+	return updateUser(x, u)
222
+}
223
+
224
+// Login validates user name and password.
225
+func UserLogin(username, password string) (*User, error) {
226
+	var user *User
227
+	if strings.Contains(username, "@") {
228
+		user = &User{Email: strings.ToLower(username)}
229
+	} else {
230
+		user = &User{LowerName: strings.ToLower(username)}
231
+	}
232
+
233
+	hasUser, err := x.Get(user)
234
+	if err != nil {
235
+		return nil, err
236
+	}
237
+
238
+	if hasUser {
239
+		if user.ValidatePassword(password) {
240
+			return user, nil
241
+		}
242
+
243
+		return nil, errors.UserNotExist{user.ID, user.UserName}
244
+	}
245
+
246
+	return nil, errors.UserNotExist{user.ID, user.UserName}
247
+}
248
+
249
+// get user by verify code
250
+func getVerifyUser(code string) (user *User) {
251
+	if len(code) <= tool.TIME_LIMIT_CODE_LENGTH {
252
+		return nil
253
+	}
254
+
255
+	// use tail hex username query user
256
+	hexStr := code[tool.TIME_LIMIT_CODE_LENGTH:]
257
+	if b, err := hex.DecodeString(hexStr); err == nil {
258
+		if user, err = GetUserByName(string(b)); user != nil {
259
+			return user
260
+		} else if !errors.IsUserNotExist(err) {
261
+			log.Error(2, "GetUserByName: %v", err)
262
+		}
263
+	}
264
+
265
+	return nil
266
+}
267
+
268
+// verify active code when active account
269
+func VerifyUserActiveCode(code string) (user *User) {
270
+	// HARDCODED
271
+	minutes := 180
272
+
273
+	if user = getVerifyUser(code); user != nil {
274
+		// time limit code
275
+		prefix := code[:tool.TIME_LIMIT_CODE_LENGTH]
276
+		data := com.ToStr(user.ID) + user.Email + user.LowerName + user.Password + user.Rands
277
+
278
+		if tool.VerifyTimeLimitCode(data, minutes, prefix) {
279
+			return user
280
+		}
281
+	}
282
+	return nil
283
+}
284
+
285
+// GenerateEmailActivateCode generates an activate code based on user information and given e-mail.
286
+func (u *User) GenerateEmailActivateCode(email string) string {
287
+	code := tool.CreateTimeLimitCode(
288
+		com.ToStr(u.ID)+email+u.LowerName+u.Password+u.Rands, 180, nil)
289
+
290
+	// Add tail hex username
291
+	code += hex.EncodeToString([]byte(u.LowerName))
292
+	return code
293
+}
294
+
295
+// GenerateActivateCode generates an activate code based on user information.
296
+func (u *User) GenerateActivateCode() string {
297
+	return u.GenerateEmailActivateCode(u.Email)
298
+}
299
+
300
+// mailerUser is a wrapper for satisfying mailer.User interface.
301
+type mailerUser struct {
302
+	user *User
303
+}
304
+
305
+func (this mailerUser) ID() int64 {
306
+	return this.user.ID
307
+}
308
+
309
+func (this mailerUser) Email() string {
310
+	return this.user.Email
311
+}
312
+
313
+func (this mailerUser) DisplayName() string {
314
+	return this.user.UserName
315
+}
316
+
317
+func (this mailerUser) GenerateActivateCode() string {
318
+	return this.user.GenerateActivateCode()
319
+}
320
+
321
+func (this mailerUser) GenerateEmailActivateCode(email string) string {
322
+	return this.user.GenerateEmailActivateCode(email)
323
+}
324
+
325
+func NewMailerUser(u *User) mailer.User {
326
+	return mailerUser{u}
327
+}

+ 34
- 0
myapp.go View File

@@ -0,0 +1,34 @@
1
+package main
2
+
3
+import (
4
+	"os"
5
+	"github.com/urfave/cli"
6
+	"dev.sigpipe.me/dashie/myapp/cmd"
7
+	"dev.sigpipe.me/dashie/myapp/setting"
8
+	"github.com/getsentry/raven-go"
9
+	"fmt"
10
+)
11
+
12
+const APP_VER = "0.2"
13
+
14
+func init() {
15
+	setting.AppVer = APP_VER
16
+	if os.Getenv("USE_RAVEN") == "true" {
17
+		raven.SetDSN(os.Getenv("RAVEN_DSN"))
18
+		fmt.Printf("Using Raven with DSN: %s\r\n", os.Getenv("RAVEN_DSN"))
19
+	} else {
20
+		fmt.Println("Running without Raven/Sentry support.")
21
+	}
22
+}
23
+
24
+func main() {
25
+	app := cli.NewApp()
26
+	app.Name = "myapp"
27
+	app.Usage = "paste stuff to the interweb with git backend"
28
+	app.Version = APP_VER
29
+	app.Commands = []cli.Command{
30
+		cmd.Web,
31
+	}
32
+	app.Flags = append(app.Flags, []cli.Flag{}...)
33
+	app.Run(os.Args)
34
+}

+ 62
- 0
pkg/auth/auth.go View File

@@ -0,0 +1,62 @@
1
+// Copyright 2014 The Gogs Authors. All rights reserved.
2
+// Use of this source code is governed by a MIT-style
3
+// license that can be found in the LICENSE file.
4
+
5
+package auth
6
+
7
+import (
8
+	"strings"
9
+	"dev.sigpipe.me/dashie/myapp/models"
10
+	"dev.sigpipe.me/dashie/myapp/models/errors"
11
+
12
+	"github.com/go-macaron/session"
13
+	log "gopkg.in/clog.v1"
14
+	"gopkg.in/macaron.v1"
15
+)
16
+
17
+func IsAPIPath(url string) bool {
18
+	return strings.HasPrefix(url, "/api/")
19
+}
20
+
21
+// SignedInID returns the id of signed in user.
22
+func SignedInID(ctx *macaron.Context, sess session.Store) int64 {
23
+	if !models.HasEngine {
24
+		return 0
25
+	}
26
+
27
+	uid := sess.Get("uid")
28
+	if uid == nil {
29
+		return 0
30
+	}
31
+	if id, ok := uid.(int64); ok {
32
+		if _, err := models.GetUserByID(id); err != nil {
33
+			if !errors.IsUserNotExist(err) {
34
+				log.Error(2, "GetUserByID: %v", err)
35
+			}
36
+			return 0
37
+		}
38
+		return id
39
+	}
40
+	return 0
41
+}
42
+
43
+// SignedInUser returns the user object of signed user.
44
+// It returns a bool value to indicate whether user uses basic auth or not.
45
+func SignedInUser(ctx *macaron.Context, sess session.Store) (*models.User, bool) {
46
+	if !models.HasEngine {
47
+		return nil, false
48
+	}
49
+
50
+	uid := SignedInID(ctx, sess)
51
+
52
+	if uid <= 0 {
53
+		return nil, false
54
+	}
55
+
56
+	u, err := models.GetUserByID(uid)
57
+	if err != nil {
58
+		log.Error(4, "GetUserById: %v", err)
59
+		return nil, false
60
+	}
61
+	return u, false
62
+}

+ 24
- 0
pkg/cron/cron.go View File

@@ -0,0 +1,24 @@
1
+package cron
2
+
3
+import (
4
+	"github.com/gogits/cron"
5
+	//log "gopkg.in/clog.v1"
6
+)
7
+
8
+var c = cron.New()
9
+
10
+func NewContext() {
11
+	var (
12
+	//entry *cron.Entry
13
+	//err   error
14
+	)
15
+
16
+	// Add crons here
17
+
18
+	c.Start()
19
+}
20
+
21
+// ListTasks returns all running cron tasks.
22
+func ListTasks() []*cron.Entry {
23
+	return c.Entries()
24
+}

+ 29
- 0
pkg/form/auth.go View File

@@ -0,0 +1,29 @@
1
+package form
2
+
3
+import (
4
+	"github.com/go-macaron/binding"
5
+	"gopkg.in/macaron.v1"
6
+)
7
+
8
+// Register
9
+type Register struct {
10
+	UserName string `binding:"Required;AlphaDashDot;MaxSize(35)"`
11
+	Email    string `binding:"Required;Email;MaxSize(254)"`
12
+	Password string `binding:"Required;MaxSize(255)"`
13
+	Repeat string
14
+}
15
+
16
+func (f *Register) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
17
+	return validate(errs, ctx.Data, f, ctx.Locale)
18
+}
19
+
20
+// Login
21
+type Login struct {
22
+	UserName string `binding:"Required;MaxSize(254)"`
23
+	Password string `binding:"Required;MaxSize(255)"`
24
+	Remember bool
25
+}
26
+
27
+func (f *Login) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
28
+	return validate(errs, ctx.Data, f, ctx.Locale)
29
+}

+ 159
- 0
pkg/form/form.go View File

@@ -0,0 +1,159 @@
1
+package form
2
+
3
+import (
4
+	"reflect"
5
+	"github.com/go-macaron/binding"
6
+	"gopkg.in/macaron.v1"
7
+	"github.com/Unknwon/com"
8
+	"strings"
9
+	"regexp"
10
+	"fmt"
11
+	log "gopkg.in/clog.v1"
12
+)
13
+
14
+const ERR_ALPHA_DASH_DOT_SLASH = "AlphaDashDotSlashError"
15
+
16
+var AlphaDashDotSlashPattern = regexp.MustCompile("[^\\d\\w-_\\./]")
17
+
18
+func init() {
19
+	binding.SetNameMapper(com.ToSnakeCase)
20
+	binding.AddRule(&binding.Rule{
21
+		IsMatch: func(rule string) bool {
22
+			return rule == "AlphaDashDotSlash"
23
+		},
24
+		IsValid: func(errs binding.Errors, name string, v interface{}) (bool, binding.Errors) {
25
+			if AlphaDashDotSlashPattern.MatchString(fmt.Sprintf("%v", v)) {
26
+				errs.Add([]string{name}, ERR_ALPHA_DASH_DOT_SLASH, "AlphaDashDotSlash")
27
+				return false, errs
28
+			}
29
+			return true, errs
30
+		},
31
+	})
32
+}
33
+
34
+type Form interface {
35
+	binding.Validator
36
+}
37
+
38
+// Assign assign form values back to the template data.
39
+func Assign(form interface{}, data map[string]interface{}) {
40
+	typ := reflect.TypeOf(form)
41
+	val := reflect.ValueOf(form)
42
+
43
+	if typ.Kind() == reflect.Ptr {
44
+		typ = typ.Elem()
45
+		val = val.Elem()
46
+	}
47
+
48
+	for i := 0; i < typ.NumField(); i++ {
49
+		field := typ.Field(i)
50
+
51
+		fieldName := field.Tag.Get("form")
52
+		// Allow ignored fields in the struct
53
+		if fieldName == "-" {
54
+			continue
55
+		} else if len(fieldName) == 0 {
56
+			fieldName = com.ToSnakeCase(field.Name)
57
+		}
58
+
59
+		data[fieldName] = val.Field(i).Interface()
60
+	}
61
+}
62
+
63
+func getRuleBody(field reflect.StructField, prefix string) string {
64
+	for _, rule := range strings.Split(field.Tag.Get("binding"), ";") {
65
+		if strings.HasPrefix(rule, prefix) {
66
+			return rule[len(prefix) : len(rule)-1]
67
+		}
68
+	}
69
+	return ""
70
+}
71
+
72
+func getSize(field reflect.StructField) string {
73
+	return getRuleBody(field, "Size(")
74
+}
75
+
76
+func getMinSize(field reflect.StructField) string {
77
+	return getRuleBody(field, "MinSize(")
78
+}
79
+
80
+func getMaxSize(field reflect.StructField) string {
81
+	return getRuleBody(field, "MaxSize(")
82
+}
83
+
84
+func getInclude(field reflect.StructField) string {
85
+	return getRuleBody(field, "Include(")
86
+}
87
+
88
+func getIn(field reflect.StructField) string {
89
+	return getRuleBody(field, "In(")
90
+}
91
+
92
+func validate(errs binding.Errors, data map[string]interface{}, f Form, l macaron.Locale) binding.Errors {
93
+	log.Trace("Validating form")
94
+
95
+	if errs.Len() == 0 {
96
+		return errs
97
+	}
98
+
99
+	data["HasError"] = true
100
+	Assign(f, data)
101
+
102
+	typ := reflect.TypeOf(f)
103
+	val := reflect.ValueOf(f)
104
+
105
+	if typ.Kind() == reflect.Ptr {
106
+		typ = typ.Elem()
107
+		val = val.Elem()
108
+	}
109
+
110
+	for i := 0; i < typ.NumField(); i++ {
111
+		field := typ.Field(i)
112
+
113
+		fieldName := field.Tag.Get("form")
114
+		// Allow ignored fields in the struct
115
+		if fieldName == "-" {
116
+			continue
117
+		}
118
+
119
+		if errs[0].FieldNames[0] == field.Name {
120
+			data["Err_"+field.Name] = true
121
+
122
+			trName := field.Tag.Get("locale")
123
+			if len(trName) == 0 {
124
+				trName = l.Tr("form." + field.Name)
125
+			} else {
126
+				trName = l.Tr(trName)
127
+			}
128
+
129
+			switch errs[0].Classification {
130
+			case binding.ERR_REQUIRED:
131
+				data["ErrorMsg"] = trName + l.Tr("form.require_error")
132
+			case binding.ERR_ALPHA_DASH:
133
+				data["ErrorMsg"] = trName + l.Tr("form.alpha_dash_error")
134
+			case binding.ERR_ALPHA_DASH_DOT:
135
+				data["ErrorMsg"] = trName + l.Tr("form.alpha_dash_dot_error")
136