diff --git a/.github/workflows/backend.yml b/.github/workflows/backend.yml
index 26801fd58..4310d47fe 100644
--- a/.github/workflows/backend.yml
+++ b/.github/workflows/backend.yml
@@ -46,21 +46,21 @@ jobs:
lint:
name: Lint
runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ pull-requests: read
+ checks: write
steps:
- - name: Checkout Repository
- uses: actions/checkout@v3
- - name: Set up Go
- uses: actions/setup-go@v3
+ - uses: actions/checkout@v3
+ - uses: actions/setup-go@v4
with:
go-version: "1.21"
- - name: Enforce linting
- run: |
- cd ./backend/ && lint_output=$(go vet ./...)
- if [[ -n "$lint_output" ]]; then
- echo "$lint_output"
- echo "::error::Linting issues found"
- exit 1
- fi
+ cache: false
+ - name: golangci-lint
+ uses: golangci/golangci-lint-action@v3
+ with:
+ version: latest
+ working-directory: ./backend/
test:
name: Test
runs-on: ubuntu-latest
diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml
index 8abe3bc6d..6d7da6357 100644
--- a/.github/workflows/cli.yml
+++ b/.github/workflows/cli.yml
@@ -46,18 +46,18 @@ jobs:
lint:
name: Lint
runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ pull-requests: read
+ checks: write
steps:
- - name: Checkout Repository
- uses: actions/checkout@v3
- - name: Set up Go
- uses: actions/setup-go@v3
+ - uses: actions/checkout@v3
+ - uses: actions/setup-go@v4
with:
go-version: "1.21"
- - name: Enforce linting
- run: |
- cd ./cli/ && lint_output=$(go vet ./...)
- if [[ -n "$lint_output" ]]; then
- echo "$lint_output"
- echo "::error::Linting issues found"
- exit 1
- fi
+ cache: false
+ - name: golangci-lint
+ uses: golangci/golangci-lint-action@v3
+ with:
+ version: latest
+ working-directory: ./cli/
diff --git a/README.md b/README.md
index af4932d48..fb5635004 100644
--- a/README.md
+++ b/README.md
@@ -3,7 +3,10 @@
-
+
+
+
@@ -16,6 +19,10 @@
+
+
+
diff --git a/backend/.golangci.yml b/backend/.golangci.yml
new file mode 100644
index 000000000..0eab32c33
--- /dev/null
+++ b/backend/.golangci.yml
@@ -0,0 +1,23 @@
+linters:
+ enable:
+ - cyclop
+ - exportloopref
+ - gocritic
+ - gosec
+ - ineffassign
+ - misspell
+ - prealloc
+ - unconvert
+ - unparam
+ - goimports
+ - whitespace
+
+linters-settings:
+ whitespace:
+ multi-func: true
+
+issues:
+ exclude-rules:
+ - path: tests/api/helpers/
+ linters:
+ - cyclop
diff --git a/backend/src/database/db.go b/backend/src/database/db.go
index 35ad1d868..b45406eff 100644
--- a/backend/src/database/db.go
+++ b/backend/src/database/db.go
@@ -141,7 +141,6 @@ func createSuperUser(settings config.Settings, db *gorm.DB) error {
}
return tx.Commit().Error
-
}
return nil
}
diff --git a/backend/src/database/super.go b/backend/src/database/super.go
index 6ad3f930f..22700693e 100644
--- a/backend/src/database/super.go
+++ b/backend/src/database/super.go
@@ -35,7 +35,7 @@ func SuperClub() models.Club {
Description: "SAC",
NumMembers: 0,
IsRecruiting: true,
- RecruitmentCycle: models.RecruitmentCycle(models.Always),
+ RecruitmentCycle: models.Always,
RecruitmentType: models.Application,
ApplicationLink: "https://generatenu.com/apply",
Logo: "https://aws.amazon.com/s3",
diff --git a/backend/src/main.go b/backend/src/main.go
index 9ace36db9..2c2f73672 100644
--- a/backend/src/main.go
+++ b/backend/src/main.go
@@ -35,5 +35,8 @@ func main() {
app := server.Init(db, config)
- app.Listen(fmt.Sprintf("%s:%d", config.Application.Host, config.Application.Port))
+ err = app.Listen(fmt.Sprintf("%s:%d", config.Application.Host, config.Application.Port))
+ if err != nil {
+ panic(err)
+ }
}
diff --git a/backend/src/middleware/auth.go b/backend/src/middleware/auth.go
index 07fd76a61..3bba7c497 100644
--- a/backend/src/middleware/auth.go
+++ b/backend/src/middleware/auth.go
@@ -23,7 +23,7 @@ func SuperSkipper(h fiber.Handler) fiber.Handler {
return skip.New(h, func(c *fiber.Ctx) bool {
claims, err := types.From(c)
if err != nil {
- err.FiberError(c)
+ _ = err.FiberError(c)
return false
}
if claims == nil {
diff --git a/backend/src/server/server.go b/backend/src/server/server.go
index f65b70c23..5d249a0a3 100644
--- a/backend/src/server/server.go
+++ b/backend/src/server/server.go
@@ -26,7 +26,11 @@ import (
func Init(db *gorm.DB, settings config.Settings) *fiber.App {
app := newFiberApp()
- validate := utilities.RegisterCustomValidators()
+ validate, err := utilities.RegisterCustomValidators()
+ if err != nil {
+ panic(err)
+ }
+
middlewareService := middleware.NewMiddlewareService(db, validate, settings.Auth)
apiv1 := app.Group("/api/v1")
diff --git a/backend/src/services/auth.go b/backend/src/services/auth.go
index 4c87dd7e4..4e0d4de72 100644
--- a/backend/src/services/auth.go
+++ b/backend/src/services/auth.go
@@ -75,7 +75,7 @@ func (a *AuthService) GetRole(id string) (*models.UserRole, *errors.Error) {
return nil, &errors.UserNotFound
}
- role := models.UserRole(user.Role)
+ role := user.Role
return &role, nil
}
diff --git a/backend/src/transactions/club.go b/backend/src/transactions/club.go
index a7fe4495c..016e2d0ef 100644
--- a/backend/src/transactions/club.go
+++ b/backend/src/transactions/club.go
@@ -17,7 +17,7 @@ func GetAdminIDs(db *gorm.DB, clubID uuid.UUID) ([]uuid.UUID, *errors.Error) {
return nil, &errors.FailedtoGetAdminIDs
}
- var adminUUIDs []uuid.UUID
+ adminUUIDs := make([]uuid.UUID, 0)
for _, adminID := range adminIDs {
adminUUIDs = append(adminUUIDs, adminID.ClubID)
}
diff --git a/backend/src/utilities/validator.go b/backend/src/utilities/validator.go
index 78c2e53ef..701f3c595 100644
--- a/backend/src/utilities/validator.go
+++ b/backend/src/utilities/validator.go
@@ -13,18 +13,32 @@ import (
"github.com/go-playground/validator/v10"
)
-func RegisterCustomValidators() *validator.Validate {
+func RegisterCustomValidators() (*validator.Validate, error) {
validate := validator.New(validator.WithRequiredStructEnabled())
- validate.RegisterValidation("neu_email", validateEmail)
- validate.RegisterValidation("password", validatePassword)
- validate.RegisterValidation("mongo_url", validateMongoURL)
- validate.RegisterValidation("s3_url", validateS3URL)
- validate.RegisterValidation("contact_pointer", func(fl validator.FieldLevel) bool {
+ if err := validate.RegisterValidation("neu_email", validateEmail); err != nil {
+ return nil, err
+ }
+
+ if err := validate.RegisterValidation("password", validatePassword); err != nil {
+ return nil, err
+ }
+
+ if err := validate.RegisterValidation("mongo_url", validateMongoURL); err != nil {
+ return nil, err
+ }
+
+ if err := validate.RegisterValidation("s3_url", validateS3URL); err != nil {
+ return nil, err
+ }
+
+ if err := validate.RegisterValidation("contact_pointer", func(fl validator.FieldLevel) bool {
return validateContactPointer(validate, fl)
- })
+ }); err != nil {
+ return nil, err
+ }
- return validate
+ return validate, nil
}
func validateEmail(fl validator.FieldLevel) bool {
diff --git a/backend/tests/api/club_test.go b/backend/tests/api/club_test.go
index bdce0892e..4a632dd42 100644
--- a/backend/tests/api/club_test.go
+++ b/backend/tests/api/club_test.go
@@ -206,8 +206,8 @@ func TestGetClubsWorks(t *testing.T) {
assert.Equal("SAC", dbClub.Description)
assert.Equal(1, dbClub.NumMembers)
assert.Equal(true, dbClub.IsRecruiting)
- assert.Equal(models.RecruitmentCycle(models.Always), dbClub.RecruitmentCycle)
- assert.Equal(models.RecruitmentType(models.Application), dbClub.RecruitmentType)
+ assert.Equal(models.Always, dbClub.RecruitmentCycle)
+ assert.Equal(models.Application, dbClub.RecruitmentType)
assert.Equal("https://generatenu.com/apply", dbClub.ApplicationLink)
assert.Equal("https://aws.amazon.com/s3", dbClub.Logo)
},
@@ -303,7 +303,7 @@ func TestCreateClubFailsOnInvalidLogo(t *testing.T) {
[]interface{}{
"Not an URL",
"@#139081#$Ad_Axf",
- //"https://google.com", <-- TODO uncomment once we figure out s3 url validation
+ // "https://google.com", <-- TODO uncomment once we figure out s3 url validation
},
)
}
@@ -343,6 +343,7 @@ func TestUpdateClubFailsOnInvalidBody(t *testing.T) {
{"application_link": "Not an URL"},
{"logo": "@12394X_2"},
} {
+ invalidData := invalidData
appAssert.TestOnErrorAndDB(
h.TestRequest{
Method: fiber.MethodPatch,
diff --git a/backend/tests/api/helpers/requests.go b/backend/tests/api/helpers/requests.go
index 85bf4fd44..3e76dc5bb 100644
--- a/backend/tests/api/helpers/requests.go
+++ b/backend/tests/api/helpers/requests.go
@@ -24,6 +24,7 @@ type TestRequest struct {
TestUserIDReplaces *string
}
+//gocyclo:ignore
func (app TestApp) Send(request TestRequest) (*http.Response, error) {
address := fmt.Sprintf("%s%s", app.Address, request.Path)
diff --git a/backend/tests/api/tag_test.go b/backend/tests/api/tag_test.go
index 531993fcd..f61cbe7d0 100644
--- a/backend/tests/api/tag_test.go
+++ b/backend/tests/api/tag_test.go
@@ -115,6 +115,7 @@ func TestCreateTagFailsBadRequest(t *testing.T) {
}
for _, badBody := range badBodys {
+ badBody := badBody
appAssert.TestOnErrorAndDB(
h.TestRequest{
Method: fiber.MethodPost,
@@ -146,6 +147,7 @@ func TestCreateTagFailsValidation(t *testing.T) {
}
for _, badBody := range badBodys {
+ badBody := badBody
appAssert.TestOnErrorAndDB(
h.TestRequest{
Method: fiber.MethodPost,
diff --git a/backend/tests/api/user_tag_test.go b/backend/tests/api/user_tag_test.go
index 013b5ddb9..9d8a2ecc4 100644
--- a/backend/tests/api/user_tag_test.go
+++ b/backend/tests/api/user_tag_test.go
@@ -66,6 +66,7 @@ func CreateSetOfTags(t *testing.T, appAssert *h.ExistingAppAssert) ([]uuid.UUID,
categoryIDs := []uuid.UUID{}
for _, category := range *categories {
+ category := category
appAssert.TestOnStatusAndDB(
h.TestRequest{
Method: fiber.MethodPost,
@@ -92,6 +93,7 @@ func CreateSetOfTags(t *testing.T, appAssert *h.ExistingAppAssert) ([]uuid.UUID,
tagIDs := []uuid.UUID{}
for _, tag := range *tags {
+ tag := tag
appAssert.TestOnStatusAndDB(
h.TestRequest{
Method: fiber.MethodPost,
@@ -212,6 +214,7 @@ func TestCreateUserTagsFailsOnInvalidKey(t *testing.T) {
}
for _, body := range invalidBody {
+ body := body
h.InitTest(t).TestOnError(
h.TestRequest{
Method: fiber.MethodPost,
diff --git a/backend/tests/api/user_test.go b/backend/tests/api/user_test.go
index 5ba723114..66bd629c4 100644
--- a/backend/tests/api/user_test.go
+++ b/backend/tests/api/user_test.go
@@ -216,6 +216,7 @@ func TestUpdateUserFailsOnInvalidBody(t *testing.T) {
{"year": 1963},
{"college": "UT-Austin"},
} {
+ invalidData := invalidData
h.InitTest(t).TestOnErrorAndDB(
h.TestRequest{
Method: fiber.MethodPatch,
diff --git a/cli/.golangci.yml b/cli/.golangci.yml
new file mode 100644
index 000000000..31fd57a91
--- /dev/null
+++ b/cli/.golangci.yml
@@ -0,0 +1,17 @@
+linters:
+ enable:
+ - cyclop
+ - exportloopref
+ - gocritic
+ - gosec
+ - ineffassign
+ - misspell
+ - prealloc
+ - unconvert
+ - unparam
+ - goimports
+ - whitespace
+
+linters-settings:
+ whitespace:
+ multi-func: true
diff --git a/cli/commands/drop.go b/cli/commands/drop.go
index 5f75e1726..3a34f1ae1 100644
--- a/cli/commands/drop.go
+++ b/cli/commands/drop.go
@@ -70,8 +70,7 @@ func DropData() error {
return fmt.Errorf("error scanning table name: %w", err)
}
- deleteStmt := fmt.Sprintf("DELETE FROM \"%s\"", tablename)
- _, err := db.Exec(deleteStmt)
+ _, err := db.Exec("DELETE FROM $1", tablename)
if err != nil {
return fmt.Errorf("error deleting rows from table %s: %w", tablename, err)
}
@@ -82,7 +81,9 @@ func DropData() error {
return fmt.Errorf("error in rows handling: %w", err)
}
- Migrate()
+ if Migrate() != nil {
+ return fmt.Errorf("error migrating database: %w", err)
+ }
fmt.Println("All rows removed successfully.")
return nil
diff --git a/cli/commands/format.go b/cli/commands/format.go
index 9b60c9fc2..cfaa9ddea 100644
--- a/cli/commands/format.go
+++ b/cli/commands/format.go
@@ -39,7 +39,10 @@ func FormatCommand() *cli.Command {
runFrontend := folder != ""
runBackend := c.Bool("backend")
- Format(folder, runFrontend, runBackend)
+ err := Format(folder, runFrontend, runBackend)
+ if err != nil {
+ return cli.Exit(err.Error(), 1)
+ }
return nil
},
@@ -56,7 +59,10 @@ func Format(folder string, runFrontend bool, runBackend bool) error {
wg.Add(1)
go func() {
defer wg.Done()
- BackendFormat()
+ err := BackendFormat()
+ if err != nil {
+ fmt.Println(err)
+ }
}()
}
@@ -65,7 +71,10 @@ func Format(folder string, runFrontend bool, runBackend bool) error {
wg.Add(1)
go func() {
defer wg.Done()
- FrontendFormat(folder)
+ err := FrontendFormat(folder)
+ if err != nil {
+ fmt.Println(err)
+ }
}()
}
diff --git a/cli/commands/insert.go b/cli/commands/insert.go
index 376468ad3..9b8a55754 100644
--- a/cli/commands/insert.go
+++ b/cli/commands/insert.go
@@ -1,15 +1,12 @@
package commands
import (
- "bytes"
"database/sql"
- "errors"
"fmt"
+ "os"
"os/exec"
- "strconv"
- "strings"
- _ "github.com/lib/pq"
+ "github.com/lib/pq"
"github.com/urfave/cli/v2"
)
@@ -65,18 +62,20 @@ func InsertDB() error {
fmt.Println("Database exists with tables.")
}
- insertCmd := exec.Command("psql", "-h", CONFIG.Database.Host, "-p", strconv.Itoa(int(CONFIG.Database.Port)), "-U", CONFIG.Database.Username, "-d", CONFIG.Database.DatabaseName, "-a", "-f", MIGRATION_FILE)
-
- var output bytes.Buffer
- insertCmd.Stdout = &output
- insertCmd.Stderr = &output
-
- if err := insertCmd.Run(); err != nil {
- return fmt.Errorf("error inserting data: %w", err)
+ migrationSQL, err := os.ReadFile(MIGRATION_FILE)
+ if err != nil {
+ return fmt.Errorf("error reading migration file: %w", err)
}
- if strings.Contains(output.String(), "ROLLBACK") {
- return errors.New("insertion failed, rolling back")
+ _, err = db.Exec(string(migrationSQL))
+ if err != nil {
+ if pqErr, ok := err.(*pq.Error); ok {
+ fmt.Println("PostgreSQL Error:")
+ fmt.Println("Code:", pqErr.Code)
+ fmt.Println("Message:", pqErr.Message)
+ } else {
+ return fmt.Errorf("error executing migration: %w", err)
+ }
}
fmt.Println("Data inserted successfully.")
diff --git a/cli/commands/lint.go b/cli/commands/lint.go
index 3351ab554..e6836ce55 100644
--- a/cli/commands/lint.go
+++ b/cli/commands/lint.go
@@ -39,7 +39,10 @@ func LintCommand() *cli.Command {
runFrontend := folder != ""
runBackend := c.Bool("backend")
- Lint(folder, runFrontend, runBackend)
+ err := Lint(folder, runFrontend, runBackend)
+ if err != nil {
+ return cli.Exit(err.Error(), 1)
+ }
return nil
},
@@ -50,26 +53,45 @@ func LintCommand() *cli.Command {
func Lint(folder string, runFrontend bool, runBackend bool) error {
var wg sync.WaitGroup
+ errChan := make(chan error, 1)
+ var errOccurred bool // Flag to indicate whether an error has occurred
// Start the backend if specified
- if runBackend {
+ if runBackend && !errOccurred {
wg.Add(1)
go func() {
defer wg.Done()
- BackendLint()
+ err := BackendLint()
+ if err != nil {
+ errChan <- err
+ errOccurred = true
+ }
}()
}
// Start the frontend if specified
- if runFrontend {
+ if runFrontend && !errOccurred {
wg.Add(1)
go func() {
defer wg.Done()
- FrontendLint(folder)
+ err := FrontendLint(folder)
+ if err != nil {
+ errChan <- err
+ errOccurred = true
+ }
}()
}
- wg.Wait()
+ go func() {
+ wg.Wait()
+ close(errChan)
+ }()
+
+ for err := range errChan {
+ if err != nil {
+ return err
+ }
+ }
return nil
}
@@ -77,7 +99,7 @@ func Lint(folder string, runFrontend bool, runBackend bool) error {
func BackendLint() error {
fmt.Println("Linting backend")
- cmd := exec.Command("go", "vet", "./...")
+ cmd := exec.Command("golangci-lint", "run")
cmd.Dir = BACKEND_DIR
err := cmd.Run()
diff --git a/cli/commands/migrate.go b/cli/commands/migrate.go
index a2189e805..7aa7d8aa4 100644
--- a/cli/commands/migrate.go
+++ b/cli/commands/migrate.go
@@ -17,7 +17,10 @@ func MigrateCommand() *cli.Command {
return cli.Exit("Invalid arguments", 1)
}
- Migrate()
+ err := Migrate()
+ if err != nil {
+ return cli.Exit(err.Error(), 1)
+ }
return nil
},
}
diff --git a/cli/commands/reset.go b/cli/commands/reset.go
index 07929c1fb..4e9cf4324 100644
--- a/cli/commands/reset.go
+++ b/cli/commands/reset.go
@@ -46,17 +46,23 @@ func ResetCommand() *cli.Command {
func ResetData() error {
fmt.Println("Clearing database")
- DropData()
+ err := DropData()
+ if err != nil {
+ return cli.Exit(err.Error(), 1)
+ }
cmd := exec.Command("sleep", "1")
cmd.Dir = BACKEND_DIR
- err := cmd.Run()
+ err = cmd.Run()
if err != nil {
return cli.Exit("Error running sleep", 1)
}
- Migrate()
+ err = Migrate()
+ if err != nil {
+ return cli.Exit(err.Error(), 1)
+ }
cmd = exec.Command("sleep", "1")
cmd.Dir = BACKEND_DIR
@@ -66,7 +72,10 @@ func ResetData() error {
return cli.Exit("Error running sleep", 1)
}
- InsertDB()
+ err = InsertDB()
+ if err != nil {
+ return cli.Exit(err.Error(), 1)
+ }
fmt.Println("Data reset successfully")
@@ -76,17 +85,23 @@ func ResetData() error {
func ResetMigration() error {
fmt.Println("Resetting migration")
- DropDB()
+ err := DropDB()
+ if err != nil {
+ return cli.Exit(err.Error(), 1)
+ }
cmd := exec.Command("sleep", "1")
cmd.Dir = BACKEND_DIR
- err := cmd.Run()
+ err = cmd.Run()
if err != nil {
return cli.Exit("Error running sleep", 1)
}
- Migrate()
+ err = Migrate()
+ if err != nil {
+ return cli.Exit(err.Error(), 1)
+ }
fmt.Println("Migration reset successfully")
diff --git a/cli/commands/swagger.go b/cli/commands/swagger.go
index bac4191d3..3881d93cf 100644
--- a/cli/commands/swagger.go
+++ b/cli/commands/swagger.go
@@ -17,7 +17,10 @@ func SwaggerCommand() *cli.Command {
return cli.Exit("Invalid arguments", 1)
}
- Swagger()
+ err := Swagger()
+ if err != nil {
+ return cli.Exit(err.Error(), 1)
+ }
return nil
},
}
diff --git a/cli/commands/test.go b/cli/commands/test.go
index 0fa9d6a27..be12543c4 100644
--- a/cli/commands/test.go
+++ b/cli/commands/test.go
@@ -38,7 +38,10 @@ func TestCommand() *cli.Command {
folder := c.String("frontend")
runFrontend := folder != ""
runBackend := c.Bool("backend")
- Test(folder, runFrontend, runBackend)
+ err := Test(folder, runFrontend, runBackend)
+ if err != nil {
+ return cli.Exit(err.Error(), 1)
+ }
return nil
},
}
@@ -47,26 +50,46 @@ func TestCommand() *cli.Command {
func Test(folder string, runFrontend bool, runBackend bool) error {
var wg sync.WaitGroup
+ errChan := make(chan error, 1)
+ var errOccurred bool // Flag to indicate whether an error has occurred
// Start the backend if specified
- if runBackend {
+ if runBackend && !errOccurred {
wg.Add(1)
go func() {
defer wg.Done()
- BackendTest()
+ err := BackendTest()
+ if err != nil {
+ errChan <- err
+ errOccurred = true
+ }
}()
}
// Start the frontend if specified
- if runFrontend {
+ if runFrontend && !errOccurred {
wg.Add(1)
go func() {
defer wg.Done()
- FrontendTest(folder)
+ err := FrontendTest(folder)
+ if err != nil {
+ errChan <- err
+ errOccurred = true
+ }
}()
}
- wg.Wait()
+ go func() {
+ wg.Wait()
+ close(errChan)
+ }()
+
+ for err := range errChan {
+ if err != nil {
+ return err
+ }
+ }
+
return nil
}
@@ -74,8 +97,6 @@ func BackendTest() error {
cmd := exec.Command("go", "test", "./...")
cmd.Dir = fmt.Sprintf("%s/..", BACKEND_DIR)
- defer CleanTestDBs()
-
out, err := cmd.CombinedOutput()
if err != nil {
fmt.Println(string(out))
@@ -83,6 +104,12 @@ func BackendTest() error {
}
fmt.Println(string(out))
+
+ err = CleanTestDBs()
+ if err != nil {
+ return cli.Exit(err.Error(), 1)
+ }
+
return nil
}
diff --git a/go.work.sum b/go.work.sum
index 9dfa3c6d2..a9dfabbc1 100644
--- a/go.work.sum
+++ b/go.work.sum
@@ -10,7 +10,12 @@ github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
+github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
+github.com/tinylib/msgp v1.1.8/go.mod h1:qkpG+2ldGg4xRFmx+jfTvZPxfGFhi64BcnL9vkCm/Tw=
+github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=