| 1 | module main |
| 2 | |
| 3 | import veb |
| 4 | import validation |
| 5 | import api |
| 6 | |
| 7 | struct ItemWithUser[T] { |
| 8 | item T |
| 9 | user User |
| 10 | } |
| 11 | |
| 12 | type IssueWithUser = ItemWithUser[Issue] |
| 13 | type CommentWithUser = ItemWithUser[Comment] |
| 14 | |
| 15 | @['/api/v1/:username/:repo_name/issues/count'] |
| 16 | fn (mut app App) handle_issues_count(username string, repo_name string) veb.Result { |
| 17 | has_access := app.has_user_repo_read_access_by_repo_name(ctx, ctx.user.id, username, repo_name) |
| 18 | if !has_access { |
| 19 | return ctx.json_error('Not found') |
| 20 | } |
| 21 | repo := app.find_repo_by_name_and_username(repo_name, username) or { |
| 22 | return ctx.json_error('Not found') |
| 23 | } |
| 24 | count := app.get_repo_issue_count(repo.id) |
| 25 | return ctx.json(api.ApiIssueCount{ |
| 26 | success: true |
| 27 | result: count |
| 28 | }) |
| 29 | } |
| 30 | |
| 31 | @['/:username/:repo_name/issues/new'] |
| 32 | pub fn (mut app App) new_issue(username string, repo_name string) veb.Result { |
| 33 | if !ctx.logged_in { |
| 34 | return ctx.not_found() |
| 35 | } |
| 36 | repo := app.find_repo_by_name_and_username(repo_name, username) or { return ctx.not_found() } |
| 37 | return $veb.html() |
| 38 | } |
| 39 | |
| 40 | @['/:username/issues'] |
| 41 | pub fn (mut app App) handle_get_user_issues(mut ctx Context, username string) veb.Result { |
| 42 | return app.user_issues(mut ctx, username, 'created') |
| 43 | } |
| 44 | |
| 45 | @['/:username/:repo_name/issues'; post] |
| 46 | pub fn (mut app App) handle_add_repo_issue(mut ctx Context, username string, repo_name string) veb.Result { |
| 47 | // TODO: use captcha instead of user restrictions |
| 48 | if !ctx.logged_in || (ctx.logged_in && ctx.user.posts_count >= posts_per_day) { |
| 49 | return ctx.redirect_to_index() |
| 50 | } |
| 51 | repo := app.find_repo_by_name_and_username(repo_name, username) or { return ctx.not_found() } |
| 52 | title := ctx.form['title'] |
| 53 | text := ctx.form['text'] |
| 54 | is_title_empty := validation.is_string_empty(title) |
| 55 | is_text_empty := validation.is_string_empty(text) |
| 56 | if is_title_empty || is_text_empty { |
| 57 | return ctx.redirect('/${username}/${repo_name}/issues/new') |
| 58 | } |
| 59 | app.increment_user_post(mut ctx.user) or { app.info(err.str()) } |
| 60 | app.add_issue(repo.id, ctx.user.id, title, text) or { app.info(err.str()) } |
| 61 | app.increment_repo_issues(repo.id) or { app.info(err.str()) } |
| 62 | app.dispatch_webhook(repo.id, 'issue', WebhookIssuePayload{ |
| 63 | action: 'opened' |
| 64 | repo: '${username}/${repo_name}' |
| 65 | title: title |
| 66 | author: ctx.user.username |
| 67 | }) |
| 68 | has_first_issue_activity := app.has_activity(ctx.user.id, 'first_issue') |
| 69 | if !has_first_issue_activity { |
| 70 | app.add_activity(ctx.user.id, 'first_issue') or { app.info(err.str()) } |
| 71 | } |
| 72 | return ctx.redirect('/${username}/${repo_name}/issues') |
| 73 | } |
| 74 | |
| 75 | @['/:username/:repo_name/issues'] |
| 76 | pub fn (mut app App) handle_get_repo_issues(mut ctx Context, username string, repo_name string) veb.Result { |
| 77 | return app.issues(mut ctx, username, repo_name, '0') |
| 78 | } |
| 79 | |
| 80 | @['/:username/:repo_name/issues/:page'] |
| 81 | pub fn (mut app App) issues(mut ctx Context, username string, repo_name string, page string) veb.Result { |
| 82 | repo := app.find_repo_by_name_and_username(repo_name, username) or { return ctx.not_found() } |
| 83 | page_i := page.int() |
| 84 | mut issues_with_users := []IssueWithUser{} |
| 85 | mut issue := Issue{} |
| 86 | mut user := User{} |
| 87 | repo_issues := app.find_repo_issues_as_page(repo.id, page_i) |
| 88 | mut i := 0 |
| 89 | for i = 0; i < repo_issues.len; i++ { |
| 90 | issue = repo_issues[i] |
| 91 | user = app.get_user_by_id(issue.author_id) or { continue } |
| 92 | issue.labels = app.get_issue_labels(issue.id) |
| 93 | issue.repo_author = repo.user_name |
| 94 | issue.repo_name = repo.name |
| 95 | issues_with_users << IssueWithUser{ |
| 96 | item: issue |
| 97 | user: user |
| 98 | } |
| 99 | } |
| 100 | show_repo_link := false |
| 101 | mut first := false |
| 102 | mut last := false |
| 103 | if repo.nr_open_issues > commits_per_page { |
| 104 | offset := page_i * commits_per_page |
| 105 | delta := repo.nr_open_issues - offset |
| 106 | if delta > 0 { |
| 107 | if delta == repo.nr_open_issues && page_i == 0 { |
| 108 | first = true |
| 109 | } else { |
| 110 | last = true |
| 111 | } |
| 112 | } |
| 113 | } else { |
| 114 | last = true |
| 115 | first = true |
| 116 | } |
| 117 | page_count := calculate_pages(repo.nr_open_issues, commits_per_page) |
| 118 | prev_page, next_page := generate_prev_next_pages(page_i) |
| 119 | return $veb.html() |
| 120 | } |
| 121 | |
| 122 | @['/:username/:repo_name/issue/:id'] |
| 123 | pub fn (mut app App) issue(mut ctx Context, username string, repo_name string, id string) veb.Result { |
| 124 | repo := app.find_repo_by_name_and_username(repo_name, username) or { return ctx.not_found() } |
| 125 | issue := app.find_issue_by_id(id.int()) or { return ctx.not_found() } |
| 126 | issue_author := app.get_user_by_id(issue.author_id) or { return ctx.not_found() } |
| 127 | mut comments_with_users := []CommentWithUser{} |
| 128 | mut comment := Comment{} |
| 129 | mut comment_author := User{} |
| 130 | issue_comments := app.get_all_issue_comments(issue.id) |
| 131 | mut i := 0 |
| 132 | for i = 0; i < issue_comments.len; i++ { |
| 133 | comment = issue_comments[i] |
| 134 | comment_author = app.get_user_by_id(comment.author_id) or { continue } |
| 135 | comments_with_users << CommentWithUser{ |
| 136 | item: comment |
| 137 | user: comment_author |
| 138 | } |
| 139 | } |
| 140 | return $veb.html() |
| 141 | } |
| 142 | |
| 143 | @['/:username/issues/:tab'] |
| 144 | pub fn (mut app App) user_issues(mut ctx Context, username string, tab string) veb.Result { |
| 145 | if !ctx.logged_in { |
| 146 | return ctx.not_found() |
| 147 | } |
| 148 | if ctx.user.username != username { |
| 149 | return ctx.not_found() |
| 150 | } |
| 151 | exists, user := app.check_username(username) |
| 152 | if !exists { |
| 153 | return ctx.not_found() |
| 154 | } |
| 155 | current_tab := if tab in ['assigned', 'created', 'mentioned', 'activity'] { |
| 156 | tab |
| 157 | } else { |
| 158 | 'created' |
| 159 | } |
| 160 | mut issues := match current_tab { |
| 161 | 'assigned' { []Issue{} } |
| 162 | 'mentioned' { app.find_user_mentioned_issues(user.username) } |
| 163 | 'activity' { app.find_user_recent_issues(user.id) } |
| 164 | else { app.find_user_issues(user.id) } |
| 165 | } |
| 166 | |
| 167 | mut issue_repo := Repo{} |
| 168 | for mut issue in issues { |
| 169 | issue_repo = app.find_repo_by_id(issue.repo_id) or { continue } |
| 170 | issue.repo_author = issue_repo.user_name |
| 171 | issue.repo_name = issue_repo.name |
| 172 | issue.labels = app.get_issue_labels(issue.id) |
| 173 | } |
| 174 | mut issues_with_users := []IssueWithUser{} |
| 175 | for issue in issues { |
| 176 | issue_author := app.get_user_by_id(issue.author_id) or { continue } |
| 177 | issues_with_users << IssueWithUser{ |
| 178 | item: issue |
| 179 | user: issue_author |
| 180 | } |
| 181 | } |
| 182 | tab_assigned_class := if current_tab == 'assigned' { |
| 183 | 'user-issues-sidebar__item user-issues-sidebar__item--active' |
| 184 | } else { |
| 185 | 'user-issues-sidebar__item' |
| 186 | } |
| 187 | tab_created_class := if current_tab == 'created' { |
| 188 | 'user-issues-sidebar__item user-issues-sidebar__item--active' |
| 189 | } else { |
| 190 | 'user-issues-sidebar__item' |
| 191 | } |
| 192 | tab_mentioned_class := if current_tab == 'mentioned' { |
| 193 | 'user-issues-sidebar__item user-issues-sidebar__item--active' |
| 194 | } else { |
| 195 | 'user-issues-sidebar__item' |
| 196 | } |
| 197 | tab_activity_class := if current_tab == 'activity' { |
| 198 | 'user-issues-sidebar__item user-issues-sidebar__item--active' |
| 199 | } else { |
| 200 | 'user-issues-sidebar__item' |
| 201 | } |
| 202 | show_repo_link := true |
| 203 | return $veb.html() |
| 204 | } |
| 205 | |