Go · 2990 bytes Raw Blame History
1 // SPDX-License-Identifier: AGPL-3.0-or-later
2
3 package repo
4
5 import (
6 "net/http"
7 "net/url"
8 "strings"
9
10 "github.com/tenseleyFlow/shithub/internal/social"
11 "github.com/tenseleyFlow/shithub/internal/web/middleware"
12 )
13
14 type repoActionView struct {
15 IsLoggedIn bool
16 LoginURL string
17 ReturnTo string
18 Starred bool
19 WatchLevel string
20 WatchOptions []repoWatchOptionView
21 }
22
23 type repoWatchOptionView struct {
24 Level string
25 Label string
26 Description string
27 Checked bool
28 }
29
30 func (h *Handlers) repoActions(r *http.Request, repoID int64) repoActionView {
31 viewer := middleware.CurrentUserFromContext(r.Context())
32 returnTo := r.URL.RequestURI()
33 if returnTo == "" {
34 returnTo = r.URL.Path
35 }
36 if returnTo == "" {
37 returnTo = "/"
38 }
39 out := repoActionView{
40 IsLoggedIn: !viewer.IsAnonymous(),
41 LoginURL: "/login?next=" + url.QueryEscape(returnTo),
42 ReturnTo: returnTo,
43 WatchLevel: string(social.WatchParticipating),
44 }
45 if viewer.IsAnonymous() {
46 out.WatchOptions = repoWatchOptions(social.WatchParticipating)
47 return out
48 }
49 deps := h.socialDeps()
50 starred, err := social.HasStar(r.Context(), deps, viewer.ID, repoID)
51 if err != nil {
52 h.d.Logger.WarnContext(r.Context(), "repo actions: star lookup", "error", err, "repo_id", repoID, "user_id", viewer.ID)
53 } else {
54 out.Starred = starred
55 }
56 level, err := social.CurrentLevel(r.Context(), deps, viewer.ID, repoID)
57 if err != nil {
58 h.d.Logger.WarnContext(r.Context(), "repo actions: watch lookup", "error", err, "repo_id", repoID, "user_id", viewer.ID)
59 level = social.WatchParticipating
60 }
61 out.WatchLevel = string(level)
62 out.WatchOptions = repoWatchOptions(level)
63 return out
64 }
65
66 func repoWatchOptions(current social.WatchLevel) []repoWatchOptionView {
67 return []repoWatchOptionView{
68 {
69 Level: string(social.WatchParticipating),
70 Label: "Participating and @mentions",
71 Description: "Only receive notifications from this repository when participating or mentioned.",
72 Checked: current == social.WatchParticipating,
73 },
74 {
75 Level: string(social.WatchAll),
76 Label: "All Activity",
77 Description: "Notified of all notifications on this repository.",
78 Checked: current == social.WatchAll,
79 },
80 {
81 Level: string(social.WatchIgnore),
82 Label: "Ignore",
83 Description: "Never notified.",
84 Checked: current == social.WatchIgnore,
85 },
86 }
87 }
88
89 func redirectAfterRepoAction(w http.ResponseWriter, r *http.Request, fallback string) {
90 dest := fallback
91 if err := r.ParseForm(); err == nil {
92 if returnTo := strings.TrimSpace(r.PostFormValue("return_to")); safeLocalPath(returnTo) {
93 dest = returnTo
94 }
95 }
96 http.Redirect(w, r, dest, http.StatusSeeOther)
97 }
98
99 func safeLocalPath(path string) bool {
100 if path == "" || !strings.HasPrefix(path, "/") || strings.HasPrefix(path, "//") {
101 return false
102 }
103 u, err := url.Parse(path)
104 if err != nil {
105 return false
106 }
107 return !u.IsAbs() && u.Host == "" && strings.HasPrefix(u.Path, "/")
108 }
109