Go · 2518 bytes Raw Blame History
1 // SPDX-License-Identifier: AGPL-3.0-or-later
2
3 package social
4
5 import (
6 "context"
7 "errors"
8 "fmt"
9
10 "github.com/jackc/pgx/v5"
11
12 "github.com/tenseleyFlow/shithub/internal/auth/audit"
13 socialdb "github.com/tenseleyFlow/shithub/internal/social/sqlc"
14 )
15
16 // WatchLevel mirrors the DB enum so handlers can pass typed values
17 // without importing socialdb.
18 type WatchLevel string
19
20 const (
21 WatchAll WatchLevel = "all"
22 WatchParticipating WatchLevel = "participating"
23 WatchIgnore WatchLevel = "ignore"
24 )
25
26 func (w WatchLevel) valid() bool {
27 switch w {
28 case WatchAll, WatchParticipating, WatchIgnore:
29 return true
30 }
31 return false
32 }
33
34 // SetWatch upserts an explicit watch level. The handler authorizes
35 // via policy.Can(ActionWatchSet, repo) before calling.
36 func SetWatch(ctx context.Context, deps Deps, actorUserID, repoID int64, level WatchLevel) error {
37 if actorUserID == 0 {
38 return ErrNotLoggedIn
39 }
40 if !level.valid() {
41 return ErrInvalidWatchLevel
42 }
43 if err := socialdb.New().UpsertWatch(ctx, deps.Pool, socialdb.UpsertWatchParams{
44 UserID: actorUserID,
45 RepoID: repoID,
46 Level: socialdb.WatchLevel(level),
47 }); err != nil {
48 return fmt.Errorf("upsert watch: %w", err)
49 }
50 if deps.Audit != nil {
51 _ = deps.Audit.Record(ctx, deps.Pool, actorUserID,
52 audit.ActionWatchSet, audit.TargetRepo, repoID,
53 map[string]any{"level": string(level)})
54 }
55 return nil
56 }
57
58 // UnsetWatch removes the explicit row, returning the user to the
59 // implicit `participating` default.
60 func UnsetWatch(ctx context.Context, deps Deps, actorUserID, repoID int64) error {
61 if actorUserID == 0 {
62 return ErrNotLoggedIn
63 }
64 if err := socialdb.New().DeleteWatch(ctx, deps.Pool, socialdb.DeleteWatchParams{
65 UserID: actorUserID, RepoID: repoID,
66 }); err != nil {
67 return fmt.Errorf("delete watch: %w", err)
68 }
69 if deps.Audit != nil {
70 _ = deps.Audit.Record(ctx, deps.Pool, actorUserID,
71 audit.ActionWatchUnset, audit.TargetRepo, repoID, nil)
72 }
73 return nil
74 }
75
76 // CurrentLevel returns the actor's current watch level for the repo,
77 // resolving the implicit `participating` default when no row exists.
78 func CurrentLevel(ctx context.Context, deps Deps, userID, repoID int64) (WatchLevel, error) {
79 if userID == 0 {
80 return WatchParticipating, nil
81 }
82 row, err := socialdb.New().GetWatch(ctx, deps.Pool, socialdb.GetWatchParams{
83 UserID: userID, RepoID: repoID,
84 })
85 if err != nil {
86 if errors.Is(err, pgx.ErrNoRows) {
87 return WatchParticipating, nil
88 }
89 return "", err
90 }
91 return WatchLevel(row.Level), nil
92 }
93