ggdgsdbsdbbb / gitly.v
380 lines · 330 sloc · 7.92 KB · 53a36b4a41aa6dcaa188b940342a2af42a3a96e9
Raw
1// Copyright (c) 2020-2021 Alexander Medvednikov. All rights reserved.
2// Use of this source code is governed by a GPL license that can be found in the LICENSE file.
3module main
4
5import veb
6import time
7import os
8import log
9import api
10import config
11import git
12
13const commits_per_page = 35
14const expire_length = 200
15const posts_per_day = 5
16const max_username_len = 40
17const max_login_attempts = 5
18const max_user_repos = 10
19const max_repo_name_len = 100
20const max_namechanges = 3
21const namechange_period = time.hour * 24
22
23@[heap]
24pub struct App {
25 veb.StaticHandler
26 veb.Middleware[Context]
27 started_at i64
28pub mut:
29 db GitlyDb
30mut:
31 version string
32 logger log.Log
33 config config.Config
34 settings Settings
35 port int
36}
37
38pub struct Context {
39 veb.Context
40mut:
41 user User
42 current_path string
43 page_gen_time string
44 is_tree bool
45 logged_in bool
46 path_split []string
47 branch string
48 lang Lang = .en //.ru
49}
50
51// fn C.sqlite3_config(int)
52
53fn new_app() !&App {
54 // C.sqlite3_config(3)
55 conf := config.read_config('./config.json') or {
56 panic('Config not found or has syntax errors')
57 }
58
59 mut app := &App{
60 // db: sqlite.connect('gitly.sqlite') or { panic(err) }
61 db: connect_db(conf)!
62 config: conf
63 started_at: time.now().unix()
64 }
65
66 set_rand_crypto_safe_seed()
67
68 app.create_tables()!
69 app.migrate_tables()!
70
71 create_directory_if_not_exists('logs')
72
73 app.setup_logger()
74
75 version_path := os.join_path('static', 'assets', 'version')
76 create_directory_if_not_exists(os.dir(version_path))
77
78 stored_version := os.read_file(version_path) or { 'unknown' }
79 mut version := stored_version
80 git_result := git.Git.exec(['rev-parse', '--short', 'HEAD'])
81
82 if git_result.exit_code == 0 && !git_result.output.contains('fatal') {
83 version = git_result.output.trim_space()
84 }
85
86 if version != stored_version {
87 os.write_file(version_path, version) or { panic(err) }
88 }
89
90 app.version = version
91
92 app.handle_static('static', true)!
93 app.serve_static('/favicon.ico', 'static/assets/favicon.svg')!
94 if !os.exists('avatars') {
95 os.mkdir('avatars')!
96 }
97 app.handle_static('avatars', false)!
98
99 app.load_settings()
100
101 create_directory_if_not_exists(app.config.repo_storage_path)
102 create_directory_if_not_exists(app.config.archive_path)
103 create_directory_if_not_exists(app.config.avatars_path)
104
105 // Create the first admin user if the db is empty
106 app.get_user_by_id(1) or {}
107
108 if '-cmdapi' in os.args {
109 spawn app.command_fetcher()
110 }
111
112 return app
113}
114
115fn (mut app App) setup_logger() {
116 app.logger.set_level(.debug)
117
118 app.logger.set_full_logpath('./logs/log_${time.now().ymmdd()}.log')
119 app.logger.log_to_console_too()
120}
121
122pub fn (mut app App) warn(msg string) {
123 app.logger.warn(msg)
124
125 app.logger.flush()
126}
127
128pub fn (mut app App) info(msg string) {
129 app.logger.info(msg)
130
131 app.logger.flush()
132}
133
134pub fn (mut app App) debug(msg string) {
135 app.logger.debug(msg)
136
137 app.logger.flush()
138}
139
140pub fn (mut app App) init_server() {
141}
142
143pub fn (mut app App) before_request(mut ctx Context) bool {
144 $if trace_prealloc ? {
145 unsafe { prealloc_scope_checkpoint(c'gitly before_request start') }
146 }
147 ctx.logged_in = app.is_logged_in(mut ctx)
148 $if trace_prealloc ? {
149 unsafe { prealloc_scope_checkpoint(c'gitly checked login') }
150 }
151 if ctx.logged_in {
152 ctx.user = app.get_user_from_cookies(ctx) or {
153 ctx.logged_in = false
154 User{}
155 }
156 }
157 $if trace_prealloc ? {
158 unsafe { prealloc_scope_checkpoint(c'gitly loaded user') }
159 }
160 lang_cookie := ctx.get_cookie('lang') or { '' }
161 ctx.lang = match lang_cookie {
162 'ru' { Lang.ru }
163 'es' { Lang.es }
164 'jp' { Lang.jp }
165 'cn' { Lang.cn }
166 'pt' { Lang.pt }
167 else { Lang.en }
168 }
169
170 $if trace_prealloc ? {
171 unsafe { prealloc_scope_checkpoint(c'gitly loaded lang') }
172 }
173 return true
174}
175
176@['/open-source']
177pub fn (mut app App) open_source() veb.Result {
178 return $veb.html()
179}
180
181@['/']
182pub fn (mut app App) index() veb.Result {
183 user_count := app.get_users_count() or { 0 }
184 no_users := user_count == 0
185 if no_users {
186 return ctx.redirect('/register')
187 }
188
189 return $veb.html()
190}
191
192@['/change_lang/:lang'; post]
193pub fn (mut app App) change_lang(lang string) veb.Result {
194 eprintln('CHANGING LANG ${lang}')
195 expire_date := time.now().add_days(400)
196 ctx.set_cookie(name: 'lang', value: lang, path: '/', expires: expire_date)
197 // return ctx.redirect('/')
198 return ctx.json('ok')
199}
200
201pub fn (mut ctx Context) redirect_to_index() veb.Result {
202 return ctx.redirect('/')
203}
204
205pub fn (mut ctx Context) redirect_to_login() veb.Result {
206 return ctx.redirect('/login')
207}
208
209pub fn (mut ctx Context) redirect_to_repository(username string, repo_name string) veb.Result {
210 return ctx.redirect('/${username}/${repo_name}')
211}
212
213fn (mut app App) create_tables() ! {
214 sql app.db {
215 create table Repo
216 }!
217 // unix time default now
218 sql app.db {
219 create table File
220 }! // missing ON CONFLIC REPLACE
221 //"created_at int default (strftime('%s', 'now'))"
222 sql app.db {
223 create table Issue
224 }!
225 sql app.db {
226 create table Label
227 }!
228 sql app.db {
229 create table IssueLabel
230 }!
231 //"created_at int default (strftime('%s', 'now'))"
232 sql app.db {
233 create table Commit
234 }!
235 // author text default '' is to to avoid joins
236 sql app.db {
237 create table LangStat
238 }!
239 sql app.db {
240 create table User
241 }!
242 sql app.db {
243 create table Email
244 }!
245 sql app.db {
246 create table Contributor
247 }!
248 sql app.db {
249 create table Activity
250 }!
251 sql app.db {
252 create table Tag
253 }!
254 sql app.db {
255 create table Release
256 }!
257 sql app.db {
258 create table SshKey
259 }!
260 sql app.db {
261 create table Comment
262 }!
263 sql app.db {
264 create table Branch
265 }!
266 sql app.db {
267 create table Settings
268 }!
269 sql app.db {
270 create table Token
271 }!
272 sql app.db {
273 create table SecurityLog
274 }!
275 sql app.db {
276 create table Star
277 }!
278 sql app.db {
279 create table Watch
280 }!
281 sql app.db {
282 create table CiStatus
283 }!
284 sql app.db {
285 create table PullRequest
286 }!
287 sql app.db {
288 create table PrComment
289 }!
290 sql app.db {
291 create table PrReview
292 }!
293 sql app.db {
294 create table PrReviewComment
295 }!
296 sql app.db {
297 create table Webhook
298 }!
299 sql app.db {
300 create table WebhookDelivery
301 }!
302 sql app.db {
303 create table Discussion
304 }!
305 sql app.db {
306 create table DiscussionComment
307 }!
308 sql app.db {
309 create table Project
310 }!
311 sql app.db {
312 create table ProjectColumn
313 }!
314 sql app.db {
315 create table ProjectCard
316 }!
317 sql app.db {
318 create table Milestone
319 }!
320 sql app.db {
321 create table TwoFactor
322 }!
323 sql app.db {
324 create table ApiToken
325 }!
326}
327
328fn (mut app App) migrate_tables() ! {
329 app.add_missing_column('File', 'is_size_calculated', db_bool_column_type())!
330 app.add_missing_column('Settings', 'disable_tree_folder_size', db_bool_column_type())!
331 app.add_missing_column('Repo', 'is_deleted', db_bool_column_type())!
332 app.add_missing_column('Repo', 'disable_discussions', db_bool_column_type())!
333 app.add_missing_column('Repo', 'disable_projects', db_bool_column_type())!
334 app.add_missing_column('Repo', 'disable_milestones', db_bool_column_type())!
335 app.add_missing_column('Repo', 'disable_wiki', db_bool_column_type())!
336 app.add_missing_column('Repo', 'is_pinned', db_bool_column_type())!
337}
338
339fn (mut app App) add_missing_column(table_name string, column_name string, column_type string) ! {
340 if db_column_exists(app.db, table_name, column_name)! {
341 return
342 }
343
344 app.db.exec('alter table ${sql_table(table_name)} add column ${sql_table(column_name)} ${column_type}')!
345}
346
347fn (mut ctx Context) json_success[T](result T) veb.Result {
348 response := api.ApiSuccessResponse[T]{
349 success: true
350 result: result
351 }
352
353 return ctx.json(response)
354}
355
356fn (mut ctx Context) json_error(message string) veb.Result {
357 return ctx.json(api.ApiErrorResponse{
358 success: false
359 message: message
360 })
361}
362
363// maybe it should be implemented with another static server, in dev
364fn (mut app App) send_file(filname string, content string) veb.Result {
365 ctx.set_header(.content_disposition, 'attachment; filename="${filname}"')
366
367 return ctx.ok(content)
368}
369
370fn (mut ctx Context) page_gen_time() string {
371 if ctx.page_gen_start == 0 {
372 return '<1ms'
373 }
374 diff := int(time.ticks() - ctx.page_gen_start)
375 return if diff == 0 {
376 '<1ms'
377 } else {
378 '${diff}ms'
379 }
380}
381