diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 00f2fc9..337911f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -4,29 +4,10 @@ run-name: Triggered by ${{ github.event_name }} to ${{ github.ref }} by @${{ git on: [push] jobs: - web_build_job: - runs-on: ubuntu-latest - name: web - defaults: - run: - shell: bash - steps: - - uses: actions/checkout@v4 - with: - submodules: true - - name: Setup Node - uses: actions/setup-node@v4 - with: - node-version: '22.x' - - name: Web - run: | - cd web - npm ci - npm run build - go_build_job: + core_build_job: runs-on: ubuntu-latest - name: sentinel + name: core defaults: run: shell: bash @@ -38,7 +19,7 @@ jobs: uses: actions/setup-go@v4 with: go-version: '1.22.0' - - name: Sentinel + - name: Build sentinel-core run: | go get . go build \ No newline at end of file diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml deleted file mode 100644 index b306bd9..0000000 --- a/.github/workflows/tests.yml +++ /dev/null @@ -1,31 +0,0 @@ -name: tests -run-name: Triggered by ${{ github.event_name }} to ${{ github.ref }} by @${{ github.actor }} - -on: [push] - -jobs: - web_lint_check: - runs-on: ubuntu-latest - name: web lint - defaults: - run: - shell: bash - working-directory: ./web - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - submodules: true - - name: Setup Node - uses: actions/setup-node@v4 - with: - node-version: '22.x' - - name: Install dependencies - run: | - npm ci - - name: Run lint - run: | - npm run lint - - name: Run Check - run: | - npm run check diff --git a/LICENSE.txt b/LICENSE.txt index 1e4979e..7c080c7 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2024 Gaucho Racing +Copyright (c) 2026 Gaucho Racing Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Makefile b/Makefile index 6c808da..78c7df7 100644 --- a/Makefile +++ b/Makefile @@ -6,9 +6,9 @@ clean: rm *.out rm coverage.html -run: - chmod +x scripts/run.sh - ./scripts/run.sh +run-core: + chmod +x scripts/run-core.sh + ./scripts/run-core.sh keygen: chmod +x scripts/keygen.sh diff --git a/commands/alumni.go b/commands/alumni.go deleted file mode 100644 index a68cbdf..0000000 --- a/commands/alumni.go +++ /dev/null @@ -1,48 +0,0 @@ -package commands - -import ( - "fmt" - "sentinel/config" - "sentinel/service" - "sentinel/utils" - "time" - - "github.com/bwmarrin/discordgo" -) - -func Alumni(args []string, s *discordgo.Session, m *discordgo.MessageCreate) { - defer s.ChannelMessageDelete(m.ChannelID, m.ID) - if m.GuildID != config.DiscordGuild { - m.GuildID = config.DiscordGuild - } - // Get user info - guildMember, err := s.GuildMember(m.GuildID, m.Author.ID) - if err != nil { - utils.SugarLogger.Errorln(err) - go service.SendDisappearingMessage(m.ChannelID, "Unexpected error occurred, please try again later!", 5*time.Second) - return - } - isOfficer := false - for _, role := range guildMember.Roles { - if role == "812948550819905546" { - isOfficer = true - break - } - } - utils.SugarLogger.Infof("User %s is officer: %t", m.Author.ID, isOfficer) - - user := service.GetUserByID(m.Author.ID) - if user.ID == "" { - // User not found - go service.SendDisappearingMessage(m.ChannelID, "You must verify your account first! (`!verify `)", 5*time.Second) - return - } else { - err = s.GuildMemberRoleAdd(m.GuildID, user.ID, config.AlumniRoleID) - if err != nil { - go service.SendDisappearingMessage(m.ChannelID, "Unexpected error occurred, please try again later!", 5*time.Second) - utils.SugarLogger.Errorln(err) - return - } - go service.SendDisappearingMessage(m.ChannelID, fmt.Sprintf("Nice to see you again %s!", user.FirstName), 5*time.Second) - } -} diff --git a/commands/drive.go b/commands/drive.go deleted file mode 100644 index 73d5440..0000000 --- a/commands/drive.go +++ /dev/null @@ -1,78 +0,0 @@ -package commands - -import ( - "fmt" - "sentinel/config" - "sentinel/service" - "sentinel/utils" - "time" - - "github.com/bwmarrin/discordgo" -) - -func Drive(args []string, s *discordgo.Session, m *discordgo.MessageCreate) { - defer s.ChannelMessageDelete(m.ChannelID, m.ID) - if m.GuildID != config.DiscordGuild { - m.GuildID = config.DiscordGuild - } - // Get user info - guildMember, err := s.GuildMember(m.GuildID, m.Author.ID) - if err != nil { - utils.SugarLogger.Errorln(err) - go service.SendDisappearingMessage(m.ChannelID, "Unexpected error occurred, please try again later!", 5*time.Second) - return - } - - user := service.GetUserByID(guildMember.User.ID) - if user.ID == "" || !(user.IsMember() || user.IsAlumni()) { - // User not found - go service.SendDisappearingMessage(m.ChannelID, "You must verify your account first! (`!verify `)", 5*time.Second) - } else { - loadingMessage, _ := s.ChannelMessageSend(m.ChannelID, "checking drive access...") - role := "writer" - if user.IsInnerCircle() { - role = "organizer" - } - perm, _ := service.GetDriveMemberPermission(config.SharedDriveID, user.Email) - if perm != nil { - // Remove and re-add user to update role - _ = service.RemoveMemberFromDrive(config.SharedDriveID, user.Email) - _ = service.AddMemberToDrive(config.SharedDriveID, user.Email, role) - perm, _ := service.GetDriveMemberPermission(config.SharedDriveID, user.Email) - service.Discord.ChannelMessageDelete(m.ChannelID, loadingMessage.ID) - go service.SendDisappearingMessage(m.ChannelID, fmt.Sprintf("Refreshed shared drive access to `%s`", perm.Role), 5*time.Second) - } else { - err = service.AddMemberToDrive(config.SharedDriveID, user.Email, role) - if err != nil { - utils.SugarLogger.Errorln(err) - service.Discord.ChannelMessageDelete(m.ChannelID, loadingMessage.ID) - go service.SendDisappearingMessage(m.ChannelID, "Unexpected error occurred, please try again later!", 5*time.Second) - } else { - service.Discord.ChannelMessageDelete(m.ChannelID, loadingMessage.ID) - go service.SendDisappearingMessage(m.ChannelID, fmt.Sprintf("You have been added to the shared drive with `%s` access!", role), 5*time.Second) - } - } - - if user.IsInnerCircle() { - perm, _ := service.GetDriveMemberPermission(config.LeadsDriveID, user.Email) - if perm != nil { - // Remove and re-add user to update role - _ = service.RemoveMemberFromDrive(config.LeadsDriveID, user.Email) - _ = service.AddMemberToDrive(config.LeadsDriveID, user.Email, role) - perm, _ := service.GetDriveMemberPermission(config.LeadsDriveID, user.Email) - service.Discord.ChannelMessageDelete(m.ChannelID, loadingMessage.ID) - go service.SendDisappearingMessage(m.ChannelID, fmt.Sprintf("Refreshed leads drive access to `%s`", perm.Role), 5*time.Second) - } else { - err = service.AddMemberToDrive(config.LeadsDriveID, user.Email, role) - if err != nil { - utils.SugarLogger.Errorln(err) - service.Discord.ChannelMessageDelete(m.ChannelID, loadingMessage.ID) - go service.SendDisappearingMessage(m.ChannelID, "Unexpected error occurred, please try again later!", 5*time.Second) - } else { - service.Discord.ChannelMessageDelete(m.ChannelID, loadingMessage.ID) - go service.SendDisappearingMessage(m.ChannelID, fmt.Sprintf("You have been added to the leads drive with `%s` access!", role), 5*time.Second) - } - } - } - } -} diff --git a/commands/github.go b/commands/github.go deleted file mode 100644 index b51731c..0000000 --- a/commands/github.go +++ /dev/null @@ -1,42 +0,0 @@ -package commands - -import ( - "sentinel/config" - "sentinel/service" - "sentinel/utils" - "time" - - "github.com/bwmarrin/discordgo" -) - -func Github(args []string, s *discordgo.Session, m *discordgo.MessageCreate) { - defer s.ChannelMessageDelete(m.ChannelID, m.ID) - if m.GuildID != config.DiscordGuild { - m.GuildID = config.DiscordGuild - } - // Get user info - guildMember, err := s.GuildMember(m.GuildID, m.Author.ID) - if err != nil { - utils.SugarLogger.Errorln(err) - go service.SendDisappearingMessage(m.ChannelID, "Unexpected error occurred, please try again later!", 5*time.Second) - return - } - - user := service.GetUserByID(guildMember.User.ID) - if user.ID == "" || !(user.IsMember() || user.IsAlumni()) { - // User not found - go service.SendDisappearingMessage(m.ChannelID, "You must verify your account first! (`!verify `)", 5*time.Second) - return - } - if len(args) < 1 { - go service.SendDisappearingMessage(m.ChannelID, "Command usage: `!github `", 5*time.Second) - return - } - err = service.AddUserToGithub(m.Author.ID, args[0]) - if err != nil { - utils.SugarLogger.Errorln(err) - go service.SendDisappearingMessage(m.ChannelID, "Unexpected error occurred, please try again later!", 5*time.Second) - return - } - go service.SendDisappearingMessage(m.ChannelID, "Successfully invited user to GitHub organization!", 5*time.Second) -} diff --git a/commands/handler.go b/commands/handler.go deleted file mode 100644 index 46ca0cb..0000000 --- a/commands/handler.go +++ /dev/null @@ -1,286 +0,0 @@ -package commands - -import ( - "fmt" - "regexp" - "sentinel/config" - "sentinel/model" - "sentinel/service" - "sentinel/utils" - "slices" - "strings" - "time" - - "github.com/bwmarrin/discordgo" - "github.com/google/uuid" -) - -var spoilerRegex = regexp.MustCompile(`(?s)\|\|.+?\|\|`) -var codeBlockRegex = regexp.MustCompile("```[\\s\\S]*?```") -var inlineCodeRegex = regexp.MustCompile("`[^`\n]*`") - -// hasSpoilersOutsideCodeBlocks checks if the content has spoilers that are not within code blocks or inline code -func hasSpoilersOutsideCodeBlocks(content string) bool { - // Get all code block and inline code ranges - codeRanges := getAllCodeRanges(content) - - // Find all potential spoiler matches - spoilerMatches := spoilerRegex.FindAllStringIndex(content, -1) - - // Check if any spoiler is completely outside of code ranges - for _, spoilerRange := range spoilerMatches { - if !isRangeInCode(spoilerRange, codeRanges) { - return true - } - } - - return false -} - -// getAllCodeRanges returns all ranges where code blocks and inline code exist -func getAllCodeRanges(content string) [][]int { - var ranges [][]int - - // Add code block ranges - codeBlockMatches := codeBlockRegex.FindAllStringIndex(content, -1) - ranges = append(ranges, codeBlockMatches...) - - // Add inline code ranges - inlineCodeMatches := inlineCodeRegex.FindAllStringIndex(content, -1) - ranges = append(ranges, inlineCodeMatches...) - - return ranges -} - -// isRangeInCode checks if a given range is completely contained within any code range -func isRangeInCode(spoilerRange []int, codeRanges [][]int) bool { - if len(spoilerRange) < 2 { - return false - } - spoilerStart, spoilerEnd := spoilerRange[0], spoilerRange[1] - - for _, codeRange := range codeRanges { - if len(codeRange) < 2 { - continue - } - codeStart, codeEnd := codeRange[0], codeRange[1] - - // Check if the entire spoiler is within this code block - if spoilerStart >= codeStart && spoilerEnd <= codeEnd { - return true - } - } - - return false -} - -func InitializeDiscordBot() { - service.Discord.AddHandler(OnDiscordMessage) - service.Discord.AddHandler(OnGuildMemberUpdate) - service.Discord.AddHandler(LogUserMessage) - service.Discord.AddHandler(LogUserReaction) - service.Discord.AddHandler(OnMessageUpdate) - service.Discord.Identify.Intents = discordgo.MakeIntent(discordgo.IntentsAll) - err := service.Discord.Open() - if err != nil { - utils.SugarLogger.Errorln("error opening connection,", err) - return - } - utils.SugarLogger.Infoln("Discord Bot is now running! [Prefix = " + config.Prefix + "]") -} - -func OnDiscordMessage(s *discordgo.Session, m *discordgo.MessageCreate) { - defer ChannelMessageFilter(s, m) - // Ignore all messages created by the bot itself - // or messages that don't start with the prefix - if m.Author.ID == s.State.User.ID || !strings.HasPrefix(m.Content, config.Prefix) { - return - } - command := strings.Split(m.Content, " ")[0][len(config.Prefix):] - args := strings.Split(m.Content, " ")[1:] - switch command { - case "ping": - Ping(args, s, m) - case "say": - Say(args, s, m) - case "verify": - Verify(args, s, m) - case "subteam": - Subteam(args, s, m) - case "rs": - RemoveSubteam(args, s, m) - case "github": - Github(args, s, m) - case "drive": - Drive(args, s, m) - case "whois": - Whois(args, s, m) - case "users": - Users(args, s, m) - case "alumni": - Alumni(args, s, m) - default: - utils.SugarLogger.Infof("Command not found: %s", command) - } -} - -func OnGuildMemberUpdate(s *discordgo.Session, m *discordgo.GuildMemberUpdate) { - if m.GuildID != config.DiscordGuild { - utils.SugarLogger.Infof("Recieved member update event for guild %s, ignoring...", m.GuildID) - service.SendMessage(config.DiscordLogChannel, fmt.Sprintf("Recieved member update event for guild %s, ignoring...", m.GuildID)) - return - } - if m.User.Bot { - utils.SugarLogger.Infof("Recieved member update event for bot %s (%s), ignoring...", m.User.ID, m.Nick) - service.SendMessage(config.DiscordLogChannel, fmt.Sprintf("Recieved member update event for bot %s (%s), ignoring...", m.User.ID, m.Nick)) - return - } - utils.SugarLogger.Infof("Member update: (%s) %s", m.User.ID, m.Nick) - service.SendMessage(config.DiscordLogChannel, fmt.Sprintf("Member update: (%s) %s", m.User.ID, m.Nick)) - newRoles := m.Roles - user := service.GetUserByID(m.User.ID) - if user.ID == "" { - // User is not in Sentinel, ensure they cannot have any roles - service.SetDiscordRolesForUser(m.User.ID, []string{}) - return - } - - // Verify discord specific role rules - // If user is alumni, they cannot have any subteam roles - if slices.Contains(newRoles, config.AlumniRoleID) { - // Remove all subteam roles - for _, role := range service.GetAllSubteams() { - err := service.Discord.GuildMemberRoleRemove(config.DiscordGuild, m.User.ID, role.ID) - if err != nil { - utils.SugarLogger.Errorf("Error removing subteam role %s from user %s (%s): %s", role.ID, m.User.ID, m.Nick, err) - service.SendMessage(config.DiscordLogChannel, fmt.Sprintf("Error removing subteam role %s from user %s (%s): %s", role.ID, m.User.ID, m.Nick, err)) - } - } - utils.SugarLogger.Infof("Removed all subteam roles from user %s (%s) as they are alumni", m.User.ID, m.Nick) - service.SendMessage(config.DiscordLogChannel, fmt.Sprintf("Removed all subteam roles from user %s (%s) as they are alumni", m.User.ID, m.Nick)) - - // User cannot have member, team member, lead, or officer roles if they are alumni (admin and special advisor ok) - removeRoles := []string{config.MemberRoleID, config.TeamMemberRoleID, config.LeadRoleID, config.OfficerRoleID} - for _, role := range removeRoles { - err := service.Discord.GuildMemberRoleRemove(config.DiscordGuild, m.User.ID, role) - if err != nil { - utils.SugarLogger.Errorf("Error removing role %s from user %s (%s): %s", role, m.User.ID, m.Nick, err) - service.SendMessage(config.DiscordLogChannel, fmt.Sprintf("Error removing role %s from user %s (%s): %s", role, m.User.ID, m.Nick, err)) - } - } - } - - // If user is not alumni or member, remove all roles - if !slices.Contains(newRoles, config.AlumniRoleID) && !slices.Contains(newRoles, config.MemberRoleID) { - service.SetDiscordRolesForUser(m.User.ID, []string{}) - } - - service.SyncDiscordRolesForUser(user.ID, newRoles) -} - -func OnMessageUpdate(s *discordgo.Session, m *discordgo.MessageUpdate) { - if m == nil { - return - } - - var ( - channelID = m.ChannelID - messageID = m.ID - content string - authorID string - authorIsBot bool - ) - - if m.Message != nil { - channelID = m.Message.ChannelID - messageID = m.Message.ID - content = m.Message.Content - if m.Message.Author != nil { - authorIsBot = m.Message.Author.Bot - authorID = m.Message.Author.ID - } - } - - if content == "" { - // Fallback: fetch the full message content if not provided - msg, err := s.ChannelMessage(channelID, messageID) - if err == nil && msg != nil { - content = msg.Content - if msg.Author != nil { - authorIsBot = msg.Author.Bot - authorID = msg.Author.ID - } - } - } - - // Ignore bot edits and empty content - if authorIsBot || content == "" { - return - } - - if hasSpoilersOutsideCodeBlocks(content) { - if authorID == "" { - authorID = "unknown" - } - utils.SugarLogger.Infof("Deleting spoiler message (edit) from %s in %s", authorID, channelID) - _ = s.ChannelMessageDelete(channelID, messageID) - service.SendDisappearingMessage(channelID, "Spoilers are not allowed on this server.", 10*time.Second) - } -} - -func LogUserMessage(s *discordgo.Session, m *discordgo.MessageCreate) { - utils.SugarLogger.Infof("Message from %s in %s: %s", m.Author.ID, m.ChannelID, m.Content) - // Get user info - user := service.GetUserByID(m.Author.ID) - if user.ID == "" { - return - } - // Log message - service.CreateActivity(model.UserActivity{ - ID: uuid.New().String(), - UserID: user.ID, - Action: "message", - }) -} - -func LogUserReaction(s *discordgo.Session, m *discordgo.MessageReactionAdd) { - utils.SugarLogger.Infof("Reaction from %s in %s: %s", m.UserID, m.ChannelID, m.Emoji.Name) - // Get user info - user := service.GetUserByID(m.UserID) - if user.ID == "" { - return - } - // Log reaction - service.CreateActivity(model.UserActivity{ - ID: uuid.New().String(), - UserID: user.ID, - Action: "reaction", - }) -} - -func ChannelMessageFilter(s *discordgo.Session, m *discordgo.MessageCreate) { - if m.Author != nil && m.Author.Bot { - return - } - - var verificationChannel = "1215484329736671282" - var rolesChannel = "1215525696286232626" - - channels := []string{verificationChannel, rolesChannel} - - for _, channel := range channels { - if m.ChannelID == channel { - utils.SugarLogger.Infof("Deleting message from %s in %s: %s", m.Author.ID, m.ChannelID, m.Content) - s.ChannelMessageDelete(m.ChannelID, m.ID) - return - } - } - - // Spoiler filter: delete messages containing Discord spoiler syntax and warn - content := m.Content - if hasSpoilersOutsideCodeBlocks(content) { - utils.SugarLogger.Infof("Deleting spoiler message from %s in %s", m.Author.ID, m.ChannelID) - _ = s.ChannelMessageDelete(m.ChannelID, m.ID) - service.SendDisappearingMessage(m.ChannelID, "Spoilers are not allowed on this server.", 10*time.Second) - } -} diff --git a/commands/ping.go b/commands/ping.go deleted file mode 100644 index 98a86a2..0000000 --- a/commands/ping.go +++ /dev/null @@ -1,16 +0,0 @@ -package commands - -import ( - "sentinel/config" - "strconv" - - "github.com/bwmarrin/discordgo" -) - -func Ping(args []string, s *discordgo.Session, m *discordgo.MessageCreate) { - message, err := s.ChannelMessageSend(m.ChannelID, "Pong from Sentinel v"+config.Version+"!") - if err == nil { - delay := message.Timestamp.Sub(m.Timestamp).Milliseconds() - s.ChannelMessageEdit(m.ChannelID, message.ID, "Pong from Sentinel v"+config.Version+"! (**"+strconv.FormatInt(delay, 10)+"ms**)") - } -} diff --git a/commands/rs.go b/commands/rs.go deleted file mode 100644 index 37bbcfa..0000000 --- a/commands/rs.go +++ /dev/null @@ -1,58 +0,0 @@ -package commands - -import ( - "sentinel/config" - "sentinel/service" - "sentinel/utils" - "strconv" - "strings" - "time" - - "github.com/bwmarrin/discordgo" -) - -func RemoveSubteam(args []string, s *discordgo.Session, m *discordgo.MessageCreate) { - defer s.ChannelMessageDelete(m.ChannelID, m.ID) - if m.GuildID != config.DiscordGuild { - m.GuildID = config.DiscordGuild - } - // Get user info - guildMember, err := s.GuildMember(m.GuildID, m.Author.ID) - if err != nil { - utils.SugarLogger.Errorln(err) - go service.SendDisappearingMessage(m.ChannelID, "Unexpected error occurred, please try again later!", 5*time.Second) - return - } - - user := service.GetUserByID(guildMember.User.ID) - if user.ID == "" || !(user.IsMember() || user.IsAlumni()) { - // User not found - go service.SendDisappearingMessage(m.ChannelID, "You must verify your account first! (`!verify `)", 5*time.Second) - return - } else { - counter := 0 - for _, arg := range args { - ar := strings.ToLower(arg) - a := []rune(ar) - a[0] = []rune(strings.ToUpper(ar))[0] - arg = string(a) - role := service.GetSubteamByName(arg) - if role.ID != "" { - err = s.GuildMemberRoleRemove(m.GuildID, user.ID, role.ID) - if err != nil { - go service.SendDisappearingMessage(m.ChannelID, "Unexpected error occurred, please try again later!", 5*time.Second) - utils.SugarLogger.Errorln(err) - } else { - counter++ - } - } else { - go service.SendDisappearingMessage(m.ChannelID, "Subteam `"+arg+"` not found!", 5*time.Second) - } - } - if counter == 0 { - go service.SendDisappearingMessage(m.ChannelID, "Command usage: `!rs `", 5*time.Second) - } else { - go service.SendDisappearingMessage(m.ChannelID, "Removed "+strconv.Itoa(counter)+" subteam roles!", 5*time.Second) - } - } -} diff --git a/commands/say.go b/commands/say.go deleted file mode 100644 index c0eab4e..0000000 --- a/commands/say.go +++ /dev/null @@ -1,46 +0,0 @@ -package commands - -import ( - "sentinel/config" - "sentinel/service" - "sentinel/utils" - "strings" - "time" - - "github.com/bwmarrin/discordgo" -) - -func Say(args []string, s *discordgo.Session, m *discordgo.MessageCreate) { - defer s.ChannelMessageDelete(m.ChannelID, m.ID) - if m.GuildID != config.DiscordGuild { - m.GuildID = config.DiscordGuild - } - // Get user info - guildMember, err := s.GuildMember(m.GuildID, m.Author.ID) - if err != nil { - utils.SugarLogger.Errorln(err) - return - } - if !utils.IsInnerCircle(guildMember.Roles) { - go service.SendDisappearingMessage(m.ChannelID, "You do not have access to this command!", 5*time.Second) - return - } - if len(args) < 1 { - if len(m.Attachments) > 0 { - SendAttachments(m.Attachments, s, m) - return - } - go service.SendDisappearingMessage(m.ChannelID, "Must be in the format: `!say `", 5*time.Second) - return - } - message, _ := strings.CutPrefix(m.Content, config.Prefix+"say ") - s.ChannelMessageSend(m.ChannelID, message) - SendAttachments(m.Attachments, s, m) -} - -func SendAttachments(attachments []*discordgo.MessageAttachment, s *discordgo.Session, m *discordgo.MessageCreate) { - for _, attachment := range attachments { - println(attachment.URL) - s.ChannelMessageSend(m.ChannelID, attachment.URL) - } -} diff --git a/commands/subteam.go b/commands/subteam.go deleted file mode 100644 index 5cc332e..0000000 --- a/commands/subteam.go +++ /dev/null @@ -1,58 +0,0 @@ -package commands - -import ( - "sentinel/config" - "sentinel/service" - "sentinel/utils" - "strconv" - "strings" - "time" - - "github.com/bwmarrin/discordgo" -) - -func Subteam(args []string, s *discordgo.Session, m *discordgo.MessageCreate) { - defer s.ChannelMessageDelete(m.ChannelID, m.ID) - if m.GuildID != config.DiscordGuild { - m.GuildID = config.DiscordGuild - } - // Get user info - guildMember, err := s.GuildMember(m.GuildID, m.Author.ID) - if err != nil { - utils.SugarLogger.Errorln(err) - go service.SendDisappearingMessage(m.ChannelID, "Unexpected error occurred, please try again later!", 5*time.Second) - return - } - - user := service.GetUserByID(guildMember.User.ID) - if user.ID == "" || !(user.IsMember() || user.IsAlumni()) { - // User not found - go service.SendDisappearingMessage(m.ChannelID, "You must verify your account first! (`!verify `)", 5*time.Second) - return - } else { - counter := 0 - for _, arg := range args { - ar := strings.ToLower(arg) - a := []rune(ar) - a[0] = []rune(strings.ToUpper(ar))[0] - arg = string(a) - role := service.GetSubteamByName(arg) - if role.ID != "" { - err = s.GuildMemberRoleAdd(m.GuildID, user.ID, role.ID) - if err != nil { - go service.SendDisappearingMessage(m.ChannelID, "Unexpected error occurred, please try again later!", 5*time.Second) - utils.SugarLogger.Errorln(err) - } else { - counter++ - } - } else { - go service.SendDisappearingMessage(m.ChannelID, "Subteam `"+arg+"` not found!", 5*time.Second) - } - } - if counter == 0 { - go service.SendDisappearingMessage(m.ChannelID, "Command usage: `!subteam `", 5*time.Second) - } else { - go service.SendDisappearingMessage(m.ChannelID, "Added "+strconv.Itoa(counter)+" subteam roles!", 5*time.Second) - } - } -} diff --git a/commands/users.go b/commands/users.go deleted file mode 100644 index 45a3c5b..0000000 --- a/commands/users.go +++ /dev/null @@ -1,93 +0,0 @@ -package commands - -import ( - "fmt" - "sentinel/config" - "sentinel/service" - "sentinel/utils" - "time" - - "github.com/bwmarrin/discordgo" -) - -func Users(args []string, s *discordgo.Session, m *discordgo.MessageCreate) { - if m.GuildID != config.DiscordGuild { - m.GuildID = config.DiscordGuild - } - // Get user info - guildMember, err := s.GuildMember(m.GuildID, m.Author.ID) - if err != nil { - utils.SugarLogger.Errorln(err) - go service.SendDisappearingMessage(m.ChannelID, "Unexpected error occurred, please try again later!", 5*time.Second) - return - } - utils.SugarLogger.Infof("User: %s", guildMember.User.ID) - user := service.GetUserByID(guildMember.User.ID) - if user.ID == "" { - // User not found - go service.SendDisappearingMessage(m.ChannelID, "You must verify your account first! (`!verify `)", 5*time.Second) - return - } - msg, _ := service.Discord.ChannelMessageSend(m.ChannelID, "Fetching user data...") - members, err := s.GuildMembers(m.GuildID, "", 1000) - if err != nil { - utils.SugarLogger.Errorln(err.Error()) - } - guildMembers := 0 - memberMembers := 0 - leadMembers := 0 - officerMembers := 0 - specialAdvisorMembers := 0 - alumniMembers := 0 - teamMembers := 0 - - subteamMap := make(map[string]int) - subteams := service.GetAllSubteams() - for _, subteam := range subteams { - subteamMap[subteam.Name] = 0 - } - - for _, member := range members { - for _, role := range member.Roles { - if role == config.MemberRoleID { - memberMembers++ - } - if role == config.LeadRoleID { - leadMembers++ - } - if role == config.OfficerRoleID { - officerMembers++ - } - if role == config.SpecialAdvisorRoleID { - specialAdvisorMembers++ - } - if role == config.AlumniRoleID { - alumniMembers++ - } - if role == config.TeamMemberRoleID { - teamMembers++ - } - for _, subteam := range subteams { - if role == subteam.ID { - subteamMap[subteam.Name]++ - } - } - } - guildMembers++ - } - messageText := fmt.Sprintf("Discord Members: %d\nMembers Role: %d\nAlumni Members: %d\nTeam Members: %d\n\n", guildMembers, memberMembers, alumniMembers, teamMembers) - utils.SugarLogger.Infof("Discord Members: %d", guildMembers) - utils.SugarLogger.Infof("Members Role: %d", memberMembers) - utils.SugarLogger.Infof("Alumni Members: %d", alumniMembers) - utils.SugarLogger.Infof("Team Members: %d", teamMembers) - for subteam, count := range subteamMap { - utils.SugarLogger.Infof("%s: %d", subteam, count) - messageText += fmt.Sprintf("%s: %d\n", subteam, count) - } - utils.SugarLogger.Infof("Lead Members: %d", leadMembers) - utils.SugarLogger.Infof("Officer Members: %d", officerMembers) - utils.SugarLogger.Infof("Special Advisor Members: %d", specialAdvisorMembers) - messageText += fmt.Sprintf("\nLeads: %d\nOfficers: %d\nSpecial Advisors: %d", leadMembers, officerMembers, specialAdvisorMembers) - - go service.Discord.ChannelMessageEdit(m.ChannelID, msg.ID, messageText) -} diff --git a/commands/verify.go b/commands/verify.go deleted file mode 100644 index db7575e..0000000 --- a/commands/verify.go +++ /dev/null @@ -1,123 +0,0 @@ -package commands - -import ( - "sentinel/config" - "sentinel/model" - "sentinel/service" - "sentinel/utils" - "strings" - "time" - - "github.com/bwmarrin/discordgo" -) - -func Verify(args []string, s *discordgo.Session, m *discordgo.MessageCreate) { - defer s.ChannelMessageDelete(m.ChannelID, m.ID) - if m.GuildID != config.DiscordGuild { - m.GuildID = config.DiscordGuild - } - // Get user info - guildMember, err := s.GuildMember(m.GuildID, m.Author.ID) - if err != nil { - utils.SugarLogger.Errorln(err) - go service.SendDisappearingMessage(m.ChannelID, "Unexpected error occurred, please try again later!", 5*time.Second) - return - } - - if len(args) < 3 { - go service.SendDisappearingMessage(m.ChannelID, "Command usage: `!verify `", 5*time.Second) - return - } - emailIndex := -1 - // find email, extract first name and last name from that - for i, arg := range args { - if strings.Contains(arg, "@ucsb.edu") || utils.IsInnerCircle(guildMember.Roles) && strings.Contains(arg, "@") { - emailIndex = i - } - } - if emailIndex == -1 { - go service.SendDisappearingMessage(m.ChannelID, "Email must be a valid UCSB email", 5*time.Second) - return - } - - id := m.Author.ID - firstName := args[0] - lastName := strings.Join(args[1:emailIndex], " ") - email := args[emailIndex] - - msg, _ := service.Discord.ChannelMessageSend(m.ChannelID, "we are checking...") - defer service.Discord.ChannelMessageDelete(m.ChannelID, msg.ID) - - // check if id flag is present - if len(args) > emailIndex+1 { - // last arg is id - id = args[emailIndex+1] - if !utils.IsInnerCircle(guildMember.Roles) { - go service.SendDisappearingMessage(m.ChannelID, "You do not have permission to verify someone else!", 5*time.Second) - return - } - } - - // check if user is already verified - if service.GetUserByID(id).ID != "" && service.GetUserByID(id).IsMember() { - go service.SendDisappearingMessage(m.ChannelID, "You are already verified!", 5*time.Second) - return - } else if service.GetUserByID(id).ID != "" && service.GetUserByID(id).IsAlumni() { - // special case where user was an alumni, and left the server, but is trying to re-verify - go service.SendDisappearingMessage(m.ChannelID, "Welcome back, we're happy to see you again!", 5*time.Second) - // assign alumni role - err = s.GuildMemberRoleAdd(m.GuildID, id, config.AlumniRoleID) - if err != nil { - utils.SugarLogger.Errorln(err) - } - return - } else if service.GetUserByEmail(email).ID != "" && service.GetUserByEmail(email).IsMember() { - go service.SendDisappearingMessage(m.ChannelID, "This email is already registered!", 5*time.Second) - return - } - - // verify name and email - if strings.Contains(firstName, "<") || strings.Contains(lastName, "<") || strings.Contains(email, "<") { - go service.SendDisappearingMessage(m.ChannelID, "Don't include the < > in your name and email!", 5*time.Second) - return - } - - member, err := s.GuildMember(m.GuildID, id) - if err != nil { - utils.SugarLogger.Errorln(err) - go service.SendDisappearingMessage(m.ChannelID, "Unexpected error occurred, please try again later!", 5*time.Second) - return - } - - // finally create user - service.CreateUser(model.User{ - ID: id, - Username: member.User.Username, - FirstName: firstName, - LastName: lastName, - Email: email, - AvatarURL: member.User.AvatarURL("256"), - Verified: false, - }, false) - - // rename user - err = s.GuildMemberNickname(m.GuildID, id, firstName) - if err != nil { - utils.SugarLogger.Errorln(err) - } - - // sync roles - service.SyncDiscordRolesForUser(id, member.Roles) - - // google drive access - _ = service.RemoveMemberFromDrive(config.SharedDriveID, email) - - // assign member role (if alumni, discord handler will remove) - err = s.GuildMemberRoleAdd(m.GuildID, id, config.MemberRoleID) - if err != nil { - utils.SugarLogger.Errorln(err) - } - - go service.SendDisappearingMessage(m.ChannelID, "You have been verified! Welcome to the server <@"+id+">!", 5*time.Second) - go service.SendUserWelcomeMessage(id) -} diff --git a/commands/whois.go b/commands/whois.go deleted file mode 100644 index 8ef531c..0000000 --- a/commands/whois.go +++ /dev/null @@ -1,62 +0,0 @@ -package commands - -import ( - "sentinel/config" - "sentinel/service" - "sentinel/utils" - "time" - - "github.com/bwmarrin/discordgo" -) - -func Whois(args []string, s *discordgo.Session, m *discordgo.MessageCreate) { - if m.GuildID != config.DiscordGuild { - m.GuildID = config.DiscordGuild - } - // Get user info - guildMember, err := s.GuildMember(m.GuildID, m.Author.ID) - if err != nil { - utils.SugarLogger.Errorln(err) - go service.SendDisappearingMessage(m.ChannelID, "Unexpected error occurred, please try again later!", 5*time.Second) - return - } - - user := service.GetUserByID(m.Author.ID) - if user.ID == "" || !(user.IsMember() || user.IsAlumni()) { - // User not found - go service.SendDisappearingMessage(m.ChannelID, "You must verify your account first! (`!verify `)", 5*time.Second) - return - } else { - if len(args) < 1 { - go service.SendDisappearingMessage(m.ChannelID, "Command usage: `!whois `", 5*time.Second) - return - } - if !utils.IsInnerCircle(guildMember.Roles) { - go service.SendDisappearingMessage(m.ChannelID, "You do not have access to this command!", 5*time.Second) - return - } - user := service.GetUserByID(args[0]) - if user.ID == "" { - user = service.GetUserByUsername(args[0]) - if user.ID == "" { - user = service.GetUserByEmail(args[0]) - if user.ID == "" { - utils.SugarLogger.Infof("User not found: %s, attempting to search...", args[0]) - searchedUsers := service.SearchUsers(args[0]) - if len(searchedUsers) == 0 { - go service.SendMessage(m.ChannelID, "User not found!") - return - } else { - for _, u := range searchedUsers { - utils.SugarLogger.Infof("User found: %s", u.Username) - service.DiscordUserEmbed(u, m.ChannelID) - } - return - } - } - } - } - utils.SugarLogger.Infof("User found: %s", user.ID) - service.DiscordUserEmbed(user, m.ChannelID) - } -} diff --git a/config/banner.go b/config/banner.go deleted file mode 100644 index 5758f05..0000000 --- a/config/banner.go +++ /dev/null @@ -1,22 +0,0 @@ -package config - -import ( - "github.com/fatih/color" -) - -var Banner = ` -███████╗███████╗███╗ ██╗████████╗██╗███╗ ██╗███████╗██╗ -██╔════╝██╔════╝████╗ ██║╚══██╔══╝██║████╗ ██║██╔════╝██║ -███████╗█████╗ ██╔██╗ ██║ ██║ ██║██╔██╗ ██║█████╗ ██║ -╚════██║██╔══╝ ██║╚██╗██║ ██║ ██║██║╚██╗██║██╔══╝ ██║ -███████║███████╗██║ ╚████║ ██║ ██║██║ ╚████║███████╗███████╗ -╚══════╝╚══════╝╚═╝ ╚═══╝ ╚═╝ ╚═╝╚═╝ ╚═══╝╚══════╝╚══════╝ -` - -func PrintStartupBanner() { - banner := color.New(color.Bold, color.FgHiMagenta).PrintlnFunc() - banner(Banner) - version := color.New(color.Bold, color.FgMagenta).PrintlnFunc() - version("Running Sentinel v" + Version + " [ENV: " + Env + "]") - println() -} diff --git a/config/config.go b/config/config.go deleted file mode 100644 index 9347ea0..0000000 --- a/config/config.go +++ /dev/null @@ -1,57 +0,0 @@ -package config - -import ( - "crypto/rsa" - "os" -) - -var Version = "4.6.1" -var Env = os.Getenv("ENV") -var Port = os.Getenv("PORT") -var Prefix = os.Getenv("PREFIX") - -var DatabaseHost = os.Getenv("DATABASE_HOST") -var DatabasePort = os.Getenv("DATABASE_PORT") -var DatabaseUser = os.Getenv("DATABASE_USER") -var DatabasePassword = os.Getenv("DATABASE_PASSWORD") -var DatabaseName = os.Getenv("DATABASE_NAME") - -var DiscordToken = os.Getenv("DISCORD_TOKEN") -var DiscordGuild = os.Getenv("DISCORD_GUILD") -var DiscordLogChannel = os.Getenv("DISCORD_LOG_CHANNEL") - -var DiscordClientID = os.Getenv("DISCORD_CLIENT_ID") -var DiscordClientSecret = os.Getenv("DISCORD_CLIENT_SECRET") -var DiscordRedirectURI = os.Getenv("DISCORD_REDIRECT_URI") - -var DriveServiceAccount = os.Getenv("DRIVE_SERVICE_ACCOUNT") -var GithubToken = os.Getenv("GITHUB_PAT") -var WikiToken = os.Getenv("WIKI_TOKEN") - -var SharedDriveID = "0ADMP93ZBlor_Uk9PVA" -var LeadsDriveID = "0AF4DbFL3cclkUk9PVA" - -var AdminRoleID = "1030681203864522823" -var OfficerRoleID = "812948550819905546" -var LeadRoleID = "970423652791246888" -var SpecialAdvisorRoleID = "1386909324596609034" -var TeamMemberRoleID = "1456575818460430522" -var MemberRoleID = "820467859477889034" -var AlumniRoleID = "817577502968512552" -var BotRoleID = "1229611357259694132" - -var SubteamRoleNames = []string{"Aero", "Business", "Chassis", "Data", "Drivetrain", "Electronics", "Firmware", "Suspension", "Systems"} -var RsaPublicKey *rsa.PublicKey -var RsaPrivateKey *rsa.PrivateKey -var RsaPublicKeyJWKS map[string]interface{} - -var RsaPublicKeyString = os.Getenv("RSA_PUBLIC_KEY") -var RsaPrivateKeyString = os.Getenv("RSA_PRIVATE_KEY") - -var MemberDirectorySheetID = "1reuLZox2daj8r2H-lZrwB4oFPYlJ6oC7983UUaZd6AY" -var MailingListSheetID = "1O5KQzpOo9Ja4Vg55TGCyc3uUDZFvHjyhZqw4Eh1SKVY" -var TeamMemberMasterListSheetID = "1tKawKKq1jk-WN8WM8gGkwOeEc0IA6-pkKxHL1DcWzd0" - -var DriveCron = os.Getenv("DRIVE_CRON") -var GithubCron = os.Getenv("GITHUB_CRON") -var DiscordCron = os.Getenv("DISCORD_CRON") diff --git a/controller/activity_controller.go b/controller/activity_controller.go deleted file mode 100644 index 14d4687..0000000 --- a/controller/activity_controller.go +++ /dev/null @@ -1,37 +0,0 @@ -package controller - -import ( - "net/http" - "sentinel/service" - - "github.com/gin-gonic/gin" -) - -// Get all activities for a user (Discord messages/reactions) -func GetActivitiesForUser(c *gin.Context) { - Require(c, Any( - RequestTokenHasScope(c, "sentinel:all"), - All( - RequestTokenHasScope(c, "user:read"), - Any(RequestUserHasID(c, c.Param("userID")), RequestUserHasRole(c, "d_admin")), - ), - )) - - activities := service.GetActivitiesForUser(c.Param("userID")) - c.JSON(http.StatusOK, activities) -} - -// Get activity counts for a user grouped by day and action -func GetActivityStatsForUser(c *gin.Context) { - Require(c, Any( - RequestTokenHasScope(c, "sentinel:all"), - All( - RequestTokenHasScope(c, "user:read"), - Any(RequestUserHasID(c, c.Param("userID")), RequestUserHasRole(c, "d_admin")), - ), - )) - - stats := service.GetActivityCountsByDayForUser(c.Param("userID")) - c.JSON(http.StatusOK, stats) -} - diff --git a/controller/auth_controller.go b/controller/auth_controller.go deleted file mode 100644 index c83b239..0000000 --- a/controller/auth_controller.go +++ /dev/null @@ -1,179 +0,0 @@ -package controller - -import ( - "net/http" - "sentinel/config" - "sentinel/model" - "sentinel/service" - "sentinel/utils" - - "github.com/gin-gonic/gin" -) - -func GetJWKS(c *gin.Context) { - c.JSON(http.StatusOK, config.RsaPublicKeyJWKS) -} - -func RegisterAccountPassword(c *gin.Context) { - Require(c, RequestTokenHasScope(c, "sentinel:all")) - - var input model.UserAuth - if err := c.ShouldBindJSON(&input); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"message": err.Error()}) - return - } - user := service.GetUserByEmail(input.Email) - if user.ID == "" { - c.JSON(http.StatusInternalServerError, gin.H{"message": "No account with this email exists. Make sure to verify your account on the discord server first!"}) - return - } - - Require(c, Any(RequestUserHasID(c, user.ID), RequestUserHasRole(c, "d_admin"))) - - token, err := service.RegisterEmailPassword(input.Email, input.Password) - if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()}) - return - } - refreshToken, err := service.GenerateRefreshToken(user.ID, "sentinel:all", "sentinel", 7*24*60*60) - if err != nil { - utils.SugarLogger.Errorln("error generating refresh token: " + err.Error()) - refreshToken = "" - } - response := model.TokenResponse{ - AccessToken: token, - RefreshToken: refreshToken, - TokenType: "Bearer", - ExpiresIn: 60 * 60, - Scope: "sentinel:all", - } - c.JSON(http.StatusOK, response) -} - -func ResetAccountPassword(c *gin.Context) { - Require(c, RequestTokenHasScope(c, "sentinel:all")) - - userID := c.Param("userID") - user := service.GetUserByID(userID) - if user.ID == "" { - c.JSON(http.StatusNotFound, gin.H{"message": "No user found with id: " + userID}) - return - } - - Require(c, Any(RequestUserHasID(c, user.ID), RequestUserHasRole(c, "d_admin"))) - - auth := service.GetUserAuthByID(userID) - if auth.ID == "" { - c.JSON(http.StatusNotFound, gin.H{"message": "No authentication found for user with id: " + userID}) - return - } - - err := service.RemovePasswordForEmail(auth.Email) - if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()}) - return - } - c.JSON(http.StatusOK, gin.H{"message": "Password reset successfully"}) -} - -func LoginAccount(c *gin.Context) { - var input model.UserAuth - if err := c.ShouldBindJSON(&input); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"message": err.Error()}) - return - } - user := service.GetUserByEmail(input.Email) - if user.ID == "" { - c.JSON(http.StatusInternalServerError, gin.H{"message": "No account with this email exists. Make sure to verify your account on the discord server first!"}) - return - } - token, err := service.LoginEmailPassword(input.Email, input.Password) - if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()}) - return - } - go service.CreateLogin(model.UserLogin{ - UserID: user.ID, - Destination: "sentinel", - Scope: "sentinel:all", - IPAddress: c.ClientIP(), - LoginType: "email", - }) - refreshToken, err := service.GenerateRefreshToken(user.ID, "sentinel:all", "sentinel", 7*24*60*60) - if err != nil { - utils.SugarLogger.Errorln("error generating refresh token: " + err.Error()) - refreshToken = "" - } - response := model.TokenResponse{ - AccessToken: token, - RefreshToken: refreshToken, - TokenType: "Bearer", - ExpiresIn: 60 * 60, - Scope: "sentinel:all", - } - c.JSON(http.StatusOK, response) -} - -func LoginDiscord(c *gin.Context) { - code := c.Query("code") - if code == "" { - c.JSON(http.StatusBadRequest, gin.H{"message": "No code provided"}) - return - } - id, err := service.GetUserIDFromDiscordCode(code) - if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()}) - return - } - user := service.GetUserByID(id) - if user.ID == "" { - c.JSON(http.StatusInternalServerError, gin.H{"message": "No account with this email exists. Make sure to verify your account on the discord server first!"}) - return - } - token, err := service.GenerateAccessToken(user.ID, "sentinel:all", "sentinel", 24*60*60) - if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()}) - return - } - go service.CreateLogin(model.UserLogin{ - UserID: user.ID, - Destination: "sentinel", - Scope: "sentinel:all", - IPAddress: c.ClientIP(), - LoginType: "discord", - }) - refreshToken, err := service.GenerateRefreshToken(user.ID, "sentinel:all", "sentinel", 7*24*60*60) - if err != nil { - utils.SugarLogger.Errorln("error generating refresh token: " + err.Error()) - refreshToken = "" - } - response := model.TokenResponse{ - AccessToken: token, - RefreshToken: refreshToken, - TokenType: "Bearer", - ExpiresIn: 60 * 60, - Scope: "sentinel:all", - } - c.JSON(http.StatusOK, response) -} - -func GetAuthForUser(c *gin.Context) { - Require(c, All( - RequestTokenHasScope(c, "sentinel:all"), - Any(RequestUserHasID(c, c.Param("userID")), RequestUserHasRole(c, "d_admin")), - )) - - userID := c.Param("userID") - user := service.GetUserByID(userID) - if user.ID == "" { - c.JSON(http.StatusNotFound, gin.H{"message": "No user found with id: " + userID}) - return - } - auth := service.GetUserAuthByID(userID) - if auth.ID == "" { - c.JSON(http.StatusNotFound, gin.H{"message": "No authentication found for user with id: " + userID}) - return - } - auth.Password = "************" - c.JSON(http.StatusOK, auth) -} diff --git a/controller/drive_controller.go b/controller/drive_controller.go deleted file mode 100644 index 3d61b83..0000000 --- a/controller/drive_controller.go +++ /dev/null @@ -1,86 +0,0 @@ -package controller - -import ( - "net/http" - "sentinel/config" - "sentinel/service" - "sentinel/utils" - - "github.com/gin-gonic/gin" -) - -func GetDriveStatusForUser(c *gin.Context) { - Require(c, Any( - RequestTokenHasScope(c, "sentinel:all"), - All( - RequestTokenHasScope(c, "drive:read"), - Any(RequestUserHasID(c, c.Param("userID")), RequestUserHasRole(c, "d_admin")), - ), - )) - - userID := c.Param("userID") - user := service.GetUserByID(userID) - if user.ID == "" { - c.JSON(http.StatusNotFound, gin.H{"message": "No user found with id: " + userID}) - return - } - perm, err := service.GetDriveMemberPermission(config.SharedDriveID, user.Email) - if err != nil { - utils.SugarLogger.Errorln(err.Error()) - } - if perm != nil { - c.JSON(http.StatusOK, perm) - return - } - c.JSON(http.StatusNotFound, gin.H{"message": "No permissions found for user with id: " + userID}) -} - -func AddUserToDrive(c *gin.Context) { - Require(c, Any( - RequestTokenHasScope(c, "sentinel:all"), - All( - RequestTokenHasScope(c, "drive:write"), - Any(RequestUserHasID(c, c.Param("userID")), RequestUserHasRole(c, "d_admin")), - ), - )) - - userID := c.Param("userID") - user := service.GetUserByID(userID) - if user.ID == "" { - c.JSON(http.StatusNotFound, gin.H{"message": "No user found with id: " + userID}) - return - } - role := "writer" - if user.IsInnerCircle() { - role = "organizer" - } - err := service.AddMemberToDrive(config.SharedDriveID, user.Email, role) - if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()}) - return - } - c.JSON(http.StatusOK, gin.H{"message": "User added to drive"}) -} - -func RemoveUserFromDrive(c *gin.Context) { - Require(c, Any( - RequestTokenHasScope(c, "sentinel:all"), - All( - RequestTokenHasScope(c, "drive:write"), - Any(RequestUserHasID(c, c.Param("userID")), RequestUserHasRole(c, "d_admin")), - ), - )) - - userID := c.Param("userID") - user := service.GetUserByID(userID) - if user.ID == "" { - c.JSON(http.StatusNotFound, gin.H{"message": "No user found with id: " + userID}) - return - } - err := service.RemoveMemberFromDrive(config.SharedDriveID, user.Email) - if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()}) - return - } - c.JSON(http.StatusOK, gin.H{"message": "User removed from drive"}) -} diff --git a/controller/github_controller.go b/controller/github_controller.go deleted file mode 100644 index 8822c41..0000000 --- a/controller/github_controller.go +++ /dev/null @@ -1,64 +0,0 @@ -package controller - -import ( - "net/http" - "sentinel/model" - "sentinel/service" - - "github.com/gin-gonic/gin" -) - -func GetGithubStatusForUser(c *gin.Context) { - Require(c, Any( - RequestTokenHasScope(c, "sentinel:all"), - All( - RequestTokenHasScope(c, "github:read"), - Any(RequestUserHasID(c, c.Param("userID")), RequestUserHasRole(c, "d_admin")), - ), - )) - - userID := c.Param("userID") - user := service.GetUserByID(userID) - if user.ID == "" { - c.JSON(http.StatusNotFound, gin.H{"message": "No user found with id: " + userID}) - return - } - github, err := service.GetGithubStatusForUser(user.ID) - if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()}) - return - } - c.JSON(http.StatusOK, github) -} - -func AddUserToGithub(c *gin.Context) { - Require(c, Any( - RequestTokenHasScope(c, "sentinel:all"), - All( - RequestTokenHasScope(c, "github:write"), - Any(RequestUserHasID(c, c.Param("userID")), RequestUserHasRole(c, "d_admin")), - ), - )) - - var input model.GithubInvite - if err := c.ShouldBindJSON(&input); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"message": err.Error()}) - return - } - if input.Username == "" { - c.JSON(http.StatusBadRequest, gin.H{"message": "Username is required"}) - return - } - userID := c.Param("userID") - user := service.GetUserByID(userID) - if user.ID == "" { - c.JSON(http.StatusNotFound, gin.H{"message": "No user found with id: " + userID}) - return - } - err := service.AddUserToGithub(userID, input.Username) - if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()}) - return - } - c.JSON(http.StatusOK, gin.H{"message": "User added to github"}) -} diff --git a/controller/login_controller.go b/controller/login_controller.go deleted file mode 100644 index 7018d23..0000000 --- a/controller/login_controller.go +++ /dev/null @@ -1,77 +0,0 @@ -package controller - -import ( - "net/http" - "sentinel/service" - "strconv" - - "github.com/gin-gonic/gin" -) - -func GetAllLogins(c *gin.Context) { - Require(c, RequestTokenHasScope(c, "sentinel:all")) - - logins := service.GetAllLogins() - c.JSON(http.StatusOK, logins) -} - -func GetLoginsForUser(c *gin.Context) { - Require(c, Any( - RequestTokenHasScope(c, "sentinel:all"), - All( - RequestTokenHasScope(c, "logins:read"), - Any(RequestUserHasID(c, c.Param("userID")), RequestUserHasRole(c, "d_admin")), - ), - )) - - userID := c.Param("userID") - if c.Query("count") != "" { - n, err := strconv.Atoi(c.Query("count")) - if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"message": "count must be a number"}) - return - } - logins := service.GetLastNLoginsForUser(userID, n) - c.JSON(http.StatusOK, logins) - return - } - logins := service.GetLoginsForUser(userID) - c.JSON(http.StatusOK, logins) -} - -func GetLoginsForDestination(c *gin.Context) { - Require(c, RequestTokenHasScope(c, "sentinel:all")) - - destination := c.Param("appID") - if c.Query("count") != "" { - n, err := strconv.Atoi(c.Query("count")) - if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"message": "count must be a number"}) - return - } - logins := service.GetLastNLoginsForDestination(destination, n) - c.JSON(http.StatusOK, logins) - return - } - logins := service.GetLoginsForDestination(destination) - c.JSON(http.StatusOK, logins) -} - -func GetLoginByID(c *gin.Context) { - loginID := c.Param("loginID") - login := service.GetLoginByID(loginID) - if login.ID == "" { - c.JSON(http.StatusNotFound, gin.H{"message": "no login found with id: " + loginID}) - return - } - - Require(c, Any( - RequestTokenHasScope(c, "sentinel:all"), - All( - RequestTokenHasScope(c, "logins:read"), - Any(RequestUserHasID(c, login.UserID), RequestUserHasRole(c, "d_admin")), - ), - )) - - c.JSON(http.StatusOK, login) -} diff --git a/controller/mailing_list_controller.go b/controller/mailing_list_controller.go deleted file mode 100644 index 03dc77f..0000000 --- a/controller/mailing_list_controller.go +++ /dev/null @@ -1,40 +0,0 @@ -package controller - -import ( - "net/http" - "sentinel/model" - "sentinel/service" - - "github.com/gin-gonic/gin" -) - -func CreateMailingListEntry(c *gin.Context) { - var entry model.MailingList - - if err := c.ShouldBindJSON(&entry); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid input."}) - return - } - - err := service.CreateMailingListEntry(entry) - if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) - return - } - - c.JSON(http.StatusOK, entry) - -} - -func GetAllMailingListEntries(c *gin.Context) { - Require(c, Any( - RequestTokenHasScope(c, "sentinel:all"), - All( - RequestTokenHasScope(c, "user:read"), - RequestUserHasRole(c, "d_admin"), - ), - )) - entries := service.GetAllMailingListEntries() - c.JSON(http.StatusOK, entries) - -} diff --git a/controller/oauth_controller.go b/controller/oauth_controller.go deleted file mode 100644 index ff4c37e..0000000 --- a/controller/oauth_controller.go +++ /dev/null @@ -1,366 +0,0 @@ -package controller - -import ( - "net/http" - "sentinel/config" - "sentinel/model" - "sentinel/service" - "sentinel/utils" - "strings" - "time" - - "github.com/gin-gonic/gin" - "github.com/golang-jwt/jwt" -) - -func GetValidOauthScopes(c *gin.Context) { - c.JSON(http.StatusOK, model.ValidOauthScopes) -} - -func GetOpenIDConfig(c *gin.Context) { - c.JSON(http.StatusOK, model.OpenIDConfig) -} - -func GetAllClientApplications(c *gin.Context) { - Require(c, Any( - RequestTokenHasScope(c, "sentinel:all"), - All( - RequestTokenHasScope(c, "applications:read"), - RequestUserHasRole(c, "d_admin"), - ), - )) - - apps := service.GetAllClientApplications() - c.JSON(http.StatusOK, apps) -} - -func GetClientApplicationsForUser(c *gin.Context) { - Require(c, Any( - RequestTokenHasScope(c, "sentinel:all"), - All( - RequestTokenHasScope(c, "applications:read"), - Any(RequestUserHasID(c, c.Param("userID")), RequestUserHasRole(c, "d_admin")), - ), - )) - - userID := c.Param("userID") - apps := service.GetClientApplicationsForUser(userID) - c.JSON(http.StatusOK, apps) -} - -func GetClientApplicationByID(c *gin.Context) { - appID := c.Param("appID") - app := service.GetClientApplicationByID(appID) - if app.ID == "" { - c.JSON(http.StatusNotFound, gin.H{"message": "no client application found with id: " + appID}) - return - } - - Require(c, Any( - RequestTokenHasScope(c, "sentinel:all"), - All( - RequestTokenHasScope(c, "applications:read"), - Any(RequestUserHasID(c, app.UserID), RequestUserHasRole(c, "d_admin")), - ), - )) - - c.JSON(http.StatusOK, app) -} - -func CreateClientApplication(c *gin.Context) { - Require(c, RequestTokenHasScope(c, "sentinel:all")) - - var app model.ClientApplication - if err := c.ShouldBindJSON(&app); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"message": err.Error()}) - return - } - - if app.ID != "" { - existing := service.GetClientApplicationByID(app.ID) - Require(c, Any( - RequestUserHasID(c, existing.UserID), - RequestUserHasRole(c, "d_admin"), - )) - } else { - app.UserID = GetRequestUserID(c) - } - - created, err := service.CreateClientApplication(app) - if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()}) - return - } - c.JSON(http.StatusOK, created) -} - -func DeleteClientApplication(c *gin.Context) { - appID := c.Param("appID") - app := service.GetClientApplicationByID(appID) - if app.ID == "" { - c.JSON(http.StatusNotFound, gin.H{"message": "no client application found with id: " + appID}) - return - } - - Require(c, All( - RequestTokenHasScope(c, "sentinel:all"), - Any(RequestUserHasID(c, app.UserID), RequestUserHasRole(c, "d_admin")), - )) - - err := service.DeleteClientApplication(appID) - if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()}) - return - } - c.JSON(http.StatusOK, gin.H{"message": "client application deleted"}) -} - -func OauthAuthorize(c *gin.Context) { - Require(c, RequestTokenHasScope(c, "sentinel:all")) - - clientID := c.Query("client_id") - if clientID == "" { - c.JSON(http.StatusBadRequest, gin.H{"message": "client_id is required"}) - return - } - client := service.GetClientApplicationByID(clientID) - if client.ID == "" { - c.JSON(http.StatusBadRequest, gin.H{"message": "no client application found with id: " + clientID}) - return - } - redirectUri := c.Query("redirect_uri") - if !service.ValidateRedirectURI(redirectUri, clientID) { - c.JSON(http.StatusBadRequest, gin.H{"message": "redirect_uri is invalid"}) - return - } - scope := c.Query("scope") - if scope == "" { - c.JSON(http.StatusBadRequest, gin.H{"message": "scope is required"}) - return - } else if !service.ValidateScope(scope) || strings.Contains(scope, "sentinel:all") { - c.JSON(http.StatusBadRequest, gin.H{"message": "scope is invalid"}) - return - } - // there seems to be a variety of prompts that people send as part of the oauth flow - // the oidc spec says to prompt when prompt=login and bypass when prompt=none - // discord prompts when prompt=consent and bypass when prompt=none - // portainer prompts when prompt=login and just doesn't send the prompt at all when bypassing - // - // We will first check if there is no prompt provided, defaulting to requiring consent (prompt=consent) - // If the prompt is set to none, we check if the user has previously authorized this client - // if any other prompt is provided, we will default to requiring consent (prompt=consent) - prompt := c.Query("prompt") - if prompt == "" { - prompt = "consent" - } - if prompt == "none" { - // check if user previously authorized this client - lastLogin := service.GetLastLoginForUserToDestinationWithScopes(GetRequestUserID(c), clientID, scope) - if lastLogin.ID != "" && time.Since(lastLogin.CreatedAt).Hours() < 24*7 { - utils.SugarLogger.Infof("User %s previously authorized client %s with scope %s", GetRequestUserID(c), clientID, scope) - prompt = "none" - } else { - prompt = "consent" - } - } else { - prompt = "consent" - } - reponseType := c.Query("response_type") - if reponseType == "" { - reponseType = "code" - } - - // Handle Validate Request - if c.Request.Method == "GET" { - c.JSON(http.StatusOK, gin.H{ - "client_id": clientID, - "redirect_uri": redirectUri, - "scope": scope, - "prompt": prompt, - }) - return - } - - // Handle Authorize Request - defer service.CreateLogin(model.UserLogin{ - UserID: GetRequestUserID(c), - Destination: clientID, - Scope: scope, - IPAddress: c.ClientIP(), - LoginType: "oauth", - }) - if reponseType == "code" { - code, err := service.GenerateAuthorizationCode(clientID, GetRequestUserID(c), scope) - if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()}) - return - } - c.JSON(http.StatusOK, code) - return - } -} - -func OauthExchange(c *gin.Context) { - // Check if refresh or authorization code - grantType := c.PostForm("grant_type") - if grantType == "refresh_token" { - handleRefreshTokenExchange(c) - return - } - // Check for Basic Auth - clientID, clientSecret, hasAuth := c.Request.BasicAuth() - if hasAuth { - // Validate client credentials - client := service.GetClientApplicationByID(clientID) - if client.ID == "" || client.Secret != clientSecret { - utils.SugarLogger.Errorf("invalid client credentials: %s %s", clientID, clientSecret) - c.JSON(http.StatusUnauthorized, gin.H{"message": "Invalid client credentials"}) - return - } - } else { - // Check for client_id and client_secret in form - clientID = c.PostForm("client_id") - clientSecret = c.PostForm("client_secret") - if clientID == "" || clientSecret == "" { - utils.SugarLogger.Errorf("client_id and client_secret are required: %s %s", clientID, clientSecret) - c.JSON(http.StatusBadRequest, gin.H{"message": "client_id and client_secret are required"}) - return - } - // Validate client credentials - client := service.GetClientApplicationByID(clientID) - if client.ID == "" || client.Secret != clientSecret { - utils.SugarLogger.Errorf("invalid client credentials: %s %s", clientID, clientSecret) - c.JSON(http.StatusUnauthorized, gin.H{"message": "Invalid client credentials"}) - return - } - } - - redirectUri := c.PostForm("redirect_uri") - if !service.ValidateRedirectURI(redirectUri, clientID) { - utils.SugarLogger.Errorf("redirect_uri is invalid: %s", redirectUri) - c.JSON(http.StatusBadRequest, gin.H{"message": "redirect_uri is invalid"}) - return - } - if grantType == "" { - utils.SugarLogger.Errorf("grant_type is required") - c.JSON(http.StatusBadRequest, gin.H{"message": "grant_type is required"}) - return - } - if grantType == "authorization_code" { - handleAuthorizationCodeExchange(c) - return - } else { - utils.SugarLogger.Errorf("unsupported grant_type: %s", grantType) - c.JSON(http.StatusBadRequest, gin.H{"message": "unsupported grant_type"}) - } -} - -func handleAuthorizationCodeExchange(c *gin.Context) { - code := c.PostForm("code") - if code == "" { - utils.SugarLogger.Errorf("code is required") - c.JSON(http.StatusBadRequest, gin.H{"message": "code is required"}) - return - } - authCode, err := service.VerifyAuthorizationCode(code) - if err != nil { - utils.SugarLogger.Errorf("error verifying authorization code: %s", err.Error()) - c.JSON(http.StatusBadRequest, gin.H{"message": err.Error()}) - return - } - token, err := service.GenerateAccessToken(authCode.UserID, authCode.Scope, authCode.ClientID, 60*60) - if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()}) - return - } - idToken := "" - if strings.Contains(authCode.Scope, "openid") { - idToken, err = service.GenerateIDToken(authCode.UserID, authCode.Scope, authCode.ClientID, 60*60) - if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()}) - return - } - } - refreshToken, err := service.GenerateRefreshToken(authCode.UserID, authCode.Scope, authCode.ClientID, 7*24*60*60) - if err != nil { - utils.SugarLogger.Errorln("error generating refresh token: " + err.Error()) - refreshToken = "" - } - response := model.TokenResponse{ - IDToken: idToken, - AccessToken: token, - RefreshToken: refreshToken, - TokenType: "Bearer", - ExpiresIn: 60 * 60, - Scope: authCode.Scope, - } - utils.SugarLogger.Infof("token response: %v", response) - c.JSON(http.StatusOK, response) -} - -func handleRefreshTokenExchange(c *gin.Context) { - refreshToken := c.PostForm("refresh_token") - if refreshToken == "" { - utils.SugarLogger.Errorf("refresh_token is required") - c.JSON(http.StatusBadRequest, gin.H{"message": "refresh_token is required"}) - return - } - if !service.ValidateRefreshToken(refreshToken) { - utils.SugarLogger.Errorf("invalid refresh_token: %s", refreshToken) - c.JSON(http.StatusUnauthorized, gin.H{"message": "invalid or expired refresh_token"}) - return - } - claims := &model.AuthClaims{} - _, err := jwt.ParseWithClaims(refreshToken, claims, func(token *jwt.Token) (interface{}, error) { - return config.RsaPublicKey, nil - }) - if err != nil { - utils.SugarLogger.Errorln(err.Error()) - c.JSON(http.StatusUnauthorized, gin.H{"message": "invalid refresh token"}) - return - } - if !strings.Contains(claims.Scope, "refresh_token") { - utils.SugarLogger.Errorf("refresh token scope is required") - c.JSON(http.StatusUnauthorized, gin.H{"message": "provided token is not a refresh token"}) - return - } - go service.RevokeRefreshToken(refreshToken) - // Remove refresh_token from scope - scopeList := strings.Split(claims.Scope, " ") - filteredScopes := make([]string, 0) - for _, s := range scopeList { - if s != "refresh_token" { - filteredScopes = append(filteredScopes, s) - } - } - claims.Scope = strings.Join(filteredScopes, " ") - - token, err := service.GenerateAccessToken(claims.Subject, claims.Scope, claims.Audience[0], 60*60) - if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()}) - return - } - idToken := "" - if strings.Contains(claims.Scope, "openid") { - idToken, err = service.GenerateIDToken(claims.Subject, claims.Scope, claims.Audience[0], 60*60) - if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()}) - return - } - } - refreshToken, err = service.GenerateRefreshToken(claims.Subject, claims.Scope, claims.Audience[0], 7*24*60*60) - if err != nil { - utils.SugarLogger.Errorln("error generating refresh token: " + err.Error()) - refreshToken = "" - } - response := model.TokenResponse{ - IDToken: idToken, - AccessToken: token, - RefreshToken: refreshToken, - TokenType: "Bearer", - ExpiresIn: 60 * 60, - Scope: claims.Scope, - } - utils.SugarLogger.Infof("token response: %v", response) - c.JSON(http.StatusOK, response) -} diff --git a/controller/ping_controller.go b/controller/ping_controller.go deleted file mode 100644 index 56d3b95..0000000 --- a/controller/ping_controller.go +++ /dev/null @@ -1,12 +0,0 @@ -package controller - -import ( - "net/http" - "sentinel/config" - - "github.com/gin-gonic/gin" -) - -func Ping(c *gin.Context) { - c.JSON(http.StatusOK, gin.H{"message": "Sentinel v" + config.Version + " is online!"}) -} diff --git a/controller/proxy_controller.go b/controller/proxy_controller.go deleted file mode 100644 index ab812c4..0000000 --- a/controller/proxy_controller.go +++ /dev/null @@ -1,81 +0,0 @@ -package controller - -import ( - "fmt" - "net/http" - "sentinel/service" - "sentinel/utils" - "strconv" - "strings" - "time" - - "github.com/gin-gonic/gin" -) - -func OauthProxyValidate(c *gin.Context) { - clientID := c.Query("client_id") - app := service.GetClientApplicationByID(clientID) - if app.ID == "" { - utils.SugarLogger.Errorf("Invalid client_id: %s", clientID) - c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"message": "you are not authorized to access this resource"}) - } - originalHost := c.GetHeader("X-Original-Host") - if originalHost == "" { - utils.SugarLogger.Infoln("Missing X-Original-Host header") - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"message": "you are not authorized to access this resource"}) - return - } - hasRedirect := false - for _, redirect := range app.RedirectURIs { - if strings.Contains(redirect, originalHost) { - hasRedirect = true - break - } - } - if !hasRedirect { - utils.SugarLogger.Errorf("Invalid X-Original-Host header: %s", originalHost) - c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"message": "you are not authorized to access this resource"}) - return - } - - // Check last auth time for client application - lastAuthTime, err := c.Cookie(fmt.Sprintf("sentinel_%s", app.ID)) - if err != nil || lastAuthTime == "" { - utils.SugarLogger.Infoln("Missing last auth time cookie") - c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"message": "you are not authorized to access this resource"}) - return - } - lastAuthTimeInt, err := strconv.Atoi(lastAuthTime) - if err != nil { - utils.SugarLogger.Errorf("Invalid last auth time cookie: %s", lastAuthTime) - c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"message": "you are not authorized to access this resource"}) - return - } - timeSinceLastAuth := (time.Now().UnixMilli() - int64(lastAuthTimeInt)) / 1000 - utils.SugarLogger.Infof("Last authorized %d seconds ago", timeSinceLastAuth) - if timeSinceLastAuth > 60*60 { - utils.SugarLogger.Errorf("Last authorized too long ago") - c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"message": "you are not authorized to access this resource"}) - return - } - - // Check sentinel access token - accessToken, err := c.Cookie("sentinel_access_token") - if err != nil || accessToken == "" { - utils.SugarLogger.Infoln("Missing sentinel_access_token cookie") - c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"message": "you are not authorized to access this resource"}) - return - } - claims, err := service.ValidateJWT(accessToken) - if err != nil { - utils.SugarLogger.Errorln("Failed to validate token: " + err.Error()) - c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"message": "you are not authorized to access this resource"}) - } else { - utils.SugarLogger.Infof("Decoded token: %s (%s)", claims.ID, claims.Email) - utils.SugarLogger.Infof("↳ Client ID: %s", claims.Audience[0]) - utils.SugarLogger.Infof("↳ Scope: %s", claims.Scope) - utils.SugarLogger.Infof("↳ Issued at: %s", claims.IssuedAt.String()) - utils.SugarLogger.Infof("↳ Expires at: %s", claims.ExpiresAt.String()) - } - c.JSON(http.StatusOK, gin.H{"message": "Authenticated"}) -} diff --git a/controller/role_controller.go b/controller/role_controller.go deleted file mode 100644 index 99c8bde..0000000 --- a/controller/role_controller.go +++ /dev/null @@ -1,35 +0,0 @@ -package controller - -import ( - "sentinel/service" - - "github.com/gin-gonic/gin" -) - -func GetAllRolesForUser(c *gin.Context) { - Require(c, Any( - RequestTokenHasScope(c, "sentinel:all"), - All( - RequestTokenHasScope(c, "user:read"), - Any(RequestUserHasID(c, c.Param("userID")), RequestUserHasRole(c, "d_admin")), - ), - )) - - roles := service.GetRolesForUser(c.Param("userID")) - c.JSON(200, roles) -} - -func SetRolesForUser(c *gin.Context) { - Require(c, All( - RequestTokenHasScope(c, "sentinel:all"), - RequestUserHasRole(c, "d_admin"), - )) - - var roles []string - if err := c.ShouldBindJSON(&roles); err != nil { - c.JSON(400, gin.H{"message": err.Error()}) - return - } - newRoles := service.SetRolesForUser(c.Param("userID"), roles) - c.JSON(200, newRoles) -} diff --git a/controller/route_controller.go b/controller/route_controller.go deleted file mode 100644 index ca74fba..0000000 --- a/controller/route_controller.go +++ /dev/null @@ -1,251 +0,0 @@ -package controller - -import ( - "bytes" - "encoding/json" - "io" - "net/http" - "sentinel/config" - "sentinel/service" - "sentinel/utils" - "strings" - "time" - - "github.com/gin-contrib/cors" - "github.com/gin-gonic/gin" -) - -func SetupRouter() *gin.Engine { - if config.Env == "PROD" { - gin.SetMode(gin.ReleaseMode) - } - r := gin.Default() - r.Use(cors.New(cors.Config{ - AllowAllOrigins: true, - AllowMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"}, - AllowHeaders: []string{"Origin", "Content-Length", "Content-Type", "Authorization"}, - MaxAge: 12 * time.Hour, - AllowCredentials: true, - })) - // r.Use(DebugRequestLogger()) - r.Use(AuthChecker()) - r.Use(UnauthorizedPanicHandler()) - return r -} - -func InitializeRoutes(router *gin.Engine) { - router.GET("/ping", Ping) - router.GET("/config/jwks.json", GetJWKS) - router.GET("/config/openid-configuration", GetOpenIDConfig) - router.POST("/auth/register", RegisterAccountPassword) - router.POST("/auth/login", LoginAccount) - router.POST("/auth/login/discord", LoginDiscord) - router.GET("/oauth/authorize", OauthAuthorize) - router.POST("/oauth/authorize", OauthAuthorize) - router.POST("/oauth/token", OauthExchange) - router.GET("/oauth/scopes", GetValidOauthScopes) - router.GET("/oauth/userinfo", GetUserInfo) - router.GET("/oauth/proxy/validate", OauthProxyValidate) - router.GET("/applications", GetAllClientApplications) - router.GET("/applications/:appID", GetClientApplicationByID) - router.POST("/applications", CreateClientApplication) - router.DELETE("/applications/:appID", DeleteClientApplication) - router.GET("/applications/:appID/logins", GetLoginsForDestination) - router.GET("/logins", GetAllLogins) - router.GET("/users", GetAllUsers) - router.GET("/users/@me", GetCurrentUser) - router.GET("/users/:userID", GetUserByID) - router.POST("/users/:userID", CreateUser) - router.GET("/users/:userID/roles", GetAllRolesForUser) - router.POST("/users/:userID/roles", SetRolesForUser) - router.GET("/users/:userID/auth", GetAuthForUser) - router.DELETE("/users/:userID/auth", ResetAccountPassword) - router.GET("/users/:userID/drive", GetDriveStatusForUser) - router.POST("/users/:userID/drive", AddUserToDrive) - router.DELETE("/users/:userID/drive", RemoveUserFromDrive) - router.GET("/users/:userID/github", GetGithubStatusForUser) - router.POST("/users/:userID/github", AddUserToGithub) - router.GET("/users/:userID/applications", GetClientApplicationsForUser) - router.GET("/users/:userID/logins", GetLoginsForUser) - router.GET("/users/:userID/activities", GetActivitiesForUser) - router.GET("/users/:userID/activity-stats", GetActivityStatsForUser) - router.GET("/mailing-list", GetAllMailingListEntries) - router.POST("/mailing-list", CreateMailingListEntry) -} - -func AuthChecker() gin.HandlerFunc { - return func(c *gin.Context) { - if c.GetHeader("Authorization") != "" { - authHeader := c.GetHeader("Authorization") - if strings.HasPrefix(authHeader, "Bearer ") { - claims, err := service.ValidateJWT(strings.Split(c.GetHeader("Authorization"), "Bearer ")[1]) - if err != nil { - utils.SugarLogger.Errorln("Failed to validate token: " + err.Error()) - c.AbortWithStatusJSON(401, gin.H{"message": err.Error()}) - } else if strings.Contains(claims.Scope, "refresh_token") { - utils.SugarLogger.Errorln("Received refresh token instead of access token") - c.AbortWithStatusJSON(401, gin.H{"message": "Received refresh token instead of access token"}) - } else { - utils.SugarLogger.Infof("Decoded token: %s (%s)", claims.ID, claims.Subject) - utils.SugarLogger.Infof("↳ Client ID: %s", claims.Audience[0]) - utils.SugarLogger.Infof("↳ Scope: %s", claims.Scope) - utils.SugarLogger.Infof("↳ Issued at: %s", claims.IssuedAt.String()) - utils.SugarLogger.Infof("↳ Expires at: %s", claims.ExpiresAt.String()) - c.Set("Auth-Token", strings.Split(c.GetHeader("Authorization"), "Bearer ")[1]) - c.Set("Auth-UserID", claims.Subject) - c.Set("Auth-Audience", claims.Audience[0]) - c.Set("Auth-Scope", claims.Scope) - } - } - } - c.Next() - } -} - -func UnauthorizedPanicHandler() gin.HandlerFunc { - return func(c *gin.Context) { - defer func() { - if err := recover(); err != nil { - if err == "Unauthorized" { - c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"message": "you are not authorized to access this resource"}) - } else { - // Handle other panics - utils.SugarLogger.Errorf("Unexpected panic: %v", err) - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"message": err.(string)}) - } - } - }() - c.Next() - } -} - -// Require checks if a condition is true, otherwise aborts the request -func Require(c *gin.Context, condition bool) { - if !condition { - panic("Unauthorized") - } -} - -// Any checks if any condition is true, otherwise returns false -func Any(conditions ...bool) bool { - for _, condition := range conditions { - if condition { - return true - } - } - return false -} - -// All checks if all conditions are true, otherwise returns false -func All(conditions ...bool) bool { - for _, condition := range conditions { - if !condition { - return false - } - } - return true -} - -func RequestUserHasID(c *gin.Context, id string) bool { - return GetRequestUserID(c) == id -} - -func RequestUserHasEmail(c *gin.Context, email string) bool { - return GetRequestUserEmail(c) == email -} - -func RequestUserHasRole(c *gin.Context, role string) bool { - user := service.GetUserByID(GetRequestUserID(c)) - return user.HasRole(role) -} - -func RequestTokenHasScope(c *gin.Context, scope string) bool { - scopes := GetRequestTokenScopes(c) - for _, s := range strings.Split(scopes, " ") { - if s == scope { - return true - } - } - return false -} - -func RequestTokenHasAudience(c *gin.Context, audience string) bool { - return GetRequestTokenAudience(c) == audience -} - -func GetRequestUserID(c *gin.Context) string { - id, exists := c.Get("Auth-UserID") - if !exists { - return "" - } - return id.(string) -} - -func GetRequestUserEmail(c *gin.Context) string { - email, exists := c.Get("Auth-Email") - if !exists { - return "" - } - return email.(string) -} - -func GetRequestTokenScopes(c *gin.Context) string { - scopes, exists := c.Get("Auth-Scope") - if !exists { - return "" - } - return scopes.(string) -} - -func GetRequestTokenAudience(c *gin.Context) string { - audience, exists := c.Get("Auth-Audience") - if !exists { - return "" - } - return audience.(string) -} - -func DebugRequestLogger() gin.HandlerFunc { - return func(c *gin.Context) { - startTime := time.Now() - - var bodyBytes []byte - if c.Request.Body != nil { - bodyBytes, _ = io.ReadAll(c.Request.Body) - c.Request.Body = io.NopCloser(bytes.NewBuffer(bodyBytes)) - } - - c.Next() - - endTime := time.Now() - - logData := map[string]interface{}{ - "timestamp": startTime.Format(time.RFC3339), - "duration": endTime.Sub(startTime).String(), - "method": c.Request.Method, - "path": c.Request.URL.Path, - "query": c.Request.URL.RawQuery, - "status": c.Writer.Status(), - "remote_ip": c.ClientIP(), - "user_agent": c.Request.UserAgent(), - "referer": c.Request.Referer(), - "request_id": c.Writer.Header().Get("X-Request-ID"), - "headers": c.Request.Header, - "body": string(bodyBytes), - "response_size": c.Writer.Size(), - "auth": map[string]string{ - "user_id": GetRequestUserID(c), - "email": GetRequestUserEmail(c), - "scopes": GetRequestTokenScopes(c), - "audience": GetRequestTokenAudience(c), - }, - } - - logJSON, err := json.Marshal(logData) - if err != nil { - utils.SugarLogger.Error("Failed to marshal request log data: ", err) - } else { - utils.SugarLogger.Infof("Request Log: %s", string(logJSON)) - } - } -} diff --git a/controller/user_controller.go b/controller/user_controller.go deleted file mode 100644 index 8dd0bb1..0000000 --- a/controller/user_controller.go +++ /dev/null @@ -1,135 +0,0 @@ -package controller - -import ( - "net/http" - "sentinel/model" - "sentinel/service" - "strings" - - "github.com/gin-gonic/gin" -) - -func GetAllUsers(c *gin.Context) { - Require(c, Any( - RequestTokenHasScope(c, "sentinel:all"), - All( - RequestTokenHasScope(c, "user:read"), - RequestUserHasRole(c, "d_admin"), - ), - )) - - result := service.GetAllUsers() - c.JSON(http.StatusOK, result) -} - -func GetUserByID(c *gin.Context) { - Require(c, Any( - RequestTokenHasScope(c, "sentinel:all"), - All( - RequestTokenHasScope(c, "user:read"), - Any(RequestUserHasID(c, c.Param("userID")), RequestUserHasRole(c, "d_admin")), - ), - )) - - result := service.GetUserByID(c.Param("userID")) - if result.ID == "" { - c.JSON(http.StatusNotFound, gin.H{"message": "No user found with given id: " + c.Param("userID")}) - return - } - c.JSON(http.StatusOK, result) -} - -func GetCurrentUser(c *gin.Context) { - Require(c, Any( - RequestTokenHasScope(c, "sentinel:all"), - RequestTokenHasScope(c, "user:read"), - )) - - user := service.GetUserByID(GetRequestUserID(c)) - if user.ID == "" { - c.JSON(http.StatusNotFound, gin.H{"message": "No user found with given id: " + GetRequestUserID(c)}) - return - } - // insanely stupid override to make singlestore work - if GetRequestTokenAudience(c) == "quZNfANBcdkW" { - user.Email = service.GauchoRacingEmailReplace(user.Email) - } - c.JSON(http.StatusOK, user) -} - -func GetUserInfo(c *gin.Context) { - Require(c, Any( - RequestTokenHasScope(c, "sentinel:all"), - RequestTokenHasScope(c, "user:read"), - )) - - user := service.GetUserByID(GetRequestUserID(c)) - if user.ID == "" { - c.JSON(http.StatusNotFound, gin.H{"message": "No user found with given id: " + GetRequestUserID(c)}) - return - } - // insanely stupid override to make singlestore work - if GetRequestTokenAudience(c) == "quZNfANBcdkW" { - user.Email = service.GauchoRacingEmailReplace(user.Email) - } - claims, _ := service.ValidateJWT(strings.Split(c.GetHeader("Authorization"), "Bearer ")[1]) - userInfo := model.UserInfo{ - Sub: claims.Subject, - Name: user.FirstName + " " + user.LastName, - GivenName: user.FirstName, - FamilyName: user.LastName, - Profile: "https://sso.gauchoracing.com/users/" + user.ID, - Picture: user.AvatarURL, - EmailVerified: true, - User: user, - } - userInfo.BookstackRoles = append(userInfo.BookstackRoles, "Editor") - if user.IsInnerCircle() { - userInfo.BookstackRoles = append(userInfo.BookstackRoles, "Lead") - } - if user.IsAdmin() { - userInfo.BookstackRoles = append(userInfo.BookstackRoles, "Admin") - } - c.JSON(http.StatusOK, userInfo) -} - -func CreateUser(c *gin.Context) { - Require(c, All( - Any( - RequestTokenHasScope(c, "sentinel:all"), - RequestTokenHasScope(c, "user:write"), - ), - Any( - RequestUserHasID(c, c.Param("userID")), - RequestUserHasRole(c, "d_admin"), - ), - )) - - var user model.User - if err := c.ShouldBindJSON(&user); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"message": err.Error()}) - return - } - user.ID = c.Param("userID") - err := service.CreateUser(user, RequestTokenHasScope(c, "sentinel:all")) - if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()}) - return - } - c.JSON(http.StatusCreated, service.GetUserByID(user.ID)) -} - -func DeleteUser(c *gin.Context) { - Require(c, All( - RequestTokenHasScope(c, "sentinel:all"), - RequestUserHasRole(c, "d_admin"), - )) - - id := c.Param("id") - err := service.DeleteUser(id) - if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()}) - return - } - c.JSON(http.StatusOK, gin.H{"message": "User with id: " + id + " has been deleted"}) -} diff --git a/core/go.mod b/core/go.mod new file mode 100644 index 0000000..67eb852 --- /dev/null +++ b/core/go.mod @@ -0,0 +1,3 @@ +module github.com/gaucho-racing/sentinel/core + +go 1.25.6 diff --git a/core/main.go b/core/main.go new file mode 100644 index 0000000..a3dd973 --- /dev/null +++ b/core/main.go @@ -0,0 +1,7 @@ +package main + +import "fmt" + +func main() { + fmt.Println("Hello, World!") +} diff --git a/database/db.go b/database/db.go deleted file mode 100644 index 0b6eab0..0000000 --- a/database/db.go +++ /dev/null @@ -1,50 +0,0 @@ -package database - -import ( - "fmt" - "sentinel/config" - "sentinel/model" - "sentinel/utils" - "time" - - singlestore "github.com/singlestore-labs/gorm-singlestore" - "gorm.io/gorm" -) - -var DB *gorm.DB - -var dbRetries = 0 - -func InitializeDB() error { - dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=UTC", config.DatabaseUser, config.DatabasePassword, config.DatabaseHost, config.DatabasePort, config.DatabaseName) - db, err := gorm.Open(singlestore.Open(dsn), &gorm.Config{}) - if err != nil { - if dbRetries < 5 { - dbRetries++ - utils.SugarLogger.Errorln("failed to connect database, retrying in 5s... ") - time.Sleep(time.Second * 5) - InitializeDB() - } else { - return fmt.Errorf("failed to connect database after 5 attempts") - } - } else { - utils.SugarLogger.Infoln("Connected to database") - db.AutoMigrate( - &model.User{}, - &model.Subteam{}, - &model.UserSubteam{}, - &model.UserRole{}, - &model.UserAuth{}, - &model.UserLogin{}, - &model.UserActivity{}, - &model.ClientApplication{}, - &model.ClientApplicationRedirectURI{}, - &model.AuthorizationCode{}, - &model.RefreshToken{}, - &model.MailingList{}, - ) - utils.SugarLogger.Infoln("AutoMigration complete") - DB = db - } - return nil -} diff --git a/docker-compose.yml b/docker-compose.yml index 73aaa9a..079fd01 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,49 +3,13 @@ name: sentinel services: db: container_name: db - image: ghcr.io/singlestore-labs/singlestoredb-dev:latest - platform: linux/amd64 - restart: unless-stopped - volumes: - - s2data:/data - - ./init.sql:/init.sql - ports: - - "3306:3306" - - "8080:8080" - - "9000:9000" - environment: - ROOT_PASSWORD: "password" - - sentinel: - container_name: sentinel - depends_on: - - db - image: gauchoracing/sentinel:latest - restart: unless-stopped - ports: - - "${PORT}:${PORT}" + image: postgres:18-alpine environment: - ENV: $ENV - PORT: $PORT - PREFIX: $PREFIX - DATABASE_HOST: $DATABASE_HOST - DATABASE_PORT: $DATABASE_PORT - DATABASE_USER: $DATABASE_USER - DATABASE_PASSWORD: $DATABASE_PASSWORD - DATABASE_NAME: $DATABASE_NAME - DISCORD_TOKEN: $DISCORD_TOKEN - DISCORD_GUILD: $DISCORD_GUILD - DISCORD_LOG_CHANNEL: $DISCORD_LOG_CHANNEL - DISCORD_CLIENT_ID: $DISCORD_CLIENT_ID - DISCORD_CLIENT_SECRET: $DISCORD_CLIENT_SECRET - DISCORD_REDIRECT_URI: $DISCORD_REDIRECT_URI - DRIVE_SERVICE_ACCOUNT: $DRIVE_SERVICE_ACCOUNT - GITHUB_PAT: $GITHUB_PAT - WIKI_TOKEN: $WIKI_TOKEN - AUTH_SIGNING_KEY: $AUTH_SIGNING_KEY - DRIVE_CRON: $DRIVE_CRON - GITHUB_CRON: $GITHUB_CRON - WIKI_CRON: $WIKI_CRON + POSTGRES_USER: postgres + POSTGRES_PASSWORD: password + POSTGRES_DB: sentinel + volumes: + - pgdata:/var/lib/postgresql volumes: - s2data: \ No newline at end of file + pgdata: \ No newline at end of file diff --git a/go.mod b/go.mod deleted file mode 100644 index 9be4f8c..0000000 --- a/go.mod +++ /dev/null @@ -1,79 +0,0 @@ -module sentinel - -go 1.22.0 - -// replace github.com/singlestore-labs/gorm-singlestore v1.2.0 => "/Users/bk1031/Documents/Projects/Dev Projects/gorm-singlestore" - -require ( - github.com/bwmarrin/discordgo v0.27.1 - github.com/gin-gonic/gin v1.9.1 - github.com/golang-jwt/jwt v3.2.2+incompatible - github.com/google/uuid v1.6.0 - github.com/lithammer/fuzzysearch v1.1.8 - github.com/singlestore-labs/gorm-singlestore v1.2.0 - go.uber.org/zap v1.27.0 - golang.org/x/oauth2 v0.21.0 - gorm.io/gorm v1.25.7 -) - -require ( - cloud.google.com/go/auth v0.6.1 // indirect - cloud.google.com/go/auth/oauth2adapt v0.2.2 // indirect - cloud.google.com/go/compute/metadata v0.3.0 // indirect - github.com/bytedance/sonic/loader v0.1.1 // indirect - github.com/cloudwego/base64x v0.1.4 // indirect - github.com/cloudwego/iasm v0.2.0 // indirect - github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/go-logr/logr v1.4.1 // indirect - github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-sql-driver/mysql v1.7.1 // indirect - github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/golang/protobuf v1.5.4 // indirect - github.com/google/s2a-go v0.1.7 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect - github.com/googleapis/gax-go/v2 v2.12.5 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect - go.opencensus.io v0.24.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect - go.opentelemetry.io/otel v1.24.0 // indirect - go.opentelemetry.io/otel/metric v1.24.0 // indirect - go.opentelemetry.io/otel/trace v1.24.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240624140628-dc46fd24d27d // indirect - google.golang.org/grpc v1.64.1 // indirect -) - -require ( - github.com/bytedance/sonic v1.11.6 // indirect - github.com/fatih/color v1.17.0 - github.com/gabriel-vasile/mimetype v1.4.3 // indirect - github.com/gin-contrib/cors v1.7.2 - github.com/gin-contrib/sse v0.1.0 // indirect - github.com/go-playground/locales v0.14.1 // indirect - github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/go-playground/validator/v10 v10.20.0 // indirect - github.com/goccy/go-json v0.10.2 // indirect - github.com/golang-jwt/jwt/v4 v4.5.0 - github.com/gorilla/websocket v1.4.2 // indirect - github.com/jinzhu/inflection v1.0.0 // indirect - github.com/jinzhu/now v1.1.5 // indirect - github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/cpuid/v2 v2.2.7 // indirect - github.com/leodido/go-urn v1.4.0 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect - github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/pelletier/go-toml/v2 v2.2.1 // indirect - github.com/robfig/cron/v3 v3.0.1 - github.com/rogpeppe/go-internal v1.12.0 // indirect - github.com/twitchyliquid64/golang-asm v0.15.1 // indirect - github.com/ugorji/go/codec v1.2.12 // indirect - go.uber.org/multierr v1.10.0 // indirect - golang.org/x/arch v0.7.0 // indirect - golang.org/x/crypto v0.24.0 - golang.org/x/net v0.26.0 // indirect - golang.org/x/sys v0.21.0 // indirect - golang.org/x/text v0.16.0 // indirect - google.golang.org/api v0.187.0 - google.golang.org/protobuf v1.34.2 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect -) diff --git a/go.sum b/go.sum deleted file mode 100644 index 7676f68..0000000 --- a/go.sum +++ /dev/null @@ -1,282 +0,0 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go/auth v0.6.1 h1:T0Zw1XM5c1GlpN2HYr2s+m3vr1p2wy+8VN+Z1FKxW38= -cloud.google.com/go/auth v0.6.1/go.mod h1:eFHG7zDzbXHKmjJddFG/rBlcGp6t25SwRUiEQSlO4x4= -cloud.google.com/go/auth/oauth2adapt v0.2.2 h1:+TTV8aXpjeChS9M+aTtN/TjdQnzJvmzKFt//oWu7HX4= -cloud.google.com/go/auth/oauth2adapt v0.2.2/go.mod h1:wcYjgpZI9+Yu7LyYBg4pqSiaRkfEK3GQcpb7C/uyF1Q= -cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc= -cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/bwmarrin/discordgo v0.27.1 h1:ib9AIc/dom1E/fSIulrBwnez0CToJE113ZGt4HoliGY= -github.com/bwmarrin/discordgo v0.27.1/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY= -github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0= -github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= -github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM= -github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= -github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= -github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= -github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= -github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= -github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= -github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= -github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= -github.com/gin-contrib/cors v1.7.2 h1:oLDHxdg8W/XDoN/8zamqk/Drgt4oVZDvaV0YmvVICQw= -github.com/gin-contrib/cors v1.7.2/go.mod h1:SUJVARKgQ40dmrzgXEVxj2m7Ig1v1qIboQkPDTQ9t2E= -github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= -github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= -github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= -github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= -github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= -github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= -github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= -github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= -github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= -github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8= -github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= -github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= -github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= -github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= -github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= -github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= -github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= -github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= -github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= -github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= -github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= -github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= -github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= -github.com/googleapis/gax-go/v2 v2.12.5 h1:8gw9KZK8TiVKB6q3zHY3SBzLnrGp6HQjyfYBYGmXdxA= -github.com/googleapis/gax-go/v2 v2.12.5/go.mod h1:BUDKcWo+RaKq5SC9vVYL0wLADa3VcfswbOMMRmB9H3E= -github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= -github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= -github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= -github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= -github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= -github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= -github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= -github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= -github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= -github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= -github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= -github.com/lithammer/fuzzysearch v1.1.8 h1:/HIuJnjHuXS8bKaiTMeeDlW2/AyIWk2brx1V8LFgLN4= -github.com/lithammer/fuzzysearch v1.1.8/go.mod h1:IdqeyBClc3FFqSzYq/MXESsS4S0FsZ5ajtkr5xPLts4= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= -github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/pelletier/go-toml/v2 v2.2.1 h1:9TA9+T8+8CUCO2+WYnDLCgrYi9+omqKXyjDtosvtEhg= -github.com/pelletier/go-toml/v2 v2.2.1/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= -github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= -github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= -github.com/singlestore-labs/gorm-singlestore v1.2.0 h1:mccrEa5tyZDvq7LdEl7oIVfizVC0gxkcf2MHoZG1TWQ= -github.com/singlestore-labs/gorm-singlestore v1.2.0/go.mod h1:Bxq1nC7Gr1I7Hb0tS4bF/aLp3/EqD64kY79LeBKYKBQ= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= -github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= -github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= -github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= -go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= -go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= -go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= -go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= -go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= -go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= -go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= -go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= -go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= -go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= -go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= -golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/arch v0.7.0 h1:pskyeJh/3AmoQ8CPE95vxHLqp1G1GfGNXTmcl9NEKTc= -golang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= -golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= -golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= -golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= -golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.187.0 h1:Mxs7VATVC2v7CY+7Xwm4ndkX71hpElcvx0D1Ji/p1eo= -google.golang.org/api v0.187.0/go.mod h1:KIHlTc4x7N7gKKuVsdmfBXN13yEEWXWFURWY6SBp2gk= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20240624140628-dc46fd24d27d h1:PksQg4dV6Sem3/HkBX+Ltq8T0ke0PKIRBNBatoDTVls= -google.golang.org/genproto/googleapis/api v0.0.0-20240610135401-a8a62080eff3 h1:QW9+G6Fir4VcRXVH8x3LilNAb6cxBGLa6+GM4hRwexE= -google.golang.org/genproto/googleapis/api v0.0.0-20240610135401-a8a62080eff3/go.mod h1:kdrSS/OiLkPrNUpzD4aHgCq2rVuC/YRxok32HXZ4vRE= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240624140628-dc46fd24d27d h1:k3zyW3BYYR30e8v3x0bTDdE9vpYFjZHK+HcyqkrppWk= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240624140628-dc46fd24d27d/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= -google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gorm.io/gorm v1.25.7 h1:VsD6acwRjz2zFxGO50gPO6AkNs7KKnvfzUjHQhZDz/A= -gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= -rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/init.sql b/init.sql deleted file mode 100644 index 677558b..0000000 --- a/init.sql +++ /dev/null @@ -1 +0,0 @@ -CREATE DATABASE IF NOT EXISTS sentinel; \ No newline at end of file diff --git a/jobs/discord.go b/jobs/discord.go deleted file mode 100644 index ae47bbe..0000000 --- a/jobs/discord.go +++ /dev/null @@ -1,52 +0,0 @@ -package jobs - -import ( - "sentinel/config" - "sentinel/service" - "sentinel/utils" - "strconv" - - cron "github.com/robfig/cron/v3" -) - -func RegisterDiscordCronJob() { - if config.Env != "PROD" { - utils.SugarLogger.Infoln("Discord CRON Job not registered because environment is not PROD") - return - } - c := cron.New() - CleanDiscordJob(c) - IncompleteProfileJob(c) -} - -func CleanDiscordJob(c *cron.Cron) { - entryID, err := c.AddFunc(config.DiscordCron, func() { - _, _ = service.Discord.ChannelMessageSend(config.DiscordLogChannel, ":alarm_clock: Starting discord member cleanup CRON Job") - utils.SugarLogger.Infoln("Starting discord member cleanup CRON Job...") - service.CleanDiscordMembers() - utils.SugarLogger.Infoln("Finished discord member cleanup CRON Job!") - _, _ = service.Discord.ChannelMessageSend(config.DiscordLogChannel, ":white_check_mark: Finished discord member cleanup CRON Job!") - }) - if err != nil { - utils.SugarLogger.Errorln("Error registering CRON Job: " + err.Error()) - return - } - c.Start() - utils.SugarLogger.Infoln("Registered CRON Job: " + strconv.Itoa(int(entryID)) + " scheduled with cron expression: " + config.DiscordCron) -} - -func IncompleteProfileJob(c *cron.Cron) { - entryID, err := c.AddFunc("0 10 */7 * *", func() { - _, _ = service.Discord.ChannelMessageSend(config.DiscordLogChannel, ":alarm_clock: Starting incomplete profile reminder CRON Job") - utils.SugarLogger.Infoln("Starting incomplete profile reminder CRON Job...") - service.IncompleteProfileReminder() - utils.SugarLogger.Infoln("Finished incomplete profile reminder CRON Job!") - _, _ = service.Discord.ChannelMessageSend(config.DiscordLogChannel, ":white_check_mark: Finished incomplete profile reminder CRON Job!") - }) - if err != nil { - utils.SugarLogger.Errorln("Error registering CRON Job: " + err.Error()) - return - } - c.Start() - utils.SugarLogger.Infoln("Registered CRON Job: " + strconv.Itoa(int(entryID)) + " scheduled with cron expression: " + config.DiscordCron) -} diff --git a/jobs/drive.go b/jobs/drive.go deleted file mode 100644 index a8f8412..0000000 --- a/jobs/drive.go +++ /dev/null @@ -1,66 +0,0 @@ -package jobs - -import ( - "sentinel/config" - "sentinel/service" - "sentinel/utils" - "strconv" - "sync" - - cron "github.com/robfig/cron/v3" -) - -func RegisterDriveCronJob() { - if config.Env != "PROD" { - utils.SugarLogger.Infoln("Drive CRON Job not registered because environment is not PROD") - return - } - c := cron.New() - entryID, err := c.AddFunc(config.DriveCron, func() { - _, _ = service.Discord.ChannelMessageSend(config.DiscordLogChannel, ":alarm_clock: Starting google drive CRON Job") - utils.SugarLogger.Infoln("Starting google drive CRON Job...") - var wg sync.WaitGroup - wg.Add(6) - go func() { - defer wg.Done() - service.PopulateMemberDirectorySheet() - }() - go func() { - defer wg.Done() - service.PopulateMailingListSheet() - }() - go func() { - defer wg.Done() - service.RemoveInactiveMembersFromDrive() - }() - go func() { - defer wg.Done() - service.CleanDriveMembers() - }() - go func() { - defer wg.Done() - service.CleanLeadsDriveMembers() - }() - go func() { - defer wg.Done() - service.UpdateTeamMembers() - }() - wg.Wait() - // utils.SugarLogger.Infoln("Finished google drive cleanup, running PopulateDriveMembers...") - // _, _ = service.Discord.ChannelMessageSend(config.DiscordLogChannel, "Finished google drive cleanup, running PopulateDriveMembers...") - // wg.Add(1) - // go func() { - // defer wg.Done() - // service.PopulateDriveMembers() - // }() - // wg.Wait() - utils.SugarLogger.Infoln("Finished google drive CRON Job!") - _, _ = service.Discord.ChannelMessageSend(config.DiscordLogChannel, ":white_check_mark: Finished google drive job!") - }) - if err != nil { - utils.SugarLogger.Errorln("Error registering CRON Job: " + err.Error()) - return - } - c.Start() - utils.SugarLogger.Infoln("Registered CRON Job: " + strconv.Itoa(int(entryID)) + " scheduled with cron expression: " + config.DriveCron) -} diff --git a/jobs/github.go b/jobs/github.go deleted file mode 100644 index 6af8aaa..0000000 --- a/jobs/github.go +++ /dev/null @@ -1,31 +0,0 @@ -package jobs - -import ( - "sentinel/config" - "sentinel/service" - "sentinel/utils" - "strconv" - - cron "github.com/robfig/cron/v3" -) - -func RegisterGithubCronJob() { - if config.Env != "PROD" { - utils.SugarLogger.Infoln("Github CRON Job not registered because environment is not PROD") - return - } - c := cron.New() - entryID, err := c.AddFunc(config.GithubCron, func() { - _, _ = service.Discord.ChannelMessageSend(config.DiscordLogChannel, ":alarm_clock: Starting github CRON Job") - utils.SugarLogger.Infoln("Starting github CRON Job...") - service.CleanGithubMembers() - utils.SugarLogger.Infoln("Finished github CRON Job!") - _, _ = service.Discord.ChannelMessageSend(config.DiscordLogChannel, ":white_check_mark: Finished github job!") - }) - if err != nil { - utils.SugarLogger.Errorln("Error registering CRON Job: " + err.Error()) - return - } - c.Start() - utils.SugarLogger.Infoln("Registered CRON Job: " + strconv.Itoa(int(entryID)) + " scheduled with cron expression: " + config.GithubCron) -} diff --git a/main.go b/main.go deleted file mode 100644 index 45271b0..0000000 --- a/main.go +++ /dev/null @@ -1,38 +0,0 @@ -package main - -import ( - "sentinel/commands" - "sentinel/config" - "sentinel/controller" - "sentinel/database" - "sentinel/jobs" - "sentinel/service" - "sentinel/utils" -) - -func main() { - config.PrintStartupBanner() - utils.InitializeLogger() - utils.VerifyConfig() - defer utils.Logger.Sync() - - database.InitializeDB() - service.InitializeKeys() - service.InitializeDrive() - service.ConnectDiscord() - service.InitializeRoles() - service.InitializeSubteams() - go service.SyncRolesForAllUsers() - commands.InitializeDiscordBot() - - jobs.RegisterDriveCronJob() - jobs.RegisterGithubCronJob() - jobs.RegisterDiscordCronJob() - - router := controller.SetupRouter() - controller.InitializeRoutes(router) - err := router.Run(":" + config.Port) - if err != nil { - utils.SugarLogger.Fatalln(err) - } -} diff --git a/model/auth.go b/model/auth.go deleted file mode 100644 index 4fd8841..0000000 --- a/model/auth.go +++ /dev/null @@ -1,58 +0,0 @@ -package model - -import ( - "fmt" - - "github.com/golang-jwt/jwt/v4" -) - -type TokenResponse struct { - IDToken string `json:"id_token,omitempty"` - AccessToken string `json:"access_token,omitempty"` - RefreshToken string `json:"refresh_token,omitempty"` - TokenType string `json:"token_type,omitempty"` - ExpiresIn int `json:"expires_in,omitempty"` - Scope string `json:"scope,omitempty"` -} - -type AuthClaims struct { - Name string `json:"name,omitempty"` - GivenName string `json:"given_name,omitempty"` - FamilyName string `json:"family_name,omitempty"` - Profile string `json:"profile,omitempty"` - Picture string `json:"picture,omitempty"` - Email string `json:"email,omitempty"` - EmailVerified bool `json:"email_verified,omitempty"` - BookstackRoles []string `json:"bookstack_roles,omitempty"` - Roles []string `json:"roles,omitempty"` - Subteams []string `json:"subteams,omitempty"` - Scope string `json:"scope,omitempty"` - jwt.RegisteredClaims -} - -func (c AuthClaims) Valid() error { - vErr := new(jwt.ValidationError) - now := jwt.TimeFunc() - - if !c.VerifyExpiresAt(now, true) { - delta := now.Sub(c.ExpiresAt.Time) - vErr.Inner = fmt.Errorf("%s by %s", jwt.ErrTokenExpired, delta) - vErr.Errors |= jwt.ValidationErrorExpired - } - - if !c.VerifyIssuedAt(now, true) { - vErr.Inner = jwt.ErrTokenUsedBeforeIssued - vErr.Errors |= jwt.ValidationErrorIssuedAt - } - - if !c.VerifyIssuer("https://sso.gauchoracing.com", true) { - vErr.Inner = jwt.ErrTokenInvalidIssuer - vErr.Errors |= jwt.ValidationErrorIssuer - } - - if vErr.Errors == 0 { - return nil - } - - return vErr -} diff --git a/model/discord.go b/model/discord.go deleted file mode 100644 index 24dbd6b..0000000 --- a/model/discord.go +++ /dev/null @@ -1,14 +0,0 @@ -package model - -type DiscordAccessTokenResponse struct { - AccessToken string `json:"access_token"` - TokenType string `json:"token_type"` - Scope string `json:"scope"` -} - -type DiscordUser struct { - ID string `json:"id"` - Username string `json:"username"` - Discriminator string `json:"discriminator"` - Avatar string `json:"avatar"` -} diff --git a/model/github.go b/model/github.go deleted file mode 100644 index 32e83f3..0000000 --- a/model/github.go +++ /dev/null @@ -1,39 +0,0 @@ -package model - -type GithubInvite struct { - Username string `json:"username"` -} - -type GithubOrgUser struct { - Url string `json:"url"` - State string `json:"state"` - Role string `json:"role"` - OrganizationUrl string `json:"organization_url"` - User GithubUser `json:"user"` - Organization GithubOrg `json:"organization"` -} - -type GithubUser struct { - ID int `json:"id"` - Login string `json:"login"` - NodeID string `json:"node_id"` - AvatarUrl string `json:"avatar_url"` - GravatarId string `json:"gravatar_id"` - Type string `json:"type"` - SiteAdmin bool `json:"site_admin"` -} - -type GithubOrg struct { - ID int `json:"id"` - Login string `json:"login"` - NodeID string `json:"node_id"` - Url string `json:"url"` - ReposUrl string `json:"repos_url"` - EventsUrl string `json:"events_url"` - HooksUrl string `json:"hooks_url"` - IssuesUrl string `json:"issues_url"` - MembersUrl string `json:"members_url"` - PublicMembersUrl string `json:"public_members_url"` - AvatarUrl string `json:"avatar_url"` - Description string `json:"description"` -} diff --git a/model/mailing_list.go b/model/mailing_list.go deleted file mode 100644 index d55689e..0000000 --- a/model/mailing_list.go +++ /dev/null @@ -1,17 +0,0 @@ -package model - -import "time" - -type MailingList struct { - Email string `gorm:"primaryKey" json:"email" binding:"required,email"` - FirstName string `json:"first_name"` - LastName string `json:"last_name"` - Role string `json:"role"` - Organization string `json:"organization"` - CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"` - UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"` -} - -func (MailingList) TableName() string { - return "mailing_list" -} diff --git a/model/oauth.go b/model/oauth.go deleted file mode 100644 index 8d82dcc..0000000 --- a/model/oauth.go +++ /dev/null @@ -1,81 +0,0 @@ -package model - -import ( - "time" -) - -var ValidOauthScopes = map[string]string{ - "openid": "OpenID Connect scope", - "profile": "OIDC profile scope", - "email": "OIDC email scope", - "user:read": "Read user account information", - "user:write": "Edit user account information", - "drive:read": "Read user's team drive access information", - "drive:write": "Add/remove user from the team drive", - "github:read": "Read user's github access information", - "github:write": "Add/remove user from the github org", - "applications:read": "Read user's applications (this includes the client id and secret)", - "logins:read": "Read users's login history", - "sentinel:all": "Internal scope for Sentinel, client applications should not request this scope.", -} - -var OpenIDConfig = map[string]interface{}{ - "issuer": "https://sso.gauchoracing.com", - "authorization_endpoint": "https://sso.gauchoracing.com/oauth/authorize", - "token_endpoint": "https://sso.gauchoracing.com/api/oauth/token", - "userinfo_endpoint": "https://sso.gauchoracing.com/api/oauth/userinfo", - "jwks_uri": "https://sso.gauchoracing.com/.well-known/jwks.json", - "response_types_supported": []string{"code", "id_token", "id_token token"}, - "subject_types_supported": []string{"public"}, - "id_token_signing_alg_values_supported": []string{"RS256"}, - "claims_supported": []string{"name", "given_name", "family_name", "profile", "picture", "email", "email_verified"}, -} - -type ClientApplication struct { - ID string `gorm:"primaryKey" json:"id"` - UserID string `json:"user_id"` - Secret string `json:"secret"` - Name string `json:"name"` - RedirectURIs []string `json:"redirect_uris" gorm:"-"` - UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"` - CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"` -} - -func (ClientApplication) TableName() string { - return "client_application" -} - -type ClientApplicationRedirectURI struct { - ClientApplicationID string `gorm:"primaryKey" json:"client_application_id"` - RedirectURI string `gorm:"primaryKey" json:"redirect_uri"` -} - -func (ClientApplicationRedirectURI) TableName() string { - return "client_application_redirect_uri" -} - -type AuthorizationCode struct { - Code string `gorm:"primaryKey" json:"code"` - ClientID string `json:"client_id"` - UserID string `json:"user_id"` - Scope string `json:"scope"` - ExpiresAt time.Time `json:"expires_at"` - CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"` -} - -func (AuthorizationCode) TableName() string { - return "authorization_code" -} - -type RefreshToken struct { - Token string `gorm:"primaryKey;type:longtext" json:"token"` - UserID string `json:"user_id"` - Scope string `json:"scope"` - Revoked bool `json:"revoked"` - ExpiresAt time.Time `json:"expires_at"` - CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"` -} - -func (RefreshToken) TableName() string { - return "refresh_token" -} diff --git a/model/subteam.go b/model/subteam.go deleted file mode 100644 index 5c0faea..0000000 --- a/model/subteam.go +++ /dev/null @@ -1,13 +0,0 @@ -package model - -import "time" - -type Subteam struct { - ID string `gorm:"primaryKey" json:"id"` - Name string `json:"name"` - CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"` -} - -func (Subteam) TableName() string { - return "subteam" -} diff --git a/model/user.go b/model/user.go deleted file mode 100644 index 1e26686..0000000 --- a/model/user.go +++ /dev/null @@ -1,112 +0,0 @@ -package model - -import "time" - -type UserInfo struct { - Sub string `json:"sub,omitempty"` - Name string `json:"name,omitempty"` - GivenName string `json:"given_name,omitempty"` - FamilyName string `json:"family_name,omitempty"` - Profile string `json:"profile,omitempty"` - Picture string `json:"picture,omitempty"` - EmailVerified bool `json:"email_verified,omitempty"` - BookstackRoles []string `json:"bookstack_roles,omitempty"` - User -} - -type User struct { - ID string `gorm:"primaryKey" json:"id"` - Username string `json:"username"` - FirstName string `json:"first_name"` - LastName string `json:"last_name"` - Email string `json:"email"` - PhoneNumber string `json:"phone_number"` - Gender string `json:"gender"` - Birthday string `json:"birthday"` - GraduateLevel string `json:"graduate_level"` - GraduationYear int `json:"graduation_year"` - Major string `json:"major"` - ShirtSize string `json:"shirt_size"` - JacketSize string `json:"jacket_size"` - SAERegistrationNumber string `json:"sae_registration_number"` - AvatarURL string `json:"avatar_url"` - Verified bool `json:"verified"` - Subteams []Subteam `gorm:"-" json:"subteams"` - Roles []string `gorm:"-" json:"roles"` - UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"` - CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"` -} - -func (User) TableName() string { - return "user" -} - -func (user User) String() string { - return "(" + user.ID + ")" + " " + user.FirstName + " " + user.LastName + " [" + user.Email + "]" -} - -func (user User) GetHighestRole() string { - if user.IsAdmin() { - return "d_admin" - } - if user.IsOfficer() { - return "d_officer" - } - if user.IsLead() { - return "d_lead" - } - if user.IsSpecialAdvisor() { - return "d_special_advisor" - } - if user.IsTeamMember() { - return "d_team_member" - } - if user.IsMember() { - return "d_member" - } - if user.IsAlumni() { - return "d_alumni" - } - return "" -} - -func (user User) HasRole(role string) bool { - for _, r := range user.Roles { - if r == role { - return true - } - } - return false -} - -func (user User) IsAdmin() bool { - return user.HasRole("d_admin") -} - -func (user User) IsOfficer() bool { - return user.HasRole("d_officer") -} - -func (user User) IsLead() bool { - return user.HasRole("d_lead") -} - -func (user User) IsSpecialAdvisor() bool { - return user.HasRole("d_special_advisor") -} - -func (user User) IsInnerCircle() bool { - return user.IsAdmin() || user.IsOfficer() || user.IsLead() || user.IsSpecialAdvisor() -} - -func (user User) IsTeamMember() bool { - return user.HasRole("d_team_member") -} - -func (user User) IsMember() bool { - return user.HasRole("d_member") -} - -func (user User) IsAlumni() bool { - return user.HasRole("d_alumni") -} diff --git a/model/user_activity.go b/model/user_activity.go deleted file mode 100644 index 38a9083..0000000 --- a/model/user_activity.go +++ /dev/null @@ -1,14 +0,0 @@ -package model - -import "time" - -type UserActivity struct { - ID string `gorm:"primaryKey" json:"id"` - UserID string `json:"user_id"` - Action string `json:"action"` - CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"` -} - -func (UserActivity) TableName() string { - return "user_activity" -} diff --git a/model/user_auth.go b/model/user_auth.go deleted file mode 100644 index 891e52a..0000000 --- a/model/user_auth.go +++ /dev/null @@ -1,15 +0,0 @@ -package model - -import "time" - -type UserAuth struct { - ID string `gorm:"primaryKey" json:"id"` - Email string `json:"email" gorm:"index"` - Password string `json:"password"` - UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"` - CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"` -} - -func (UserAuth) TableName() string { - return "user_auth" -} diff --git a/model/user_login.go b/model/user_login.go deleted file mode 100644 index 0361d90..0000000 --- a/model/user_login.go +++ /dev/null @@ -1,17 +0,0 @@ -package model - -import "time" - -type UserLogin struct { - ID string `gorm:"primaryKey" json:"id"` - UserID string `json:"user_id"` - Destination string `json:"destination"` - Scope string `json:"scope"` - IPAddress string `json:"ip_address"` - LoginType string `json:"login_type"` - CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"` -} - -func (UserLogin) TableName() string { - return "user_login" -} diff --git a/model/user_role.go b/model/user_role.go deleted file mode 100644 index 4cae16e..0000000 --- a/model/user_role.go +++ /dev/null @@ -1,13 +0,0 @@ -package model - -import "time" - -type UserRole struct { - UserID string `json:"user_id" gorm:"primaryKey"` - Role string `json:"role" gorm:"primaryKey"` - CreatedAt time.Time `json:"time" gorm:"autoCreateTime"` -} - -func (UserRole) TableName() string { - return "user_role" -} diff --git a/model/user_subteam.go b/model/user_subteam.go deleted file mode 100644 index 67aa09f..0000000 --- a/model/user_subteam.go +++ /dev/null @@ -1,10 +0,0 @@ -package model - -type UserSubteam struct { - UserID string `gorm:"primaryKey" json:"user_id"` - RoleID string `gorm:"primaryKey" json:"role_id"` -} - -func (UserSubteam) TableName() string { - return "user_subteam" -} diff --git a/model/wiki.go b/model/wiki.go deleted file mode 100644 index e1612d7..0000000 --- a/model/wiki.go +++ /dev/null @@ -1,55 +0,0 @@ -package model - -type WikiArrayResponse[T any] struct { - Data []T `json:"data"` -} - -type WikiUserCreate struct { - Name string `json:"name"` - Email string `json:"email"` - Roles []int `json:"roles"` - ExternalAuthID string `json:"external_auth_id"` - Password string `json:"password"` - SendInvite bool `json:"send_invite"` -} - -type WikiUser struct { - ID int `json:"id"` - Name string `json:"name"` - Email string `json:"email"` - CreatedAt string `json:"created_at"` - UpdatedAt string `json:"updated_at"` - ExternalAuthID string `json:"external_auth_id"` - Slug string `json:"slug"` - LastActivityAt string `json:"last_activity_at"` - ProfileURL string `json:"profile_url"` - EditURL string `json:"edit_url"` - AvatarURL string `json:"avatar_url"` -} - -type WikiRole int - -const ( - WikiRoleAdmin WikiRole = 1 - WikiRoleDevOps WikiRole = 6 - WikiRoleEditor WikiRole = 2 - WikiRoleLead WikiRole = 5 - WikiRolePublic WikiRole = 4 - WikiRoleViewer WikiRole = 3 -) - -/* -{ - "id": 1, - "name": "GR Admin", - "email": "admin@gauchoracing.com", - "created_at": "2024-01-08T22:25:05.000000Z", - "updated_at": "2024-01-08T22:59:47.000000Z", - "external_auth_id": "", - "slug": "gr-admin", - "last_activity_at": "2024-07-06T07:00:59.000000Z", - "profile_url": "https:\/\/wiki.gauchoracing.com\/user\/gr-admin", - "edit_url": "https:\/\/wiki.gauchoracing.com\/settings\/users\/1", - "avatar_url": "https:\/\/wiki.gauchoracing.com\/uploads\/images\/user\/2024-01\/thumbs-50-50\/O1tgkEgkCZ4df2Wv-gr-logo-blank.png" - } -*/ diff --git a/scripts/run.sh b/scripts/run-core.sh similarity index 83% rename from scripts/run.sh rename to scripts/run-core.sh index b6b9689..3dc3af6 100755 --- a/scripts/run.sh +++ b/scripts/run-core.sh @@ -1,8 +1,8 @@ #!/bin/bash # check if go.mod exists in current directory -if [ ! -f go.mod ]; then - echo "go.mod not found" +if [ ! -f core/go.mod ]; then + echo "core/go.mod not found" echo "Please make sure you are in the root sentinel directory" exit 1 fi @@ -17,5 +17,6 @@ fi set -a . .env +cd core go get . go run main.go \ No newline at end of file diff --git a/service/activity_service.go b/service/activity_service.go deleted file mode 100644 index beaaa5f..0000000 --- a/service/activity_service.go +++ /dev/null @@ -1,85 +0,0 @@ -package service - -import ( - "sentinel/database" - "sentinel/model" - "time" -) - -func GetAllActivities() []model.UserActivity { - var activities []model.UserActivity - database.DB.Find(&activities) - return activities -} - -func GetActivitiesForUser(userID string) []model.UserActivity { - var activities []model.UserActivity - database.DB.Where("user_id = ?", userID).Order("created_at asc").Find(&activities) - return activities -} - -func GetLastActivityForUser(userID string) model.UserActivity { - var activity model.UserActivity - database.DB.Where("user_id = ?", userID).Order("created_at desc").First(&activity) - return activity -} - -func GetActivityByID(activityID string) model.UserActivity { - var activity model.UserActivity - database.DB.Where("id = ?", activityID).Find(&activity) - return activity -} - -func CreateActivity(activity model.UserActivity) error { - if result := database.DB.Create(&activity); result.Error != nil { - return result.Error - } - return nil -} - -func DeleteActivity(activityID string) error { - if result := database.DB.Where("id = ?", activityID).Delete(&model.UserActivity{}); result.Error != nil { - return result.Error - } - return nil -} - -// ActivityCount represents aggregated counts for charting -type ActivityCount struct { - Date string `json:"date"` - Action string `json:"action"` - Count int `json:"count"` -} - -// GetActivityCountsByDayForUser aggregates last 90 days of activity (messages/reactions) -func GetActivityCountsByDayForUser(userID string) []ActivityCount { - end := time.Now() - start := end.AddDate(0, 0, -89) - start = start.Truncate(time.Microsecond) - - buckets := make(map[string]map[string]int) - for d := start; !d.After(end); d = d.AddDate(0, 0, 1) { - key := d.Format("2006-01-02") - buckets[key] = map[string]int{"message": 0, "reaction": 0} - } - - var activities []model.UserActivity - database.DB.Where("user_id = ? AND created_at >= ?", userID, start).Find(&activities) - for _, a := range activities { - key := a.CreatedAt.Format("2006-01-02") - if _, ok := buckets[key]; !ok { - buckets[key] = map[string]int{} - } - buckets[key][a.Action] = buckets[key][a.Action] + 1 - } - - out := make([]ActivityCount, 0, len(buckets)*2) - for d := start; !d.After(end); d = d.AddDate(0, 0, 1) { - key := d.Format("2006-01-02") - counts := buckets[key] - for action, count := range counts { - out = append(out, ActivityCount{Date: key, Action: action, Count: count}) - } - } - return out -} diff --git a/service/auth_service.go b/service/auth_service.go deleted file mode 100644 index f45bbe9..0000000 --- a/service/auth_service.go +++ /dev/null @@ -1,289 +0,0 @@ -package service - -import ( - "crypto/rsa" - "encoding/base64" - "fmt" - "math/big" - "sentinel/config" - "sentinel/database" - "sentinel/model" - "sentinel/utils" - "strings" - "time" - "unicode" - - "github.com/golang-jwt/jwt/v4" - "github.com/google/uuid" - "golang.org/x/crypto/bcrypt" -) - -func InitializeKeys() { - // Parse the RSA public key - publicKey, err := jwt.ParseRSAPublicKeyFromPEM([]byte(config.RsaPublicKeyString)) - if err != nil { - utils.SugarLogger.Errorln("Failed to parse RSA public key:", err) - } - config.RsaPublicKey = publicKey - // Parse the RSA private key - privateKey, err := jwt.ParseRSAPrivateKeyFromPEM([]byte(config.RsaPrivateKeyString)) - if err != nil { - utils.SugarLogger.Errorln("Failed to parse RSA private key:", err) - } - config.RsaPrivateKey = privateKey - config.RsaPublicKeyJWKS = PublicKeyToJWKS(publicKey) -} - -func PublicKeyToJWKS(publicKey *rsa.PublicKey) map[string]interface{} { - e := base64.RawURLEncoding.EncodeToString(big.NewInt(int64(publicKey.E)).Bytes()) - n := base64.RawURLEncoding.EncodeToString(publicKey.N.Bytes()) - - return map[string]interface{}{ - "keys": []map[string]interface{}{ - { - "kty": "RSA", - "use": "sig", - "alg": "RS256", - "kid": "1", - "n": n, - "e": e, - }, - }, - } -} - -func RegisterEmailPassword(email string, password string) (string, error) { - user := GetUserByEmail(email) - if user.ID == "" { - return "", fmt.Errorf("user does not exist") - } - hash := GetPasswordForEmail(email) - if hash != "" { - return "", fmt.Errorf("email/password already registered") - } - err := ValidatePassword(password) - if err != nil { - return "", err - } - hash, err = HashPassword(password) - if err != nil { - return "", err - } - CreateUserAuth(model.UserAuth{ - ID: user.ID, - Email: email, - Password: hash, - }) - token, err := GenerateAccessToken(user.ID, "sentinel:all", "sentinel", 60*60) - if err != nil { - return "", err - } - return token, nil -} - -func RemovePasswordForEmail(email string) error { - result := database.DB.Table("user_auth").Where("email = ?", email).Delete(&model.UserAuth{}) - return result.Error -} - -func LoginEmailPassword(email string, password string) (string, error) { - user := GetUserByEmail(email) - if user.ID == "" { - return "", fmt.Errorf("user does not exist") - } - hash := GetPasswordForEmail(email) - if hash == "" { - return "", fmt.Errorf("email/password login does not exist, please login with discord") - } - err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) - if err != nil { - utils.SugarLogger.Errorln(err.Error()) - return "", err - } - token, err := GenerateAccessToken(user.ID, "sentinel:all", "sentinel", 60*60) - if err != nil { - return "", err - } - return token, nil -} - -func GetUserIDFromDiscordCode(code string) (string, error) { - accessToken, err := ExchangeCodeForToken(code) - if err != nil { - return "", err - } - user, err := GetDiscordUserFromToken(accessToken.AccessToken) - if err != nil { - return "", err - } - return user.ID, nil -} - -func HashPassword(password string) (string, error) { - hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) - if err != nil { - utils.SugarLogger.Errorln(err.Error()) - return "", err - } - return string(hash), nil -} - -func GenerateAccessToken(userID string, scope string, client_id string, expiresIn int) (string, error) { - scopeList := strings.Split(scope, " ") - filteredScopes := make([]string, 0) - // filter out openid scopes - for _, s := range scopeList { - if !(strings.HasPrefix(s, "openid") || strings.HasPrefix(s, "profile") || strings.HasPrefix(s, "email") || strings.HasPrefix(s, "roles") || strings.HasPrefix(s, "bookstack")) { - filteredScopes = append(filteredScopes, s) - } - } - filteredScope := strings.Join(filteredScopes, " ") - return GenerateJWT(userID, filteredScope, client_id, expiresIn) -} - -func GenerateRefreshToken(userID string, scope string, client_id string, expiresIn int) (string, error) { - scopeList := strings.Split(scope, " ") - // note: keep all scopes, but add refresh_token to the end - scopeList = append(scopeList, "refresh_token") - filteredScope := strings.Join(scopeList, " ") - token, err := GenerateJWT(userID, filteredScope, client_id, expiresIn) - if err != nil { - return "", err - } - err = SaveRefreshToken(token, userID, filteredScope, expiresIn) - if err != nil { - return "", err - } - return token, nil -} - -func GenerateJWT(userID string, scope string, client_id string, expiresIn int) (string, error) { - expirationTime := time.Now().Add(time.Duration(expiresIn) * time.Second) - claims := &model.AuthClaims{ - Scope: scope, - RegisteredClaims: jwt.RegisteredClaims{ - ID: uuid.NewString(), - Subject: userID, - Issuer: "https://sso.gauchoracing.com", - Audience: jwt.ClaimStrings{client_id}, - IssuedAt: jwt.NewNumericDate(time.Now()), - ExpiresAt: jwt.NewNumericDate(expirationTime), - }, - } - - user := GetUserByID(userID) - if strings.Contains(scope, "email") { - claims.Email = user.Email - } - if strings.Contains(scope, "profile") { - claims.Name = user.FirstName + " " + user.LastName - claims.GivenName = user.FirstName - claims.FamilyName = user.LastName - claims.Profile = "https://sso.gauchoracing.com/users/" + user.ID - claims.Picture = user.AvatarURL - claims.EmailVerified = true - claims.Roles = user.Roles - claims.Subteams = []string{} - for _, subteam := range user.Subteams { - claims.Subteams = append(claims.Subteams, subteam.Name) - } - claims.BookstackRoles = append(claims.BookstackRoles, "Editor") - if user.IsInnerCircle() { - claims.BookstackRoles = append(claims.BookstackRoles, "Lead") - } - if user.IsAdmin() { - claims.BookstackRoles = append(claims.BookstackRoles, "Admin") - } - } - - // insanely stupid override to make singlestore work - if client_id == "quZNfANBcdkW" { - claims.Email = GauchoRacingEmailReplace(claims.Email) - } - - token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims) - signedToken, err := token.SignedString(config.RsaPrivateKey) - if err != nil { - utils.SugarLogger.Errorln(err.Error()) - return "", err - } - return signedToken, nil -} - -func ValidateJWT(token string) (*model.AuthClaims, error) { - claims := &model.AuthClaims{} - _, err := jwt.ParseWithClaims(token, claims, func(token *jwt.Token) (interface{}, error) { - return config.RsaPublicKey, nil - }) - if err != nil { - utils.SugarLogger.Errorln(err.Error()) - return nil, err - } - if !ValidateScope(claims.Scope) { - return nil, fmt.Errorf("token has invalid scope") - } - if len(claims.Audience) == 0 { - return nil, fmt.Errorf("token has invalid audience") - } - if claims.Audience[0] != "sentinel" { - if GetClientApplicationByID(claims.Audience[0]).ID == "" { - return nil, fmt.Errorf("token has invalid audience") - } - } - if claims.Audience[0] != "sentinel" && strings.Contains(claims.Scope, "sentinel:all") { - return nil, fmt.Errorf("token has unauthorized scope") - } - return claims, nil -} - -func ValidatePassword(password string) error { - if len(password) < 8 { - return fmt.Errorf("password must be at least 8 characters") - } - if len(password) > 64 { - return fmt.Errorf("password must be at most 64 characters") - } - hasNumber := false - hasCapital := false - for _, char := range password { - if unicode.IsNumber(char) { - hasNumber = true - } - if unicode.IsUpper(char) { - hasCapital = true - } - } - if !hasNumber { - return fmt.Errorf("password must contain at least one number") - } - if !hasCapital { - return fmt.Errorf("password must contain at least one capital letter") - } - return nil -} - -func GetPasswordForEmail(email string) string { - var password string - database.DB.Table("user_auth").Where("email = ?", email).Select("password").Scan(&password) - return password -} - -func GetUserAuthByID(id string) model.UserAuth { - var userAuth model.UserAuth - database.DB.Where("id = ?", id).First(&userAuth) - return userAuth -} - -func GetUserAuthByEmail(email string) model.UserAuth { - var userAuth model.UserAuth - database.DB.Where("email = ?", email).First(&userAuth) - return userAuth -} - -func CreateUserAuth(userAuth model.UserAuth) { - if database.DB.Where("id = ?", userAuth.ID).Updates(&userAuth).RowsAffected == 0 { - database.DB.Create(&userAuth) - } else { - utils.SugarLogger.Infof("UserAuth with id: %s has been updated!", userAuth.ID) - } -} diff --git a/service/discord_service.go b/service/discord_service.go deleted file mode 100644 index cc6cd3b..0000000 --- a/service/discord_service.go +++ /dev/null @@ -1,543 +0,0 @@ -package service - -import ( - "encoding/json" - "fmt" - "io" - "net/http" - "net/url" - "sentinel/config" - "sentinel/model" - "sentinel/utils" - "slices" - "strings" - "time" - - "github.com/bwmarrin/discordgo" -) - -var Discord *discordgo.Session - -func ConnectDiscord() { - dg, err := discordgo.New("Bot " + config.DiscordToken) - if err != nil { - utils.SugarLogger.Infoln("Error creating Discord session, ", err) - return - } - Discord = dg - _, err = Discord.ChannelMessageSend(config.DiscordLogChannel, ":white_check_mark: Sentinel v"+config.Version+" online! `[ENV = "+config.Env+"]` `[PREFIX = "+config.Prefix+"]`") - if err != nil { - utils.SugarLogger.Errorln("Error sending Discord message, ", err) - return - } -} - -func InitializeRoles() { - g, err := Discord.Guild(config.DiscordGuild) - if err != nil { - utils.SugarLogger.Errorln("Error getting guild,", err) - return - } - for _, r := range g.Roles { - if strings.Contains(strings.ToLower(r.Name), "member") { - if strings.Contains(strings.ToLower(r.Name), "team") { - // team member - utils.SugarLogger.Infof("Found Team Member Role: %s", r.ID) - config.TeamMemberRoleID = r.ID - } else { - // member - utils.SugarLogger.Infof("Found Member Role: %s", r.ID) - config.MemberRoleID = r.ID - } - } else if strings.Contains(strings.ToLower(r.Name), "alumni") { - utils.SugarLogger.Infof("Found Alumni Role: %s", r.ID) - config.AlumniRoleID = r.ID - } else if strings.Contains(strings.ToLower(r.Name), "admin") { - utils.SugarLogger.Infof("Found Admin Role: %s", r.ID) - config.AdminRoleID = r.ID - } else if strings.Contains(strings.ToLower(r.Name), "officer") { - utils.SugarLogger.Infof("Found Officer Role: %s", r.ID) - config.OfficerRoleID = r.ID - } else if strings.Contains(strings.ToLower(r.Name), "lead") { - utils.SugarLogger.Infof("Found Lead Role: %s", r.ID) - config.LeadRoleID = r.ID - } else if strings.Contains(strings.ToLower(r.Name), "bot") { - utils.SugarLogger.Infof("Found Bot Role: %s", r.ID) - config.BotRoleID = r.ID - } - } -} - -func SyncRolesForAllUsers() { - members, err := Discord.GuildMembers(config.DiscordGuild, "", 1000) - if err != nil { - utils.SugarLogger.Errorln(err.Error()) - } - count := 0 - for _, member := range members { - user := GetUserByID(member.User.ID) - if user.ID != "" { - SyncDiscordRolesForUser(user.ID, member.Roles) - count++ - } - } - utils.SugarLogger.Infof("Synced roles for %d users", count) -} - -func SetDiscordRolesForUser(userID string, roleIds []string) { - guildMember, err := Discord.GuildMember(config.DiscordGuild, userID) - if err != nil { - utils.SugarLogger.Errorln("Error getting guild member, ", err) - return - } - existingRoles := guildMember.Roles - rolesToAdd := []string{} - rolesToRemove := []string{} - for _, id := range roleIds { - if !contains(existingRoles, id) { - rolesToAdd = append(rolesToAdd, id) - } - } - for _, id := range existingRoles { - if !contains(roleIds, id) { - rolesToRemove = append(rolesToRemove, id) - } - } - utils.SugarLogger.Infof("Adding roles %v, removing roles %v to user %s", rolesToAdd, rolesToRemove, userID) - for _, id := range rolesToAdd { - err := Discord.GuildMemberRoleAdd(config.DiscordGuild, userID, id) - if err != nil { - utils.SugarLogger.Errorln("Error adding role, ", err) - } - } - for _, id := range rolesToRemove { - err := Discord.GuildMemberRoleRemove(config.DiscordGuild, userID, id) - if err != nil { - utils.SugarLogger.Errorln("Error removing role, ", err) - } - } -} - -func SetDiscordNicknameForAllUsers() { - users := GetAllUsers() - for _, user := range users { - SetDiscordNicknameForUser(user.ID) - } -} - -func SetDiscordNicknameForUser(userID string) { - user := GetUserByID(userID) - if user.ID == "" { - utils.SugarLogger.Errorln("User not found") - return - } - nickname := user.FirstName - err := Discord.GuildMemberNickname(config.DiscordGuild, userID, nickname) - if err != nil { - utils.SugarLogger.Errorln("Error setting nickname, ", err) - } - utils.SugarLogger.Infof("Set nickname for user %s to %s", userID, nickname) -} - -func SendMessage(channelID string, message string) { - _, err := Discord.ChannelMessageSend(channelID, message) - if err != nil { - utils.SugarLogger.Errorln(err.Error()) - } -} - -func SendDisappearingMessage(channelID string, message string, delay time.Duration) { - msg, err := Discord.ChannelMessageSend(channelID, message) - if err != nil { - utils.SugarLogger.Errorln(err.Error()) - } - go DelayedMessageDelete(channelID, msg.ID, delay) -} - -func DelayedMessageDelete(channelID string, messageID string, delay time.Duration) { - time.Sleep(delay) - _ = Discord.ChannelMessageDelete(channelID, messageID) -} - -func SendDirectMessage(userID string, message string) { - channel, err := Discord.UserChannelCreate(userID) - if err != nil { - utils.SugarLogger.Errorln(err.Error()) - } - _, err = Discord.ChannelMessageSend(channel.ID, message) - if err != nil { - utils.SugarLogger.Errorln(err.Error()) - } -} - -func DiscordLogNewUser(user model.User) { - var embeds []*discordgo.MessageEmbed - var fields []*discordgo.MessageEmbedField - fields = append(fields, &discordgo.MessageEmbedField{ - Name: "ID", - Value: user.ID, - Inline: false, - }) - fields = append(fields, &discordgo.MessageEmbedField{ - Name: "Name", - Value: user.FirstName + " " + user.LastName, - Inline: true, - }) - fields = append(fields, &discordgo.MessageEmbedField{ - Name: "Email", - Value: user.Email, - Inline: false, - }) - embeds = append(embeds, &discordgo.MessageEmbed{ - Title: "New Member Verified!", - Color: 6609663, - Author: &discordgo.MessageEmbedAuthor{ - IconURL: user.AvatarURL, - }, - Fields: fields, - Thumbnail: &discordgo.MessageEmbedThumbnail{ - URL: user.AvatarURL, - }, - }) - _, err := Discord.ChannelMessageSendEmbeds(config.DiscordLogChannel, embeds) - if err != nil { - utils.SugarLogger.Errorln(err.Error()) - } -} - -func DiscordUserEmbed(user model.User, channelID string) { - guildMember, err := Discord.GuildMember(config.DiscordGuild, user.ID) - if err != nil { - utils.SugarLogger.Errorln("User no longer in the server: " + err.Error()) - DeleteUser(user.ID) - return - } - var topRole *discordgo.Role - var roleStrings []string - for _, role := range guildMember.Roles { - r, _ := Discord.State.Role(config.DiscordGuild, role) - roleStrings = append(roleStrings, r.Name) - if topRole == nil || r.Position > topRole.Position { - topRole = r - } - } - if topRole == nil { - utils.SugarLogger.Errorln("User has no roles, how are they even here lmao") - topRole = &discordgo.Role{ - Name: "No Role", - Color: 0x000000, - } - } - utils.SugarLogger.Infof("%s (%d) %d", topRole.Name, topRole.Position, topRole.Color) - color := topRole.Color - var embeds []*discordgo.MessageEmbed - var fields []*discordgo.MessageEmbedField - fields = append(fields, &discordgo.MessageEmbedField{ - Name: "ID", - Value: user.ID, - Inline: false, - }) - fields = append(fields, &discordgo.MessageEmbedField{ - Name: "Username", - Value: user.Username, - Inline: false, - }) - fields = append(fields, &discordgo.MessageEmbedField{ - Name: "Email", - Value: user.Email, - Inline: false, - }) - fields = append(fields, &discordgo.MessageEmbedField{ - Name: "Roles", - Value: strings.Join(roleStrings, ", "), - Inline: false, - }) - lastActivity := GetLastActivityForUser(user.ID) - if lastActivity.ID != "" { - fields = append(fields, &discordgo.MessageEmbedField{ - Name: "Last Activity", - Value: lastActivity.Action + " on " + lastActivity.CreatedAt.Format("Jan 2, 2006 3:04 PM"), - Inline: false, - }) - } - embeds = append(embeds, &discordgo.MessageEmbed{ - Title: fmt.Sprintf("%s %s", user.FirstName, user.LastName), - Color: color, - Author: &discordgo.MessageEmbedAuthor{ - IconURL: user.AvatarURL, - }, - Fields: fields, - Thumbnail: &discordgo.MessageEmbedThumbnail{ - URL: user.AvatarURL, - }, - }) - _, err = Discord.ChannelMessageSendEmbeds(channelID, embeds) - if err != nil { - utils.SugarLogger.Errorln(err.Error()) - } -} - -func ExchangeCodeForToken(code string) (*model.DiscordAccessTokenResponse, error) { - tokenURL := "https://discord.com/api/oauth2/token" - - data := url.Values{} - data.Set("client_id", config.DiscordClientID) - data.Set("client_secret", config.DiscordClientSecret) - data.Set("grant_type", "authorization_code") - data.Set("code", code) - data.Set("redirect_uri", config.DiscordRedirectURI) - - resp, err := http.PostForm(tokenURL, data) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - body, err := io.ReadAll(resp.Body) - if err != nil { - return nil, err - } - - if resp.StatusCode != 200 { - utils.SugarLogger.Errorln("error exchanging code for token: ", string(body)) - return nil, fmt.Errorf("error exchanging code for token") - } - - var accessToken model.DiscordAccessTokenResponse - err = json.Unmarshal(body, &accessToken) - if err != nil { - return nil, err - } - - return &accessToken, nil -} - -func GetDiscordUserFromToken(accessToken string) (*model.DiscordUser, error) { - userURL := "https://discord.com/api/users/@me" - - req, err := http.NewRequest("GET", userURL, nil) - if err != nil { - return nil, err - } - req.Header.Add("Authorization", "Bearer "+accessToken) - - client := &http.Client{} - resp, err := client.Do(req) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - body, err := io.ReadAll(resp.Body) - if err != nil { - return nil, err - } - - if resp.StatusCode != 200 { - utils.SugarLogger.Errorln("error getting user from token: ", string(body)) - return nil, fmt.Errorf("error getting user from token: %s", string(body)) - } - - var user model.DiscordUser - err = json.Unmarshal(body, &user) - if err != nil { - return nil, err - } - - return &user, nil -} - -func SendUserWelcomeMessage(userID string) { - user := GetUserByID(userID) - if user.ID == "" { - utils.SugarLogger.Errorln("User not found") - return - } - message := fmt.Sprintf("Welcome to Gaucho Racing, %s! We're super excited to have you on board.\n\nPlease take a moment to complete your Sentinel profile at https://sso.gauchoracing.com/users/%s/edit. This is where you will be able to access all our internal tools and resources. The first time you login to Sentinel you will need to use your Discord account. Once you're in you can then set a password to be able to login with email/password in the future. You should have been added to our shared drive already and you can login to the wiki with your Sentinel account.\n\nHere are some important links:\n**Website:** \n**Wiki:** \n**GitHub:** \n**Google Drive:** \n\nIf you have any questions, feel free to ask in <#756738476887638111> or DM an officer or lead.", user.FirstName, user.ID) - SendDirectMessage(userID, message) -} - -func FindAllNonVerifiedUsers() { - members, err := Discord.GuildMembers(config.DiscordGuild, "", 1000) - if err != nil { - utils.SugarLogger.Errorln(err.Error()) - } - sendIds := []string{} - guildMembers := 0 - memberMembers := 0 - verifiedMembers := 0 - for _, member := range members { - user := GetUserByID(member.User.ID) - if user.ID != "" { - utils.SugarLogger.Infof("User found: %s", user.ID) - verifiedMembers++ - } else { - utils.SugarLogger.Infof("User not found: %s", member.User.ID) - sendIds = append(sendIds, member.User.ID) - } - for _, role := range member.Roles { - if role == config.MemberRoleID { - memberMembers++ - } - } - guildMembers++ - } - for _, id := range sendIds { - SendDirectMessage(id, "Hey there Gaucho Racer! It look's like you haven't verified your account yet. Please use the `!verify` command to verify your account before September 7th to avoid any disruption to your server access. You can run this command in any channel in the Gaucho Racing discord server!\n\nHere's the command usage: `!verify `\nAnd here's an example: `!verify Bharat Kathi bkathi@ucsb.edu`") - } - utils.SugarLogger.Infof("Total Members: %d", guildMembers) - utils.SugarLogger.Infof("Members Role: %d", memberMembers) - utils.SugarLogger.Infof("Verified Members: %d", verifiedMembers) -} - -// PopulateDiscordMembers populates the discord roles for all users in the sentinel database -// Can be used for disaster recovery if all user roles are removed from the discord server -func PopulateDiscordMembers() { - users := GetAllUsers() - for _, user := range users { - utils.SugarLogger.Infof("Populating discord member for user %s %s (%s)", user.FirstName, user.LastName, user.Email) - member, err := Discord.GuildMember(config.DiscordGuild, user.ID) - if err != nil { - utils.SugarLogger.Errorf("Error getting discord member for user %s: %s", user.ID, err.Error()) - } - if member != nil { - utils.SugarLogger.Infof("Found user in discord") - utils.SugarLogger.Infof("User has roles: %s", user.Roles) - err := Discord.GuildMemberRoleAdd(config.DiscordGuild, user.ID, config.MemberRoleID) - if err != nil { - utils.SugarLogger.Errorf("Error adding role to user %s: %s", user.Email, err.Error()) - } - if user.HasRole("d_alumni") { - err := Discord.GuildMemberRoleAdd(config.DiscordGuild, user.ID, config.AlumniRoleID) - if err != nil { - utils.SugarLogger.Errorf("Error adding role to user %s: %s", user.Email, err.Error()) - } - } else if user.HasRole("d_team_member") { - err := Discord.GuildMemberRoleAdd(config.DiscordGuild, user.ID, config.TeamMemberRoleID) - if err != nil { - utils.SugarLogger.Errorf("Error adding role to user %s: %s", user.Email, err.Error()) - } - } else if user.HasRole("d_officer") { - err := Discord.GuildMemberRoleAdd(config.DiscordGuild, user.ID, config.OfficerRoleID) - if err != nil { - utils.SugarLogger.Errorf("Error adding role to user %s: %s", user.Email, err.Error()) - } - } else if user.HasRole("d_lead") { - err := Discord.GuildMemberRoleAdd(config.DiscordGuild, user.ID, config.LeadRoleID) - if err != nil { - utils.SugarLogger.Errorf("Error adding role to user %s: %s", user.Email, err.Error()) - } - } else if user.HasRole("d_admin") { - err := Discord.GuildMemberRoleAdd(config.DiscordGuild, user.ID, config.AdminRoleID) - if err != nil { - utils.SugarLogger.Errorf("Error adding role to user %s: %s", user.Email, err.Error()) - } - } - utils.SugarLogger.Infof("Added main roles to user %s", user.Email) - for _, subteam := range user.Subteams { - err := Discord.GuildMemberRoleAdd(config.DiscordGuild, user.ID, subteam.ID) - if err != nil { - utils.SugarLogger.Errorf("Error adding role to user %s: %s", user.Email, err.Error()) - } - } - utils.SugarLogger.Infof("Added subteam roles to user %s", user.Email) - } else { - utils.SugarLogger.Infof("User not found in discord: %s", user.ID) - } - } -} - -// CleanDiscordMembers does the following: -// 1. Remove all roles from users who are in the discord server but not in the sentinel database -// 2. Remove all roles from users who no longer have the member or alumni role in the sentinel database -// 3. Remove all sentinel roles from users who are no longer a member of the discord server -// -// NOTE: This will NOT kick anyone from the discord server nor DELETE any users from the sentinel database -func CleanDiscordMembers() { - members, err := Discord.GuildMembers(config.DiscordGuild, "", 1000) - if err != nil { - utils.SugarLogger.Errorln(err.Error()) - } - for _, member := range members { - // Check if member is a bot - if member.User.Bot { - utils.SugarLogger.Infof("Discord user %s (%s) is a bot, skipping", member.User.ID, member.Nick) - SendMessage(config.DiscordLogChannel, fmt.Sprintf("Discord user %s (%s) is a bot, skipping", member.User.ID, member.Nick)) - // make sure they have the bot role - err := Discord.GuildMemberRoleAdd(config.DiscordGuild, member.User.ID, config.BotRoleID) - if err != nil { - utils.SugarLogger.Errorf("Error adding bot role to user %s: %s", member.User.ID, err.Error()) - SendMessage(config.DiscordLogChannel, fmt.Sprintf("Error adding bot role to user %s: %s", member.User.ID, err.Error())) - } - continue - } - user := GetUserByID(member.User.ID) - if user.ID == "" { - // User is in the discord server but not in the sentinel database - // Remove all roles from user - if len(member.Roles) > 0 { - utils.SugarLogger.Infof("Discord user not found in Sentinel: %s (%s)", member.User.ID, member.Nick) - SendMessage(config.DiscordLogChannel, fmt.Sprintf("Discord user not found in Sentinel: %s (%s)", member.User.ID, member.Nick)) - for _, role := range member.Roles { - err := Discord.GuildMemberRoleRemove(config.DiscordGuild, member.User.ID, role) - if err != nil { - utils.SugarLogger.Errorf("Error removing role %s from user %s (%s): %s", role, member.User.ID, member.Nick, err.Error()) - SendMessage(config.DiscordLogChannel, fmt.Sprintf("Error removing role %s from user %s (%s): %s", role, member.User.ID, member.Nick, err.Error())) - } - } - utils.SugarLogger.Infof("Removed all roles from user %s (%s) as they are not in the sentinel database", member.User.ID, member.Nick) - SendMessage(config.DiscordLogChannel, fmt.Sprintf("Removed all roles from user %s (%s) as they are not in the sentinel database", member.User.ID, member.Nick)) - } - } else if !(user.IsMember() || user.IsAlumni()) { - // User is in the sentinel database but not a member or alumni - // Remove all roles from user - if len(member.Roles) > 0 { - if slices.Contains(member.Roles, config.MemberRoleID) || slices.Contains(member.Roles, config.AdminRoleID) { - // User is actually a member or alumni, looks like we hit an inconsistency between discord and sentinel roles (bruh edge case) - utils.SugarLogger.Infof("Discord user has roles that are not in Sentinel: %s (%s), Discord roles: %v, Sentinel roles: %v", member.User.ID, member.Nick, member.Roles, user.Roles) - SendMessage(config.DiscordLogChannel, fmt.Sprintf("Discord user has roles that are not in Sentinel: %s (%s), Discord roles: %v, Sentinel roles: %v", member.User.ID, member.Nick, member.Roles, user.Roles)) - // trigger a sync of roles for this user - SyncDiscordRolesForUser(user.ID, member.Roles) - continue - } - utils.SugarLogger.Infof("Discord user not a member or alumni: %s (%s)", member.User.ID, member.Nick) - SendMessage(config.DiscordLogChannel, fmt.Sprintf("Discord user not a member or alumni: %s (%s)", member.User.ID, member.Nick)) - for _, role := range member.Roles { - err := Discord.GuildMemberRoleRemove(config.DiscordGuild, member.User.ID, role) - if err != nil { - utils.SugarLogger.Errorf("Error removing role %s from user %s (%s): %s", role, member.User.ID, member.Nick, err.Error()) - SendMessage(config.DiscordLogChannel, fmt.Sprintf("Error removing role %s from user %s (%s): %s", role, member.User.ID, member.Nick, err.Error())) - } - } - utils.SugarLogger.Infof("Removed all roles from user %s (%s) as they are not a member or alumni", member.User.ID, member.Nick) - SendMessage(config.DiscordLogChannel, fmt.Sprintf("Removed all roles from user %s (%s) as they are not a member or alumni", member.User.ID, member.Nick)) - } - } - } - for _, user := range GetAllUsers() { - member, err := Discord.GuildMember(config.DiscordGuild, user.ID) - if err != nil { - utils.SugarLogger.Errorf("Error getting discord member for user %s: %s", user.ID, err.Error()) - continue - } - if member == nil { - // User is in the sentinel database but no longer in the discord server - // Delete user roles from sentinel (except if alumni), other jobs will take care of the rest - if len(user.Roles) == 1 && user.IsAlumni() { - utils.SugarLogger.Infof("User %s is an alumni and has only the alumni role in Sentinel, skipping", user.ID) - SendMessage(config.DiscordLogChannel, fmt.Sprintf("User %s is an alumni and has only the alumni role in Sentinel, skipping", user.ID)) - continue - } - if len(user.Roles) > 0 { - utils.SugarLogger.Infof("Removing sentinel roles from user %s as they are no longer in the discord server", user.ID) - SendMessage(config.DiscordLogChannel, fmt.Sprintf("Removing sentinel roles from user %s as they are no longer in the discord server", user.ID)) - roles := []string{} - if user.IsAlumni() { - roles = append(roles, "d_alumni") - } - SetRolesForUser(user.ID, roles) - SendMessage(config.DiscordLogChannel, fmt.Sprintf("Updated sentinel roles for user %s (%s) as they are no longer in the discord server: %v", user.ID, fmt.Sprintf("%s %s", user.FirstName, user.LastName), roles)) - } - } - } -} diff --git a/service/drive_service.go b/service/drive_service.go deleted file mode 100644 index 94a8cb2..0000000 --- a/service/drive_service.go +++ /dev/null @@ -1,657 +0,0 @@ -package service - -import ( - "context" - "encoding/base64" - "fmt" - "log" - "sentinel/config" - "sentinel/model" - "sentinel/utils" - "sort" - "strings" - "time" - - "golang.org/x/oauth2/google" - "google.golang.org/api/drive/v3" - "google.golang.org/api/option" - "google.golang.org/api/sheets/v4" -) - -var DriveClient *drive.Service -var SheetClient *sheets.Service - -func InitializeDrive() { - ctx := context.Background() - decoded, err := base64.StdEncoding.DecodeString(config.DriveServiceAccount) - if err != nil { - utils.SugarLogger.Fatalln("Error decoding service account: %v\n", err) - } - creds, err := google.CredentialsFromJSON(ctx, []byte(decoded), drive.DriveScope) - if err != nil { - log.Fatalf("Unable to parse client secret file to config: %v", err) - } - - srv, err := drive.NewService(ctx, option.WithCredentials(creds)) - if err != nil { - log.Fatalf("Unable to create Drive service: %v", err) - } - DriveClient = srv - - srv2, err := sheets.NewService(ctx, option.WithCredentials(creds)) - if err != nil { - log.Fatalf("Unable to create Sheets service: %v", err) - } - SheetClient = srv2 -} - -// GetDriveMemberPermission returns the permissions of the user in the shared drive. -// The included fields are nextPageToken, permissions(id, type, emailAddress, role). -func GetDriveMemberPermission(driveID string, email string) (*drive.Permission, error) { - resp, err := DriveClient.Permissions.List(driveID). - SupportsAllDrives(true). - Fields("nextPageToken,permissions(id, type, emailAddress, role)"). - Do() - if err != nil { - utils.SugarLogger.Errorln(err) - return nil, err - } - for _, perm := range resp.Permissions { - if perm.EmailAddress == email { - return perm, nil - } - } - nextPageToken := resp.NextPageToken - for nextPageToken != "" { - resp, err = DriveClient.Permissions.List(driveID). - SupportsAllDrives(true). - Fields("nextPageToken,permissions(id, type, emailAddress, role)"). - PageToken(nextPageToken). - Do() - if err != nil { - utils.SugarLogger.Errorln(err) - return nil, err - } - for _, perm := range resp.Permissions { - if perm.EmailAddress == email { - return perm, nil - } - } - nextPageToken = resp.NextPageToken - } - return nil, nil -} - -// RemoveMemberFromDrive removes a user from the shared drive. -func RemoveMemberFromDrive(driveID string, email string) error { - perm, err := GetDriveMemberPermission(driveID, email) - if err != nil { - utils.SugarLogger.Errorln(err) - return err - } else if perm == nil { - return fmt.Errorf("user not found in drive") - } - err = DriveClient.Permissions.Delete(driveID, perm.Id).SupportsAllDrives(true).Do() - if err != nil { - utils.SugarLogger.Errorln(err) - return err - } - SendMessage(config.DiscordLogChannel, fmt.Sprintf("Removed %s from drive", email)) - return nil -} - -// AddMemberToDrive adds a user to the shared drive with the specified role. -// The role can be "organizer", "fileOrganizer", "writer", "commenter", or "reader". -func AddMemberToDrive(driveID string, email string, role string) error { - perm, err := GetDriveMemberPermission(driveID, email) - if err != nil { - utils.SugarLogger.Errorln(err) - return err - } else if perm != nil { - return fmt.Errorf("user already in drive") - } - perm = &drive.Permission{ - EmailAddress: email, - Role: role, - Type: "user", - } - resp, err := DriveClient.Permissions.Create(driveID, perm).SupportsAllDrives(true).Do() - if err != nil { - utils.SugarLogger.Errorln(err) - return err - } - utils.SugarLogger.Infof("Permission ID: %s", resp.Id) - SendMessage(config.DiscordLogChannel, fmt.Sprintf("Added %s to drive with `%s` role", email, role)) - return nil -} - -// PopulateDriveMembers adds all users to the shared drive and the leads drive with the appropriate role. -// Useful for when you accidentally remove everyone from the shared drive lmfao -func PopulateDriveMembers() { - users := GetAllUsers() - for _, user := range users { - if user.IsInnerCircle() { - AddMemberToDrive(config.SharedDriveID, user.Email, "organizer") - AddMemberToDrive(config.LeadsDriveID, user.Email, "organizer") - } else if user.IsMember() { - AddMemberToDrive(config.SharedDriveID, user.Email, "writer") - } - } -} - -// RemoveInactiveMembersFromDrive removes inactive users from the shared drive. -func RemoveInactiveMembersFromDrive() { - keepEmails := []string{ - "sentinel-drive@sentinel-416604.iam.gserviceaccount.com", - "ucsantabarbarasae@gmail.com", - "team@gauchoracing.com", - } - resp, err := DriveClient.Permissions.List(config.SharedDriveID). - SupportsAllDrives(true). - Fields("nextPageToken,permissions(id, type, emailAddress, role)"). - Do() - if err != nil { - utils.SugarLogger.Errorln(err) - return - } - for _, perm := range resp.Permissions { - if contains(keepEmails, perm.EmailAddress) { - utils.SugarLogger.Infof("Keeping %s in drive", perm.EmailAddress) - SendMessage(config.DiscordLogChannel, fmt.Sprintf("Keeping %s in drive", perm.EmailAddress)) - continue - } - user := GetUserByEmail(perm.EmailAddress) - if user.ID == "" { - utils.SugarLogger.Infof("Removing %s from drive", perm.EmailAddress) - RemoveMemberFromDrive(config.SharedDriveID, perm.EmailAddress) - } else { - inactivityThreshold := time.Now().AddDate(0, 0, -180) - if user.UpdatedAt.After(inactivityThreshold) { - continue - } - lastActivity := GetLastActivityForUser(user.ID) - if lastActivity.ID != "" && lastActivity.CreatedAt.After(inactivityThreshold) { - continue - } - lastLogins := GetLastNLoginsForUser(user.ID, 1) - if len(lastLogins) > 0 && lastLogins[0].ID != "" && lastLogins[0].CreatedAt.After(inactivityThreshold) { - continue - } - utils.SugarLogger.Infof("Notifying user %s and removing them from drive due to inactivity.", perm.EmailAddress) - RemoveMemberFromDrive(config.SharedDriveID, perm.EmailAddress) - SendDirectMessage(user.ID, "You have been automatically removed from our shared Google Drive! Due to Google Drive's member limits, we periodically reset access after 180 days of Sentinel or Discord inactivity. **However, you can easily regain access by using the** `!drive` **command in our #roles channel!**") - SendMessage(config.DiscordLogChannel, fmt.Sprintf("Sent inactivity google drive removal notice to %s (%s %s)", user.ID, user.FirstName, user.LastName)) - } - } - nextPageToken := resp.NextPageToken - for nextPageToken != "" { - resp, err = DriveClient.Permissions.List(config.SharedDriveID). - SupportsAllDrives(true). - Fields("nextPageToken,permissions(id, type, emailAddress, role)"). - PageToken(nextPageToken). - Do() - if err != nil { - utils.SugarLogger.Errorln(err) - return - } - for _, perm := range resp.Permissions { - if contains(keepEmails, perm.EmailAddress) { - utils.SugarLogger.Infof("Keeping %s in drive", perm.EmailAddress) - SendMessage(config.DiscordLogChannel, fmt.Sprintf("Keeping %s in drive", perm.EmailAddress)) - continue - } - user := GetUserByEmail(perm.EmailAddress) - if user.ID == "" { - utils.SugarLogger.Infof("Removing %s from drive", perm.EmailAddress) - RemoveMemberFromDrive(config.SharedDriveID, perm.EmailAddress) - } else { - inactivityThreshold := time.Now().AddDate(0, 0, -180) - if user.UpdatedAt.After(inactivityThreshold) { - continue - } - lastActivity := GetLastActivityForUser(user.ID) - if lastActivity.ID != "" && lastActivity.CreatedAt.After(inactivityThreshold) { - continue - } - lastLogins := GetLastNLoginsForUser(user.ID, 1) - if len(lastLogins) > 0 && lastLogins[0].ID != "" && lastLogins[0].CreatedAt.After(inactivityThreshold) { - continue - } - utils.SugarLogger.Infof("Notifying user %s and removing them from drive due to inactivity.", perm.EmailAddress) - RemoveMemberFromDrive(config.SharedDriveID, perm.EmailAddress) - SendDirectMessage(user.ID, "You have been automatically removed from our shared Google Drive! Due to Google Drive's member limits, we periodically reset access after 180 days of Sentinel or Discord inactivity. **However, you can easily regain access by using the** `!drive` **command in our #roles channel!**") - SendMessage(config.DiscordLogChannel, fmt.Sprintf("Sent inactivity google drive removal notice to %s (%s %s)", user.ID, user.FirstName, user.LastName)) - } - } - nextPageToken = resp.NextPageToken - } -} - -// CleanDriveMembers removes users from the shared drive that are not in the member directory. -func CleanDriveMembers() { - keepEmails := []string{ - "sentinel-drive@sentinel-416604.iam.gserviceaccount.com", - "ucsantabarbarasae@gmail.com", - "team@gauchoracing.com", - } - - resp, err := DriveClient.Permissions.List(config.SharedDriveID). - SupportsAllDrives(true). - Fields("nextPageToken,permissions(id, type, emailAddress, role)"). - Do() - if err != nil { - utils.SugarLogger.Errorln(err) - return - } - for _, perm := range resp.Permissions { - if contains(keepEmails, perm.EmailAddress) { - utils.SugarLogger.Infof("Keeping %s in drive", perm.EmailAddress) - SendMessage(config.DiscordLogChannel, fmt.Sprintf("Keeping %s in drive", perm.EmailAddress)) - continue - } - user := GetUserByEmail(perm.EmailAddress) - if user.ID == "" { - utils.SugarLogger.Infof("Removing %s from drive", perm.EmailAddress) - RemoveMemberFromDrive(config.SharedDriveID, perm.EmailAddress) - } else if user.IsInnerCircle() { - if perm.Role != "organizer" { - // User needs organizer role but doesn't currently have it - utils.SugarLogger.Infof("Updating %s drive permission to organizer", perm.EmailAddress) - RemoveMemberFromDrive(config.SharedDriveID, perm.EmailAddress) - AddMemberToDrive(config.SharedDriveID, perm.EmailAddress, "organizer") - } - } else if user.IsMember() { - if perm.Role != "writer" { - // User needs writer role but doesn't currently have it - utils.SugarLogger.Infof("Updating %s drive permission to writer", perm.EmailAddress) - RemoveMemberFromDrive(config.SharedDriveID, perm.EmailAddress) - AddMemberToDrive(config.SharedDriveID, perm.EmailAddress, "writer") - } - } else { - // User is not a member, remove from drive - utils.SugarLogger.Infof("Removing %s from drive", perm.EmailAddress) - RemoveMemberFromDrive(config.SharedDriveID, perm.EmailAddress) - } - } - nextPageToken := resp.NextPageToken - for nextPageToken != "" { - resp, err = DriveClient.Permissions.List(config.SharedDriveID). - SupportsAllDrives(true). - Fields("nextPageToken,permissions(id, type, emailAddress, role)"). - PageToken(nextPageToken). - Do() - if err != nil { - utils.SugarLogger.Errorln(err) - return - } - for _, perm := range resp.Permissions { - if contains(keepEmails, perm.EmailAddress) { - utils.SugarLogger.Infof("Keeping %s in drive", perm.EmailAddress) - SendMessage(config.DiscordLogChannel, fmt.Sprintf("Keeping %s in drive", perm.EmailAddress)) - continue - } - user := GetUserByEmail(perm.EmailAddress) - if user.ID == "" { - utils.SugarLogger.Infof("Removing %s from drive", perm.EmailAddress) - RemoveMemberFromDrive(config.SharedDriveID, perm.EmailAddress) - } else if user.IsInnerCircle() { - if perm.Role != "organizer" { - // User needs organizer role but doesn't currently have it - utils.SugarLogger.Infof("Updating %s drive permission to organizer", perm.EmailAddress) - RemoveMemberFromDrive(config.SharedDriveID, perm.EmailAddress) - AddMemberToDrive(config.SharedDriveID, perm.EmailAddress, "organizer") - } - } else if user.IsMember() { - if perm.Role != "writer" { - // User needs writer role but doesn't currently have it - utils.SugarLogger.Infof("Updating %s drive permission to writer", perm.EmailAddress) - RemoveMemberFromDrive(config.SharedDriveID, perm.EmailAddress) - AddMemberToDrive(config.SharedDriveID, perm.EmailAddress, "writer") - } - } else { - // User is not a member, remove from drive - utils.SugarLogger.Infof("Removing %s from drive", perm.EmailAddress) - RemoveMemberFromDrive(config.SharedDriveID, perm.EmailAddress) - } - } - nextPageToken = resp.NextPageToken - } -} - -// CleanLeadsDriveMembers removes users from the leads drive that are not in the member directory. -func CleanLeadsDriveMembers() { - keepEmails := []string{ - "sentinel-drive@sentinel-416604.iam.gserviceaccount.com", - "ucsantabarbarasae@gmail.com", - "team@gauchoracing.com", - } - - resp, err := DriveClient.Permissions.List(config.LeadsDriveID). - SupportsAllDrives(true). - Fields("nextPageToken,permissions(id, type, emailAddress, role)"). - Do() - if err != nil { - utils.SugarLogger.Errorln(err) - return - } - for _, perm := range resp.Permissions { - if contains(keepEmails, perm.EmailAddress) { - utils.SugarLogger.Infof("Keeping %s in leads drive", perm.EmailAddress) - SendMessage(config.DiscordLogChannel, fmt.Sprintf("Keeping %s in leads drive", perm.EmailAddress)) - continue - } - user := GetUserByEmail(perm.EmailAddress) - if user.ID == "" { - utils.SugarLogger.Infof("Removing %s from leads drive", perm.EmailAddress) - RemoveMemberFromDrive(config.LeadsDriveID, perm.EmailAddress) - } else if user.IsInnerCircle() { - if perm.Role != "organizer" { - // User needs organizer role but doesn't currently have it - utils.SugarLogger.Infof("Updating %s leads drive permission to organizer", perm.EmailAddress) - RemoveMemberFromDrive(config.LeadsDriveID, perm.EmailAddress) - AddMemberToDrive(config.LeadsDriveID, perm.EmailAddress, "organizer") - } - } else { - // User is not inner circle, remove from leads drive - utils.SugarLogger.Infof("Removing %s from leads drive", perm.EmailAddress) - RemoveMemberFromDrive(config.LeadsDriveID, perm.EmailAddress) - } - } - nextPageToken := resp.NextPageToken - for nextPageToken != "" { - resp, err = DriveClient.Permissions.List(config.LeadsDriveID). - SupportsAllDrives(true). - Fields("nextPageToken,permissions(id, type, emailAddress, role)"). - PageToken(nextPageToken). - Do() - if err != nil { - utils.SugarLogger.Errorln(err) - return - } - for _, perm := range resp.Permissions { - if contains(keepEmails, perm.EmailAddress) { - utils.SugarLogger.Infof("Keeping %s in leads drive", perm.EmailAddress) - SendMessage(config.DiscordLogChannel, fmt.Sprintf("Keeping %s in leads drive", perm.EmailAddress)) - continue - } - user := GetUserByEmail(perm.EmailAddress) - if user.ID == "" { - utils.SugarLogger.Infof("Removing %s from leads drive", perm.EmailAddress) - RemoveMemberFromDrive(config.LeadsDriveID, perm.EmailAddress) - } else if user.IsInnerCircle() { - if perm.Role != "organizer" { - // User needs organizer role but doesn't currently have it - utils.SugarLogger.Infof("Updating %s leads drive permission to organizer", perm.EmailAddress) - RemoveMemberFromDrive(config.LeadsDriveID, perm.EmailAddress) - AddMemberToDrive(config.LeadsDriveID, perm.EmailAddress, "organizer") - } - } else { - // User is not inner circle, remove from leads drive - utils.SugarLogger.Infof("Removing %s from leads drive", perm.EmailAddress) - RemoveMemberFromDrive(config.LeadsDriveID, perm.EmailAddress) - } - } - nextPageToken = resp.NextPageToken - } -} - -func PopulateMemberDirectorySheet() { - // Helper function to clear and populate a sheet - populateUserSheet := func(sheetName string, users []model.User) { - // Get sheet ID by name - spreadsheet, err := SheetClient.Spreadsheets.Get(config.MemberDirectorySheetID).Do() - if err != nil { - utils.SugarLogger.Errorf("Unable to get spreadsheet: %v", err) - return - } - - sheetId := -1 - for _, sheet := range spreadsheet.Sheets { - if sheet.Properties.Title == sheetName { - utils.SugarLogger.Infof("Found sheet %s: %v", sheet.Properties.Title, sheet.Properties.SheetId) - sheetId = int(sheet.Properties.SheetId) - break - } - } - if sheetId == -1 { - utils.SugarLogger.Errorf("Sheet %s not found", sheetName) - return - } - - // Clear existing data using sheet ID - clearRequest := &sheets.BatchUpdateSpreadsheetRequest{ - Requests: []*sheets.Request{ - { - UpdateCells: &sheets.UpdateCellsRequest{ - Range: &sheets.GridRange{ - SheetId: int64(sheetId), - StartRowIndex: 5, // A6 starts at index 5 - StartColumnIndex: 0, // A column - EndColumnIndex: 15, // O column - }, - Fields: "userEnteredValue", - }, - }, - }, - } - - _, err = SheetClient.Spreadsheets.BatchUpdate(config.MemberDirectorySheetID, clearRequest).Do() - if err != nil { - utils.SugarLogger.Errorf("Unable to clear data from sheet %s: %v", sheetName, err) - return - } - - // Sort users by first name - sort.Slice(users, func(i, j int) bool { - return users[i].FirstName < users[j].FirstName - }) - - // Prepare values - values := make([][]interface{}, len(users)) - for i, user := range users { - subteams := make([]string, len(user.Subteams)) - for j, subteam := range user.Subteams { - subteams[j] = subteam.Name - } - subteamString := strings.Join(subteams, ", ") - roleString := strings.Join(user.Roles, ", ") - values[i] = []interface{}{ - user.ID, - user.FirstName, - user.LastName, - user.Email, - user.PhoneNumber, - user.Gender, - user.Birthday, - user.GraduateLevel, - user.GraduationYear, - user.Major, - user.ShirtSize, - user.JacketSize, - user.SAERegistrationNumber, - subteamString, - roleString, - } - } - - // Write data (can still use A1 notation for updates as it's more convenient) - writeRange := fmt.Sprintf("'%s'!A6:O", sheetName) - writeRequest := &sheets.ValueRange{ - Values: values, - } - _, err = SheetClient.Spreadsheets.Values.Update(config.MemberDirectorySheetID, writeRange, writeRequest). - ValueInputOption("RAW"). - Do() - if err != nil { - utils.SugarLogger.Errorf("Unable to write data to sheet %s: %v", sheetName, err) - return - } - - utils.SugarLogger.Infof("Successfully populated %s sheet with %d users", sheetName, len(users)) - SendMessage(config.DiscordLogChannel, fmt.Sprintf("Successfully populated `%s` sheet with %d users", sheetName, len(users))) - } - - allUsers := GetAllUsers() - - // Filter users for each sheet - var memberUsers []model.User - var alumniUsers []model.User - var leadUsers []model.User - var specialAdvisorUsers []model.User - - for _, user := range allUsers { - if user.IsMember() { - memberUsers = append(memberUsers, user) - } - if user.HasRole("d_alumni") { - alumniUsers = append(alumniUsers, user) - } - if user.IsLead() || user.IsOfficer() { - leadUsers = append(leadUsers, user) - } - if user.IsSpecialAdvisor() { - specialAdvisorUsers = append(specialAdvisorUsers, user) - } - } - - // Populate each sheet - populateUserSheet("All", allUsers) - populateUserSheet("Members", memberUsers) - populateUserSheet("Alumni", alumniUsers) - populateUserSheet("Leads", leadUsers) - populateUserSheet("Special Advisors", specialAdvisorUsers) -} - -func PopulateMailingListSheet() { - // Helper function to clear and populate a sheet - - populateMailingListSheet := func(sheetName string, entries []model.MailingList) { - // Get sheet ID by name - spreadsheet, err := SheetClient.Spreadsheets.Get(config.MailingListSheetID).Do() - if err != nil { - utils.SugarLogger.Errorf("Unable to get spreadsheet: %v", err) - return - } - - sheetId := -1 - for _, sheet := range spreadsheet.Sheets { - if sheet.Properties.Title == sheetName { - utils.SugarLogger.Infof("Found sheet %s: %v", sheet.Properties.Title, sheet.Properties.SheetId) - sheetId = int(sheet.Properties.SheetId) - break - } - } - if sheetId == -1 { - utils.SugarLogger.Errorf("Sheet %s not found", sheetName) - return - } - - // Clear existing data using sheet ID - clearRequest := &sheets.BatchUpdateSpreadsheetRequest{ - Requests: []*sheets.Request{ - { - UpdateCells: &sheets.UpdateCellsRequest{ - Range: &sheets.GridRange{ - SheetId: int64(sheetId), - StartRowIndex: 5, // A6 starts at index 5 - StartColumnIndex: 0, // A column - EndColumnIndex: 5, // F column - }, - Fields: "userEnteredValue", - }, - }, - }, - } - - _, err = SheetClient.Spreadsheets.BatchUpdate(config.MailingListSheetID, clearRequest).Do() - if err != nil { - utils.SugarLogger.Errorf("Unable to clear data from sheet %s: %v", sheetName, err) - return - } - - // Prepare values - values := make([][]interface{}, len(entries)) - for i, entry := range entries { - values[i] = []interface{}{ - entry.Email, - entry.FirstName, - entry.LastName, - entry.Role, - entry.Organization, - } - } - - // Write data (can still use A1 notation for updates as it's more convenient) - writeRange := fmt.Sprintf("'%s'!A6:O", sheetName) - writeRequest := &sheets.ValueRange{ - Values: values, - } - _, err = SheetClient.Spreadsheets.Values.Update(config.MailingListSheetID, writeRange, writeRequest). - ValueInputOption("RAW"). - Do() - if err != nil { - utils.SugarLogger.Errorf("Unable to write data to sheet %s: %v", sheetName, err) - return - } - - utils.SugarLogger.Infof("Successfully populated %s sheet with %d emails", sheetName, len(entries)) - SendMessage(config.DiscordLogChannel, fmt.Sprintf("Successfully populated `%s` sheet with %d emails", sheetName, len(entries))) - } - - allMailingListEntries := GetAllMailingListEntries() - populateMailingListSheet("All", allMailingListEntries) - - externalMailingListEntries := GetExternalMailingListEntries() - populateMailingListSheet("External", externalMailingListEntries) -} - -// UpdateTeamMembers checks the Team Members google sheet and gives the Team Member Discord role to all users with a TRUE cell -func UpdateTeamMembers() { - sheetName := "New Members" - readRange := fmt.Sprintf("'%s'!A:H", sheetName) - resp, err := SheetClient.Spreadsheets.Values.Get(config.TeamMemberMasterListSheetID, readRange).Do() - if err != nil { - utils.SugarLogger.Errorf("Unable to read sheet: %v", err) - return - } - - var emails []string - for i, row := range resp.Values { - // Skip column names - if i == 0 { - continue - } - - // Skip rows that aren't filled until column H - if len(row) < 8 { - continue - } - - // Check if column H is TRUE - if hValue, ok := row[7].(string); ok && hValue == "TRUE" { - if email, ok := row[1].(string); ok && email != "" { - emails = append(emails, email) - } - } - } - count := 0 - for _, email := range emails { - user := GetUserByEmail(email) - - if user.ID == "" { - continue - } - if user.IsAlumni() || user.IsTeamMember() || !user.IsMember() { - continue - } - utils.SugarLogger.Infof("Updating %s with Team Member role", email) - err := Discord.GuildMemberRoleAdd(config.DiscordGuild, user.ID, config.TeamMemberRoleID) - if err != nil { - utils.SugarLogger.Errorln("Error adding role, ", err) - continue - } - count++ - } - utils.SugarLogger.Infof("Gave %d users the Team Member role", count) - SendMessage(config.DiscordLogChannel, fmt.Sprintf("Gave %d users the Team Member role", count)) -} diff --git a/service/github_service.go b/service/github_service.go deleted file mode 100644 index 3bd7ea2..0000000 --- a/service/github_service.go +++ /dev/null @@ -1,219 +0,0 @@ -package service - -import ( - "encoding/json" - "fmt" - "io" - "net/http" - "sentinel/config" - "sentinel/database" - "sentinel/model" - "sentinel/utils" - "strings" -) - -func GetAllGithubUsers() ([]*model.GithubUser, error) { - req, err := http.NewRequest("GET", "https://api.github.com/orgs/gaucho-racing/members", nil) - if err != nil { - utils.SugarLogger.Errorln(err) - return nil, err - } - req.Header.Set("Authorization", "Bearer "+config.GithubToken) - client := &http.Client{} - resp, err := client.Do(req) - if err != nil { - utils.SugarLogger.Errorln(err) - return nil, err - } - defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - body, _ := io.ReadAll(resp.Body) - return nil, fmt.Errorf("failed to get all GitHub users: %s", string(body)) - } - var githubUsers []*model.GithubUser - err = json.NewDecoder(resp.Body).Decode(&githubUsers) - if err != nil { - utils.SugarLogger.Errorln(err) - return nil, err - } - return githubUsers, nil -} - -func GetGithubStatusForUser(userID string) (*model.GithubOrgUser, error) { - username := getGithubUsernameForUser(userID) - if username == "" { - return nil, fmt.Errorf("user does not have a GitHub account linked") - } - req, err := http.NewRequest("GET", "https://api.github.com/orgs/gaucho-racing/memberships/"+username, nil) - if err != nil { - utils.SugarLogger.Errorln(err) - return nil, err - } - req.Header.Set("Authorization", "Bearer "+config.GithubToken) - client := &http.Client{} - resp, err := client.Do(req) - if err != nil { - utils.SugarLogger.Errorln(err) - return nil, err - } - defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - body, _ := io.ReadAll(resp.Body) - return nil, fmt.Errorf("failed to get user membership status from GitHub organization: %s", string(body)) - } - var githubUser *model.GithubOrgUser - err = json.NewDecoder(resp.Body).Decode(&githubUser) - if err != nil { - utils.SugarLogger.Errorln(err) - return nil, err - } - return githubUser, nil -} - -func AddUserToGithub(userID string, username string) error { - user := GetUserByID(userID) - reqBody := `{"role": "member"}` - if user.IsInnerCircle() { - reqBody = `{"role": "admin"}` - } - req, err := http.NewRequest("PUT", "https://api.github.com/orgs/gaucho-racing/memberships/"+username, strings.NewReader(reqBody)) - if err != nil { - utils.SugarLogger.Errorln(err) - return err - } - req.Header.Set("Authorization", "Bearer "+config.GithubToken) - client := &http.Client{} - resp, err := client.Do(req) - if err != nil { - utils.SugarLogger.Errorln(err) - return err - } - defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - body, _ := io.ReadAll(resp.Body) - return fmt.Errorf("failed to add user to GitHub organization: %s", string(body)) - } - addGithubUsernameToRoles(username, userID) - SendMessage(config.DiscordLogChannel, fmt.Sprintf("Added %s (%s) to GitHub organization: %s", username, user.Email, reqBody)) - return nil -} - -func RemoveUserFromGithub(userID string, username string) error { - req, err := http.NewRequest("DELETE", "https://api.github.com/orgs/gaucho-racing/memberships/"+username, nil) - if err != nil { - utils.SugarLogger.Errorln(err) - return err - } - req.Header.Set("Authorization", "Bearer "+config.GithubToken) - client := &http.Client{} - resp, err := client.Do(req) - if err != nil { - utils.SugarLogger.Errorln(err) - return err - } - defer resp.Body.Close() - if resp.StatusCode != http.StatusNoContent { - if resp.StatusCode == http.StatusNotFound { - return nil - } - body, _ := io.ReadAll(resp.Body) - return fmt.Errorf("failed to remove user from GitHub organization: %s", string(body)) - } - removeGithubUsernameFromRoles(username, userID) - SendMessage(config.DiscordLogChannel, fmt.Sprintf("Removed %s from GitHub organization", username)) - return nil -} - -func PopulateGithubMembers() { - users := GetAllUsers() - for _, user := range users { - ghUser := getGithubUsernameForUser(user.ID) - if ghUser != "" { - utils.SugarLogger.Infof("User %s has github username %s", user.ID, ghUser) - AddUserToGithub(user.ID, ghUser) - } - } -} - -func CleanGithubMembers() { - keepUsers := []string{ - "gauchoracing", - } - githubUsers, err := GetAllGithubUsers() - if err != nil { - utils.SugarLogger.Errorln(err) - return - } - for _, ghUser := range githubUsers { - if contains(keepUsers, ghUser.Login) { - utils.SugarLogger.Infof("Keeping user %s in GitHub organization", ghUser.Login) - SendMessage(config.DiscordLogChannel, fmt.Sprintf("Keeping user %s in GitHub organization", ghUser.Login)) - continue - } - user := getUserForGithubUsername(ghUser.Login) - if user.ID == "" { - utils.SugarLogger.Infof("Removing user %s from GitHub organization", ghUser.Login) - RemoveUserFromGithub(user.ID, ghUser.Login) - } else if user.IsInnerCircle() { - // if inner circle, make sure they are admin instead of member - orgUser, err := GetGithubStatusForUser(user.ID) - if err != nil { - utils.SugarLogger.Errorf("Error getting GitHub status for user %s: %s", user.ID, err.Error()) - continue - } - if orgUser.Role != "admin" { - AddUserToGithub(user.ID, ghUser.Login) - } - } else if user.IsMember() || user.IsAlumni() { - // if member or alumni, make sure they are member instead of admin - orgUser, err := GetGithubStatusForUser(user.ID) - if err != nil { - utils.SugarLogger.Errorf("Error getting GitHub status for user %s: %s", user.ID, err.Error()) - continue - } - if orgUser.Role != "member" { - AddUserToGithub(user.ID, ghUser.Login) - } - } else { - // User is not longer a member, remove from GitHub organization - utils.SugarLogger.Infof("Removing user %s from GitHub organization", ghUser.Login) - RemoveUserFromGithub(user.ID, ghUser.Login) - } - } -} - -func addGithubUsernameToRoles(ghUsername string, userID string) { - roles := GetRolesForUser(userID) - for _, role := range roles { - if strings.HasPrefix(role, "github_") { - roles = removeValue(roles, role) - } - } - roles = append(roles, "github_"+ghUsername) - SetRolesForUser(userID, roles) -} - -func removeGithubUsernameFromRoles(ghUsername string, userID string) { - roles := GetRolesForUser(userID) - newRoles := removeValue(roles, "github_"+ghUsername) - SetRolesForUser(userID, newRoles) -} - -func getGithubUsernameForUser(userID string) string { - roles := GetRolesForUser(userID) - for _, role := range roles { - if strings.HasPrefix(role, "github_") { - return strings.TrimPrefix(role, "github_") - } - } - return "" -} - -func getUserForGithubUsername(ghUsername string) model.User { - var userID string - database.DB.Table("user_role").Where("role = ?", "github_"+ghUsername).Select("user_id").Scan(&userID) - if userID == "" { - return model.User{} - } - return GetUserByID(userID) -} diff --git a/service/login_service.go b/service/login_service.go deleted file mode 100644 index 2d6d2bc..0000000 --- a/service/login_service.go +++ /dev/null @@ -1,64 +0,0 @@ -package service - -import ( - "sentinel/database" - "sentinel/model" - "sentinel/utils" - - "github.com/google/uuid" -) - -func GetAllLogins() []model.UserLogin { - var logins []model.UserLogin - database.DB.Order("created_at DESC").Find(&logins) - return logins -} - -func GetLoginsForUser(userID string) []model.UserLogin { - var logins []model.UserLogin - database.DB.Where("user_id = ?", userID).Order("created_at DESC").Find(&logins) - return logins -} - -func GetLastNLoginsForUser(userID string, n int) []model.UserLogin { - var logins []model.UserLogin - database.DB.Where("user_id = ?", userID).Order("created_at DESC").Limit(n).Find(&logins) - return logins -} - -func GetLoginsForDestination(destination string) []model.UserLogin { - var logins []model.UserLogin - database.DB.Where("destination = ?", destination).Order("created_at DESC").Find(&logins) - return logins -} - -func GetLastNLoginsForDestination(destination string, n int) []model.UserLogin { - var logins []model.UserLogin - database.DB.Where("destination = ?", destination).Order("created_at DESC").Limit(n).Find(&logins) - return logins -} - -func GetLastLoginForUserToDestinationWithScopes(userID string, destination string, scope string) model.UserLogin { - var login model.UserLogin - database.DB.Where("user_id = ? AND destination = ? AND scope = ?", userID, destination, scope).Order("created_at DESC").First(&login) - return login -} - -func GetLoginByID(loginID string) model.UserLogin { - var login model.UserLogin - database.DB.Where("id = ?", loginID).First(&login) - return login -} - -func CreateLogin(login model.UserLogin) error { - if login.ID == "" { - login.ID = uuid.New().String() - } - if database.DB.Where("id = ?", login.ID).Updates(&login).RowsAffected == 0 { - utils.SugarLogger.Infof("New login from %s for %s", login.UserID, login.Destination) - if result := database.DB.Create(&login); result.Error != nil { - return result.Error - } - } - return nil -} diff --git a/service/mailing_list_service.go b/service/mailing_list_service.go deleted file mode 100644 index 7fa07a6..0000000 --- a/service/mailing_list_service.go +++ /dev/null @@ -1,47 +0,0 @@ -package service - -import ( - "sentinel/database" - "sentinel/model" - "sentinel/utils" -) - -func CreateMailingListEntry(entry model.MailingList) error { - if database.DB.Where("email = ?", entry.Email).Updates(&entry).RowsAffected == 0 { - utils.SugarLogger.Infoln("New entry created with email: " + entry.Email) - if result := database.DB.Create(&entry); result.Error != nil { - return result.Error - } - } else { - utils.SugarLogger.Infoln("Entry with email: " + entry.Email + " has been updated!") - } - - return nil -} - -func GetAllMailingListEntries() []model.MailingList { - var entries []model.MailingList - database.DB.Find(&entries) - - // Merge with sentinel users - users := GetAllUsers() - for _, user := range users { - var entry model.MailingList - entry.Email = user.Email - entry.FirstName = user.FirstName - entry.LastName = user.LastName - entry.Role = user.GetHighestRole() - entry.Organization = "Gaucho Racing" - - entries = append(entries, entry) - } - - return entries -} - -func GetExternalMailingListEntries() []model.MailingList { - var entries []model.MailingList - database.DB.Where("organization != ?", "Gaucho Racing").Find(&entries) - - return entries -} diff --git a/service/oauth_service.go b/service/oauth_service.go deleted file mode 100644 index 09af999..0000000 --- a/service/oauth_service.go +++ /dev/null @@ -1,223 +0,0 @@ -package service - -import ( - "crypto/rand" - "fmt" - "sentinel/database" - "sentinel/model" - "sentinel/utils" - "strings" - "time" -) - -func GetAllClientApplications() []model.ClientApplication { - var clientApplications []model.ClientApplication - database.DB.Order("name asc").Find(&clientApplications) - for i := range clientApplications { - clientApplications[i].RedirectURIs = GetRedirectURIsForClientApplication(clientApplications[i].ID) - } - return clientApplications -} - -func GetClientApplicationsForUser(userID string) []model.ClientApplication { - var clientApplications []model.ClientApplication - database.DB.Where("user_id = ?", userID).Order("name asc").Find(&clientApplications) - for i := range clientApplications { - clientApplications[i].RedirectURIs = GetRedirectURIsForClientApplication(clientApplications[i].ID) - } - return clientApplications -} - -func GetClientApplicationByID(clientID string) model.ClientApplication { - var clientApplication model.ClientApplication - database.DB.Where("id = ?", clientID).First(&clientApplication) - clientApplication.RedirectURIs = GetRedirectURIsForClientApplication(clientID) - return clientApplication -} - -func CreateClientApplication(clientApplication model.ClientApplication) (model.ClientApplication, error) { - if clientApplication.ID == "" { - clientApplication.ID = generateCryptoString(12) - clientApplication.Secret = generateCryptoString(32) - } else { - existing := GetClientApplicationByID(clientApplication.ID) - if existing.ID != "" { - clientApplication.Secret = existing.Secret - } else { - return model.ClientApplication{}, fmt.Errorf("client application with id: %s does not exist", clientApplication.ID) - } - } - if clientApplication.Name == "" { - return model.ClientApplication{}, fmt.Errorf("client application name cannot be empty") - } - user := GetUserByID(clientApplication.UserID) - if user.ID == "" { - return model.ClientApplication{}, fmt.Errorf("user with id: %s does not exist", clientApplication.UserID) - } - if database.DB.Where("id = ?", clientApplication.ID).Updates(&clientApplication).RowsAffected == 0 { - utils.SugarLogger.Infof("New client application created with id: %s", clientApplication.ID) - if result := database.DB.Create(&clientApplication); result.Error != nil { - return model.ClientApplication{}, result.Error - } - } else { - utils.SugarLogger.Infof("Client application with id: %s has been updated!", clientApplication.ID) - } - SetRedirectURIsForClientApplication(clientApplication.ID, clientApplication.RedirectURIs) - return GetClientApplicationByID(clientApplication.ID), nil -} - -func DeleteClientApplication(clientID string) error { - if result := database.DB.Where("id = ?", clientID).Delete(&model.ClientApplication{}); result.Error != nil { - return result.Error - } - SetRedirectURIsForClientApplication(clientID, []string{}) - return nil -} - -func GetRedirectURIsForClientApplication(clientID string) []string { - var redirectURIs []model.ClientApplicationRedirectURI - database.DB.Where("client_application_id = ?", clientID).Find(&redirectURIs) - uriStrings := make([]string, len(redirectURIs)) - for i, uri := range redirectURIs { - uriStrings[i] = uri.RedirectURI - } - return uriStrings -} - -func SetRedirectURIsForClientApplication(clientID string, redirectURIs []string) []string { - existingURIs := GetRedirectURIsForClientApplication(clientID) - for _, nr := range redirectURIs { - if !contains(existingURIs, nr) { - database.DB.Create(&model.ClientApplicationRedirectURI{ - ClientApplicationID: clientID, - RedirectURI: nr, - }) - } - } - for _, er := range existingURIs { - if !contains(redirectURIs, er) { - database.DB.Where("client_application_id = ? AND redirect_uri = ?", clientID, er).Delete(&model.ClientApplicationRedirectURI{}) - } - } - return GetRedirectURIsForClientApplication(clientID) -} - -func generateCryptoString(length int) string { - const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" - b := make([]byte, length) - _, err := rand.Read(b) - if err != nil { - panic(err) - } - for i := range b { - b[i] = charset[int(b[i])%len(charset)] - } - return string(b) -} - -func ValidateRedirectURI(uri string, clientID string) bool { - validUris := GetRedirectURIsForClientApplication(clientID) - return contains(validUris, uri) -} - -func ValidateScope(scopes string) bool { - validScopes := []string{} - for k := range model.ValidOauthScopes { - validScopes = append(validScopes, k) - } - inputScopes := strings.Split(scopes, " ") - for _, scope := range inputScopes { - if !contains(validScopes, scope) { - return false - } - } - return true -} - -func GenerateAuthorizationCode(clientID, userID, scope string) (model.AuthorizationCode, error) { - code := generateCryptoString(8) - expiresAt := time.Now().Add(5 * time.Minute) - - // Check if scope contains "oidc" and add "user:read" if it does - scopes := strings.Split(scope, " ") - if contains(scopes, "oidc") && !contains(scopes, "user:read") { - scopes = append(scopes, "user:read") - } - updatedScope := strings.Join(scopes, " ") - - authCode := model.AuthorizationCode{ - Code: code, - ClientID: clientID, - UserID: userID, - Scope: updatedScope, - ExpiresAt: utils.WithPrecision(expiresAt), - } - result := database.DB.Create(&authCode) - if result.Error != nil { - return authCode, result.Error - } - return authCode, nil -} - -func VerifyAuthorizationCode(code string) (model.AuthorizationCode, error) { - var authCode model.AuthorizationCode - database.DB.Where("code = ?", code).First(&authCode) - if authCode.Code == "" { - return model.AuthorizationCode{}, fmt.Errorf("invalid code") - } - defer database.DB.Delete(&authCode) - if time.Now().After(authCode.ExpiresAt) { - return model.AuthorizationCode{}, fmt.Errorf("invalid code") - } - return authCode, nil -} - -func GenerateIDToken(userID string, scope string, client_id string, expiresIn int) (string, error) { - scopeList := strings.Split(scope, " ") - filteredScopes := make([]string, 0) - // only include openid scopes - for _, s := range scopeList { - if strings.HasPrefix(s, "openid") || strings.HasPrefix(s, "profile") || strings.HasPrefix(s, "email") || strings.HasPrefix(s, "roles") || strings.HasPrefix(s, "bookstack") { - filteredScopes = append(filteredScopes, s) - } - } - filteredScopes = append(filteredScopes, "user:read") - filteredScope := strings.Join(filteredScopes, " ") - return GenerateJWT(userID, filteredScope, client_id, expiresIn) -} - -func SaveRefreshToken(token string, userID string, scope string, expiresIn int) error { - expiresAt := time.Now().Add(time.Duration(expiresIn) * time.Second) - refreshToken := model.RefreshToken{ - Token: token, - UserID: userID, - Scope: scope, - Revoked: false, - ExpiresAt: utils.WithPrecision(expiresAt), - } - result := database.DB.Create(&refreshToken) - if result.Error != nil { - return result.Error - } - return nil -} - -func ValidateRefreshToken(token string) bool { - var refreshToken model.RefreshToken - database.DB.Where("token = ?", token).First(&refreshToken) - if refreshToken.Token == "" { - return false - } - if refreshToken.Revoked { - return false - } - if time.Now().After(refreshToken.ExpiresAt) { - return false - } - return true -} - -func RevokeRefreshToken(token string) error { - database.DB.Model(&model.RefreshToken{}).Where("token = ?", token).Update("revoked", true) - return nil -} diff --git a/service/role_service.go b/service/role_service.go deleted file mode 100644 index 38a597e..0000000 --- a/service/role_service.go +++ /dev/null @@ -1,132 +0,0 @@ -package service - -import ( - "fmt" - "sentinel/config" - "sentinel/database" - "sentinel/model" - "sentinel/utils" - "strings" - "time" -) - -func GetRolesForUser(userID string) []string { - var roles []model.UserRole - var roleNames = make([]string, 0) - result := database.DB.Where("user_id = ?", userID).Find(&roles) - if result.Error != nil { - return roleNames - } - for _, r := range roles { - roleNames = append(roleNames, r.Role) - } - return roleNames -} - -func GetDiscordRolesForUser(userID string) []string { - var roles []model.UserRole - var roleNames = make([]string, 0) - result := database.DB.Where("user_id = ? AND role LIKE ?", userID, "d_%").Find(&roles) - if result.Error != nil { - return roleNames - } - for _, r := range roles { - roleNames = append(roleNames, r.Role) - } - return roleNames -} - -func SetRolesForUser(userID string, roles []string) []string { - existingRoles := GetRolesForUser(userID) - for _, nr := range roles { - if !contains(existingRoles, nr) { - result := database.DB.Create(&model.UserRole{ - UserID: userID, - Role: nr, - CreatedAt: time.Time{}, - }) - if result.Error != nil { - utils.SugarLogger.Errorln(result.Error.Error()) - } - } - } - for _, er := range existingRoles { - if !contains(roles, er) { - database.DB.Where("user_id = ? AND role = ?", userID, er).Delete(&model.UserRole{}) - } - } - return GetRolesForUser(userID) -} - -// SyncDiscordRolesForUser syncs user's roles from Discord with Sentinel -// This should NOT modify the user's Discord roles -// Any role conflicts should be resolved by the OnGuildMemberUpdate callback -func SyncDiscordRolesForUser(userID string, roleIds []string) { - subteamRoles := make([]model.UserSubteam, 0) - roles := GetRolesForUser(userID) - for _, role := range roles { - if strings.HasPrefix(role, "d_") { - roles = removeValue(roles, role) - } - } - for _, id := range roleIds { - subteam := GetSubteamByID(id) - if subteam.ID != "" { - subteamRoles = append(subteamRoles, model.UserSubteam{ - UserID: userID, - RoleID: subteam.ID, - }) - } else if id == config.AdminRoleID { - roles = append(roles, "d_admin") - } else if id == config.OfficerRoleID { - roles = append(roles, "d_officer") - } else if id == config.LeadRoleID { - roles = append(roles, "d_lead") - } else if id == config.SpecialAdvisorRoleID { - roles = append(roles, "d_special_advisor") - } else if id == config.TeamMemberRoleID { - roles = append(roles, "d_team_member") - } else if id == config.MemberRoleID { - roles = append(roles, "d_member") - } else if id == config.AlumniRoleID { - roles = append(roles, "d_alumni") - } - } - SetSubteamsForUser(userID, subteamRoles) - SetRolesForUser(userID, roles) - - user := GetUserByID(userID) - finalRoles := GetRolesForUser(userID) - finalSubteams := GetSubteamsForUser(userID) - subteamNames := make([]string, 0) - for _, s := range finalSubteams { - subteamNames = append(subteamNames, s.Name) - } - SendMessage(config.DiscordLogChannel, fmt.Sprintf("Synced roles for %s (%s),\nroles: %v, \nsubteams: %v", userID, fmt.Sprintf("%s %s", user.FirstName, user.LastName), finalRoles, subteamNames)) -} - -func RemoveAllSubteamDiscordRolesForUser(userID string) { - subteams := GetSubteamsForUser(userID) - for _, subteam := range subteams { - Discord.GuildMemberRoleRemove(config.DiscordGuild, userID, subteam.ID) - } -} - -func contains(s []string, e string) bool { - for _, a := range s { - if a == e { - return true - } - } - return false -} - -func removeValue(s []string, value string) []string { - result := []string{} - for _, v := range s { - if v != value { - result = append(result, v) - } - } - return result -} diff --git a/service/subteam_service.go b/service/subteam_service.go deleted file mode 100644 index 39e09ef..0000000 --- a/service/subteam_service.go +++ /dev/null @@ -1,86 +0,0 @@ -package service - -import ( - "sentinel/config" - "sentinel/database" - "sentinel/model" - "sentinel/utils" - "strings" -) - -func GetSubteamsForUser(userID string) []model.Subteam { - var userSubteams []model.UserSubteam - database.DB.Where("user_id = ?", userID).Find(&userSubteams) - var subteams = make([]model.Subteam, 0) - for i := range userSubteams { - subteams = append(subteams, GetSubteamByID(userSubteams[i].RoleID)) - } - return subteams -} - -func SetSubteamsForUser(userID string, subteams []model.UserSubteam) error { - database.DB.Where("user_id = ?", userID).Delete(&model.UserSubteam{}) - for _, r := range subteams { - if result := database.DB.Create(&r); result.Error != nil { - return result.Error - } - } - return nil -} - -func GetAllSubteams() []model.Subteam { - var subteams []model.Subteam - database.DB.Find(&subteams) - return subteams -} - -func GetSubteamByID(subteamID string) model.Subteam { - var subteam model.Subteam - database.DB.Where("id = ?", subteamID).Find(&subteam) - return subteam -} - -func GetSubteamByName(subteamName string) model.Subteam { - var subteam model.Subteam - database.DB.Where("name = ?", subteamName).Find(&subteam) - return subteam -} - -func CreateSubteam(subteam model.Subteam) error { - if database.DB.Where("id = ?", subteam.ID).Updates(&subteam).RowsAffected == 0 { - utils.SugarLogger.Infoln("New subteam created with id: " + subteam.ID) - if result := database.DB.Create(&subteam); result.Error != nil { - return result.Error - } - } else { - utils.SugarLogger.Infoln("Subteam with id: " + subteam.ID + " has been updated!") - } - return nil -} - -func DeleteAllSubteams() error { - if result := database.DB.Where("1 = 1").Delete(&model.Subteam{}); result.Error != nil { - return result.Error - } - return nil -} - -func InitializeSubteams() { - g, err := Discord.Guild(config.DiscordGuild) - if err != nil { - utils.SugarLogger.Errorln("Error getting guild,", err) - return - } - DeleteAllSubteams() - for _, r := range g.Roles { - for _, name := range config.SubteamRoleNames { - if strings.Contains(strings.ToLower(r.Name), strings.ToLower(name)) { - utils.SugarLogger.Infof("Found subteam role: %s for %s", r.ID, name) - CreateSubteam(model.Subteam{ - ID: r.ID, - Name: name, - }) - } - } - } -} diff --git a/service/user_service.go b/service/user_service.go deleted file mode 100644 index 0c4942a..0000000 --- a/service/user_service.go +++ /dev/null @@ -1,118 +0,0 @@ -package service - -import ( - "fmt" - "sentinel/config" - "sentinel/database" - "sentinel/model" - "sentinel/utils" - "sort" - "strings" - - "github.com/lithammer/fuzzysearch/fuzzy" -) - -func GetAllUsers() []model.User { - var users []model.User - database.DB.Order("first_name").Find(&users) - for i := range users { - users[i].Subteams = GetSubteamsForUser(users[i].ID) - users[i].Roles = GetRolesForUser(users[i].ID) - } - return users -} - -func GetUserByID(userID string) model.User { - var user model.User - database.DB.Where("id = ?", userID).Find(&user) - user.Subteams = GetSubteamsForUser(user.ID) - user.Roles = GetRolesForUser(user.ID) - return user -} - -func GetUserByUsername(username string) model.User { - var user model.User - database.DB.Where("username = ?", username).Find(&user) - user.Subteams = GetSubteamsForUser(user.ID) - user.Roles = GetRolesForUser(user.ID) - return user -} - -func GetUserByEmail(email string) model.User { - var user model.User - database.DB.Where("email = ?", email).Find(&user) - user.Subteams = GetSubteamsForUser(user.ID) - user.Roles = GetRolesForUser(user.ID) - return user -} - -func CreateUser(user model.User, setRoles bool) error { - if user.ID == "" { - return fmt.Errorf("user id cannot be empty") - } else if user.Email == "" { - return fmt.Errorf("user email cannot be empty") - } - if database.DB.Where("id = ?", user.ID).Updates(&user).RowsAffected == 0 { - utils.SugarLogger.Infoln("New user created with id: " + user.ID) - if result := database.DB.Create(&user); result.Error != nil { - return result.Error - } - go DiscordLogNewUser(user) - } else { - utils.SugarLogger.Infoln("User with id: " + user.ID + " has been updated!") - } - if setRoles { - SetRolesForUser(user.ID, user.Roles) - } - return nil -} - -func DeleteUser(userID string) error { - if result := database.DB.Where("id = ?", userID).Delete(&model.User{}); result.Error != nil { - return result.Error - } - SetSubteamsForUser(userID, []model.UserSubteam{}) - SetRolesForUser(userID, []string{}) - result := database.DB.Table("user_auth").Where("id = ?", userID).Delete(&model.UserAuth{}) - if result.Error != nil { - return result.Error - } - return nil -} - -func SearchUsers(search string) []model.User { - utils.SugarLogger.Infof("Searching for users with: %s", search) - var users []model.User - userStrings := []string{} - allUsers := GetAllUsers() - for _, user := range allUsers { - userStrings = append(userStrings, fmt.Sprintf("%s %s %s %s %s", user.ID, user.Username, user.FirstName, user.LastName, user.Email)) - } - matches := fuzzy.RankFindNormalizedFold(search, userStrings) - sort.Sort(matches) - utils.SugarLogger.Infof("Found %d matches", len(matches)) - for i := 0; i < 5 && i < len(matches); i++ { - users = append(users, allUsers[matches[i].OriginalIndex]) - } - return users -} - -func IncompleteProfileReminder() { - allUsers := GetAllUsers() - for _, user := range allUsers { - if user.FirstName == "" || user.LastName == "" || user.Email == "" || user.GraduationYear == 0 || user.GraduateLevel == "" || user.Major == "" || user.ShirtSize == "" || user.JacketSize == "" { - utils.SugarLogger.Infof("User %s has incomplete profile", user.ID) - SendDirectMessage(user.ID, fmt.Sprintf("Hey there %s! It look's like you haven't completed your Sentinel profile yet. Please go to https://sso.gauchoracing.com/users/%s/edit to complete it when you get a chance. It only takes a few minutes and saves us a lot of trouble later on :pray: Let us know if you need any help.", user.FirstName, user.ID)) - SendMessage(config.DiscordLogChannel, fmt.Sprintf("Sent incomplete profile reminder to %s (%s %s)", user.ID, user.FirstName, user.LastName)) - } - } -} - -func GauchoRacingEmailReplace(email string) string { - if strings.HasSuffix(email, "@ucsb.edu") { - return strings.TrimSuffix(email, "@ucsb.edu") + "@gauchoracing.com" - } else if email == "ucsantabarbarasae@gmail.com" { - return "team@gauchoracing.com" - } - return email -} diff --git a/service/wiki_service.go b/service/wiki_service.go deleted file mode 100644 index b1cf2a0..0000000 --- a/service/wiki_service.go +++ /dev/null @@ -1,277 +0,0 @@ -package service - -import ( - "bytes" - "encoding/json" - "fmt" - "io" - "net/http" - "sentinel/config" - "sentinel/database" - "sentinel/model" - "sentinel/utils" - "strconv" - "strings" -) - -func GetAllWikiUsers() ([]model.WikiUser, error) { - var userResponse model.WikiArrayResponse[model.WikiUser] - var users []model.WikiUser - - client := &http.Client{} - req, err := http.NewRequest("GET", "https://wiki.gauchoracing.com/api/users", nil) - if err != nil { - utils.SugarLogger.Errorln(err) - return users, err - } - req.Header.Set("Authorization", "Token "+config.WikiToken) - - resp, err := client.Do(req) - if err != nil { - utils.SugarLogger.Errorln(err) - return users, err - } - defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - body, _ := io.ReadAll(resp.Body) - return users, fmt.Errorf("failed to get all wiki users: %s", string(body)) - } - - err = json.NewDecoder(resp.Body).Decode(&userResponse) - if err != nil { - utils.SugarLogger.Errorln(err) - return users, err - } - users = userResponse.Data - - return users, nil -} - -func GetWikiUserByID(id int) (model.WikiUser, error) { - var user model.WikiUser - - client := &http.Client{} - req, err := http.NewRequest("GET", fmt.Sprintf("https://wiki.gauchoracing.com/api/users/%d", id), nil) - if err != nil { - utils.SugarLogger.Errorln(err) - return user, err - } - req.Header.Set("Authorization", "Token "+config.WikiToken) - - resp, err := client.Do(req) - if err != nil { - utils.SugarLogger.Errorln(err) - return user, err - } - defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - body, _ := io.ReadAll(resp.Body) - return user, fmt.Errorf("failed to get wiki user: %s", string(body)) - } - - err = json.NewDecoder(resp.Body).Decode(&user) - if err != nil { - utils.SugarLogger.Errorln(err) - return user, err - } - return user, nil -} - -func CreateWikiUser(input model.WikiUserCreate) (int, error) { - jsonData, err := json.Marshal(input) - if err != nil { - utils.SugarLogger.Errorln(err) - return 0, err - } - - client := &http.Client{} - req, err := http.NewRequest("POST", "https://wiki.gauchoracing.com/api/users", bytes.NewBuffer(jsonData)) - if err != nil { - utils.SugarLogger.Errorln(err) - return 0, err - } - req.Header.Set("Authorization", "Token "+config.WikiToken) - req.Header.Set("Content-Type", "application/json") - - resp, err := client.Do(req) - if err != nil { - utils.SugarLogger.Errorln(err) - return 0, err - } - defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - body, _ := io.ReadAll(resp.Body) - return 0, fmt.Errorf("failed to create wiki user: %s", string(body)) - } - - var user model.WikiUser - err = json.NewDecoder(resp.Body).Decode(&user) - if err != nil { - utils.SugarLogger.Errorln(err) - return 0, err - } - utils.SugarLogger.Infof("Created wiki user: %s (%s)", user.Name, user.Email) - SendMessage(config.DiscordLogChannel, fmt.Sprintf("Created wiki user: %s (%s)", user.Name, user.Email)) - return user.ID, nil -} - -func UpdateWikiUser(id int, input model.WikiUserCreate) error { - jsonData, err := json.Marshal(input) - if err != nil { - utils.SugarLogger.Errorln(err) - return err - } - - client := &http.Client{} - req, err := http.NewRequest("PUT", fmt.Sprintf("https://wiki.gauchoracing.com/api/users/%d", id), bytes.NewBuffer(jsonData)) - if err != nil { - utils.SugarLogger.Errorln(err) - return err - } - req.Header.Set("Authorization", "Token "+config.WikiToken) - req.Header.Set("Content-Type", "application/json") - - resp, err := client.Do(req) - if err != nil { - utils.SugarLogger.Errorln(err) - return err - } - defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - body, _ := io.ReadAll(resp.Body) - return fmt.Errorf("failed to update wiki user: %s", string(body)) - } - utils.SugarLogger.Infof("Updated wiki user: %d", id) - SendMessage(config.DiscordLogChannel, fmt.Sprintf("Updated wiki user: %d", id)) - return nil -} - -func DeleteWikiUser(id int) error { - client := &http.Client{} - req, err := http.NewRequest("DELETE", fmt.Sprintf("https://wiki.gauchoracing.com/api/users/%d", id), nil) - if err != nil { - utils.SugarLogger.Errorln(err) - return err - } - req.Header.Set("Authorization", "Token "+config.WikiToken) - - resp, err := client.Do(req) - if err != nil { - utils.SugarLogger.Errorln(err) - return err - } - defer resp.Body.Close() - if resp.StatusCode != http.StatusNoContent { - body, _ := io.ReadAll(resp.Body) - return fmt.Errorf("failed to delete wiki user: %s", string(body)) - } - utils.SugarLogger.Infof("Deleted wiki user: %d", id) - SendMessage(config.DiscordLogChannel, fmt.Sprintf("Deleted wiki user: %d", id)) - return nil -} - -func CreateWikiUserWithPassword(password string, userID string) error { - user := GetUserByID(userID) - if user.ID == "" { - return fmt.Errorf("user not found") - } - roles := []int{int(model.WikiRoleEditor)} - if user.IsInnerCircle() { - roles = append(roles, int(model.WikiRoleLead)) - } - input := model.WikiUserCreate{ - Name: user.FirstName + " " + user.LastName, - Email: user.Email, - Roles: roles, - ExternalAuthID: userID, - Password: password, - SendInvite: false, - } - id, err := CreateWikiUser(input) - if err != nil { - return err - } - addWikiIDToRoles(id, userID) - return nil -} - -func UpdateWikiUserWithPassword(password string, userID string) error { - user := GetUserByID(userID) - if user.ID == "" { - return fmt.Errorf("user not found") - } - roles := []int{int(model.WikiRoleEditor)} - if user.IsInnerCircle() { - roles = append(roles, int(model.WikiRoleLead)) - } - wikiID := getWikiIDForUser(userID) - if wikiID == 0 { - return fmt.Errorf("wiki user not found") - } - wikiUser, err := GetWikiUserByID(wikiID) - if err != nil { - return err - } - wikiCreateUser := model.WikiUserCreate{ - Name: user.FirstName + " " + user.LastName, - Email: user.Email, - Roles: roles, - ExternalAuthID: userID, - Password: password, - SendInvite: false, - } - return UpdateWikiUser(wikiUser.ID, wikiCreateUser) -} - -func CleanWikiMembers() { - keepUsers := []string{"ucsantabarbarasae@gmail.com"} - wikiUsers, err := GetAllWikiUsers() - if err != nil { - utils.SugarLogger.Errorln(err) - return - } - for _, wikiUser := range wikiUsers { - senUser := getUserForWikiID(wikiUser.ID) - if senUser.ID == "" && !contains(keepUsers, wikiUser.Email) { - utils.SugarLogger.Infof("Deleting wiki user: %s (%s)", wikiUser.Name, wikiUser.Email) - DeleteWikiUser(wikiUser.ID) - removeWikiIDFromRoles(wikiUser.ID, senUser.ID) - } - } -} - -func addWikiIDToRoles(wikiID int, userID string) { - roles := GetRolesForUser(userID) - roles = append(roles, "wiki_"+fmt.Sprint(wikiID)) - SetRolesForUser(userID, roles) -} - -func removeWikiIDFromRoles(wikiID int, userID string) { - roles := GetRolesForUser(userID) - newRoles := removeValue(roles, "wiki_"+fmt.Sprint(wikiID)) - SetRolesForUser(userID, newRoles) -} - -func getWikiIDForUser(userID string) int { - roles := GetRolesForUser(userID) - for _, role := range roles { - if strings.HasPrefix(role, "wiki_") { - id, err := strconv.Atoi(strings.TrimPrefix(role, "wiki_")) - if err != nil { - utils.SugarLogger.Errorln(err) - return 0 - } - return id - } - } - return 0 -} - -func getUserForWikiID(wikiID int) model.User { - var userID string - database.DB.Table("user_role").Where("role = ?", "wiki_"+fmt.Sprint(wikiID)).Select("user_id").Scan(&userID) - if userID == "" { - return model.User{} - } - return GetUserByID(userID) -} diff --git a/utils/config.go b/utils/config.go deleted file mode 100644 index 0df7253..0000000 --- a/utils/config.go +++ /dev/null @@ -1,67 +0,0 @@ -package utils - -import ( - "sentinel/config" - "strings" -) - -func VerifyConfig() { - if config.Port == "" { - config.Port = "9999" - SugarLogger.Infof("PORT is not set, defaulting to %s", config.Port) - } - if config.DatabaseHost == "" { - config.DatabaseHost = "localhost" - SugarLogger.Infof("DATABASE_HOST is not set, defaulting to %s", config.DatabaseHost) - } - if config.DatabasePort == "" { - config.DatabasePort = "3306" - SugarLogger.Infof("DATABASE_PORT is not set, defaulting to %s", config.DatabasePort) - } - if config.DatabaseUser == "" { - config.DatabaseUser = "root" - SugarLogger.Infof("DATABASE_USER is not set, defaulting to %s", config.DatabaseUser) - } - if config.DatabasePassword == "" { - config.DatabasePassword = "password" - SugarLogger.Infof("DATABASE_PASSWORD is not set, defaulting to %s", config.DatabasePassword) - } - if config.DiscordToken == "" { - SugarLogger.Fatalf("DISCORD_TOKEN is not set") - } - if config.DiscordGuild == "" { - SugarLogger.Fatalf("DISCORD_GUILD is not set") - } - if config.DiscordLogChannel == "" { - SugarLogger.Fatalf("DISCORD_LOG_CHANNEL is not set") - } - if config.GithubToken == "" { - SugarLogger.Fatalf("GITHUB_PAT is not set") - } - if config.DriveServiceAccount == "" { - SugarLogger.Fatalf("DRIVE_SERVICE_ACCOUNT is not set") - } - if config.WikiToken == "" { - SugarLogger.Fatalf("WIKI_TOKEN is not set") - } - if config.RsaPublicKeyString == "" { - SugarLogger.Fatalf("RSA_PUBLIC_KEY is not set") - } - config.RsaPublicKeyString = strings.ReplaceAll(config.RsaPublicKeyString, "\\n", "\n") - if config.RsaPrivateKeyString == "" { - SugarLogger.Fatalf("RSA_PRIVATE_KEY is not set") - } - config.RsaPrivateKeyString = strings.ReplaceAll(config.RsaPrivateKeyString, "\\n", "\n") - if config.DriveCron == "" { - config.DriveCron = "0 * * * *" - SugarLogger.Infof("DRIVE_CRON is not set, defaulting to %s", config.DriveCron) - } - if config.GithubCron == "" { - config.GithubCron = "0 * * * *" - SugarLogger.Infof("GITHUB_CRON is not set, defaulting to %s", config.GithubCron) - } - if config.DiscordCron == "" { - config.DiscordCron = "0 * * * *" - SugarLogger.Infof("DISCORD_CRON is not set, defaulting to %s", config.DiscordCron) - } -} diff --git a/utils/logger.go b/utils/logger.go deleted file mode 100644 index e3f99bd..0000000 --- a/utils/logger.go +++ /dev/null @@ -1,17 +0,0 @@ -package utils - -import ( - "go.uber.org/zap" - "sentinel/config" -) - -var Logger *zap.Logger -var SugarLogger *zap.SugaredLogger - -func InitializeLogger() { - Logger = zap.Must(zap.NewProduction()) - if config.Env == "DEV" { - Logger = zap.Must(zap.NewDevelopment()) - } - SugarLogger = Logger.Sugar() -} diff --git a/utils/roles.go b/utils/roles.go deleted file mode 100644 index 0cc35fa..0000000 --- a/utils/roles.go +++ /dev/null @@ -1,53 +0,0 @@ -package utils - -import "sentinel/config" - -// IsAdmin checks if the user has the admin role. -// The function takes in a list of role IDs and returns a boolean. -func IsAdmin(roles []string) bool { - for _, role := range roles { - if role == config.AdminRoleID { - return true - } - } - return false -} - -// IsOfficer checks if the user has the officer role. -// The function takes in a list of role IDs and returns a boolean. -func IsOfficer(roles []string) bool { - for _, role := range roles { - if role == config.OfficerRoleID { - return true - } - } - return false -} - -// IsLead checks if the user has the lead role. -// The function takes in a list of role IDs and returns a boolean. -func IsLead(roles []string) bool { - for _, role := range roles { - if role == config.LeadRoleID { - return true - } - } - return false -} - -// IsSpecialAdvisor checks if the user has the special advisor role. -// The function takes in a list of role IDs and returns a boolean. -func IsSpecialAdvisor(roles []string) bool { - for _, role := range roles { - if role == config.SpecialAdvisorRoleID { - return true - } - } - return false -} - -// isInnerCircle checks if the user has any of the inner circle roles. -// The function takes in a list of role IDs and returns a boolean. -func IsInnerCircle(roles []string) bool { - return IsAdmin(roles) || IsOfficer(roles) || IsLead(roles) || IsSpecialAdvisor(roles) -} diff --git a/utils/time.go b/utils/time.go deleted file mode 100644 index d6899ed..0000000 --- a/utils/time.go +++ /dev/null @@ -1,11 +0,0 @@ -package utils - -import ( - "math" - "time" -) - -func WithPrecision(t time.Time) time.Time { - round := time.Second / time.Duration(math.Pow10(6)) - return t.Round(round) -} diff --git a/web/.eslintrc.cjs b/web/.eslintrc.cjs deleted file mode 100644 index 7060681..0000000 --- a/web/.eslintrc.cjs +++ /dev/null @@ -1,17 +0,0 @@ -module.exports = { - root: true, - env: { browser: true, es2020: true }, - extends: [ - "eslint:recommended", - "plugin:@typescript-eslint/recommended", - "plugin:react-hooks/recommended", - ], - ignorePatterns: ["dist", ".eslintrc.cjs"], - parser: "@typescript-eslint/parser", - plugins: ["react-refresh"], - rules: { - "@typescript-eslint/no-explicit-any": ["off"], - "react-refresh/only-export-components": ["off"], - "react-hooks/exhaustive-deps": ["off"], - }, -}; diff --git a/web/.gitignore b/web/.gitignore deleted file mode 100644 index a547bf3..0000000 --- a/web/.gitignore +++ /dev/null @@ -1,24 +0,0 @@ -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -pnpm-debug.log* -lerna-debug.log* - -node_modules -dist -dist-ssr -*.local - -# Editor directories and files -.vscode/* -!.vscode/extensions.json -.idea -.DS_Store -*.suo -*.ntvs* -*.njsproj -*.sln -*.sw? diff --git a/web/.prettierrc b/web/.prettierrc deleted file mode 100644 index b4bfed3..0000000 --- a/web/.prettierrc +++ /dev/null @@ -1,3 +0,0 @@ -{ - "plugins": ["prettier-plugin-tailwindcss"] -} diff --git a/web/.vite/deps/_metadata.json b/web/.vite/deps/_metadata.json deleted file mode 100644 index 278dace..0000000 --- a/web/.vite/deps/_metadata.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "hash": "c0522b83", - "configHash": "5e59faf6", - "lockfileHash": "e3b0c442", - "browserHash": "efeb9fa0", - "optimized": {}, - "chunks": {} -} diff --git a/web/.vite/deps/package.json b/web/.vite/deps/package.json deleted file mode 100644 index 3dbc1ca..0000000 --- a/web/.vite/deps/package.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "type": "module" -} diff --git a/web/README.md b/web/README.md deleted file mode 100644 index bb15685..0000000 --- a/web/README.md +++ /dev/null @@ -1,30 +0,0 @@ -# React + TypeScript + Vite - -This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. - -Currently, two official plugins are available: - -- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh -- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh - -## Expanding the ESLint configuration - -If you are developing a production application, we recommend updating the configuration to enable type aware lint rules: - -- Configure the top-level `parserOptions` property like this: - -```js -export default { - // other rules... - parserOptions: { - ecmaVersion: "latest", - sourceType: "module", - project: ["./tsconfig.json", "./tsconfig.node.json"], - tsconfigRootDir: __dirname, - }, -}; -``` - -- Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked` -- Optionally add `plugin:@typescript-eslint/stylistic-type-checked` -- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list diff --git a/web/components.json b/web/components.json deleted file mode 100644 index 73e99a6..0000000 --- a/web/components.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "$schema": "https://ui.shadcn.com/schema.json", - "style": "default", - "rsc": false, - "tsx": true, - "tailwind": { - "config": "tailwind.config.js", - "css": "src/index.css", - "baseColor": "slate", - "cssVariables": false, - "prefix": "" - }, - "aliases": { - "components": "@/components", - "utils": "@/lib/utils" - } -} diff --git a/web/index.html b/web/index.html deleted file mode 100644 index 5f2119a..0000000 --- a/web/index.html +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - GR Sentinel - - -
- - - diff --git a/web/netlify.toml b/web/netlify.toml deleted file mode 100644 index 4ad6c5b..0000000 --- a/web/netlify.toml +++ /dev/null @@ -1,16 +0,0 @@ -[[redirects]] - from = "/api/*" - to = "https://sentinel-api.gauchoracing.com/:splat" - status = 200 - force = true - -[[redirects]] - from = "/.well-known/*" - to = "https://sentinel-api.gauchoracing.com/config/:splat" - status = 200 - force = true - -[[redirects]] - from = "/*" - to = "/index.html" - status = 200 \ No newline at end of file diff --git a/web/package-lock.json b/web/package-lock.json deleted file mode 100644 index 5f601e9..0000000 --- a/web/package-lock.json +++ /dev/null @@ -1,8441 +0,0 @@ -{ - "name": "sentinel", - "version": "0.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "sentinel", - "version": "0.0.0", - "dependencies": { - "@fortawesome/fontawesome-svg-core": "^6.5.1", - "@fortawesome/free-brands-svg-icons": "^6.5.1", - "@fortawesome/free-regular-svg-icons": "^6.5.1", - "@fortawesome/free-solid-svg-icons": "^6.5.1", - "@fortawesome/react-fontawesome": "^0.2.0", - "@radix-ui/react-alert-dialog": "^1.0.5", - "@radix-ui/react-avatar": "^1.0.4", - "@radix-ui/react-checkbox": "^1.1.1", - "@radix-ui/react-dialog": "^1.0.5", - "@radix-ui/react-dropdown-menu": "^2.0.6", - "@radix-ui/react-label": "^2.0.2", - "@radix-ui/react-popover": "^1.0.7", - "@radix-ui/react-progress": "^1.0.3", - "@radix-ui/react-scroll-area": "^1.1.0", - "@radix-ui/react-select": "^2.1.1", - "@radix-ui/react-separator": "^1.0.3", - "@radix-ui/react-slot": "^1.0.2", - "@radix-ui/react-switch": "^1.1.0", - "@radix-ui/react-toast": "^1.1.5", - "@radix-ui/react-tooltip": "^1.1.2", - "@types/mapbox-gl": "^3.1.0", - "axios": "^1.6.5", - "class-variance-authority": "^0.7.0", - "clsx": "^2.1.0", - "cmdk": "^1.0.0", - "crypto-js": "^4.2.0", - "date-fns": "^3.6.0", - "dotenv": "^16.3.1", - "fuse.js": "^7.0.0", - "highcharts": "^11.4.3", - "highcharts-react-official": "^3.2.1", - "lucide-react": "^0.309.0", - "mapbox-gl": "^3.4.0", - "moment": "^2.30.1", - "mqtt": "^5.3.5", - "next-themes": "^0.2.1", - "react": "^18.2.0", - "react-charts": "^0.0.1", - "react-day-picker": "^8.10.1", - "react-dom": "^18.2.0", - "react-grid-layout": "^1.4.4", - "react-router-dom": "^6.21.2", - "react-superstore": "^0.1.4", - "react-use-websocket": "^4.5.0", - "reactflow": "^11.11.3", - "recharts": "^2.12.7", - "sonner": "^1.4.0", - "tailwind-merge": "^2.2.1", - "tailwindcss-animate": "^1.0.7" - }, - "devDependencies": { - "@types/crypto-js": "^4.2.2", - "@types/node": "^20.11.0", - "@types/react": "^18.2.43", - "@types/react-dom": "^18.2.17", - "@typescript-eslint/eslint-plugin": "^6.14.0", - "@typescript-eslint/parser": "^6.14.0", - "@vitejs/plugin-react-swc": "^3.5.0", - "autoprefixer": "^10.4.16", - "eslint": "^8.55.0", - "eslint-plugin-react-hooks": "^4.6.0", - "eslint-plugin-react-refresh": "^0.4.5", - "postcss": "^8.4.33", - "prettier": "^3.2.4", - "prettier-plugin-tailwindcss": "^0.5.11", - "tailwindcss": "^3.4.1", - "typescript": "^5.2.2", - "vite": "^5.0.8" - } - }, - "node_modules/@aashutoshrathi/word-wrap": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", - "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@alloc/quick-lru": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", - "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@babel/runtime": { - "version": "7.23.8", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.8.tgz", - "integrity": "sha512-Y7KbAP984rn1VGMbGqKmBLio9V7y5Je9GvU4rQPCPinCyNfUcToxIXl06d59URp/F3LwinvODxab5N/G6qggkw==", - "dependencies": { - "regenerator-runtime": "^0.14.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz", - "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz", - "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz", - "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz", - "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz", - "integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz", - "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz", - "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz", - "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz", - "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz", - "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz", - "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz", - "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==", - "cpu": [ - "loong64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz", - "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==", - "cpu": [ - "mips64el" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz", - "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz", - "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==", - "cpu": [ - "riscv64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz", - "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==", - "cpu": [ - "s390x" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz", - "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz", - "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz", - "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz", - "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz", - "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz", - "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz", - "integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", - "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^3.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" - } - }, - "node_modules/@eslint-community/regexpp": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", - "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", - "dev": true, - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", - "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", - "dev": true, - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/@eslint/eslintrc/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/@eslint/js": { - "version": "8.56.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.56.0.tgz", - "integrity": "sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/@floating-ui/core": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.5.3.tgz", - "integrity": "sha512-O0WKDOo0yhJuugCx6trZQj5jVJ9yR0ystG2JaNAemYUWce+pmM6WUEFIibnWyEJKdrDxhm75NoSRME35FNaM/Q==", - "dependencies": { - "@floating-ui/utils": "^0.2.0" - } - }, - "node_modules/@floating-ui/dom": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.5.4.tgz", - "integrity": "sha512-jByEsHIY+eEdCjnTVu+E3ephzTOzkQ8hgUfGwos+bg7NlH33Zc5uO+QHz1mrQUOgIKKDD1RtS201P9NvAfq3XQ==", - "dependencies": { - "@floating-ui/core": "^1.5.3", - "@floating-ui/utils": "^0.2.0" - } - }, - "node_modules/@floating-ui/react-dom": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.6.tgz", - "integrity": "sha512-IB8aCRFxr8nFkdYZgH+Otd9EVQPJoynxeFRGTB8voPoZMRWo8XjYuCRgpI1btvuKY69XMiLnW+ym7zoBHM90Rw==", - "dependencies": { - "@floating-ui/dom": "^1.5.4" - }, - "peerDependencies": { - "react": ">=16.8.0", - "react-dom": ">=16.8.0" - } - }, - "node_modules/@floating-ui/utils": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.1.tgz", - "integrity": "sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q==" - }, - "node_modules/@fortawesome/fontawesome-common-types": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.5.1.tgz", - "integrity": "sha512-GkWzv+L6d2bI5f/Vk6ikJ9xtl7dfXtoRu3YGE6nq0p/FFqA1ebMOAWg3XgRyb0I6LYyYkiAo+3/KrwuBp8xG7A==", - "hasInstallScript": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/@fortawesome/fontawesome-svg-core": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.5.1.tgz", - "integrity": "sha512-MfRCYlQPXoLlpem+egxjfkEuP9UQswTrlCOsknus/NcMoblTH2g0jPrapbcIb04KGA7E2GZxbAccGZfWoYgsrQ==", - "hasInstallScript": true, - "dependencies": { - "@fortawesome/fontawesome-common-types": "6.5.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@fortawesome/free-brands-svg-icons": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-6.5.1.tgz", - "integrity": "sha512-093l7DAkx0aEtBq66Sf19MgoZewv1zeY9/4C7vSKPO4qMwEsW/2VYTUTpBtLwfb9T2R73tXaRDPmE4UqLCYHfg==", - "hasInstallScript": true, - "dependencies": { - "@fortawesome/fontawesome-common-types": "6.5.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@fortawesome/free-regular-svg-icons": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.5.1.tgz", - "integrity": "sha512-m6ShXn+wvqEU69wSP84coxLbNl7sGVZb+Ca+XZq6k30SzuP3X4TfPqtycgUh9ASwlNh5OfQCd8pDIWxl+O+LlQ==", - "hasInstallScript": true, - "dependencies": { - "@fortawesome/fontawesome-common-types": "6.5.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@fortawesome/free-solid-svg-icons": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.5.1.tgz", - "integrity": "sha512-S1PPfU3mIJa59biTtXJz1oI0+KAXW6bkAb31XKhxdxtuXDiUIFsih4JR1v5BbxY7hVHsD1RKq+jRkVRaf773NQ==", - "hasInstallScript": true, - "dependencies": { - "@fortawesome/fontawesome-common-types": "6.5.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@fortawesome/react-fontawesome": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.2.0.tgz", - "integrity": "sha512-uHg75Rb/XORTtVt7OS9WoK8uM276Ufi7gCzshVWkUJbHhh3svsUUeqXerrM96Wm7fRiDzfKRwSoahhMIkGAYHw==", - "dependencies": { - "prop-types": "^15.8.1" - }, - "peerDependencies": { - "@fortawesome/fontawesome-svg-core": "~1 || ~6", - "react": ">=16.3" - } - }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.11.14", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", - "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", - "dev": true, - "dependencies": { - "@humanwhocodes/object-schema": "^2.0.2", - "debug": "^4.3.1", - "minimatch": "^3.0.5" - }, - "engines": { - "node": ">=10.10.0" - } - }, - "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz", - "integrity": "sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==", - "dev": true - }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", - "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", - "dependencies": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", - "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.21", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.21.tgz", - "integrity": "sha512-SRfKmRe1KvYnxjEMtxEr+J4HIeMX5YBg/qhRHpxEIGjhX1rshcHlnFUE9K0GazhVKWM7B+nARSkV8LuvJdJ5/g==", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@mapbox/jsonlint-lines-primitives": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@mapbox/jsonlint-lines-primitives/-/jsonlint-lines-primitives-2.0.2.tgz", - "integrity": "sha512-rY0o9A5ECsTQRVhv7tL/OyDpGAoUB4tTvLiW1DSzQGq4bvTPhNw1VpSNjDJc5GFZ2XuyOtSWSVN05qOtcD71qQ==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/@mapbox/mapbox-gl-supported": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@mapbox/mapbox-gl-supported/-/mapbox-gl-supported-3.0.0.tgz", - "integrity": "sha512-2XghOwu16ZwPJLOFVuIOaLbN0iKMn867evzXFyf0P22dqugezfJwLmdanAgU25ITvz1TvOfVP4jsDImlDJzcWg==" - }, - "node_modules/@mapbox/point-geometry": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/@mapbox/point-geometry/-/point-geometry-0.1.0.tgz", - "integrity": "sha512-6j56HdLTwWGO0fJPlrZtdU/B13q8Uwmo18Ck2GnGgN9PCFyKTZ3UbXeEdRFh18i9XQ92eH2VdtpJHpBD3aripQ==" - }, - "node_modules/@mapbox/tiny-sdf": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@mapbox/tiny-sdf/-/tiny-sdf-2.0.6.tgz", - "integrity": "sha512-qMqa27TLw+ZQz5Jk+RcwZGH7BQf5G/TrutJhspsca/3SHwmgKQ1iq+d3Jxz5oysPVYTGP6aXxCo5Lk9Er6YBAA==" - }, - "node_modules/@mapbox/unitbezier": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/@mapbox/unitbezier/-/unitbezier-0.0.1.tgz", - "integrity": "sha512-nMkuDXFv60aBr9soUG5q+GvZYL+2KZHVvsqFCzqnkGEf46U2fvmytHaEVc1/YZbiLn8X+eR3QzX1+dwDO1lxlw==" - }, - "node_modules/@mapbox/vector-tile": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@mapbox/vector-tile/-/vector-tile-1.3.1.tgz", - "integrity": "sha512-MCEddb8u44/xfQ3oD+Srl/tNcQoqTw3goGk2oLsrFxOTc3dUp+kAnby3PvAeeBYSMSjSPD1nd1AJA6W49WnoUw==", - "dependencies": { - "@mapbox/point-geometry": "~0.1.0" - } - }, - "node_modules/@mapbox/whoots-js": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@mapbox/whoots-js/-/whoots-js-3.1.0.tgz", - "integrity": "sha512-Es6WcD0nO5l+2BOQS4uLfNPYQaNDfbot3X1XUoloz+x0mPDS3eeORZJl06HXjwBG1fOGwCRnzK88LMdxKRrd6Q==", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@next/env": { - "version": "14.2.3", - "resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.3.tgz", - "integrity": "sha512-W7fd7IbkfmeeY2gXrzJYDx8D2lWKbVoTIj1o1ScPHNzvp30s1AuoEFSdr39bC5sjxJaxTtq3OTCZboNp0lNWHA==", - "peer": true - }, - "node_modules/@next/swc-darwin-arm64": { - "version": "14.2.3", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.3.tgz", - "integrity": "sha512-3pEYo/RaGqPP0YzwnlmPN2puaF2WMLM3apt5jLW2fFdXD9+pqcoTzRk+iZsf8ta7+quAe4Q6Ms0nR0SFGFdS1A==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-darwin-x64": { - "version": "14.2.3", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.3.tgz", - "integrity": "sha512-6adp7waE6P1TYFSXpY366xwsOnEXM+y1kgRpjSRVI2CBDOcbRjsJ67Z6EgKIqWIue52d2q/Mx8g9MszARj8IEA==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-arm64-gnu": { - "version": "14.2.3", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.3.tgz", - "integrity": "sha512-cuzCE/1G0ZSnTAHJPUT1rPgQx1w5tzSX7POXSLaS7w2nIUJUD+e25QoXD/hMfxbsT9rslEXugWypJMILBj/QsA==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-arm64-musl": { - "version": "14.2.3", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.3.tgz", - "integrity": "sha512-0D4/oMM2Y9Ta3nGuCcQN8jjJjmDPYpHX9OJzqk42NZGJocU2MqhBq5tWkJrUQOQY9N+In9xOdymzapM09GeiZw==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-x64-gnu": { - "version": "14.2.3", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.3.tgz", - "integrity": "sha512-ENPiNnBNDInBLyUU5ii8PMQh+4XLr4pG51tOp6aJ9xqFQ2iRI6IH0Ds2yJkAzNV1CfyagcyzPfROMViS2wOZ9w==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-x64-musl": { - "version": "14.2.3", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.3.tgz", - "integrity": "sha512-BTAbq0LnCbF5MtoM7I/9UeUu/8ZBY0i8SFjUMCbPDOLv+un67e2JgyN4pmgfXBwy/I+RHu8q+k+MCkDN6P9ViQ==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-win32-arm64-msvc": { - "version": "14.2.3", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.3.tgz", - "integrity": "sha512-AEHIw/dhAMLNFJFJIJIyOFDzrzI5bAjI9J26gbO5xhAKHYTZ9Or04BesFPXiAYXDNdrwTP2dQceYA4dL1geu8A==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-win32-ia32-msvc": { - "version": "14.2.3", - "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.3.tgz", - "integrity": "sha512-vga40n1q6aYb0CLrM+eEmisfKCR45ixQYXuBXxOOmmoV8sYST9k7E3US32FsY+CkkF7NtzdcebiFT4CHuMSyZw==", - "cpu": [ - "ia32" - ], - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-win32-x64-msvc": { - "version": "14.2.3", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.3.tgz", - "integrity": "sha512-Q1/zm43RWynxrO7lW4ehciQVj+5ePBhOK+/K2P7pLFX3JaJ/IZVC69SHidrmZSOkqz7ECIOhhy7XhAFG4JYyHA==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">= 10" - } - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "optional": true, - "engines": { - "node": ">=14" - } - }, - "node_modules/@radix-ui/number": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.0.tgz", - "integrity": "sha512-V3gRzhVNU1ldS5XhAPTom1fOIo4ccrjjJgmE+LI2h/WaFpHmx0MQApT+KZHnx8abG6Avtfcz4WoEciMnpFT3HQ==" - }, - "node_modules/@radix-ui/primitive": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.0.1.tgz", - "integrity": "sha512-yQ8oGX2GVsEYMWGxcovu1uGWPCxV5BFfeeYxqPmuAzUyLT9qmaMXSAhXpb0WrspIeqYzdJpkh2vHModJPgRIaw==", - "dependencies": { - "@babel/runtime": "^7.13.10" - } - }, - "node_modules/@radix-ui/react-alert-dialog": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@radix-ui/react-alert-dialog/-/react-alert-dialog-1.0.5.tgz", - "integrity": "sha512-OrVIOcZL0tl6xibeuGt5/+UxoT2N27KCFOPjFyfXMnchxSHZ/OW7cCX2nGlIYJrbHK/fczPcFzAwvNBB6XBNMA==", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/primitive": "1.0.1", - "@radix-ui/react-compose-refs": "1.0.1", - "@radix-ui/react-context": "1.0.1", - "@radix-ui/react-dialog": "1.0.5", - "@radix-ui/react-primitive": "1.0.3", - "@radix-ui/react-slot": "1.0.2" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-arrow": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.0.3.tgz", - "integrity": "sha512-wSP+pHsB/jQRaL6voubsQ/ZlrGBHHrOjmBnr19hxYgtS0WvAFwZhK2WP/YY5yF9uKECCEEDGxuLxq1NBK51wFA==", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-primitive": "1.0.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-avatar": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-avatar/-/react-avatar-1.0.4.tgz", - "integrity": "sha512-kVK2K7ZD3wwj3qhle0ElXhOjbezIgyl2hVvgwfIdexL3rN6zJmy5AqqIf+D31lxVppdzV8CjAfZ6PklkmInZLw==", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-context": "1.0.1", - "@radix-ui/react-primitive": "1.0.3", - "@radix-ui/react-use-callback-ref": "1.0.1", - "@radix-ui/react-use-layout-effect": "1.0.1" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-checkbox": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.1.1.tgz", - "integrity": "sha512-0i/EKJ222Afa1FE0C6pNJxDq1itzcl3HChE9DwskA4th4KRse8ojx8a1nVcOjwJdbpDLcz7uol77yYnQNMHdKw==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.0", - "@radix-ui/react-compose-refs": "1.1.0", - "@radix-ui/react-context": "1.1.0", - "@radix-ui/react-presence": "1.1.0", - "@radix-ui/react-primitive": "2.0.0", - "@radix-ui/react-use-controllable-state": "1.1.0", - "@radix-ui/react-use-previous": "1.1.0", - "@radix-ui/react-use-size": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-checkbox/node_modules/@radix-ui/primitive": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.0.tgz", - "integrity": "sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==", - "license": "MIT" - }, - "node_modules/@radix-ui/react-checkbox/node_modules/@radix-ui/react-compose-refs": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.0.tgz", - "integrity": "sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-checkbox/node_modules/@radix-ui/react-context": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.0.tgz", - "integrity": "sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-checkbox/node_modules/@radix-ui/react-presence": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.0.tgz", - "integrity": "sha512-Gq6wuRN/asf9H/E/VzdKoUtT8GC9PQc9z40/vEr0VCJ4u5XvvhWIrSsCB6vD2/cH7ugTdSfYq9fLJCcM00acrQ==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.0", - "@radix-ui/react-use-layout-effect": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-checkbox/node_modules/@radix-ui/react-primitive": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.0.tgz", - "integrity": "sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-slot": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-checkbox/node_modules/@radix-ui/react-slot": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.0.tgz", - "integrity": "sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-checkbox/node_modules/@radix-ui/react-use-callback-ref": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz", - "integrity": "sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-checkbox/node_modules/@radix-ui/react-use-controllable-state": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.1.0.tgz", - "integrity": "sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-use-callback-ref": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-checkbox/node_modules/@radix-ui/react-use-layout-effect": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.0.tgz", - "integrity": "sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-checkbox/node_modules/@radix-ui/react-use-size": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.0.tgz", - "integrity": "sha512-XW3/vWuIXHa+2Uwcc2ABSfcCledmXhhQPlGbfcRXbiUQI5Icjcg19BGCZVKKInYbvUCut/ufbbLLPFC5cbb1hw==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-collection": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.0.3.tgz", - "integrity": "sha512-3SzW+0PW7yBBoQlT8wNcGtaxaD0XSu0uLUFgrtHY08Acx05TaHaOmVLR73c0j/cqpDy53KBMO7s0dx2wmOIDIA==", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-compose-refs": "1.0.1", - "@radix-ui/react-context": "1.0.1", - "@radix-ui/react-primitive": "1.0.3", - "@radix-ui/react-slot": "1.0.2" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-compose-refs": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.1.tgz", - "integrity": "sha512-fDSBgd44FKHa1FRMU59qBMPFcl2PZE+2nmqunj+BWFyYYjnhIDWL2ItDs3rrbJDQOtzt5nIebLCQc4QRfz6LJw==", - "dependencies": { - "@babel/runtime": "^7.13.10" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-context": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.0.1.tgz", - "integrity": "sha512-ebbrdFoYTcuZ0v4wG5tedGnp9tzcV8awzsxYph7gXUyvnNLuTIcCk1q17JEbnVhXAKG9oX3KtchwiMIAYp9NLg==", - "dependencies": { - "@babel/runtime": "^7.13.10" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-dialog": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.0.5.tgz", - "integrity": "sha512-GjWJX/AUpB703eEBanuBnIWdIXg6NvJFCXcNlSZk4xdszCdhrJgBoUd1cGk67vFO+WdA2pfI/plOpqz/5GUP6Q==", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/primitive": "1.0.1", - "@radix-ui/react-compose-refs": "1.0.1", - "@radix-ui/react-context": "1.0.1", - "@radix-ui/react-dismissable-layer": "1.0.5", - "@radix-ui/react-focus-guards": "1.0.1", - "@radix-ui/react-focus-scope": "1.0.4", - "@radix-ui/react-id": "1.0.1", - "@radix-ui/react-portal": "1.0.4", - "@radix-ui/react-presence": "1.0.1", - "@radix-ui/react-primitive": "1.0.3", - "@radix-ui/react-slot": "1.0.2", - "@radix-ui/react-use-controllable-state": "1.0.1", - "aria-hidden": "^1.1.1", - "react-remove-scroll": "2.5.5" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-direction": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.0.1.tgz", - "integrity": "sha512-RXcvnXgyvYvBEOhCBuddKecVkoMiI10Jcm5cTI7abJRAHYfFxeu+FBQs/DvdxSYucxR5mna0dNsL6QFlds5TMA==", - "dependencies": { - "@babel/runtime": "^7.13.10" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-dismissable-layer": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.0.5.tgz", - "integrity": "sha512-aJeDjQhywg9LBu2t/At58hCvr7pEm0o2Ke1x33B+MhjNmmZ17sy4KImo0KPLgsnc/zN7GPdce8Cnn0SWvwZO7g==", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/primitive": "1.0.1", - "@radix-ui/react-compose-refs": "1.0.1", - "@radix-ui/react-primitive": "1.0.3", - "@radix-ui/react-use-callback-ref": "1.0.1", - "@radix-ui/react-use-escape-keydown": "1.0.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-dropdown-menu": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.0.6.tgz", - "integrity": "sha512-i6TuFOoWmLWq+M/eCLGd/bQ2HfAX1RJgvrBQ6AQLmzfvsLdefxbWu8G9zczcPFfcSPehz9GcpF6K9QYreFV8hA==", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/primitive": "1.0.1", - "@radix-ui/react-compose-refs": "1.0.1", - "@radix-ui/react-context": "1.0.1", - "@radix-ui/react-id": "1.0.1", - "@radix-ui/react-menu": "2.0.6", - "@radix-ui/react-primitive": "1.0.3", - "@radix-ui/react-use-controllable-state": "1.0.1" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-focus-guards": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.0.1.tgz", - "integrity": "sha512-Rect2dWbQ8waGzhMavsIbmSVCgYxkXLxxR3ZvCX79JOglzdEy4JXMb98lq4hPxUbLr77nP0UOGf4rcMU+s1pUA==", - "dependencies": { - "@babel/runtime": "^7.13.10" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-focus-scope": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.0.4.tgz", - "integrity": "sha512-sL04Mgvf+FmyvZeYfNu1EPAaaxD+aw7cYeIB9L9Fvq8+urhltTRaEo5ysKOpHuKPclsZcSUMKlN05x4u+CINpA==", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-compose-refs": "1.0.1", - "@radix-ui/react-primitive": "1.0.3", - "@radix-ui/react-use-callback-ref": "1.0.1" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-id": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.0.1.tgz", - "integrity": "sha512-tI7sT/kqYp8p96yGWY1OAnLHrqDgzHefRBKQ2YAkBS5ja7QLcZ9Z/uY7bEjPUatf8RomoXM8/1sMj1IJaE5UzQ==", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-use-layout-effect": "1.0.1" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-label": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.0.2.tgz", - "integrity": "sha512-N5ehvlM7qoTLx7nWPodsPYPgMzA5WM8zZChQg8nyFJKnDO5WHdba1vv5/H6IO5LtJMfD2Q3wh1qHFGNtK0w3bQ==", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-primitive": "1.0.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-menu": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.0.6.tgz", - "integrity": "sha512-BVkFLS+bUC8HcImkRKPSiVumA1VPOOEC5WBMiT+QAVsPzW1FJzI9KnqgGxVDPBcql5xXrHkD3JOVoXWEXD8SYA==", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/primitive": "1.0.1", - "@radix-ui/react-collection": "1.0.3", - "@radix-ui/react-compose-refs": "1.0.1", - "@radix-ui/react-context": "1.0.1", - "@radix-ui/react-direction": "1.0.1", - "@radix-ui/react-dismissable-layer": "1.0.5", - "@radix-ui/react-focus-guards": "1.0.1", - "@radix-ui/react-focus-scope": "1.0.4", - "@radix-ui/react-id": "1.0.1", - "@radix-ui/react-popper": "1.1.3", - "@radix-ui/react-portal": "1.0.4", - "@radix-ui/react-presence": "1.0.1", - "@radix-ui/react-primitive": "1.0.3", - "@radix-ui/react-roving-focus": "1.0.4", - "@radix-ui/react-slot": "1.0.2", - "@radix-ui/react-use-callback-ref": "1.0.1", - "aria-hidden": "^1.1.1", - "react-remove-scroll": "2.5.5" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-popover": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.0.7.tgz", - "integrity": "sha512-shtvVnlsxT6faMnK/a7n0wptwBD23xc1Z5mdrtKLwVEfsEMXodS0r5s0/g5P0hX//EKYZS2sxUjqfzlg52ZSnQ==", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/primitive": "1.0.1", - "@radix-ui/react-compose-refs": "1.0.1", - "@radix-ui/react-context": "1.0.1", - "@radix-ui/react-dismissable-layer": "1.0.5", - "@radix-ui/react-focus-guards": "1.0.1", - "@radix-ui/react-focus-scope": "1.0.4", - "@radix-ui/react-id": "1.0.1", - "@radix-ui/react-popper": "1.1.3", - "@radix-ui/react-portal": "1.0.4", - "@radix-ui/react-presence": "1.0.1", - "@radix-ui/react-primitive": "1.0.3", - "@radix-ui/react-slot": "1.0.2", - "@radix-ui/react-use-controllable-state": "1.0.1", - "aria-hidden": "^1.1.1", - "react-remove-scroll": "2.5.5" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-popper": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.1.3.tgz", - "integrity": "sha512-cKpopj/5RHZWjrbF2846jBNacjQVwkP068DfmgrNJXpvVWrOvlAmE9xSiy5OqeE+Gi8D9fP+oDhUnPqNMY8/5w==", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@floating-ui/react-dom": "^2.0.0", - "@radix-ui/react-arrow": "1.0.3", - "@radix-ui/react-compose-refs": "1.0.1", - "@radix-ui/react-context": "1.0.1", - "@radix-ui/react-primitive": "1.0.3", - "@radix-ui/react-use-callback-ref": "1.0.1", - "@radix-ui/react-use-layout-effect": "1.0.1", - "@radix-ui/react-use-rect": "1.0.1", - "@radix-ui/react-use-size": "1.0.1", - "@radix-ui/rect": "1.0.1" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-portal": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.0.4.tgz", - "integrity": "sha512-Qki+C/EuGUVCQTOTD5vzJzJuMUlewbzuKyUy+/iHM2uwGiru9gZeBJtHAPKAEkB5KWGi9mP/CHKcY0wt1aW45Q==", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-primitive": "1.0.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-presence": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.0.1.tgz", - "integrity": "sha512-UXLW4UAbIY5ZjcvzjfRFo5gxva8QirC9hF7wRE4U5gz+TP0DbRk+//qyuAQ1McDxBt1xNMBTaciFGvEmJvAZCg==", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-compose-refs": "1.0.1", - "@radix-ui/react-use-layout-effect": "1.0.1" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-primitive": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-1.0.3.tgz", - "integrity": "sha512-yi58uVyoAcK/Nq1inRY56ZSjKypBNKTa/1mcL8qdl6oJeEaDbOldlzrGn7P6Q3Id5d+SYNGc5AJgc4vGhjs5+g==", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-slot": "1.0.2" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-progress": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.0.3.tgz", - "integrity": "sha512-5G6Om/tYSxjSeEdrb1VfKkfZfn/1IlPWd731h2RfPuSbIfNUgfqAwbKfJCg/PP6nuUCTrYzalwHSpSinoWoCag==", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-context": "1.0.1", - "@radix-ui/react-primitive": "1.0.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-roving-focus": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.0.4.tgz", - "integrity": "sha512-2mUg5Mgcu001VkGy+FfzZyzbmuUWzgWkj3rvv4yu+mLw03+mTzbxZHvfcGyFp2b8EkQeMkpRQ5FiA2Vr2O6TeQ==", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/primitive": "1.0.1", - "@radix-ui/react-collection": "1.0.3", - "@radix-ui/react-compose-refs": "1.0.1", - "@radix-ui/react-context": "1.0.1", - "@radix-ui/react-direction": "1.0.1", - "@radix-ui/react-id": "1.0.1", - "@radix-ui/react-primitive": "1.0.3", - "@radix-ui/react-use-callback-ref": "1.0.1", - "@radix-ui/react-use-controllable-state": "1.0.1" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-scroll-area": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.1.0.tgz", - "integrity": "sha512-9ArIZ9HWhsrfqS765h+GZuLoxaRHD/j0ZWOWilsCvYTpYJp8XwCqNG7Dt9Nu/TItKOdgLGkOPCodQvDc+UMwYg==", - "dependencies": { - "@radix-ui/number": "1.1.0", - "@radix-ui/primitive": "1.1.0", - "@radix-ui/react-compose-refs": "1.1.0", - "@radix-ui/react-context": "1.1.0", - "@radix-ui/react-direction": "1.1.0", - "@radix-ui/react-presence": "1.1.0", - "@radix-ui/react-primitive": "2.0.0", - "@radix-ui/react-use-callback-ref": "1.1.0", - "@radix-ui/react-use-layout-effect": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-scroll-area/node_modules/@radix-ui/primitive": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.0.tgz", - "integrity": "sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==" - }, - "node_modules/@radix-ui/react-scroll-area/node_modules/@radix-ui/react-compose-refs": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.0.tgz", - "integrity": "sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-scroll-area/node_modules/@radix-ui/react-context": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.0.tgz", - "integrity": "sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A==", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-scroll-area/node_modules/@radix-ui/react-direction": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.0.tgz", - "integrity": "sha512-BUuBvgThEiAXh2DWu93XsT+a3aWrGqolGlqqw5VU1kG7p/ZH2cuDlM1sRLNnY3QcBS69UIz2mcKhMxDsdewhjg==", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-scroll-area/node_modules/@radix-ui/react-presence": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.0.tgz", - "integrity": "sha512-Gq6wuRN/asf9H/E/VzdKoUtT8GC9PQc9z40/vEr0VCJ4u5XvvhWIrSsCB6vD2/cH7ugTdSfYq9fLJCcM00acrQ==", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.0", - "@radix-ui/react-use-layout-effect": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-scroll-area/node_modules/@radix-ui/react-primitive": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.0.tgz", - "integrity": "sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==", - "dependencies": { - "@radix-ui/react-slot": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-scroll-area/node_modules/@radix-ui/react-slot": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.0.tgz", - "integrity": "sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-scroll-area/node_modules/@radix-ui/react-use-callback-ref": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz", - "integrity": "sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-scroll-area/node_modules/@radix-ui/react-use-layout-effect": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.0.tgz", - "integrity": "sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-select": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.1.1.tgz", - "integrity": "sha512-8iRDfyLtzxlprOo9IicnzvpsO1wNCkuwzzCM+Z5Rb5tNOpCdMvcc2AkzX0Fz+Tz9v6NJ5B/7EEgyZveo4FBRfQ==", - "dependencies": { - "@radix-ui/number": "1.1.0", - "@radix-ui/primitive": "1.1.0", - "@radix-ui/react-collection": "1.1.0", - "@radix-ui/react-compose-refs": "1.1.0", - "@radix-ui/react-context": "1.1.0", - "@radix-ui/react-direction": "1.1.0", - "@radix-ui/react-dismissable-layer": "1.1.0", - "@radix-ui/react-focus-guards": "1.1.0", - "@radix-ui/react-focus-scope": "1.1.0", - "@radix-ui/react-id": "1.1.0", - "@radix-ui/react-popper": "1.2.0", - "@radix-ui/react-portal": "1.1.1", - "@radix-ui/react-primitive": "2.0.0", - "@radix-ui/react-slot": "1.1.0", - "@radix-ui/react-use-callback-ref": "1.1.0", - "@radix-ui/react-use-controllable-state": "1.1.0", - "@radix-ui/react-use-layout-effect": "1.1.0", - "@radix-ui/react-use-previous": "1.1.0", - "@radix-ui/react-visually-hidden": "1.1.0", - "aria-hidden": "^1.1.1", - "react-remove-scroll": "2.5.7" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-select/node_modules/@radix-ui/primitive": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.0.tgz", - "integrity": "sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==" - }, - "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-arrow": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.0.tgz", - "integrity": "sha512-FmlW1rCg7hBpEBwFbjHwCW6AmWLQM6g/v0Sn8XbP9NvmSZ2San1FpQeyPtufzOMSIx7Y4dzjlHoifhp+7NkZhw==", - "dependencies": { - "@radix-ui/react-primitive": "2.0.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-collection": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.0.tgz", - "integrity": "sha512-GZsZslMJEyo1VKm5L1ZJY8tGDxZNPAoUeQUIbKeJfoi7Q4kmig5AsgLMYYuyYbfjd8fBmFORAIwYAkXMnXZgZw==", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.0", - "@radix-ui/react-context": "1.1.0", - "@radix-ui/react-primitive": "2.0.0", - "@radix-ui/react-slot": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-compose-refs": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.0.tgz", - "integrity": "sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-context": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.0.tgz", - "integrity": "sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A==", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-direction": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.0.tgz", - "integrity": "sha512-BUuBvgThEiAXh2DWu93XsT+a3aWrGqolGlqqw5VU1kG7p/ZH2cuDlM1sRLNnY3QcBS69UIz2mcKhMxDsdewhjg==", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-dismissable-layer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.0.tgz", - "integrity": "sha512-/UovfmmXGptwGcBQawLzvn2jOfM0t4z3/uKffoBlj724+n3FvBbZ7M0aaBOmkp6pqFYpO4yx8tSVJjx3Fl2jig==", - "dependencies": { - "@radix-ui/primitive": "1.1.0", - "@radix-ui/react-compose-refs": "1.1.0", - "@radix-ui/react-primitive": "2.0.0", - "@radix-ui/react-use-callback-ref": "1.1.0", - "@radix-ui/react-use-escape-keydown": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-focus-guards": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.0.tgz", - "integrity": "sha512-w6XZNUPVv6xCpZUqb/yN9DL6auvpGX3C/ee6Hdi16v2UUy25HV2Q5bcflsiDyT/g5RwbPQ/GIT1vLkeRb+ITBw==", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-focus-scope": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.0.tgz", - "integrity": "sha512-200UD8zylvEyL8Bx+z76RJnASR2gRMuxlgFCPAe/Q/679a/r0eK3MBVYMb7vZODZcffZBdob1EGnky78xmVvcA==", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.0", - "@radix-ui/react-primitive": "2.0.0", - "@radix-ui/react-use-callback-ref": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-id": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.0.tgz", - "integrity": "sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==", - "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-popper": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.0.tgz", - "integrity": "sha512-ZnRMshKF43aBxVWPWvbj21+7TQCvhuULWJ4gNIKYpRlQt5xGRhLx66tMp8pya2UkGHTSlhpXwmjqltDYHhw7Vg==", - "dependencies": { - "@floating-ui/react-dom": "^2.0.0", - "@radix-ui/react-arrow": "1.1.0", - "@radix-ui/react-compose-refs": "1.1.0", - "@radix-ui/react-context": "1.1.0", - "@radix-ui/react-primitive": "2.0.0", - "@radix-ui/react-use-callback-ref": "1.1.0", - "@radix-ui/react-use-layout-effect": "1.1.0", - "@radix-ui/react-use-rect": "1.1.0", - "@radix-ui/react-use-size": "1.1.0", - "@radix-ui/rect": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-portal": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.1.tgz", - "integrity": "sha512-A3UtLk85UtqhzFqtoC8Q0KvR2GbXF3mtPgACSazajqq6A41mEQgo53iPzY4i6BwDxlIFqWIhiQ2G729n+2aw/g==", - "dependencies": { - "@radix-ui/react-primitive": "2.0.0", - "@radix-ui/react-use-layout-effect": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-primitive": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.0.tgz", - "integrity": "sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==", - "dependencies": { - "@radix-ui/react-slot": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-slot": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.0.tgz", - "integrity": "sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-use-callback-ref": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz", - "integrity": "sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-use-controllable-state": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.1.0.tgz", - "integrity": "sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==", - "dependencies": { - "@radix-ui/react-use-callback-ref": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-use-escape-keydown": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.0.tgz", - "integrity": "sha512-L7vwWlR1kTTQ3oh7g1O0CBF3YCyyTj8NmhLR+phShpyA50HCfBFKVJTpshm9PzLiKmehsrQzTYTpX9HvmC9rhw==", - "dependencies": { - "@radix-ui/react-use-callback-ref": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-use-layout-effect": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.0.tgz", - "integrity": "sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-use-rect": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.0.tgz", - "integrity": "sha512-0Fmkebhr6PiseyZlYAOtLS+nb7jLmpqTrJyv61Pe68MKYW6OWdRE2kI70TaYY27u7H0lajqM3hSMMLFq18Z7nQ==", - "dependencies": { - "@radix-ui/rect": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-use-size": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.0.tgz", - "integrity": "sha512-XW3/vWuIXHa+2Uwcc2ABSfcCledmXhhQPlGbfcRXbiUQI5Icjcg19BGCZVKKInYbvUCut/ufbbLLPFC5cbb1hw==", - "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-visually-hidden": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.1.0.tgz", - "integrity": "sha512-N8MDZqtgCgG5S3aV60INAB475osJousYpZ4cTJ2cFbMpdHS5Y6loLTH8LPtkj2QN0x93J30HT/M3qJXM0+lyeQ==", - "dependencies": { - "@radix-ui/react-primitive": "2.0.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-select/node_modules/@radix-ui/rect": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.0.tgz", - "integrity": "sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==" - }, - "node_modules/@radix-ui/react-select/node_modules/react-remove-scroll": { - "version": "2.5.7", - "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.7.tgz", - "integrity": "sha512-FnrTWO4L7/Bhhf3CYBNArEG/yROV0tKmTv7/3h9QCFvH6sndeFf1wPqOcbFVu5VAulS5dV1wGT3GZZ/1GawqiA==", - "dependencies": { - "react-remove-scroll-bar": "^2.3.4", - "react-style-singleton": "^2.2.1", - "tslib": "^2.1.0", - "use-callback-ref": "^1.3.0", - "use-sidecar": "^1.1.2" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-separator": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.0.3.tgz", - "integrity": "sha512-itYmTy/kokS21aiV5+Z56MZB54KrhPgn6eHDKkFeOLR34HMN2s8PaN47qZZAGnvupcjxHaFZnW4pQEh0BvvVuw==", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-primitive": "1.0.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-slot": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.2.tgz", - "integrity": "sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-compose-refs": "1.0.1" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-switch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.1.0.tgz", - "integrity": "sha512-OBzy5WAj641k0AOSpKQtreDMe+isX0MQJ1IVyF03ucdF3DunOnROVrjWs8zsXUxC3zfZ6JL9HFVCUlMghz9dJw==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.0", - "@radix-ui/react-compose-refs": "1.1.0", - "@radix-ui/react-context": "1.1.0", - "@radix-ui/react-primitive": "2.0.0", - "@radix-ui/react-use-controllable-state": "1.1.0", - "@radix-ui/react-use-previous": "1.1.0", - "@radix-ui/react-use-size": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-switch/node_modules/@radix-ui/primitive": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.0.tgz", - "integrity": "sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==", - "license": "MIT" - }, - "node_modules/@radix-ui/react-switch/node_modules/@radix-ui/react-compose-refs": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.0.tgz", - "integrity": "sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-switch/node_modules/@radix-ui/react-context": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.0.tgz", - "integrity": "sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-switch/node_modules/@radix-ui/react-primitive": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.0.tgz", - "integrity": "sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-slot": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-switch/node_modules/@radix-ui/react-slot": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.0.tgz", - "integrity": "sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-switch/node_modules/@radix-ui/react-use-callback-ref": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz", - "integrity": "sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-switch/node_modules/@radix-ui/react-use-controllable-state": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.1.0.tgz", - "integrity": "sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-use-callback-ref": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-switch/node_modules/@radix-ui/react-use-layout-effect": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.0.tgz", - "integrity": "sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-switch/node_modules/@radix-ui/react-use-size": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.0.tgz", - "integrity": "sha512-XW3/vWuIXHa+2Uwcc2ABSfcCledmXhhQPlGbfcRXbiUQI5Icjcg19BGCZVKKInYbvUCut/ufbbLLPFC5cbb1hw==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-toast": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.1.5.tgz", - "integrity": "sha512-fRLn227WHIBRSzuRzGJ8W+5YALxofH23y0MlPLddaIpLpCDqdE0NZlS2NRQDRiptfxDeeCjgFIpexB1/zkxDlw==", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/primitive": "1.0.1", - "@radix-ui/react-collection": "1.0.3", - "@radix-ui/react-compose-refs": "1.0.1", - "@radix-ui/react-context": "1.0.1", - "@radix-ui/react-dismissable-layer": "1.0.5", - "@radix-ui/react-portal": "1.0.4", - "@radix-ui/react-presence": "1.0.1", - "@radix-ui/react-primitive": "1.0.3", - "@radix-ui/react-use-callback-ref": "1.0.1", - "@radix-ui/react-use-controllable-state": "1.0.1", - "@radix-ui/react-use-layout-effect": "1.0.1", - "@radix-ui/react-visually-hidden": "1.0.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-tooltip": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.1.2.tgz", - "integrity": "sha512-9XRsLwe6Yb9B/tlnYCPVUd/TFS4J7HuOZW345DCeC6vKIxQGMZdx21RK4VoZauPD5frgkXTYVS5y90L+3YBn4w==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.0", - "@radix-ui/react-compose-refs": "1.1.0", - "@radix-ui/react-context": "1.1.0", - "@radix-ui/react-dismissable-layer": "1.1.0", - "@radix-ui/react-id": "1.1.0", - "@radix-ui/react-popper": "1.2.0", - "@radix-ui/react-portal": "1.1.1", - "@radix-ui/react-presence": "1.1.0", - "@radix-ui/react-primitive": "2.0.0", - "@radix-ui/react-slot": "1.1.0", - "@radix-ui/react-use-controllable-state": "1.1.0", - "@radix-ui/react-visually-hidden": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/primitive": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.0.tgz", - "integrity": "sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==", - "license": "MIT" - }, - "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-arrow": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.0.tgz", - "integrity": "sha512-FmlW1rCg7hBpEBwFbjHwCW6AmWLQM6g/v0Sn8XbP9NvmSZ2San1FpQeyPtufzOMSIx7Y4dzjlHoifhp+7NkZhw==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-primitive": "2.0.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-compose-refs": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.0.tgz", - "integrity": "sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-context": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.0.tgz", - "integrity": "sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-dismissable-layer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.0.tgz", - "integrity": "sha512-/UovfmmXGptwGcBQawLzvn2jOfM0t4z3/uKffoBlj724+n3FvBbZ7M0aaBOmkp6pqFYpO4yx8tSVJjx3Fl2jig==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.0", - "@radix-ui/react-compose-refs": "1.1.0", - "@radix-ui/react-primitive": "2.0.0", - "@radix-ui/react-use-callback-ref": "1.1.0", - "@radix-ui/react-use-escape-keydown": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-id": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.0.tgz", - "integrity": "sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-popper": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.0.tgz", - "integrity": "sha512-ZnRMshKF43aBxVWPWvbj21+7TQCvhuULWJ4gNIKYpRlQt5xGRhLx66tMp8pya2UkGHTSlhpXwmjqltDYHhw7Vg==", - "license": "MIT", - "dependencies": { - "@floating-ui/react-dom": "^2.0.0", - "@radix-ui/react-arrow": "1.1.0", - "@radix-ui/react-compose-refs": "1.1.0", - "@radix-ui/react-context": "1.1.0", - "@radix-ui/react-primitive": "2.0.0", - "@radix-ui/react-use-callback-ref": "1.1.0", - "@radix-ui/react-use-layout-effect": "1.1.0", - "@radix-ui/react-use-rect": "1.1.0", - "@radix-ui/react-use-size": "1.1.0", - "@radix-ui/rect": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-portal": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.1.tgz", - "integrity": "sha512-A3UtLk85UtqhzFqtoC8Q0KvR2GbXF3mtPgACSazajqq6A41mEQgo53iPzY4i6BwDxlIFqWIhiQ2G729n+2aw/g==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-primitive": "2.0.0", - "@radix-ui/react-use-layout-effect": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-presence": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.0.tgz", - "integrity": "sha512-Gq6wuRN/asf9H/E/VzdKoUtT8GC9PQc9z40/vEr0VCJ4u5XvvhWIrSsCB6vD2/cH7ugTdSfYq9fLJCcM00acrQ==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.0", - "@radix-ui/react-use-layout-effect": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-primitive": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.0.tgz", - "integrity": "sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-slot": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-slot": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.0.tgz", - "integrity": "sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-use-callback-ref": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz", - "integrity": "sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-use-controllable-state": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.1.0.tgz", - "integrity": "sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-use-callback-ref": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-use-escape-keydown": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.0.tgz", - "integrity": "sha512-L7vwWlR1kTTQ3oh7g1O0CBF3YCyyTj8NmhLR+phShpyA50HCfBFKVJTpshm9PzLiKmehsrQzTYTpX9HvmC9rhw==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-use-callback-ref": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-use-layout-effect": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.0.tgz", - "integrity": "sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-use-rect": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.0.tgz", - "integrity": "sha512-0Fmkebhr6PiseyZlYAOtLS+nb7jLmpqTrJyv61Pe68MKYW6OWdRE2kI70TaYY27u7H0lajqM3hSMMLFq18Z7nQ==", - "license": "MIT", - "dependencies": { - "@radix-ui/rect": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-use-size": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.0.tgz", - "integrity": "sha512-XW3/vWuIXHa+2Uwcc2ABSfcCledmXhhQPlGbfcRXbiUQI5Icjcg19BGCZVKKInYbvUCut/ufbbLLPFC5cbb1hw==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-visually-hidden": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.1.0.tgz", - "integrity": "sha512-N8MDZqtgCgG5S3aV60INAB475osJousYpZ4cTJ2cFbMpdHS5Y6loLTH8LPtkj2QN0x93J30HT/M3qJXM0+lyeQ==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-primitive": "2.0.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/rect": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.0.tgz", - "integrity": "sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==", - "license": "MIT" - }, - "node_modules/@radix-ui/react-use-callback-ref": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.1.tgz", - "integrity": "sha512-D94LjX4Sp0xJFVaoQOd3OO9k7tpBYNOXdVhkltUbGv2Qb9OXdrg/CpsjlZv7ia14Sylv398LswWBVVu5nqKzAQ==", - "dependencies": { - "@babel/runtime": "^7.13.10" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-controllable-state": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.0.1.tgz", - "integrity": "sha512-Svl5GY5FQeN758fWKrjM6Qb7asvXeiZltlT4U2gVfl8Gx5UAv2sMR0LWo8yhsIZh2oQ0eFdZ59aoOOMV7b47VA==", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-use-callback-ref": "1.0.1" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-escape-keydown": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.0.3.tgz", - "integrity": "sha512-vyL82j40hcFicA+M4Ex7hVkB9vHgSse1ZWomAqV2Je3RleKGO5iM8KMOEtfoSB0PnIelMd2lATjTGMYqN5ylTg==", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-use-callback-ref": "1.0.1" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-layout-effect": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.0.1.tgz", - "integrity": "sha512-v/5RegiJWYdoCvMnITBkNNx6bCj20fiaJnWtRkU18yITptraXjffz5Qbn05uOiQnOvi+dbkznkoaMltz1GnszQ==", - "dependencies": { - "@babel/runtime": "^7.13.10" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-previous": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.0.tgz", - "integrity": "sha512-Z/e78qg2YFnnXcW88A4JmTtm4ADckLno6F7OXotmkQfeuCVaKuYzqAATPhVzl3delXE7CxIV8shofPn3jPc5Og==", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-rect": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.0.1.tgz", - "integrity": "sha512-Cq5DLuSiuYVKNU8orzJMbl15TXilTnJKUCltMVQg53BQOF1/C5toAaGrowkgksdBQ9H+SRL23g0HDmg9tvmxXw==", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/rect": "1.0.1" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-size": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.0.1.tgz", - "integrity": "sha512-ibay+VqrgcaI6veAojjofPATwledXiSmX+C0KrBk/xgpX9rBzPV3OsfwlhQdUOFbh+LKQorLYT+xTXW9V8yd0g==", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-use-layout-effect": "1.0.1" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-visually-hidden": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.0.3.tgz", - "integrity": "sha512-D4w41yN5YRKtu464TLnByKzMDG/JlMPHtfZgQAu9v6mNakUqGUI9vUrfQKz8NK41VMm/xbZbh76NUTVtIYqOMA==", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-primitive": "1.0.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/rect": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.0.1.tgz", - "integrity": "sha512-fyrgCaedtvMg9NK3en0pnOYJdtfwxUcNolezkNPUsoX57X8oQk+NkqcvzHXD2uKNij6GXmWU9NDru2IWjrO4BQ==", - "dependencies": { - "@babel/runtime": "^7.13.10" - } - }, - "node_modules/@reactflow/background": { - "version": "11.3.13", - "resolved": "https://registry.npmjs.org/@reactflow/background/-/background-11.3.13.tgz", - "integrity": "sha512-hkvpVEhgvfTDyCvdlitw4ioKCYLaaiRXnuEG+1QM3Np+7N1DiWF1XOv5I8AFyNoJL07yXEkbECUTsHvkBvcG5A==", - "dependencies": { - "@reactflow/core": "11.11.3", - "classcat": "^5.0.3", - "zustand": "^4.4.1" - }, - "peerDependencies": { - "react": ">=17", - "react-dom": ">=17" - } - }, - "node_modules/@reactflow/controls": { - "version": "11.2.13", - "resolved": "https://registry.npmjs.org/@reactflow/controls/-/controls-11.2.13.tgz", - "integrity": "sha512-3xgEg6ALIVkAQCS4NiBjb7ad8Cb3D8CtA7Vvl4Hf5Ar2PIVs6FOaeft9s2iDZGtsWP35ECDYId1rIFVhQL8r+A==", - "dependencies": { - "@reactflow/core": "11.11.3", - "classcat": "^5.0.3", - "zustand": "^4.4.1" - }, - "peerDependencies": { - "react": ">=17", - "react-dom": ">=17" - } - }, - "node_modules/@reactflow/core": { - "version": "11.11.3", - "resolved": "https://registry.npmjs.org/@reactflow/core/-/core-11.11.3.tgz", - "integrity": "sha512-+adHdUa7fJSEM93fWfjQwyWXeI92a1eLKwWbIstoCakHpL8UjzwhEh6sn+mN2h/59MlVI7Ehr1iGTt3MsfcIFA==", - "dependencies": { - "@types/d3": "^7.4.0", - "@types/d3-drag": "^3.0.1", - "@types/d3-selection": "^3.0.3", - "@types/d3-zoom": "^3.0.1", - "classcat": "^5.0.3", - "d3-drag": "^3.0.0", - "d3-selection": "^3.0.0", - "d3-zoom": "^3.0.0", - "zustand": "^4.4.1" - }, - "peerDependencies": { - "react": ">=17", - "react-dom": ">=17" - } - }, - "node_modules/@reactflow/minimap": { - "version": "11.7.13", - "resolved": "https://registry.npmjs.org/@reactflow/minimap/-/minimap-11.7.13.tgz", - "integrity": "sha512-m2MvdiGSyOu44LEcERDEl1Aj6x//UQRWo3HEAejNU4HQTlJnYrSN8tgrYF8TxC1+c/9UdyzQY5VYgrTwW4QWdg==", - "dependencies": { - "@reactflow/core": "11.11.3", - "@types/d3-selection": "^3.0.3", - "@types/d3-zoom": "^3.0.1", - "classcat": "^5.0.3", - "d3-selection": "^3.0.0", - "d3-zoom": "^3.0.0", - "zustand": "^4.4.1" - }, - "peerDependencies": { - "react": ">=17", - "react-dom": ">=17" - } - }, - "node_modules/@reactflow/node-resizer": { - "version": "2.2.13", - "resolved": "https://registry.npmjs.org/@reactflow/node-resizer/-/node-resizer-2.2.13.tgz", - "integrity": "sha512-X7ceQ2s3jFLgbkg03n2RYr4hm3jTVrzkW2W/8ANv/SZfuVmF8XJxlERuD8Eka5voKqLda0ywIZGAbw9GoHLfUQ==", - "dependencies": { - "@reactflow/core": "11.11.3", - "classcat": "^5.0.4", - "d3-drag": "^3.0.0", - "d3-selection": "^3.0.0", - "zustand": "^4.4.1" - }, - "peerDependencies": { - "react": ">=17", - "react-dom": ">=17" - } - }, - "node_modules/@reactflow/node-toolbar": { - "version": "1.3.13", - "resolved": "https://registry.npmjs.org/@reactflow/node-toolbar/-/node-toolbar-1.3.13.tgz", - "integrity": "sha512-aknvNICO10uWdthFSpgD6ctY/CTBeJUMV9co8T9Ilugr08Nb89IQ4uD0dPmr031ewMQxixtYIkw+sSDDzd2aaQ==", - "dependencies": { - "@reactflow/core": "11.11.3", - "classcat": "^5.0.3", - "zustand": "^4.4.1" - }, - "peerDependencies": { - "react": ">=17", - "react-dom": ">=17" - } - }, - "node_modules/@remix-run/router": { - "version": "1.14.2", - "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.14.2.tgz", - "integrity": "sha512-ACXpdMM9hmKZww21yEqWwiLws/UPLhNKvimN8RrYSqPSvB3ov7sLvAcfvaxePeLvccTQKGdkDIhLYApZVDFuKg==", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.18.0.tgz", - "integrity": "sha512-Tya6xypR10giZV1XzxmH5wr25VcZSncG0pZIjfePT0OVBvqNEurzValetGNarVrGiq66EBVAFn15iYX4w6FKgQ==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.18.0.tgz", - "integrity": "sha512-avCea0RAP03lTsDhEyfy+hpfr85KfyTctMADqHVhLAF3MlIkq83CP8UfAHUssgXTYd+6er6PaAhx/QGv4L1EiA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.18.0.tgz", - "integrity": "sha512-IWfdwU7KDSm07Ty0PuA/W2JYoZ4iTj3TUQjkVsO/6U+4I1jN5lcR71ZEvRh52sDOERdnNhhHU57UITXz5jC1/w==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.18.0.tgz", - "integrity": "sha512-n2LMsUz7Ynu7DoQrSQkBf8iNrjOGyPLrdSg802vk6XT3FtsgX6JbE8IHRvposskFm9SNxzkLYGSq9QdpLYpRNA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.18.0.tgz", - "integrity": "sha512-C/zbRYRXFjWvz9Z4haRxcTdnkPt1BtCkz+7RtBSuNmKzMzp3ZxdM28Mpccn6pt28/UWUCTXa+b0Mx1k3g6NOMA==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.18.0.tgz", - "integrity": "sha512-l3m9ewPgjQSXrUMHg93vt0hYCGnrMOcUpTz6FLtbwljo2HluS4zTXFy2571YQbisTnfTKPZ01u/ukJdQTLGh9A==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.18.0.tgz", - "integrity": "sha512-rJ5D47d8WD7J+7STKdCUAgmQk49xuFrRi9pZkWoRD1UeSMakbcepWXPF8ycChBoAqs1pb2wzvbY6Q33WmN2ftw==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.18.0.tgz", - "integrity": "sha512-be6Yx37b24ZwxQ+wOQXXLZqpq4jTckJhtGlWGZs68TgdKXJgw54lUUoFYrg6Zs/kjzAQwEwYbp8JxZVzZLRepQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.18.0.tgz", - "integrity": "sha512-hNVMQK+qrA9Todu9+wqrXOHxFiD5YmdEi3paj6vP02Kx1hjd2LLYR2eaN7DsEshg09+9uzWi2W18MJDlG0cxJA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.18.0.tgz", - "integrity": "sha512-ROCM7i+m1NfdrsmvwSzoxp9HFtmKGHEqu5NNDiZWQtXLA8S5HBCkVvKAxJ8U+CVctHwV2Gb5VUaK7UAkzhDjlg==", - "cpu": [ - "riscv64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.18.0.tgz", - "integrity": "sha512-0UyyRHyDN42QL+NbqevXIIUnKA47A+45WyasO+y2bGJ1mhQrfrtXUpTxCOrfxCR4esV3/RLYyucGVPiUsO8xjg==", - "cpu": [ - "s390x" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.18.0.tgz", - "integrity": "sha512-xuglR2rBVHA5UsI8h8UbX4VJ470PtGCf5Vpswh7p2ukaqBGFTnsfzxUBetoWBWymHMxbIG0Cmx7Y9qDZzr648w==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.18.0.tgz", - "integrity": "sha512-LKaqQL9osY/ir2geuLVvRRs+utWUNilzdE90TpyoX0eNqPzWjRm14oMEE+YLve4k/NAqCdPkGYDaDF5Sw+xBfg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.18.0.tgz", - "integrity": "sha512-7J6TkZQFGo9qBKH0pk2cEVSRhJbL6MtfWxth7Y5YmZs57Pi+4x6c2dStAUvaQkHQLnEQv1jzBUW43GvZW8OFqA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.18.0.tgz", - "integrity": "sha512-Txjh+IxBPbkUB9+SXZMpv+b/vnTEtFyfWZgJ6iyCmt2tdx0OF5WhFowLmnh8ENGNpfUlUZkdI//4IEmhwPieNg==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.18.0.tgz", - "integrity": "sha512-UOo5FdvOL0+eIVTgS4tIdbW+TtnBLWg1YBCcU2KWM7nuNwRz9bksDX1bekJJCpu25N1DVWaCwnT39dVQxzqS8g==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@swc/core": { - "version": "1.3.102", - "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.3.102.tgz", - "integrity": "sha512-OAjNLY/f6QWKSDzaM3bk31A+OYHu6cPa9P/rFIx8X5d24tHXUpRiiq6/PYI6SQRjUPlB72GjsjoEU8F+ALadHg==", - "dev": true, - "hasInstallScript": true, - "dependencies": { - "@swc/counter": "^0.1.1", - "@swc/types": "^0.1.5" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/swc" - }, - "optionalDependencies": { - "@swc/core-darwin-arm64": "1.3.102", - "@swc/core-darwin-x64": "1.3.102", - "@swc/core-linux-arm-gnueabihf": "1.3.102", - "@swc/core-linux-arm64-gnu": "1.3.102", - "@swc/core-linux-arm64-musl": "1.3.102", - "@swc/core-linux-x64-gnu": "1.3.102", - "@swc/core-linux-x64-musl": "1.3.102", - "@swc/core-win32-arm64-msvc": "1.3.102", - "@swc/core-win32-ia32-msvc": "1.3.102", - "@swc/core-win32-x64-msvc": "1.3.102" - }, - "peerDependencies": { - "@swc/helpers": "^0.5.0" - }, - "peerDependenciesMeta": { - "@swc/helpers": { - "optional": true - } - } - }, - "node_modules/@swc/core-darwin-arm64": { - "version": "1.3.102", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.3.102.tgz", - "integrity": "sha512-CJDxA5Wd2cUMULj3bjx4GEoiYyyiyL8oIOu4Nhrs9X+tlg8DnkCm4nI57RJGP8Mf6BaXPIJkHX8yjcefK2RlDA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-darwin-x64": { - "version": "1.3.102", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.3.102.tgz", - "integrity": "sha512-X5akDkHwk6oAer49oER0qZMjNMkLH3IOZaV1m98uXIasAGyjo5WH1MKPeMLY1sY6V6TrufzwiSwD4ds571ytcg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-linux-arm-gnueabihf": { - "version": "1.3.102", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.3.102.tgz", - "integrity": "sha512-kJH3XtZP9YQdjq/wYVBeFuiVQl4HaC4WwRrIxAHwe2OyvrwUI43dpW3LpxSggBnxXcVCXYWf36sTnv8S75o2Gw==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-linux-arm64-gnu": { - "version": "1.3.102", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.3.102.tgz", - "integrity": "sha512-flQP2WDyCgO24WmKA1wjjTx+xfCmavUete2Kp6yrM+631IHLGnr17eu7rYJ/d4EnDBId/ytMyrnWbTVkaVrpbQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-linux-arm64-musl": { - "version": "1.3.102", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.3.102.tgz", - "integrity": "sha512-bQEQSnC44DyoIGLw1+fNXKVGoCHi7eJOHr8BdH0y1ooy9ArskMjwobBFae3GX4T1AfnrTaejyr0FvLYIb0Zkog==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-linux-x64-gnu": { - "version": "1.3.102", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.3.102.tgz", - "integrity": "sha512-dFvnhpI478svQSxqISMt00MKTDS0e4YtIr+ioZDG/uJ/q+RpcNy3QI2KMm05Fsc8Y0d4krVtvCKWgfUMsJZXAg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-linux-x64-musl": { - "version": "1.3.102", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.3.102.tgz", - "integrity": "sha512-+a0M3CvjeIRNA/jTCzWEDh2V+mhKGvLreHOL7J97oULZy5yg4gf7h8lQX9J8t9QLbf6fsk+0F8bVH1Ie/PbXjA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-win32-arm64-msvc": { - "version": "1.3.102", - "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.3.102.tgz", - "integrity": "sha512-w76JWLjkZNOfkB25nqdWUNCbt0zJ41CnWrJPZ+LxEai3zAnb2YtgB/cCIrwxDebRuMgE9EJXRj7gDDaTEAMOOQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-win32-ia32-msvc": { - "version": "1.3.102", - "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.3.102.tgz", - "integrity": "sha512-vlDb09HiGqKwz+2cxDS9T5/461ipUQBplvuhW+cCbzzGuPq8lll2xeyZU0N1E4Sz3MVdSPx1tJREuRvlQjrwNg==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-win32-x64-msvc": { - "version": "1.3.102", - "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.3.102.tgz", - "integrity": "sha512-E/jfSD7sShllxBwwgDPeXp1UxvIqehj/ShSUqq1pjR/IDRXngcRSXKJK92mJkNFY7suH6BcCWwzrxZgkO7sWmw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/counter": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", - "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==" - }, - "node_modules/@swc/helpers": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.5.tgz", - "integrity": "sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==", - "peer": true, - "dependencies": { - "@swc/counter": "^0.1.3", - "tslib": "^2.4.0" - } - }, - "node_modules/@swc/types": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.5.tgz", - "integrity": "sha512-myfUej5naTBWnqOCc/MdVOLVjXUXtIA+NpDrDBKJtLLg2shUjBu3cZmB/85RyitKc55+lUUyl7oRfLOvkr2hsw==", - "dev": true - }, - "node_modules/@types/crypto-js": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@types/crypto-js/-/crypto-js-4.2.2.tgz", - "integrity": "sha512-sDOLlVbHhXpAUAL0YHDUUwDZf3iN4Bwi4W6a0W0b+QcAezUbRtH4FVb+9J4h+XFPW7l/gQ9F8qC7P+Ec4k8QVQ==", - "dev": true - }, - "node_modules/@types/d3": { - "version": "7.4.3", - "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz", - "integrity": "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==", - "dependencies": { - "@types/d3-array": "*", - "@types/d3-axis": "*", - "@types/d3-brush": "*", - "@types/d3-chord": "*", - "@types/d3-color": "*", - "@types/d3-contour": "*", - "@types/d3-delaunay": "*", - "@types/d3-dispatch": "*", - "@types/d3-drag": "*", - "@types/d3-dsv": "*", - "@types/d3-ease": "*", - "@types/d3-fetch": "*", - "@types/d3-force": "*", - "@types/d3-format": "*", - "@types/d3-geo": "*", - "@types/d3-hierarchy": "*", - "@types/d3-interpolate": "*", - "@types/d3-path": "*", - "@types/d3-polygon": "*", - "@types/d3-quadtree": "*", - "@types/d3-random": "*", - "@types/d3-scale": "*", - "@types/d3-scale-chromatic": "*", - "@types/d3-selection": "*", - "@types/d3-shape": "*", - "@types/d3-time": "*", - "@types/d3-time-format": "*", - "@types/d3-timer": "*", - "@types/d3-transition": "*", - "@types/d3-zoom": "*" - } - }, - "node_modules/@types/d3-array": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz", - "integrity": "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==" - }, - "node_modules/@types/d3-axis": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.6.tgz", - "integrity": "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==", - "dependencies": { - "@types/d3-selection": "*" - } - }, - "node_modules/@types/d3-brush": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.6.tgz", - "integrity": "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==", - "dependencies": { - "@types/d3-selection": "*" - } - }, - "node_modules/@types/d3-chord": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.6.tgz", - "integrity": "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==" - }, - "node_modules/@types/d3-color": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", - "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==" - }, - "node_modules/@types/d3-contour": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.6.tgz", - "integrity": "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==", - "dependencies": { - "@types/d3-array": "*", - "@types/geojson": "*" - } - }, - "node_modules/@types/d3-delaunay": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz", - "integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==" - }, - "node_modules/@types/d3-dispatch": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.6.tgz", - "integrity": "sha512-4fvZhzMeeuBJYZXRXrRIQnvUYfyXwYmLsdiN7XXmVNQKKw1cM8a5WdID0g1hVFZDqT9ZqZEY5pD44p24VS7iZQ==" - }, - "node_modules/@types/d3-drag": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz", - "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==", - "dependencies": { - "@types/d3-selection": "*" - } - }, - "node_modules/@types/d3-dsv": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.7.tgz", - "integrity": "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==" - }, - "node_modules/@types/d3-ease": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", - "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==" - }, - "node_modules/@types/d3-fetch": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.7.tgz", - "integrity": "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==", - "dependencies": { - "@types/d3-dsv": "*" - } - }, - "node_modules/@types/d3-force": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.9.tgz", - "integrity": "sha512-IKtvyFdb4Q0LWna6ymywQsEYjK/94SGhPrMfEr1TIc5OBeziTi+1jcCvttts8e0UWZIxpasjnQk9MNk/3iS+kA==" - }, - "node_modules/@types/d3-format": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.4.tgz", - "integrity": "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==" - }, - "node_modules/@types/d3-geo": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.0.tgz", - "integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==", - "dependencies": { - "@types/geojson": "*" - } - }, - "node_modules/@types/d3-hierarchy": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz", - "integrity": "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==" - }, - "node_modules/@types/d3-interpolate": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", - "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", - "dependencies": { - "@types/d3-color": "*" - } - }, - "node_modules/@types/d3-path": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.0.tgz", - "integrity": "sha512-P2dlU/q51fkOc/Gfl3Ul9kicV7l+ra934qBFXCFhrZMOL6du1TM0pm1ThYvENukyOn5h9v+yMJ9Fn5JK4QozrQ==" - }, - "node_modules/@types/d3-polygon": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.2.tgz", - "integrity": "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==" - }, - "node_modules/@types/d3-quadtree": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz", - "integrity": "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==" - }, - "node_modules/@types/d3-random": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.3.tgz", - "integrity": "sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==" - }, - "node_modules/@types/d3-scale": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.8.tgz", - "integrity": "sha512-gkK1VVTr5iNiYJ7vWDI+yUFFlszhNMtVeneJ6lUTKPjprsvLLI9/tgEGiXJOnlINJA8FyA88gfnQsHbybVZrYQ==", - "dependencies": { - "@types/d3-time": "*" - } - }, - "node_modules/@types/d3-scale-chromatic": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.0.3.tgz", - "integrity": "sha512-laXM4+1o5ImZv3RpFAsTRn3TEkzqkytiOY0Dz0sq5cnd1dtNlk6sHLon4OvqaiJb28T0S/TdsBI3Sjsy+keJrw==" - }, - "node_modules/@types/d3-selection": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.10.tgz", - "integrity": "sha512-cuHoUgS/V3hLdjJOLTT691+G2QoqAjCVLmr4kJXR4ha56w1Zdu8UUQ5TxLRqudgNjwXeQxKMq4j+lyf9sWuslg==" - }, - "node_modules/@types/d3-shape": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.6.tgz", - "integrity": "sha512-5KKk5aKGu2I+O6SONMYSNflgiP0WfZIQvVUMan50wHsLG1G94JlxEVnCpQARfTtzytuY0p/9PXXZb3I7giofIA==", - "dependencies": { - "@types/d3-path": "*" - } - }, - "node_modules/@types/d3-time": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.3.tgz", - "integrity": "sha512-2p6olUZ4w3s+07q3Tm2dbiMZy5pCDfYwtLXXHUnVzXgQlZ/OyPtUz6OL382BkOuGlLXqfT+wqv8Fw2v8/0geBw==" - }, - "node_modules/@types/d3-time-format": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.3.tgz", - "integrity": "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==" - }, - "node_modules/@types/d3-timer": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", - "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==" - }, - "node_modules/@types/d3-transition": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.8.tgz", - "integrity": "sha512-ew63aJfQ/ms7QQ4X7pk5NxQ9fZH/z+i24ZfJ6tJSfqxJMrYLiK01EAs2/Rtw/JreGUsS3pLPNV644qXFGnoZNQ==", - "dependencies": { - "@types/d3-selection": "*" - } - }, - "node_modules/@types/d3-zoom": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz", - "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==", - "dependencies": { - "@types/d3-interpolate": "*", - "@types/d3-selection": "*" - } - }, - "node_modules/@types/estree": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", - "dev": true - }, - "node_modules/@types/geojson": { - "version": "7946.0.14", - "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.14.tgz", - "integrity": "sha512-WCfD5Ht3ZesJUsONdhvm84dmzWOiOzOAqOncN0++w0lBw1o8OuDNJF2McvvCef/yBqb/HYRahp1BYtODFQ8bRg==" - }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true - }, - "node_modules/@types/mapbox-gl": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@types/mapbox-gl/-/mapbox-gl-3.1.0.tgz", - "integrity": "sha512-hI6cQDjw1bkJw7MC/eHMqq5TWUamLwsujnUUeiIX2KDRjxRNSYMjnHz07+LATz9I9XIsKumOtUz4gRYnZOJ/FA==", - "dependencies": { - "@types/geojson": "*" - } - }, - "node_modules/@types/node": { - "version": "20.11.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.0.tgz", - "integrity": "sha512-o9bjXmDNcF7GbM4CNQpmi+TutCgap/K3w1JyKgxAjqx41zp9qlIAVFi0IhCNsJcXolEqLWhbFbEeL0PvYm4pcQ==", - "dependencies": { - "undici-types": "~5.26.4" - } - }, - "node_modules/@types/prop-types": { - "version": "15.7.11", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz", - "integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==", - "devOptional": true - }, - "node_modules/@types/react": { - "version": "18.2.47", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.47.tgz", - "integrity": "sha512-xquNkkOirwyCgoClNk85BjP+aqnIS+ckAJ8i37gAbDs14jfW/J23f2GItAf33oiUPQnqNMALiFeoM9Y5mbjpVQ==", - "devOptional": true, - "dependencies": { - "@types/prop-types": "*", - "@types/scheduler": "*", - "csstype": "^3.0.2" - } - }, - "node_modules/@types/react-dom": { - "version": "18.2.18", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.18.tgz", - "integrity": "sha512-TJxDm6OfAX2KJWJdMEVTwWke5Sc/E/RlnPGvGfS0W7+6ocy2xhDVQVh/KvC2Uf7kACs+gDytdusDSdWfWkaNzw==", - "devOptional": true, - "dependencies": { - "@types/react": "*" - } - }, - "node_modules/@types/readable-stream": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/@types/readable-stream/-/readable-stream-4.0.10.tgz", - "integrity": "sha512-AbUKBjcC8SHmImNi4yK2bbjogQlkFSg7shZCcicxPQapniOlajG8GCc39lvXzCWX4lLRRs7DM3VAeSlqmEVZUA==", - "dependencies": { - "@types/node": "*", - "safe-buffer": "~5.1.1" - } - }, - "node_modules/@types/scheduler": { - "version": "0.16.8", - "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz", - "integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==", - "devOptional": true - }, - "node_modules/@types/semver": { - "version": "7.5.6", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.6.tgz", - "integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==", - "dev": true - }, - "node_modules/@types/ws": { - "version": "8.5.10", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz", - "integrity": "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.18.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.18.1.tgz", - "integrity": "sha512-nISDRYnnIpk7VCFrGcu1rnZfM1Dh9LRHnfgdkjcbi/l7g16VYRri3TjXi9Ir4lOZSw5N/gnV/3H7jIPQ8Q4daA==", - "dev": true, - "dependencies": { - "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.18.1", - "@typescript-eslint/type-utils": "6.18.1", - "@typescript-eslint/utils": "6.18.1", - "@typescript-eslint/visitor-keys": "6.18.1", - "debug": "^4.3.4", - "graphemer": "^1.4.0", - "ignore": "^5.2.4", - "natural-compare": "^1.4.0", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", - "eslint": "^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/parser": { - "version": "6.18.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.18.1.tgz", - "integrity": "sha512-zct/MdJnVaRRNy9e84XnVtRv9Vf91/qqe+hZJtKanjojud4wAVy/7lXxJmMyX6X6J+xc6c//YEWvpeif8cAhWA==", - "dev": true, - "dependencies": { - "@typescript-eslint/scope-manager": "6.18.1", - "@typescript-eslint/types": "6.18.1", - "@typescript-eslint/typescript-estree": "6.18.1", - "@typescript-eslint/visitor-keys": "6.18.1", - "debug": "^4.3.4" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "6.18.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.18.1.tgz", - "integrity": "sha512-BgdBwXPFmZzaZUuw6wKiHKIovms97a7eTImjkXCZE04TGHysG+0hDQPmygyvgtkoB/aOQwSM/nWv3LzrOIQOBw==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "6.18.1", - "@typescript-eslint/visitor-keys": "6.18.1" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/type-utils": { - "version": "6.18.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.18.1.tgz", - "integrity": "sha512-wyOSKhuzHeU/5pcRDP2G2Ndci+4g653V43gXTpt4nbyoIOAASkGDA9JIAgbQCdCkcr1MvpSYWzxTz0olCn8+/Q==", - "dev": true, - "dependencies": { - "@typescript-eslint/typescript-estree": "6.18.1", - "@typescript-eslint/utils": "6.18.1", - "debug": "^4.3.4", - "ts-api-utils": "^1.0.1" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/types": { - "version": "6.18.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.18.1.tgz", - "integrity": "sha512-4TuMAe+tc5oA7wwfqMtB0Y5OrREPF1GeJBAjqwgZh1lEMH5PJQgWgHGfYufVB51LtjD+peZylmeyxUXPfENLCw==", - "dev": true, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "6.18.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.18.1.tgz", - "integrity": "sha512-fv9B94UAhywPRhUeeV/v+3SBDvcPiLxRZJw/xZeeGgRLQZ6rLMG+8krrJUyIf6s1ecWTzlsbp0rlw7n9sjufHA==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "6.18.1", - "@typescript-eslint/visitor-keys": "6.18.1", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "minimatch": "9.0.3", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/utils": { - "version": "6.18.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.18.1.tgz", - "integrity": "sha512-zZmTuVZvD1wpoceHvoQpOiewmWu3uP9FuTWo8vqpy2ffsmfCE8mklRPi+vmnIYAIk9t/4kOThri2QCDgor+OpQ==", - "dev": true, - "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@types/json-schema": "^7.0.12", - "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.18.1", - "@typescript-eslint/types": "6.18.1", - "@typescript-eslint/typescript-estree": "6.18.1", - "semver": "^7.5.4" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" - } - }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "6.18.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.18.1.tgz", - "integrity": "sha512-/kvt0C5lRqGoCfsbmm7/CwMqoSkY3zzHLIjdhHZQW3VFrnz7ATecOHR7nb7V+xn4286MBxfnQfQhAmCI0u+bJA==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "6.18.1", - "eslint-visitor-keys": "^3.4.1" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@ungap/structured-clone": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", - "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", - "dev": true - }, - "node_modules/@vitejs/plugin-react-swc": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react-swc/-/plugin-react-swc-3.5.0.tgz", - "integrity": "sha512-1PrOvAaDpqlCV+Up8RkAh9qaiUjoDUcjtttyhXDKw53XA6Ve16SOp6cCOpRs8Dj8DqUQs6eTW5YkLcLJjrXAig==", - "dev": true, - "dependencies": { - "@swc/core": "^1.3.96" - }, - "peerDependencies": { - "vite": "^4 || ^5" - } - }, - "node_modules/abort-controller": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", - "dependencies": { - "event-target-shim": "^5.0.0" - }, - "engines": { - "node": ">=6.5" - } - }, - "node_modules/acorn": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", - "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/amdefine": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", - "integrity": "sha512-S2Hw0TtNkMJhIabBwIojKL9YHO5T0n5eNqWJ7Lrlel/zDbftQpxpapi8tZs3X1HWa+u+QeydGmzzNU0m09+Rcg==", - "license": "BSD-3-Clause OR MIT", - "peer": true, - "engines": { - "node": ">=0.4.2" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/any-promise": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==" - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/arg": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", - "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==" - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "node_modules/aria-hidden": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.3.tgz", - "integrity": "sha512-xcLxITLe2HYa1cnYnwCjkOO1PqUHQpozB8x9AR0OgWN2woOBi5kSDVxKfd0b7sb1hw5qFeJhXm9H1nu3xSfLeQ==", - "dependencies": { - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" - }, - "node_modules/autoprefixer": { - "version": "10.4.16", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.16.tgz", - "integrity": "sha512-7vd3UC6xKp0HLfua5IjZlcXvGAGy7cBAXTg2lyQ/8WpNhd6SiZ8Be+xm3FyBSYJx5GKcpRCzBh7RH4/0dnY+uQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/autoprefixer" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "browserslist": "^4.21.10", - "caniuse-lite": "^1.0.30001538", - "fraction.js": "^4.3.6", - "normalize-range": "^0.1.2", - "picocolors": "^1.0.0", - "postcss-value-parser": "^4.2.0" - }, - "bin": { - "autoprefixer": "bin/autoprefixer" - }, - "engines": { - "node": "^10 || ^12 || >=14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/axios": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.5.tgz", - "integrity": "sha512-fZu86yCo+svH3uqJ/yTdQ0QHpQu5oL+/QE+QPSv6BZSkDAoky9vytxp7u5qk83OJFS3kEBcesWni9WTZAv3tSw==", - "license": "MIT", - "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" - }, - "node_modules/base62": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/base62/-/base62-0.1.1.tgz", - "integrity": "sha512-QtExujIOq/F672OkHmDi3CdkphOA1kSQ38gv03Ro3cplYQk831dq9GM3Q1oXAxpR5HNJjGjjjT2pHtBGAJu1jw==", - "peer": true, - "engines": { - "node": "*" - } - }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "engines": { - "node": ">=8" - } - }, - "node_modules/bl": { - "version": "6.0.10", - "resolved": "https://registry.npmjs.org/bl/-/bl-6.0.10.tgz", - "integrity": "sha512-F14DFhDZfxtVm2FY0k9kG2lWAwzZkO9+jX3Ytuoy/V0E1/5LBuBzzQHXAjqpxXEDIpmTPZZf5GVIGPQcLxFpaA==", - "dependencies": { - "buffer": "^6.0.3", - "inherits": "^2.0.4", - "readable-stream": "^4.2.0" - } - }, - "node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browserslist": { - "version": "4.22.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.2.tgz", - "integrity": "sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "caniuse-lite": "^1.0.30001565", - "electron-to-chromium": "^1.4.601", - "node-releases": "^2.0.14", - "update-browserslist-db": "^1.0.13" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" - }, - "node_modules/busboy": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", - "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", - "peer": true, - "dependencies": { - "streamsearch": "^1.1.0" - }, - "engines": { - "node": ">=10.16.0" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/camelcase-css": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", - "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", - "engines": { - "node": ">= 6" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001583", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001583.tgz", - "integrity": "sha512-acWTYaha8xfhA/Du/z4sNZjHUWjkiuoAi2LM+T/aL+kemKQgPT1xBb/YKjlQ0Qo8gvbHsGNplrEJ+9G3gL7i4Q==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ] - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/cheap-ruler": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/cheap-ruler/-/cheap-ruler-3.0.2.tgz", - "integrity": "sha512-02T332h1/HTN6cDSufLP8x4JzDs2+VC+8qZ/N0kWIVPyc2xUkWwWh3B2fJxR7raXkL4Mq7k554mfuM9ofv/vGg==" - }, - "node_modules/chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/chokidar/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/class-variance-authority": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.0.tgz", - "integrity": "sha512-jFI8IQw4hczaL4ALINxqLEXQbWcNjoSkloa4IaufXCJr6QawJyw7tuRysRsrE8w2p/4gGaxKIt/hX3qz/IbD1A==", - "dependencies": { - "clsx": "2.0.0" - }, - "funding": { - "url": "https://joebell.co.uk" - } - }, - "node_modules/class-variance-authority/node_modules/clsx": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz", - "integrity": "sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==", - "engines": { - "node": ">=6" - } - }, - "node_modules/classcat": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/classcat/-/classcat-5.0.5.tgz", - "integrity": "sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==" - }, - "node_modules/client-only": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", - "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", - "peer": true - }, - "node_modules/clsx": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.0.tgz", - "integrity": "sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==", - "engines": { - "node": ">=6" - } - }, - "node_modules/cmdk": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/cmdk/-/cmdk-1.0.0.tgz", - "integrity": "sha512-gDzVf0a09TvoJ5jnuPvygTB77+XdOSwEmJ88L6XPFPlv7T3RxbP9jgenfylrAMD0+Le1aO0nVjQUzl2g+vjz5Q==", - "dependencies": { - "@radix-ui/react-dialog": "1.0.5", - "@radix-ui/react-primitive": "1.0.3" - }, - "peerDependencies": { - "react": "^18.0.0", - "react-dom": "^18.0.0" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/commander": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", - "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", - "engines": { - "node": ">= 6" - } - }, - "node_modules/commist": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/commist/-/commist-3.2.0.tgz", - "integrity": "sha512-4PIMoPniho+LqXmpS5d3NuGYncG6XWlkBSVGiWycL22dd42OYdUGil2CWuzklaJoNxyxUSpO4MKIBU94viWNAw==" - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true - }, - "node_modules/concat-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", - "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", - "engines": [ - "node >= 6.0" - ], - "dependencies": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.0.2", - "typedarray": "^0.0.6" - } - }, - "node_modules/concat-stream/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/crypto-js": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", - "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==" - }, - "node_modules/csscolorparser": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/csscolorparser/-/csscolorparser-1.0.3.tgz", - "integrity": "sha512-umPSgYwZkdFoUrH5hIq5kf0wPSXiro51nPw0j2K/c83KflkPSTBGMz6NJvMB+07VlL0y7VPo6QJcDjcgKTTm3w==" - }, - "node_modules/cssesc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "bin": { - "cssesc": "bin/cssesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/csstype": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" - }, - "node_modules/d3-array": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", - "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", - "dependencies": { - "internmap": "1 - 2" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-color": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", - "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-dispatch": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", - "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-drag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", - "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", - "dependencies": { - "d3-dispatch": "1 - 3", - "d3-selection": "3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-ease": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", - "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-format": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", - "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-interpolate": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", - "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", - "dependencies": { - "d3-color": "1 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-path": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", - "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-scale": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", - "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", - "dependencies": { - "d3-array": "2.10.0 - 3", - "d3-format": "1 - 3", - "d3-interpolate": "1.2.0 - 3", - "d3-time": "2.1.1 - 3", - "d3-time-format": "2 - 4" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-selection": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", - "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-shape": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", - "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", - "dependencies": { - "d3-path": "^3.1.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-time": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", - "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", - "dependencies": { - "d3-array": "2 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-time-format": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", - "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", - "dependencies": { - "d3-time": "1 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-timer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", - "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-transition": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", - "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", - "dependencies": { - "d3-color": "1 - 3", - "d3-dispatch": "1 - 3", - "d3-ease": "1 - 3", - "d3-interpolate": "1 - 3", - "d3-timer": "1 - 3" - }, - "engines": { - "node": ">=12" - }, - "peerDependencies": { - "d3-selection": "2 - 3" - } - }, - "node_modules/d3-zoom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", - "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", - "dependencies": { - "d3-dispatch": "1 - 3", - "d3-drag": "2 - 3", - "d3-interpolate": "1 - 3", - "d3-selection": "2 - 3", - "d3-transition": "2 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/date-fns": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz", - "integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/kossnocorp" - } - }, - "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/decimal.js-light": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", - "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==" - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/detect-node-es": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", - "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==" - }, - "node_modules/didyoumean": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", - "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==" - }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/dlv": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", - "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==" - }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/dom-helpers": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", - "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", - "dependencies": { - "@babel/runtime": "^7.8.7", - "csstype": "^3.0.2" - } - }, - "node_modules/dotenv": { - "version": "16.3.1", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", - "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/motdotla/dotenv?sponsor=1" - } - }, - "node_modules/earcut": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/earcut/-/earcut-2.2.4.tgz", - "integrity": "sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ==" - }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" - }, - "node_modules/electron-to-chromium": { - "version": "1.4.630", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.630.tgz", - "integrity": "sha512-osHqhtjojpCsACVnuD11xO5g9xaCyw7Qqn/C2KParkMv42i8jrJJgx3g7mkHfpxwhy9MnOJr8+pKOdZ7qzgizg==", - "dev": true - }, - "node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" - }, - "node_modules/envify": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/envify/-/envify-1.2.1.tgz", - "integrity": "sha512-iShXdC8O/5jo8hPIh65Y1M28YkxW4cqPw3/mg4g5q4RQbMUljOeWp2Ctno4S1ZyTUyoGrioGAZu2t9kP79wp0Q==", - "license": "MIT", - "peer": true, - "dependencies": { - "esprima-fb": "~3001.1.0-dev-harmony-fb", - "jstransform": "~3.0.0", - "through": "~2.3.4", - "xtend": "~2.1.2" - }, - "bin": { - "envify": "bin/envify" - } - }, - "node_modules/esbuild": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz", - "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==", - "dev": true, - "hasInstallScript": true, - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.20.2", - "@esbuild/android-arm": "0.20.2", - "@esbuild/android-arm64": "0.20.2", - "@esbuild/android-x64": "0.20.2", - "@esbuild/darwin-arm64": "0.20.2", - "@esbuild/darwin-x64": "0.20.2", - "@esbuild/freebsd-arm64": "0.20.2", - "@esbuild/freebsd-x64": "0.20.2", - "@esbuild/linux-arm": "0.20.2", - "@esbuild/linux-arm64": "0.20.2", - "@esbuild/linux-ia32": "0.20.2", - "@esbuild/linux-loong64": "0.20.2", - "@esbuild/linux-mips64el": "0.20.2", - "@esbuild/linux-ppc64": "0.20.2", - "@esbuild/linux-riscv64": "0.20.2", - "@esbuild/linux-s390x": "0.20.2", - "@esbuild/linux-x64": "0.20.2", - "@esbuild/netbsd-x64": "0.20.2", - "@esbuild/openbsd-x64": "0.20.2", - "@esbuild/sunos-x64": "0.20.2", - "@esbuild/win32-arm64": "0.20.2", - "@esbuild/win32-ia32": "0.20.2", - "@esbuild/win32-x64": "0.20.2" - } - }, - "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint": { - "version": "8.56.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.56.0.tgz", - "integrity": "sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==", - "dev": true, - "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.56.0", - "@humanwhocodes/config-array": "^0.11.13", - "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "@ungap/structured-clone": "^1.2.0", - "ajv": "^6.12.4", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.2", - "eslint-visitor-keys": "^3.4.3", - "espree": "^9.6.1", - "esquery": "^1.4.2", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-plugin-react-hooks": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz", - "integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==", - "dev": true, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" - } - }, - "node_modules/eslint-plugin-react-refresh": { - "version": "0.4.5", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.5.tgz", - "integrity": "sha512-D53FYKJa+fDmZMtriODxvhwrO+IOqrxoEo21gMA0sjHdU6dPVH4OhyFip9ypl8HOF5RV5KdTo+rBQLvnY2cO8w==", - "dev": true, - "peerDependencies": { - "eslint": ">=7" - } - }, - "node_modules/eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/eslint/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", - "dev": true, - "dependencies": { - "acorn": "^8.9.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esprima-fb": { - "version": "3001.1.0-dev-harmony-fb", - "resolved": "https://registry.npmjs.org/esprima-fb/-/esprima-fb-3001.0001.0000-dev-harmony-fb.tgz", - "integrity": "sha512-a3RFiCVBiy8KdO6q/C+8BQiP/sRk8XshBU3QHHDP8tNzjYwR3FKBOImu+PXfVhPoZL0JKtJLBAOWlDMCCFY8SQ==", - "peer": true, - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/esquery": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", - "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", - "dev": true, - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/event-target-shim": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", - "engines": { - "node": ">=6" - } - }, - "node_modules/eventemitter3": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" - }, - "node_modules/events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "engines": { - "node": ">=0.8.x" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "node_modules/fast-equals": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.0.1.tgz", - "integrity": "sha512-WF1Wi8PwwSY7/6Kx0vKXtw8RwuSGoM1bvDaJbu7MxDlR1vovZjIAKrnzyrThgAjm6JDTu0fVgWXDlMGspodfoQ==", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/fast-glob": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", - "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-glob/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true - }, - "node_modules/fast-unique-numbers": { - "version": "8.0.13", - "resolved": "https://registry.npmjs.org/fast-unique-numbers/-/fast-unique-numbers-8.0.13.tgz", - "integrity": "sha512-7OnTFAVPefgw2eBJ1xj2PGGR9FwYzSUso9decayHgCDX4sJkHLdcsYTytTg+tYv+wKF3U8gJuSBz2jJpQV4u/g==", - "dependencies": { - "@babel/runtime": "^7.23.8", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.1.0" - } - }, - "node_modules/fastq": { - "version": "1.16.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.16.0.tgz", - "integrity": "sha512-ifCoaXsDrsdkWTtiNJX5uzHDsrck5TzfKKDcuFFTIrrc/BS076qgEIfoIy1VeZqViznfKiysPYTh/QeHtnIsYA==", - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/fflate": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", - "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==" - }, - "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "dependencies": { - "flat-cache": "^3.0.4" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/flat-cache": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", - "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", - "dev": true, - "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.3", - "rimraf": "^3.0.2" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/flatted": { - "version": "3.2.9", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", - "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", - "dev": true - }, - "node_modules/follow-redirects": { - "version": "1.15.6", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", - "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/foreground-child": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", - "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", - "dependencies": { - "cross-spawn": "^7.0.0", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/fraction.js": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", - "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", - "dev": true, - "engines": { - "node": "*" - }, - "funding": { - "type": "patreon", - "url": "https://github.com/sponsors/rawify" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/fuse.js": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-7.0.0.tgz", - "integrity": "sha512-14F4hBIxqKvD4Zz/XjDc3y94mNZN6pRv3U13Udo0lNLCWRBUsrMv2xwcF/y/Z5sV6+FQW+/ow68cHpm4sunt8Q==", - "license": "Apache-2.0", - "engines": { - "node": ">=10" - } - }, - "node_modules/geojson-vt": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/geojson-vt/-/geojson-vt-3.2.1.tgz", - "integrity": "sha512-EvGQQi/zPrDA6zr6BnJD/YhwAkBP8nnJ9emh3EnHQKVMfg/MRVtPbMYdgVy/IaEmn4UfagD2a6fafPDL5hbtwg==" - }, - "node_modules/get-nonce": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", - "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", - "engines": { - "node": ">=6" - } - }, - "node_modules/gl-matrix": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-3.4.3.tgz", - "integrity": "sha512-wcCp8vu8FT22BnvKVPjXa/ICBWRq/zjFfdofZy1WSpQZpphblv12/bOQLBC1rMM7SGOFS9ltVmKOHil5+Ml7gA==" - }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/glob/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/glob/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", - "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "peer": true - }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true - }, - "node_modules/grid-index": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/grid-index/-/grid-index-1.1.0.tgz", - "integrity": "sha512-HZRwumpOGUrHyxO5bqKZL0B0GlUpwtCAzZ42sgxUPniu33R1LSFH5yrIcBCHjkctCAh3mtWKcKd9J4vDDdeVHA==" - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/hasown": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", - "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/help-me": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/help-me/-/help-me-5.0.0.tgz", - "integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==" - }, - "node_modules/highcharts": { - "version": "11.4.3", - "resolved": "https://registry.npmjs.org/highcharts/-/highcharts-11.4.3.tgz", - "integrity": "sha512-rMmvYvcdwyUVfnRPfiZ0PnW6TgVhoS0FTBI8fc4Fp8l8ocoC9dMecvxS6E6tm7h7LrnSGoEo3b/0IRHuLatD2w==" - }, - "node_modules/highcharts-react-official": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/highcharts-react-official/-/highcharts-react-official-3.2.1.tgz", - "integrity": "sha512-hyQTX7ezCxl7JqumaWiGsroGWalzh24GedQIgO3vJbkGOZ6ySRAltIYjfxhrq4HszJOySZegotEF7v+haQ75UA==", - "peerDependencies": { - "highcharts": ">=6.0.0", - "react": ">=16.8.0" - } - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/ignore": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz", - "integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "node_modules/internmap": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", - "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", - "engines": { - "node": ">=12" - } - }, - "node_modules/invariant": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", - "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", - "dependencies": { - "loose-envify": "^1.0.0" - } - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-core-module": { - "version": "2.13.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", - "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", - "dependencies": { - "hasown": "^2.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" - }, - "node_modules/jackspeak": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", - "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, - "node_modules/jiti": { - "version": "1.21.0", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.0.tgz", - "integrity": "sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==", - "bin": { - "jiti": "bin/jiti.js" - } - }, - "node_modules/js-sdsl": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.3.0.tgz", - "integrity": "sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ==", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/js-sdsl" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" - }, - "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true - }, - "node_modules/jstransform": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/jstransform/-/jstransform-3.0.0.tgz", - "integrity": "sha512-sMwqW0EdQk2A5NjddlcSSLp6t7pIknOrJtxPU3kMN82RJXPGbdC3fcM5VhIsApNKL1hpeH38iSQsJRbgprPQZg==", - "peer": true, - "dependencies": { - "base62": "0.1.1", - "esprima-fb": "~3001.1.0-dev-harmony-fb", - "source-map": "0.1.31" - }, - "engines": { - "node": ">=0.8.8" - } - }, - "node_modules/kdbush": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/kdbush/-/kdbush-4.0.2.tgz", - "integrity": "sha512-WbCVYJ27Sz8zi9Q7Q0xHC+05iwkm3Znipc2XTlrnJbsHMYktW4hPhXUE8Ys1engBrvffoSCqbil1JQAa7clRpA==" - }, - "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, - "dependencies": { - "json-buffer": "3.0.1" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/lilconfig": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", - "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", - "engines": { - "node": ">=10" - } - }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" - }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" - }, - "node_modules/lodash.clonedeep": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", - "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==" - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "node_modules/loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" - }, - "bin": { - "loose-envify": "cli.js" - } - }, - "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/lucide-react": { - "version": "0.309.0", - "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.309.0.tgz", - "integrity": "sha512-zNVPczuwFrCfksZH3zbd1UDE6/WYhYAdbe2k7CImVyPAkXLgIwbs6eXQ4loigqDnUFjyFYCI5jZ1y10Kqal0dg==", - "peerDependencies": { - "react": "^16.5.1 || ^17.0.0 || ^18.0.0" - } - }, - "node_modules/mapbox-gl": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/mapbox-gl/-/mapbox-gl-3.4.0.tgz", - "integrity": "sha512-QWgL28zg/zuIOHeF8DXPvHy1UHTgO5p4Oy6ifCAHwI9/hoI9/Fruya0yI4HkDtX1OgzTLO6SHO13A781BGJvyw==", - "dependencies": { - "@mapbox/jsonlint-lines-primitives": "^2.0.2", - "@mapbox/mapbox-gl-supported": "^3.0.0", - "@mapbox/point-geometry": "^0.1.0", - "@mapbox/tiny-sdf": "^2.0.6", - "@mapbox/unitbezier": "^0.0.1", - "@mapbox/vector-tile": "^1.3.1", - "@mapbox/whoots-js": "^3.1.0", - "cheap-ruler": "^3.0.1", - "csscolorparser": "~1.0.3", - "earcut": "^2.2.4", - "fflate": "^0.8.1", - "geojson-vt": "^3.2.1", - "gl-matrix": "^3.4.3", - "grid-index": "^1.1.0", - "kdbush": "^4.0.1", - "lodash.clonedeep": "^4.5.0", - "murmurhash-js": "^1.0.0", - "pbf": "^3.2.1", - "potpack": "^2.0.0", - "quickselect": "^2.0.0", - "rw": "^1.3.3", - "serialize-to-js": "^3.1.2", - "supercluster": "^8.0.0", - "tiny-lru": "^11.2.6", - "tinyqueue": "^2.0.3", - "tweakpane": "^4.0.3", - "vt-pbf": "^3.1.3" - } - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "engines": { - "node": ">= 8" - } - }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "license": "MIT", - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/minipass": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", - "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/moment": { - "version": "2.30.1", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", - "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", - "engines": { - "node": "*" - } - }, - "node_modules/mqtt": { - "version": "5.3.5", - "resolved": "https://registry.npmjs.org/mqtt/-/mqtt-5.3.5.tgz", - "integrity": "sha512-xd7qt/LEM721U6yHQcqjlaAKXL1Fsqf/MXq6C2WPi/6OXK2jdSzL1eZ7ZUgjea7IY2yFLRWD5LNdT1mL0arPoA==", - "dependencies": { - "@types/readable-stream": "^4.0.5", - "@types/ws": "^8.5.9", - "commist": "^3.2.0", - "concat-stream": "^2.0.0", - "debug": "^4.3.4", - "help-me": "^5.0.0", - "lru-cache": "^10.0.1", - "minimist": "^1.2.8", - "mqtt": "^5.2.0", - "mqtt-packet": "^9.0.0", - "number-allocator": "^1.0.14", - "readable-stream": "^4.4.2", - "reinterval": "^1.1.0", - "rfdc": "^1.3.0", - "split2": "^4.2.0", - "worker-timers": "^7.0.78", - "ws": "^8.14.2" - }, - "bin": { - "mqtt": "build/bin/mqtt.js", - "mqtt_pub": "build/bin/pub.js", - "mqtt_sub": "build/bin/sub.js" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/mqtt-packet": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/mqtt-packet/-/mqtt-packet-9.0.0.tgz", - "integrity": "sha512-8v+HkX+fwbodsWAZIZTI074XIoxVBOmPeggQuDFCGg1SqNcC+uoRMWu7J6QlJPqIUIJXmjNYYHxBBLr1Y/Df4w==", - "dependencies": { - "bl": "^6.0.8", - "debug": "^4.3.4", - "process-nextick-args": "^2.0.1" - } - }, - "node_modules/mqtt/node_modules/lru-cache": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz", - "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==", - "engines": { - "node": "14 || >=16.14" - } - }, - "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "node_modules/murmurhash-js": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/murmurhash-js/-/murmurhash-js-1.0.0.tgz", - "integrity": "sha512-TvmkNhkv8yct0SVBSy+o8wYzXjE4Zz3PCesbfs8HiCXXdcTuocApFv11UWlNFWKYsP2okqrhb7JNlSm9InBhIw==" - }, - "node_modules/mz": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", - "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", - "dependencies": { - "any-promise": "^1.0.0", - "object-assign": "^4.0.1", - "thenify-all": "^1.0.0" - } - }, - "node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true - }, - "node_modules/next": { - "version": "14.2.3", - "resolved": "https://registry.npmjs.org/next/-/next-14.2.3.tgz", - "integrity": "sha512-dowFkFTR8v79NPJO4QsBUtxv0g9BrS/phluVpMAt2ku7H+cbcBJlopXjkWlwxrk/xGqMemr7JkGPGemPrLLX7A==", - "peer": true, - "dependencies": { - "@next/env": "14.2.3", - "@swc/helpers": "0.5.5", - "busboy": "1.6.0", - "caniuse-lite": "^1.0.30001579", - "graceful-fs": "^4.2.11", - "postcss": "8.4.31", - "styled-jsx": "5.1.1" - }, - "bin": { - "next": "dist/bin/next" - }, - "engines": { - "node": ">=18.17.0" - }, - "optionalDependencies": { - "@next/swc-darwin-arm64": "14.2.3", - "@next/swc-darwin-x64": "14.2.3", - "@next/swc-linux-arm64-gnu": "14.2.3", - "@next/swc-linux-arm64-musl": "14.2.3", - "@next/swc-linux-x64-gnu": "14.2.3", - "@next/swc-linux-x64-musl": "14.2.3", - "@next/swc-win32-arm64-msvc": "14.2.3", - "@next/swc-win32-ia32-msvc": "14.2.3", - "@next/swc-win32-x64-msvc": "14.2.3" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.1.0", - "@playwright/test": "^1.41.2", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "sass": "^1.3.0" - }, - "peerDependenciesMeta": { - "@opentelemetry/api": { - "optional": true - }, - "@playwright/test": { - "optional": true - }, - "sass": { - "optional": true - } - } - }, - "node_modules/next-themes": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.2.1.tgz", - "integrity": "sha512-B+AKNfYNIzh0vqQQKqQItTS8evEouKD7H5Hj3kmuPERwddR2TxvDSFZuTj6T7Jfn1oyeUyJMydPl1Bkxkh0W7A==", - "peerDependencies": { - "next": "*", - "react": "*", - "react-dom": "*" - } - }, - "node_modules/next/node_modules/postcss": { - "version": "8.4.31", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", - "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "peer": true, - "dependencies": { - "nanoid": "^3.3.6", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/node-releases": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", - "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", - "dev": true - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/normalize-range": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/number-allocator": { - "version": "1.0.14", - "resolved": "https://registry.npmjs.org/number-allocator/-/number-allocator-1.0.14.tgz", - "integrity": "sha512-OrL44UTVAvkKdOdRQZIJpLkAdjXGTRda052sN4sO77bKEzYYqWKMBjQvrJFzqygI99gL6Z4u2xctPW1tB8ErvA==", - "dependencies": { - "debug": "^4.3.1", - "js-sdsl": "4.3.0" - } - }, - "node_modules/numeral": { - "version": "1.5.6", - "resolved": "https://registry.npmjs.org/numeral/-/numeral-1.5.6.tgz", - "integrity": "sha512-ajp+xurmcvkOLZURhHP2O7AyyF+v2xQDeCODlzALrNeAQnriYaWu0c8I/mu985WaVl2O2lgdOt0QgQHlCAQ3UA==", - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-hash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", - "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", - "engines": { - "node": ">= 6" - } - }, - "node_modules/object-keys": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-0.4.0.tgz", - "integrity": "sha512-ncrLw+X55z7bkl5PnUvHwFK9FcGuFYo9gtjws2XtSzL+aZ8tm830P60WJ0dSmFVaSalWieW5MD7kEdnXda9yJw==", - "license": "MIT", - "peer": true - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/optionator": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", - "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", - "dev": true, - "dependencies": { - "@aashutoshrathi/word-wrap": "^1.2.3", - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" - }, - "node_modules/path-scurry": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz", - "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==", - "dependencies": { - "lru-cache": "^9.1.1 || ^10.0.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/path-scurry/node_modules/lru-cache": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.1.0.tgz", - "integrity": "sha512-/1clY/ui8CzjKFyjdvwPWJUYKiFVXG2I2cY0ssG7h4+hwk+XOIX7ZSG9Q7TW8TW3Kp3BUSqgFWBLgL4PJ+Blag==", - "engines": { - "node": "14 || >=16.14" - } - }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/pbf": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/pbf/-/pbf-3.2.1.tgz", - "integrity": "sha512-ClrV7pNOn7rtmoQVF4TS1vyU0WhYRnP92fzbfF75jAIwpnzdJXf8iTd4CMEqO4yUenH6NDqLiwjqlh6QgZzgLQ==", - "dependencies": { - "ieee754": "^1.1.12", - "resolve-protobuf-schema": "^2.1.0" - }, - "bin": { - "pbf": "bin/pbf" - } - }, - "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/pirates": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", - "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", - "engines": { - "node": ">= 6" - } - }, - "node_modules/postcss": { - "version": "8.4.38", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", - "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "nanoid": "^3.3.7", - "picocolors": "^1.0.0", - "source-map-js": "^1.2.0" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/postcss-import": { - "version": "15.1.0", - "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", - "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", - "dependencies": { - "postcss-value-parser": "^4.0.0", - "read-cache": "^1.0.0", - "resolve": "^1.1.7" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "postcss": "^8.0.0" - } - }, - "node_modules/postcss-js": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", - "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", - "dependencies": { - "camelcase-css": "^2.0.1" - }, - "engines": { - "node": "^12 || ^14 || >= 16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - "peerDependencies": { - "postcss": "^8.4.21" - } - }, - "node_modules/postcss-load-config": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz", - "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "lilconfig": "^3.0.0", - "yaml": "^2.3.4" - }, - "engines": { - "node": ">= 14" - }, - "peerDependencies": { - "postcss": ">=8.0.9", - "ts-node": ">=9.0.0" - }, - "peerDependenciesMeta": { - "postcss": { - "optional": true - }, - "ts-node": { - "optional": true - } - } - }, - "node_modules/postcss-load-config/node_modules/lilconfig": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.0.0.tgz", - "integrity": "sha512-K2U4W2Ff5ibV7j7ydLr+zLAkIg5JJ4lPn1Ltsdt+Tz/IjQ8buJ55pZAxoP34lqIiwtF9iAvtLv3JGv7CAyAg+g==", - "engines": { - "node": ">=14" - } - }, - "node_modules/postcss-nested": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.1.tgz", - "integrity": "sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==", - "dependencies": { - "postcss-selector-parser": "^6.0.11" - }, - "engines": { - "node": ">=12.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - "peerDependencies": { - "postcss": "^8.2.14" - } - }, - "node_modules/postcss-selector-parser": { - "version": "6.0.15", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.15.tgz", - "integrity": "sha512-rEYkQOMUCEMhsKbK66tbEU9QVIxbhN18YiniAwA7XQYTVBqrBy+P2p5JcdqsHgKM2zWylp8d7J6eszocfds5Sw==", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" - }, - "node_modules/potpack": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/potpack/-/potpack-2.0.0.tgz", - "integrity": "sha512-Q+/tYsFU9r7xoOJ+y/ZTtdVQwTWfzjbiXBDMM/JKUux3+QPP02iUuIoeBQ+Ot6oEDlC+/PGjB/5A3K7KKb7hcw==" - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/prettier": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.4.tgz", - "integrity": "sha512-FWu1oLHKCrtpO1ypU6J0SbK2d9Ckwysq6bHj/uaCP26DxrPpppCLQRGVuqAxSTvhF00AcvDRyYrLNW7ocBhFFQ==", - "dev": true, - "bin": { - "prettier": "bin/prettier.cjs" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/prettier-plugin-tailwindcss": { - "version": "0.5.11", - "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.5.11.tgz", - "integrity": "sha512-AvI/DNyMctyyxGOjyePgi/gqj5hJYClZ1avtQvLlqMT3uDZkRbi4HhGUpok3DRzv9z7Lti85Kdj3s3/1CeNI0w==", - "dev": true, - "engines": { - "node": ">=14.21.3" - }, - "peerDependencies": { - "@ianvs/prettier-plugin-sort-imports": "*", - "@prettier/plugin-pug": "*", - "@shopify/prettier-plugin-liquid": "*", - "@trivago/prettier-plugin-sort-imports": "*", - "prettier": "^3.0", - "prettier-plugin-astro": "*", - "prettier-plugin-css-order": "*", - "prettier-plugin-import-sort": "*", - "prettier-plugin-jsdoc": "*", - "prettier-plugin-marko": "*", - "prettier-plugin-organize-attributes": "*", - "prettier-plugin-organize-imports": "*", - "prettier-plugin-style-order": "*", - "prettier-plugin-svelte": "*" - }, - "peerDependenciesMeta": { - "@ianvs/prettier-plugin-sort-imports": { - "optional": true - }, - "@prettier/plugin-pug": { - "optional": true - }, - "@shopify/prettier-plugin-liquid": { - "optional": true - }, - "@trivago/prettier-plugin-sort-imports": { - "optional": true - }, - "prettier-plugin-astro": { - "optional": true - }, - "prettier-plugin-css-order": { - "optional": true - }, - "prettier-plugin-import-sort": { - "optional": true - }, - "prettier-plugin-jsdoc": { - "optional": true - }, - "prettier-plugin-marko": { - "optional": true - }, - "prettier-plugin-organize-attributes": { - "optional": true - }, - "prettier-plugin-organize-imports": { - "optional": true - }, - "prettier-plugin-style-order": { - "optional": true - }, - "prettier-plugin-svelte": { - "optional": true - }, - "prettier-plugin-twig-melody": { - "optional": true - } - } - }, - "node_modules/process": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", - "engines": { - "node": ">= 0.6.0" - } - }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" - }, - "node_modules/prop-types": { - "version": "15.8.1", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", - "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "dependencies": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.13.1" - } - }, - "node_modules/protocol-buffers-schema": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.6.0.tgz", - "integrity": "sha512-TdDRD+/QNdrCGCE7v8340QyuXd4kIWIgapsE2+n/SaGiSSbomYl4TjHlvIoCWRpE7wFt02EpB35VVA2ImcBVqw==" - }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" - }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/quickselect": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-2.0.0.tgz", - "integrity": "sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw==" - }, - "node_modules/randgen": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/randgen/-/randgen-0.1.0.tgz", - "integrity": "sha512-z0xKLFpnQdh+HxSDrR71trUqR/U8OnPUU3FYRXdZDr3sw99+scgFIZmwSHbR2L//sgX5oq9opfZTkC5v1WCkoQ==", - "license": "MIT" - }, - "node_modules/react": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", - "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", - "dependencies": { - "loose-envify": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-charts": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/react-charts/-/react-charts-0.0.1.tgz", - "integrity": "sha512-tdR8ywYrhC0lC/5XNs99DenhA62S8LvMvH4oY2mnZnHCYWMwrMI+4u7GRq3DIIavBfUi4h/FDQUzpScy5s1upw==", - "license": "MIT", - "dependencies": { - "numeral": "^1.5.3", - "randgen": "^0.1.0", - "react": "~0.10.0" - } - }, - "node_modules/react-charts/node_modules/react": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/react/-/react-0.10.0.tgz", - "integrity": "sha512-BJHANYsDIKYPZVKp1qjynvmELGETFvFzGsj1SLPOwRjnm1QtMp38fUcf67uc5T+QBBx29xHcgn3BNVug7/mwBA==", - "engines": { - "node": ">=0.10.0" - }, - "peerDependencies": { - "envify": "~1.2.0" - } - }, - "node_modules/react-day-picker": { - "version": "8.10.1", - "resolved": "https://registry.npmjs.org/react-day-picker/-/react-day-picker-8.10.1.tgz", - "integrity": "sha512-TMx7fNbhLk15eqcMt+7Z7S2KF7mfTId/XJDjKE8f+IUcFn0l08/kI4FiYTL/0yuOLmEcbR4Fwe3GJf/NiiMnPA==", - "funding": { - "type": "individual", - "url": "https://github.com/sponsors/gpbl" - }, - "peerDependencies": { - "date-fns": "^2.28.0 || ^3.0.0", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - } - }, - "node_modules/react-dom": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", - "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", - "dependencies": { - "loose-envify": "^1.1.0", - "scheduler": "^0.23.0" - }, - "peerDependencies": { - "react": "^18.2.0" - } - }, - "node_modules/react-draggable": { - "version": "4.4.6", - "resolved": "https://registry.npmjs.org/react-draggable/-/react-draggable-4.4.6.tgz", - "integrity": "sha512-LtY5Xw1zTPqHkVmtM3X8MUOxNDOUhv/khTgBgrUvwaS064bwVvxT+q5El0uUFNx5IEPKXuRejr7UqLwBIg5pdw==", - "dependencies": { - "clsx": "^1.1.1", - "prop-types": "^15.8.1" - }, - "peerDependencies": { - "react": ">= 16.3.0", - "react-dom": ">= 16.3.0" - } - }, - "node_modules/react-draggable/node_modules/clsx": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", - "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", - "engines": { - "node": ">=6" - } - }, - "node_modules/react-grid-layout": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/react-grid-layout/-/react-grid-layout-1.4.4.tgz", - "integrity": "sha512-7+Lg8E8O8HfOH5FrY80GCIR1SHTn2QnAYKh27/5spoz+OHhMmEhU/14gIkRzJOtympDPaXcVRX/nT1FjmeOUmQ==", - "dependencies": { - "clsx": "^2.0.0", - "fast-equals": "^4.0.3", - "prop-types": "^15.8.1", - "react-draggable": "^4.4.5", - "react-resizable": "^3.0.5", - "resize-observer-polyfill": "^1.5.1" - }, - "peerDependencies": { - "react": ">= 16.3.0", - "react-dom": ">= 16.3.0" - } - }, - "node_modules/react-grid-layout/node_modules/fast-equals": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-4.0.3.tgz", - "integrity": "sha512-G3BSX9cfKttjr+2o1O22tYMLq0DPluZnYtq1rXumE1SpL/F/SLIfHx08WYQoWSIpeMYf8sRbJ8++71+v6Pnxfg==" - }, - "node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" - }, - "node_modules/react-remove-scroll": { - "version": "2.5.5", - "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.5.tgz", - "integrity": "sha512-ImKhrzJJsyXJfBZ4bzu8Bwpka14c/fQt0k+cyFp/PBhTfyDnU5hjOtM4AG/0AMyy8oKzOTR0lDgJIM7pYXI0kw==", - "dependencies": { - "react-remove-scroll-bar": "^2.3.3", - "react-style-singleton": "^2.2.1", - "tslib": "^2.1.0", - "use-callback-ref": "^1.3.0", - "use-sidecar": "^1.1.2" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/react-remove-scroll-bar": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.4.tgz", - "integrity": "sha512-63C4YQBUt0m6ALadE9XV56hV8BgJWDmmTPY758iIJjfQKt2nYwoUrPk0LXRXcB/yIj82T1/Ixfdpdk68LwIB0A==", - "dependencies": { - "react-style-singleton": "^2.2.1", - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/react-resizable": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/react-resizable/-/react-resizable-3.0.5.tgz", - "integrity": "sha512-vKpeHhI5OZvYn82kXOs1bC8aOXktGU5AmKAgaZS4F5JPburCtbmDPqE7Pzp+1kN4+Wb81LlF33VpGwWwtXem+w==", - "dependencies": { - "prop-types": "15.x", - "react-draggable": "^4.0.3" - }, - "peerDependencies": { - "react": ">= 16.3" - } - }, - "node_modules/react-router": { - "version": "6.21.2", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.21.2.tgz", - "integrity": "sha512-jJcgiwDsnaHIeC+IN7atO0XiSRCrOsQAHHbChtJxmgqG2IaYQXSnhqGb5vk2CU/wBQA12Zt+TkbuJjIn65gzbA==", - "dependencies": { - "@remix-run/router": "1.14.2" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "react": ">=16.8" - } - }, - "node_modules/react-router-dom": { - "version": "6.21.2", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.21.2.tgz", - "integrity": "sha512-tE13UukgUOh2/sqYr6jPzZTzmzc70aGRP4pAjG2if0IP3aUT+sBtAKUJh0qMh0zylJHGLmzS+XWVaON4UklHeg==", - "dependencies": { - "@remix-run/router": "1.14.2", - "react-router": "6.21.2" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "react": ">=16.8", - "react-dom": ">=16.8" - } - }, - "node_modules/react-smooth": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.1.tgz", - "integrity": "sha512-OE4hm7XqR0jNOq3Qmk9mFLyd6p2+j6bvbPJ7qlB7+oo0eNcL2l7WQzG6MBnT3EXY6xzkLMUBec3AfewJdA0J8w==", - "dependencies": { - "fast-equals": "^5.0.1", - "prop-types": "^15.8.1", - "react-transition-group": "^4.4.5" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" - } - }, - "node_modules/react-style-singleton": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz", - "integrity": "sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==", - "dependencies": { - "get-nonce": "^1.0.0", - "invariant": "^2.2.4", - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/react-superstore": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/react-superstore/-/react-superstore-0.1.4.tgz", - "integrity": "sha512-aa2/yGdm0ElhlwrUxq3/xwou3yMbYZfM96Ez/vS346ZXqTbqUBhXaaUlWb4GDGCoxu8yhD5oXiEIzD/i08tx2w==", - "license": "MIT", - "dependencies": { - "react": "^18.0.0" - } - }, - "node_modules/react-transition-group": { - "version": "4.4.5", - "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", - "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", - "dependencies": { - "@babel/runtime": "^7.5.5", - "dom-helpers": "^5.0.1", - "loose-envify": "^1.4.0", - "prop-types": "^15.6.2" - }, - "peerDependencies": { - "react": ">=16.6.0", - "react-dom": ">=16.6.0" - } - }, - "node_modules/react-use-websocket": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/react-use-websocket/-/react-use-websocket-4.5.0.tgz", - "integrity": "sha512-oxYVLWM3Lv0InCfjW7hG/Hk0hkE0P1SiLd5/I3d5x0W4riAnDUkD4VEu7qNVAqxNjBF3nU7k0jLMOetLXpwfsA==", - "peerDependencies": { - "react": ">= 18.0.0", - "react-dom": ">= 18.0.0" - } - }, - "node_modules/reactflow": { - "version": "11.11.3", - "resolved": "https://registry.npmjs.org/reactflow/-/reactflow-11.11.3.tgz", - "integrity": "sha512-wusd1Xpn1wgsSEv7UIa4NNraCwH9syBtubBy4xVNXg3b+CDKM+sFaF3hnMx0tr0et4km9urIDdNvwm34QiZong==", - "dependencies": { - "@reactflow/background": "11.3.13", - "@reactflow/controls": "11.2.13", - "@reactflow/core": "11.11.3", - "@reactflow/minimap": "11.7.13", - "@reactflow/node-resizer": "2.2.13", - "@reactflow/node-toolbar": "1.3.13" - }, - "peerDependencies": { - "react": ">=17", - "react-dom": ">=17" - } - }, - "node_modules/read-cache": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", - "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", - "dependencies": { - "pify": "^2.3.0" - } - }, - "node_modules/readable-stream": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", - "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", - "dependencies": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10", - "string_decoder": "^1.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/recharts": { - "version": "2.12.7", - "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.12.7.tgz", - "integrity": "sha512-hlLJMhPQfv4/3NBSAyq3gzGg4h2v69RJh6KU7b3pXYNNAELs9kEoXOjbkxdXpALqKBoVmVptGfLpxdaVYqjmXQ==", - "dependencies": { - "clsx": "^2.0.0", - "eventemitter3": "^4.0.1", - "lodash": "^4.17.21", - "react-is": "^16.10.2", - "react-smooth": "^4.0.0", - "recharts-scale": "^0.4.4", - "tiny-invariant": "^1.3.1", - "victory-vendor": "^36.6.8" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "react": "^16.0.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0" - } - }, - "node_modules/recharts-scale": { - "version": "0.4.5", - "resolved": "https://registry.npmjs.org/recharts-scale/-/recharts-scale-0.4.5.tgz", - "integrity": "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==", - "dependencies": { - "decimal.js-light": "^2.4.1" - } - }, - "node_modules/regenerator-runtime": { - "version": "0.14.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", - "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" - }, - "node_modules/reinterval": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/reinterval/-/reinterval-1.1.0.tgz", - "integrity": "sha512-QIRet3SYrGp0HUHO88jVskiG6seqUGC5iAG7AwI/BV4ypGcuqk9Du6YQBUOUqm9c8pw1eyLoIaONifRua1lsEQ==" - }, - "node_modules/resize-observer-polyfill": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", - "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==" - }, - "node_modules/resolve": { - "version": "1.22.8", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", - "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", - "dependencies": { - "is-core-module": "^2.13.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/resolve-protobuf-schema": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/resolve-protobuf-schema/-/resolve-protobuf-schema-2.1.0.tgz", - "integrity": "sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ==", - "dependencies": { - "protocol-buffers-schema": "^3.3.1" - } - }, - "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rfdc": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.1.tgz", - "integrity": "sha512-r5a3l5HzYlIC68TpmYKlxWjmOP6wiPJ1vWv2HeLhNsRZMrCkxeqxiHlQ21oXmQ4F3SiryXBHhAD7JZqvOJjFmg==" - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rollup": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.18.0.tgz", - "integrity": "sha512-QmJz14PX3rzbJCN1SG4Xe/bAAX2a6NpCP8ab2vfu2GiUr8AQcr2nCV/oEO3yneFarB67zk8ShlIyWb2LGTb3Sg==", - "dev": true, - "dependencies": { - "@types/estree": "1.0.5" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.18.0", - "@rollup/rollup-android-arm64": "4.18.0", - "@rollup/rollup-darwin-arm64": "4.18.0", - "@rollup/rollup-darwin-x64": "4.18.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.18.0", - "@rollup/rollup-linux-arm-musleabihf": "4.18.0", - "@rollup/rollup-linux-arm64-gnu": "4.18.0", - "@rollup/rollup-linux-arm64-musl": "4.18.0", - "@rollup/rollup-linux-powerpc64le-gnu": "4.18.0", - "@rollup/rollup-linux-riscv64-gnu": "4.18.0", - "@rollup/rollup-linux-s390x-gnu": "4.18.0", - "@rollup/rollup-linux-x64-gnu": "4.18.0", - "@rollup/rollup-linux-x64-musl": "4.18.0", - "@rollup/rollup-win32-arm64-msvc": "4.18.0", - "@rollup/rollup-win32-ia32-msvc": "4.18.0", - "@rollup/rollup-win32-x64-msvc": "4.18.0", - "fsevents": "~2.3.2" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/rw": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", - "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==" - }, - "node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "node_modules/scheduler": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", - "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", - "dependencies": { - "loose-envify": "^1.1.0" - } - }, - "node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/serialize-to-js": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/serialize-to-js/-/serialize-to-js-3.1.2.tgz", - "integrity": "sha512-owllqNuDDEimQat7EPG0tH7JjO090xKNzUtYz6X+Sk2BXDnOCilDdNLwjWeFywG9xkJul1ULvtUQa9O4pUaY0w==", - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "engines": { - "node": ">=8" - } - }, - "node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/sonner": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/sonner/-/sonner-1.4.0.tgz", - "integrity": "sha512-nvkTsIuOmi9e5Wz5If8ldasJjZNVfwiXYijBi2dbijvTQnQppvMcXTFNxL/NUFWlI2yJ1JX7TREDsg+gYm9WyA==", - "peerDependencies": { - "react": "^18.0.0", - "react-dom": "^18.0.0" - } - }, - "node_modules/source-map": { - "version": "0.1.31", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.31.tgz", - "integrity": "sha512-qFALUiKHo35Duky0Ubmb5YKj9b3c6CcgGNGeI60sd6Nn3KaY7h9fclEOcCVk0hwszwYYP6+X2/jpS5hHqqVuig==", - "peer": true, - "dependencies": { - "amdefine": ">=0.0.4" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/source-map-js": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", - "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/split2": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", - "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", - "engines": { - "node": ">= 10.x" - } - }, - "node_modules/streamsearch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", - "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", - "peer": true, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/string_decoder/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/string-width-cjs": { - "name": "string-width", - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - }, - "node_modules/string-width/node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/string-width/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/styled-jsx": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.1.tgz", - "integrity": "sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==", - "peer": true, - "dependencies": { - "client-only": "0.0.1" - }, - "engines": { - "node": ">= 12.0.0" - }, - "peerDependencies": { - "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0" - }, - "peerDependenciesMeta": { - "@babel/core": { - "optional": true - }, - "babel-plugin-macros": { - "optional": true - } - } - }, - "node_modules/sucrase": { - "version": "3.35.0", - "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", - "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.2", - "commander": "^4.0.0", - "glob": "^10.3.10", - "lines-and-columns": "^1.1.6", - "mz": "^2.7.0", - "pirates": "^4.0.1", - "ts-interface-checker": "^0.1.9" - }, - "bin": { - "sucrase": "bin/sucrase", - "sucrase-node": "bin/sucrase-node" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/sucrase/node_modules/glob": { - "version": "10.3.10", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", - "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^2.3.5", - "minimatch": "^9.0.1", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", - "path-scurry": "^1.10.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/supercluster": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/supercluster/-/supercluster-8.0.1.tgz", - "integrity": "sha512-IiOea5kJ9iqzD2t7QJq/cREyLHTtSmUT6gQsweojg9WH2sYJqZK9SswTu6jrscO6D1G5v5vYZ9ru/eq85lXeZQ==", - "dependencies": { - "kdbush": "^4.0.2" - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/tailwind-merge": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.2.1.tgz", - "integrity": "sha512-o+2GTLkthfa5YUt4JxPfzMIpQzZ3adD1vLVkvKE1Twl9UAhGsEbIZhHHZVRttyW177S8PDJI3bTQNaebyofK3Q==", - "dependencies": { - "@babel/runtime": "^7.23.7" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/dcastil" - } - }, - "node_modules/tailwindcss": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.1.tgz", - "integrity": "sha512-qAYmXRfk3ENzuPBakNK0SRrUDipP8NQnEY6772uDhflcQz5EhRdD7JNZxyrFHVQNCwULPBn6FNPp9brpO7ctcA==", - "dependencies": { - "@alloc/quick-lru": "^5.2.0", - "arg": "^5.0.2", - "chokidar": "^3.5.3", - "didyoumean": "^1.2.2", - "dlv": "^1.1.3", - "fast-glob": "^3.3.0", - "glob-parent": "^6.0.2", - "is-glob": "^4.0.3", - "jiti": "^1.19.1", - "lilconfig": "^2.1.0", - "micromatch": "^4.0.5", - "normalize-path": "^3.0.0", - "object-hash": "^3.0.0", - "picocolors": "^1.0.0", - "postcss": "^8.4.23", - "postcss-import": "^15.1.0", - "postcss-js": "^4.0.1", - "postcss-load-config": "^4.0.1", - "postcss-nested": "^6.0.1", - "postcss-selector-parser": "^6.0.11", - "resolve": "^1.22.2", - "sucrase": "^3.32.0" - }, - "bin": { - "tailwind": "lib/cli.js", - "tailwindcss": "lib/cli.js" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/tailwindcss-animate": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/tailwindcss-animate/-/tailwindcss-animate-1.0.7.tgz", - "integrity": "sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==", - "peerDependencies": { - "tailwindcss": ">=3.0.0 || insiders" - } - }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true - }, - "node_modules/thenify": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", - "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", - "dependencies": { - "any-promise": "^1.0.0" - } - }, - "node_modules/thenify-all": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", - "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", - "dependencies": { - "thenify": ">= 3.1.0 < 4" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", - "license": "MIT", - "peer": true - }, - "node_modules/tiny-invariant": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", - "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==" - }, - "node_modules/tiny-lru": { - "version": "11.2.6", - "resolved": "https://registry.npmjs.org/tiny-lru/-/tiny-lru-11.2.6.tgz", - "integrity": "sha512-0PU3c9PjMnltZaFo2sGYv/nnJsMjG0Cxx8X6FXHPPGjFyoo1SJDxvUXW1207rdiSxYizf31roo+GrkIByQeZoA==", - "engines": { - "node": ">=12" - } - }, - "node_modules/tinyqueue": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/tinyqueue/-/tinyqueue-2.0.3.tgz", - "integrity": "sha512-ppJZNDuKGgxzkHihX8v9v9G5f+18gzaTfrukGrq6ueg0lmH4nqVnA2IPG0AEH3jKEk2GRJCUhDoqpoiw3PHLBA==" - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/ts-api-utils": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz", - "integrity": "sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==", - "dev": true, - "engines": { - "node": ">=16.13.0" - }, - "peerDependencies": { - "typescript": ">=4.2.0" - } - }, - "node_modules/ts-interface-checker": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", - "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==" - }, - "node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" - }, - "node_modules/tweakpane": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/tweakpane/-/tweakpane-4.0.3.tgz", - "integrity": "sha512-BlcWOAe8oe4c+k9pmLBARGdWB6MVZMszayekkixQXTgkxTaYoTUpHpwVEp+3HkoamZkomodpbBf0CkguIHTgLg==", - "funding": { - "url": "https://github.com/sponsors/cocopon" - } - }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" - }, - "node_modules/typescript": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", - "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", - "dev": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" - }, - "node_modules/update-browserslist-db": { - "version": "1.0.13", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", - "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/use-callback-ref": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.1.tgz", - "integrity": "sha512-Lg4Vx1XZQauB42Hw3kK7JM6yjVjgFmFC5/Ab797s79aARomD2nEErc4mCgM8EZrARLmmbWpi5DGCadmK50DcAQ==", - "dependencies": { - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/use-sidecar": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.2.tgz", - "integrity": "sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==", - "dependencies": { - "detect-node-es": "^1.1.0", - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "^16.9.0 || ^17.0.0 || ^18.0.0", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/use-sync-external-store": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", - "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" - }, - "node_modules/victory-vendor": { - "version": "36.9.2", - "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.9.2.tgz", - "integrity": "sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==", - "dependencies": { - "@types/d3-array": "^3.0.3", - "@types/d3-ease": "^3.0.0", - "@types/d3-interpolate": "^3.0.1", - "@types/d3-scale": "^4.0.2", - "@types/d3-shape": "^3.1.0", - "@types/d3-time": "^3.0.0", - "@types/d3-timer": "^3.0.0", - "d3-array": "^3.1.6", - "d3-ease": "^3.0.1", - "d3-interpolate": "^3.0.1", - "d3-scale": "^4.0.2", - "d3-shape": "^3.1.0", - "d3-time": "^3.0.0", - "d3-timer": "^3.0.1" - } - }, - "node_modules/vite": { - "version": "5.2.12", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.12.tgz", - "integrity": "sha512-/gC8GxzxMK5ntBwb48pR32GGhENnjtY30G4A0jemunsBkiEZFw60s8InGpN8gkhHEkjnRK1aSAxeQgwvFhUHAA==", - "dev": true, - "dependencies": { - "esbuild": "^0.20.1", - "postcss": "^8.4.38", - "rollup": "^4.13.0" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^18.0.0 || >=20.0.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^18.0.0 || >=20.0.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.4.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - } - } - }, - "node_modules/vt-pbf": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/vt-pbf/-/vt-pbf-3.1.3.tgz", - "integrity": "sha512-2LzDFzt0mZKZ9IpVF2r69G9bXaP2Q2sArJCmcCgvfTdCCZzSyz4aCLoQyUilu37Ll56tCblIZrXFIjNUpGIlmA==", - "dependencies": { - "@mapbox/point-geometry": "0.1.0", - "@mapbox/vector-tile": "^1.3.1", - "pbf": "^3.2.1" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/worker-timers": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/worker-timers/-/worker-timers-7.1.1.tgz", - "integrity": "sha512-axtq83GwPqYwkQmQmei2abQ9cT7oSwmLw4lQCZ9VmMH9g4t4kuEF1Gw+tdnIJGHCiZ2QPDnr/+307bYx6tynLA==", - "dependencies": { - "@babel/runtime": "^7.23.8", - "tslib": "^2.6.2", - "worker-timers-broker": "^6.1.1", - "worker-timers-worker": "^7.0.65" - } - }, - "node_modules/worker-timers-broker": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/worker-timers-broker/-/worker-timers-broker-6.1.1.tgz", - "integrity": "sha512-CTlDnkXAewtYvw5gOwVIc6UuIPcNHJrqWxBMhZbCWOmadvl20nPs9beAsXlaTEwW3G2KBpuKiSgkhBkhl3mxDA==", - "dependencies": { - "@babel/runtime": "^7.23.8", - "fast-unique-numbers": "^8.0.13", - "tslib": "^2.6.2", - "worker-timers-worker": "^7.0.65" - } - }, - "node_modules/worker-timers-worker": { - "version": "7.0.65", - "resolved": "https://registry.npmjs.org/worker-timers-worker/-/worker-timers-worker-7.0.65.tgz", - "integrity": "sha512-Dl4nGONr8A8Fr+vQnH7Ee+o2iB480S1fBcyJYqnMyMwGRVyQZLZU+o91vbMvU1vHqiryRQmjXzzMYlh86wx+YQ==", - "dependencies": { - "@babel/runtime": "^7.23.8", - "tslib": "^2.6.2" - } - }, - "node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs": { - "name": "wrap-ansi", - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - }, - "node_modules/wrap-ansi-cjs/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true - }, - "node_modules/ws": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", - "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/xtend": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-2.1.2.tgz", - "integrity": "sha512-vMNKzr2rHP9Dp/e1NQFnLQlwlhp9L/LfvnsVdHxN1f+uggyVI3i08uD14GPvCToPkdsRfyPqIyYGmIk58V98ZQ==", - "peer": true, - "dependencies": { - "object-keys": "~0.4.0" - }, - "engines": { - "node": ">=0.4" - } - }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/yaml": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz", - "integrity": "sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==", - "engines": { - "node": ">= 14" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/zustand": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.2.tgz", - "integrity": "sha512-2cN1tPkDVkwCy5ickKrI7vijSjPksFRfqS6237NzT0vqSsztTNnQdHw9mmN7uBdk3gceVXU0a+21jFzFzAc9+g==", - "dependencies": { - "use-sync-external-store": "1.2.0" - }, - "engines": { - "node": ">=12.7.0" - }, - "peerDependencies": { - "@types/react": ">=16.8", - "immer": ">=9.0.6", - "react": ">=16.8" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "immer": { - "optional": true - }, - "react": { - "optional": true - } - } - } - } -} diff --git a/web/package.json b/web/package.json deleted file mode 100644 index 6070e80..0000000 --- a/web/package.json +++ /dev/null @@ -1,84 +0,0 @@ -{ - "name": "sentinel", - "private": true, - "version": "0.0.0", - "type": "module", - "scripts": { - "dev": "vite", - "build": "tsc && vite build", - "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", - "format": "npx prettier --write .", - "check": "npx prettier --check .", - "preview": "vite preview" - }, - "dependencies": { - "@fortawesome/fontawesome-svg-core": "^6.5.1", - "@fortawesome/free-brands-svg-icons": "^6.5.1", - "@fortawesome/free-regular-svg-icons": "^6.5.1", - "@fortawesome/free-solid-svg-icons": "^6.5.1", - "@fortawesome/react-fontawesome": "^0.2.0", - "@radix-ui/react-alert-dialog": "^1.0.5", - "@radix-ui/react-avatar": "^1.0.4", - "@radix-ui/react-checkbox": "^1.1.1", - "@radix-ui/react-dialog": "^1.0.5", - "@radix-ui/react-dropdown-menu": "^2.0.6", - "@radix-ui/react-label": "^2.0.2", - "@radix-ui/react-popover": "^1.0.7", - "@radix-ui/react-progress": "^1.0.3", - "@radix-ui/react-scroll-area": "^1.1.0", - "@radix-ui/react-select": "^2.1.1", - "@radix-ui/react-separator": "^1.0.3", - "@radix-ui/react-slot": "^1.0.2", - "@radix-ui/react-switch": "^1.1.0", - "@radix-ui/react-toast": "^1.1.5", - "@radix-ui/react-tooltip": "^1.1.2", - "@types/mapbox-gl": "^3.1.0", - "axios": "^1.6.5", - "class-variance-authority": "^0.7.0", - "clsx": "^2.1.0", - "cmdk": "^1.0.0", - "crypto-js": "^4.2.0", - "date-fns": "^3.6.0", - "dotenv": "^16.3.1", - "fuse.js": "^7.0.0", - "highcharts": "^11.4.3", - "highcharts-react-official": "^3.2.1", - "lucide-react": "^0.309.0", - "mapbox-gl": "^3.4.0", - "moment": "^2.30.1", - "mqtt": "^5.3.5", - "next-themes": "^0.2.1", - "react": "^18.2.0", - "react-charts": "^0.0.1", - "react-day-picker": "^8.10.1", - "react-dom": "^18.2.0", - "react-grid-layout": "^1.4.4", - "react-router-dom": "^6.21.2", - "react-superstore": "^0.1.4", - "react-use-websocket": "^4.5.0", - "reactflow": "^11.11.3", - "recharts": "^2.12.7", - "sonner": "^1.4.0", - "tailwind-merge": "^2.2.1", - "tailwindcss-animate": "^1.0.7" - }, - "devDependencies": { - "@types/crypto-js": "^4.2.2", - "@types/node": "^20.11.0", - "@types/react": "^18.2.43", - "@types/react-dom": "^18.2.17", - "@typescript-eslint/eslint-plugin": "^6.14.0", - "@typescript-eslint/parser": "^6.14.0", - "@vitejs/plugin-react-swc": "^3.5.0", - "autoprefixer": "^10.4.16", - "eslint": "^8.55.0", - "eslint-plugin-react-hooks": "^4.6.0", - "eslint-plugin-react-refresh": "^0.4.5", - "postcss": "^8.4.33", - "prettier": "^3.2.4", - "prettier-plugin-tailwindcss": "^0.5.11", - "tailwindcss": "^3.4.1", - "typescript": "^5.2.2", - "vite": "^5.0.8" - } -} diff --git a/web/postcss.config.js b/web/postcss.config.js deleted file mode 100644 index 2aa7205..0000000 --- a/web/postcss.config.js +++ /dev/null @@ -1,6 +0,0 @@ -export default { - plugins: { - tailwindcss: {}, - autoprefixer: {}, - }, -}; diff --git a/web/public/favicon-16x16.png b/web/public/favicon-16x16.png deleted file mode 100644 index 60916cc..0000000 Binary files a/web/public/favicon-16x16.png and /dev/null differ diff --git a/web/public/favicon-32x32.png b/web/public/favicon-32x32.png deleted file mode 100644 index 2702931..0000000 Binary files a/web/public/favicon-32x32.png and /dev/null differ diff --git a/web/public/favicon.ico b/web/public/favicon.ico deleted file mode 100644 index f224760..0000000 Binary files a/web/public/favicon.ico and /dev/null differ diff --git a/web/public/logo/apps/drive.png b/web/public/logo/apps/drive.png deleted file mode 100644 index 6830e2a..0000000 Binary files a/web/public/logo/apps/drive.png and /dev/null differ diff --git a/web/public/logo/apps/portainer.png b/web/public/logo/apps/portainer.png deleted file mode 100644 index 565b15b..0000000 Binary files a/web/public/logo/apps/portainer.png and /dev/null differ diff --git a/web/public/logo/apps/s2.png b/web/public/logo/apps/s2.png deleted file mode 100644 index 273bb56..0000000 Binary files a/web/public/logo/apps/s2.png and /dev/null differ diff --git a/web/public/logo/apps/singlestore.png b/web/public/logo/apps/singlestore.png deleted file mode 100644 index b97fa14..0000000 Binary files a/web/public/logo/apps/singlestore.png and /dev/null differ diff --git a/web/public/logo/gr-logo-blank.png b/web/public/logo/gr-logo-blank.png deleted file mode 100644 index f2f0869..0000000 Binary files a/web/public/logo/gr-logo-blank.png and /dev/null differ diff --git a/web/public/logo/gr-logo.png b/web/public/logo/gr-logo.png deleted file mode 100644 index 102b73a..0000000 Binary files a/web/public/logo/gr-logo.png and /dev/null differ diff --git a/web/public/logo/mapache.png b/web/public/logo/mapache.png deleted file mode 100644 index 59637c5..0000000 Binary files a/web/public/logo/mapache.png and /dev/null differ diff --git a/web/public/logo/mechanic-logo.png b/web/public/logo/mechanic-logo.png deleted file mode 100644 index 68e9fe0..0000000 Binary files a/web/public/logo/mechanic-logo.png and /dev/null differ diff --git a/web/src/App.css b/web/src/App.css deleted file mode 100644 index e69de29..0000000 diff --git a/web/src/App.tsx b/web/src/App.tsx deleted file mode 100644 index efcba6e..0000000 --- a/web/src/App.tsx +++ /dev/null @@ -1,830 +0,0 @@ -import React from "react"; -import axios from "axios"; -import { - GITHUB_ORG_URL, - SENTINEL_API_URL, - SHARED_DRIVE_URL, - WIKI_URL, -} from "@/consts/config"; -import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; -import { Card } from "@/components/ui/card"; -import { Loader2 } from "lucide-react"; -import { getAxiosErrorMessage } from "@/lib/axios-error-handler"; -import { useNavigate } from "react-router-dom"; -import { Separator } from "@/components/ui/separator"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { - faArrowUpRightFromSquare, - faBook, - faCheckCircle, - faLock, - faUser, -} from "@fortawesome/free-solid-svg-icons"; -import { - checkCredentials, - logout, - saveAccessToken, - saveRefreshToken, -} from "@/lib/auth"; -import Footer from "@/components/Footer"; -import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar"; -import { - faAppStore, - faGithub, - faGoogleDrive, -} from "@fortawesome/free-brands-svg-icons"; -import { ClientApplication } from "@/models/application"; -import { OutlineButton } from "@/components/ui/outline-button"; -import { AuthLoading } from "@/components/AuthLoading"; -import { notify } from "@/lib/notify"; -import { - AlertDialog, - AlertDialogAction, - AlertDialogCancel, - AlertDialogContent, - AlertDialogDescription, - AlertDialogFooter, - AlertDialogHeader, - AlertDialogTitle, - AlertDialogTrigger, -} from "@/components/ui/alert-dialog"; -import { getUser, useUser } from "@/lib/store"; -import AppGrid from "@/components/AppGrid"; - -function App() { - const navigate = useNavigate(); - const currentUser = useUser(); - - const [cardWidth, setCardWidth] = React.useState(500); - - const [authCheckLoading, setAuthCheckLoading] = React.useState(false); - - const [loginLoading, setLoginLoading] = React.useState(false); - const [loginAccess, setLoginAccess] = React.useState({}); - - const [driveLoading, setDriveLoading] = React.useState(false); - const [driveAccess, setDriveAccess] = React.useState({}); - - const [githubLoading, setGithubLoading] = React.useState(false); - const [githubAccess, setGithubAccess] = React.useState({}); - - const [applicationsLoading, setApplicationsLoading] = React.useState(false); - const [applications, setApplications] = React.useState( - [], - ); - - const handleResize = () => { - const width = window.innerWidth; - if (width < 600) { - setCardWidth(width - 32); - } else { - setCardWidth(500); - } - }; - - React.useEffect(() => { - window.addEventListener("resize", handleResize); - return () => { - window.removeEventListener("resize", handleResize); - }; - }, []); - - React.useEffect(() => { - checkAuth().then(() => { - checkLoginAccess(); - checkDriveAccess(); - checkGithubAccess(); - getApplications(); - }); - }, []); - - const checkAuth = async () => { - setAuthCheckLoading(true); - const currentRoute = window.location.pathname + window.location.search; - const status = await checkCredentials(); - if (status != 0) { - if (currentRoute == "/") { - navigate(`/auth/login`); - } else { - navigate(`/auth/login?route=${encodeURIComponent(currentRoute)}`); - } - } else { - setAuthCheckLoading(false); - } - }; - - const checkLoginAccess = async () => { - const currentUser = getUser(); - setLoginLoading(true); - try { - const response = await axios.get( - `${SENTINEL_API_URL}/users/${currentUser.id}/auth`, - { - headers: { - Authorization: `Bearer ${localStorage.getItem("sentinel_access_token")}`, - }, - }, - ); - setLoginAccess(response.data); - } catch (error: any) { - if (!getAxiosErrorMessage(error).includes("No authentication found")) { - notify.error(getAxiosErrorMessage(error)); - } - } - setLoginLoading(false); - }; - - const registerPassword = async (password: string) => { - const currentUser = getUser(); - setLoginLoading(true); - try { - const response = await axios.post( - `${SENTINEL_API_URL}/auth/register`, - { - email: currentUser.email, - password: password, - }, - { - headers: { - Authorization: `Bearer ${localStorage.getItem("sentinel_access_token")}`, - }, - }, - ); - if (response.status == 200) { - saveAccessToken(response.data.access_token); - saveRefreshToken(response.data.refresh_token); - checkCredentials(); - } - } catch (error: any) { - notify.error(getAxiosErrorMessage(error)); - } - checkLoginAccess(); - }; - - const resetPassword = async () => { - const currentUser = getUser(); - setLoginLoading(true); - try { - const response = await axios.delete( - `${SENTINEL_API_URL}/users/${currentUser.id}/auth`, - { - headers: { - Authorization: `Bearer ${localStorage.getItem("sentinel_access_token")}`, - }, - }, - ); - if (response.status == 200) { - window.location.reload(); - } - } catch (error: any) { - notify.error(getAxiosErrorMessage(error)); - } - checkLoginAccess(); - }; - - const checkDriveAccess = async () => { - const currentUser = getUser(); - setDriveLoading(true); - try { - const response = await axios.get( - `${SENTINEL_API_URL}/users/${currentUser.id}/drive`, - { - headers: { - Authorization: `Bearer ${localStorage.getItem("sentinel_access_token")}`, - }, - }, - ); - setDriveAccess(response.data); - } catch (error: any) { - if (!getAxiosErrorMessage(error).includes("No permissions found")) { - notify.error(getAxiosErrorMessage(error)); - } - } - setDriveLoading(false); - }; - - const addUserToDrive = async () => { - const currentUser = getUser(); - setDriveLoading(true); - try { - const response = await axios.post( - `${SENTINEL_API_URL}/users/${currentUser.id}/drive`, - {}, - { - headers: { - Authorization: `Bearer ${localStorage.getItem("sentinel_access_token")}`, - }, - }, - ); - setDriveAccess(response.data); - } catch (error: any) { - notify.error(getAxiosErrorMessage(error)); - } - checkDriveAccess(); - }; - - const removeUserFromDrive = async () => { - const currentUser = getUser(); - setDriveLoading(true); - try { - const response = await axios.delete( - `${SENTINEL_API_URL}/users/${currentUser.id}/drive`, - { - headers: { - Authorization: `Bearer ${localStorage.getItem("sentinel_access_token")}`, - }, - }, - ); - setDriveAccess(response.data); - } catch (error: any) { - notify.error(getAxiosErrorMessage(error)); - } - checkDriveAccess(); - }; - - const checkGithubAccess = async () => { - const currentUser = getUser(); - setGithubLoading(true); - try { - const response = await axios.get( - `${SENTINEL_API_URL}/users/${currentUser.id}/github`, - { - headers: { - Authorization: `Bearer ${localStorage.getItem("sentinel_access_token")}`, - }, - }, - ); - setGithubAccess(response.data); - } catch (error: any) { - if (!getAxiosErrorMessage(error).includes("user does not have")) { - notify.error(getAxiosErrorMessage(error)); - } - } - setGithubLoading(false); - }; - - const addUserToGithub = async (username: string) => { - const currentUser = getUser(); - setGithubLoading(true); - try { - const response = await axios.post( - `${SENTINEL_API_URL}/users/${currentUser.id}/github`, - { - username: username, - }, - { - headers: { - Authorization: `Bearer ${localStorage.getItem("sentinel_access_token")}`, - }, - }, - ); - setGithubAccess(response.data); - } catch (error: any) { - notify.error(getAxiosErrorMessage(error)); - } - checkGithubAccess(); - }; - - const getApplications = async () => { - const currentUser = getUser(); - setApplicationsLoading(true); - try { - const response = await axios.get( - `${SENTINEL_API_URL}/users/${currentUser.id}/applications`, - { - headers: { - Authorization: `Bearer ${localStorage.getItem("sentinel_access_token")}`, - }, - }, - ); - setApplications(response.data); - } catch (error: any) { - notify.error(getAxiosErrorMessage(error)); - } - setApplicationsLoading(false); - }; - - const ProfileField = (props: { label: string; value: string }) => { - return ( -
-
{props.label}:
-
- {props.value != "" ? props.value : "Not set"} -
-
- ); - }; - - const ProfileCard = () => { - return ( - -
-
- -

Profile

-
- -
- -
- - - CN - -
-

- {currentUser.first_name} {currentUser.last_name} -

-

{currentUser.email}

-
-
- - - - - - - - - - - subteam.name).join(", ")} - /> -
-
Roles:
-
- {currentUser.roles.map((role) => ( -
- - {role} - -
- ))} -
-
- - -
- ); - }; - - const LoginCard = () => { - const [password, setPassword] = React.useState(""); - return ( - -
- -

Authentication

-
- -
-
-

- Email / Password -

-

- {loginAccess.password != null - ? "Log into Sentinel using your email and password." - : "Create a password to log into Sentinel."} -

-
- {loginAccess.password != null ? ( - - -

- Reset password? -

-
- - - Are you sure? - - You will no longer be able to sign into Sentinel using - your email and password. - - - - Cancel - - Reset - - - -
- ) : ( - <> - )} -
-
- {loginLoading ? ( - - ) : ( -
- {loginAccess.password != null ? ( - - ) : ( - <> - )} -
- )} -
- {!loginLoading && loginAccess.password == null ? ( -
- { - setPassword(e.target.value); - }} - /> - { - registerPassword(password); - }} - > - Set Password - -
- ) : ( - <> - )} -
-
-

- OAuth: Discord -

-

- Log into Sentinel using your Discord account. -

-
- {loginLoading ? ( - - ) : ( -
- -
- )} -
-
- ); - }; - - const WikiCard = () => { - return ( - -
- -

Wiki

-
- -
-
- {loginAccess.password != null ? ( -

Login with your Sentinel account.

- ) : ( -

Please set a password for your sentinel account first!

- )} -

- Access all Gaucho Racing documentation through the team's{" "} - window.open(WIKI_URL, "_blank")} - > - wiki - - . -

-
- {loginLoading ? ( - - ) : ( -
- {loginAccess.password != null ? ( - window.open(WIKI_URL, "_blank")} - > - - - - Launch Wiki - - ) : ( - <> - )} -
- )} -
-
- ); - }; - - const DriveCard = () => { - return ( - -
- -

Team Drive

-
- -
-
-

- Team Drive: Gaucho Racing -

-

- Access all Gaucho Racing documents through the team's{" "} - window.open(SHARED_DRIVE_URL, "_blank")} - > - shared drive - - . -

-
- {driveLoading ? ( - - ) : ( -
- {driveAccess.role != null ? ( - - ) : ( - - Request Access - - )} -
- )} -
-
- ); - }; - - const GithubCard = () => { - const [githubUsername, setGithubUsername] = React.useState(""); - return ( - -
- -

GitHub

-
- -
-
-

- GitHub Org: Gaucho Racing -

-

- Access all Gaucho Racing software through the team's{" "} - window.open(GITHUB_ORG_URL, "_blank")} - > - GitHub organization - - . -

-
- {githubLoading ? ( - - ) : ( -
- {githubAccess.role != null ? ( - - ) : ( - <> - )} -
- )} -
- {!githubLoading && githubAccess.role == null ? ( -
- { - setGithubUsername(e.target.value); - }} - /> - { - addUserToGithub(githubUsername); - }} - > - Request Access - -
- ) : ( - <> - )} -
- ); - }; - - const ApplicationsCard = () => { - return ( - -
- -

My Applications

-
- -
- {applicationsLoading ? ( -
- -
- ) : ( -
- {applications.length > 0 ? ( - applications.map((application) => ( -
- -
- )) - ) : ( - - )} -
- )} -
-
- ); - }; - - const ApplicationListItem = (props: { application: ClientApplication }) => { - return ( - -
-
-
{props.application.name}
-
- Client ID: {props.application.id} -
-
- -
-
- ); - }; - - const NoApplicationsCard = () => { - return ( -
-

- You have not created any applications yet. -

- navigate("/applications/new")} - className="mt-4" - > - Create Application - -
- ); - }; - - return ( - <> - {authCheckLoading ? ( - - ) : ( -
-
-

Hello {currentUser.first_name}

-

- Welcome to Sentinel, Gaucho Racing's central authentication - service and member directory. Sentinel also provides Single Sign - On (SSO) access to all our internal services. If you would like to - build an application using Sentinel, check out our API - documentation{" "} - - window.open( - "https://wiki.gauchoracing.com/books/sentinel/page/api-documentation", - "_blank", - ) - } - > - here - - . -

-
-
- -
- -
-
-
- - - - -
-
- - -
-
-
-
-
- )} - - ); -} - -export default App; diff --git a/web/src/components/AppGrid.tsx b/web/src/components/AppGrid.tsx deleted file mode 100644 index 72cc96b..0000000 --- a/web/src/components/AppGrid.tsx +++ /dev/null @@ -1,109 +0,0 @@ -import { Card } from "@/components/ui/card"; -import { GITHUB_ORG_URL, SHARED_DRIVE_URL, WIKI_URL } from "@/consts/config"; -import { faGithub } from "@fortawesome/free-brands-svg-icons"; -import { faBook, faChartPie, faUsers } from "@fortawesome/free-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; - -export default function AppGrid() { - return ( -
-
- { - window.open(WIKI_URL, "_blank"); - }} - > -
- -

Wiki

-
-
- { - window.open(SHARED_DRIVE_URL, "_blank"); - }} - > -
- -

Drive

-
-
- { - window.open(GITHUB_ORG_URL, "_blank"); - }} - > -
- -

GitHub

-
-
-
-
- { - window.location.href = "/users"; - }} - > -
- -

Users

-
-
- { - window.open( - "https://portal.singlestore.com?ssoHint=614fcbae-8669-4adb-8a10-3d902ecc4f38", - "_blank", - ); - }} - > -
- -

SingleStore

-
-
- { - window.open("https://s2.gauchoracing.com", "_blank"); - }} - > -
- -

S2DB

-
-
- { - window.open("https://portainer.gauchoracing.com", "_blank"); - }} - > -
- -

Portainer

-
-
-
-
- { - window.location.href = "/analytics"; - }} - > -
- -

Analytics

-
-
-
-
- ); -} diff --git a/web/src/components/AuthLoading.tsx b/web/src/components/AuthLoading.tsx deleted file mode 100644 index 97a32bd..0000000 --- a/web/src/components/AuthLoading.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { Loader2 } from "lucide-react"; -import { Card } from "@/components/ui/card"; - -export const AuthLoading = () => { - return ( -
- -
- Gaucho Racing - -
-
-
- ); -}; diff --git a/web/src/components/Footer.tsx b/web/src/components/Footer.tsx deleted file mode 100644 index dceb9fb..0000000 --- a/web/src/components/Footer.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import { SENTINEL_API_URL, SOCIAL_LINKS } from "@/consts/config"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { - faGithub, - faInstagram, - faLinkedinIn, - faTwitter, -} from "@fortawesome/free-brands-svg-icons"; -import React from "react"; -import axios from "axios"; -import { getAxiosErrorMessage } from "@/lib/axios-error-handler"; -import { toast } from "sonner"; - -export default function Footer() { - const [serverMessage, setServerMessage] = React.useState(""); - - React.useEffect(() => { - ping(); - }, []); - - const ping = async () => { - try { - const response = await axios.get(`${SENTINEL_API_URL}/ping`); - console.log(response.data); - setServerMessage(response.data.message); - } catch (error: any) { - toast(getAxiosErrorMessage(error)); - } - }; - return ( -
-
-
-
- Logo -

Gaucho Racing

-
-
-
-

{serverMessage}

-
-
-
-
-

- © 2020 - {new Date().getFullYear()} Gaucho Racing -

-
- - - - -
-
-
- ); -} diff --git a/web/src/components/Header.tsx b/web/src/components/Header.tsx deleted file mode 100644 index 859ca14..0000000 --- a/web/src/components/Header.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuSeparator, - DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu"; -import { useNavigate } from "react-router-dom"; -import { logout } from "@/lib/auth"; -import { useUser } from "@/lib/store"; - -interface HeaderProps { - className?: string; - style?: React.CSSProperties; -} - -const Header = (props: HeaderProps) => { - const navigate = useNavigate(); - const currentUser = useUser(); - return ( -
-
-
-

Sentinel

-
-
- - - - - CN - - - - -
-

- {currentUser.first_name} {currentUser.last_name} -

-

{currentUser.email}

-
-
- - -
Profile
-
- -
Settings
-
- - { - logout(); - navigate("/auth/register"); - }} - > -
Sign Out
-
-
-
-
-
-
- ); -}; - -export default Header; diff --git a/web/src/components/ui/alert-dialog.tsx b/web/src/components/ui/alert-dialog.tsx deleted file mode 100644 index 3add93c..0000000 --- a/web/src/components/ui/alert-dialog.tsx +++ /dev/null @@ -1,141 +0,0 @@ -"use client"; - -import * as React from "react"; -import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"; - -import { cn } from "@/lib/utils"; -import { buttonVariants } from "@/components/ui/button"; - -const AlertDialog = AlertDialogPrimitive.Root; - -const AlertDialogTrigger = AlertDialogPrimitive.Trigger; - -const AlertDialogPortal = AlertDialogPrimitive.Portal; - -const AlertDialogOverlay = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); -AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName; - -const AlertDialogContent = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - - - - -)); -AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName; - -const AlertDialogHeader = ({ - className, - ...props -}: React.HTMLAttributes) => ( -
-); -AlertDialogHeader.displayName = "AlertDialogHeader"; - -const AlertDialogFooter = ({ - className, - ...props -}: React.HTMLAttributes) => ( -
-); -AlertDialogFooter.displayName = "AlertDialogFooter"; - -const AlertDialogTitle = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); -AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName; - -const AlertDialogDescription = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); -AlertDialogDescription.displayName = - AlertDialogPrimitive.Description.displayName; - -const AlertDialogAction = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); -AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName; - -const AlertDialogCancel = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); -AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName; - -export { - AlertDialog, - AlertDialogPortal, - AlertDialogOverlay, - AlertDialogTrigger, - AlertDialogContent, - AlertDialogHeader, - AlertDialogFooter, - AlertDialogTitle, - AlertDialogDescription, - AlertDialogAction, - AlertDialogCancel, -}; diff --git a/web/src/components/ui/avatar.tsx b/web/src/components/ui/avatar.tsx deleted file mode 100644 index d86e4b7..0000000 --- a/web/src/components/ui/avatar.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import * as React from "react"; -import * as AvatarPrimitive from "@radix-ui/react-avatar"; - -import { cn } from "@/lib/utils"; - -const Avatar = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); -Avatar.displayName = AvatarPrimitive.Root.displayName; - -const AvatarImage = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); -AvatarImage.displayName = AvatarPrimitive.Image.displayName; - -const AvatarFallback = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); -AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName; - -export { Avatar, AvatarImage, AvatarFallback }; diff --git a/web/src/components/ui/badge.tsx b/web/src/components/ui/badge.tsx deleted file mode 100644 index d3d5d60..0000000 --- a/web/src/components/ui/badge.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import * as React from "react"; -import { cva, type VariantProps } from "class-variance-authority"; - -import { cn } from "@/lib/utils"; - -const badgeVariants = cva( - "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", - { - variants: { - variant: { - default: - "border-transparent bg-primary text-primary-foreground hover:bg-primary/80", - secondary: - "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", - destructive: - "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80", - outline: "text-foreground", - }, - }, - defaultVariants: { - variant: "default", - }, - }, -); - -export interface BadgeProps - extends React.HTMLAttributes, - VariantProps {} - -function Badge({ className, variant, ...props }: BadgeProps) { - return ( -
- ); -} - -export { Badge, badgeVariants }; diff --git a/web/src/components/ui/button.tsx b/web/src/components/ui/button.tsx deleted file mode 100644 index ee3907a..0000000 --- a/web/src/components/ui/button.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import * as React from "react"; -import { Slot } from "@radix-ui/react-slot"; -import { cva, type VariantProps } from "class-variance-authority"; - -import { cn } from "@/lib/utils"; - -const buttonVariants = cva( - "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", - { - variants: { - variant: { - default: "bg-primary text-primary-foreground hover:bg-primary/90", - destructive: - "bg-destructive text-destructive-foreground hover:bg-destructive/90", - outline: - "border border-input bg-background hover:bg-accent hover:text-accent-foreground", - secondary: - "bg-secondary text-secondary-foreground hover:bg-secondary/80", - ghost: "hover:bg-accent hover:text-accent-foreground", - link: "bg-gradient-to-r from-gr-purple to-gr-pink text-transparent bg-clip-text underline-offset-4 hover:underline", - }, - size: { - default: "h-10 px-4 py-2", - sm: "h-9 rounded-md px-3", - lg: "h-11 rounded-md px-8", - icon: "h-10 w-10", - }, - }, - defaultVariants: { - variant: "default", - size: "default", - }, - }, -); - -export interface ButtonProps - extends React.ButtonHTMLAttributes, - VariantProps { - asChild?: boolean; -} - -const Button = React.forwardRef( - ({ className, variant, size, asChild = false, ...props }, ref) => { - const Comp = asChild ? Slot : "button"; - return ( - - ); - }, -); -Button.displayName = "Button"; - -export { Button, buttonVariants }; diff --git a/web/src/components/ui/calendar.tsx b/web/src/components/ui/calendar.tsx deleted file mode 100644 index 615c3b8..0000000 --- a/web/src/components/ui/calendar.tsx +++ /dev/null @@ -1,66 +0,0 @@ -"use client"; - -import * as React from "react"; -import { ChevronLeft, ChevronRight } from "lucide-react"; -import { DayPicker } from "react-day-picker"; - -import { cn } from "@/lib/utils"; -import { buttonVariants } from "@/components/ui/button"; - -export type CalendarProps = React.ComponentProps; - -function Calendar({ - className, - classNames, - showOutsideDays = true, - ...props -}: CalendarProps) { - return ( - , - IconRight: () => , - }} - {...props} - /> - ); -} -Calendar.displayName = "Calendar"; - -export { Calendar }; diff --git a/web/src/components/ui/card.tsx b/web/src/components/ui/card.tsx deleted file mode 100644 index 704e43a..0000000 --- a/web/src/components/ui/card.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import * as React from "react"; - -import { cn } from "@/lib/utils"; - -const Card = React.forwardRef< - HTMLDivElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( -
-)); -Card.displayName = "Card"; - -const CardHeader = React.forwardRef< - HTMLDivElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( -
-)); -CardHeader.displayName = "CardHeader"; - -const CardTitle = React.forwardRef< - HTMLParagraphElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( -

-)); -CardTitle.displayName = "CardTitle"; - -const CardDescription = React.forwardRef< - HTMLParagraphElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( -

-)); -CardDescription.displayName = "CardDescription"; - -const CardContent = React.forwardRef< - HTMLDivElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( -

-)); -CardContent.displayName = "CardContent"; - -const CardFooter = React.forwardRef< - HTMLDivElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( -
-)); -CardFooter.displayName = "CardFooter"; - -export { - Card, - CardHeader, - CardFooter, - CardTitle, - CardDescription, - CardContent, -}; diff --git a/web/src/components/ui/checkbox.tsx b/web/src/components/ui/checkbox.tsx deleted file mode 100644 index 5985e3c..0000000 --- a/web/src/components/ui/checkbox.tsx +++ /dev/null @@ -1,30 +0,0 @@ -"use client"; - -import * as React from "react"; -import * as CheckboxPrimitive from "@radix-ui/react-checkbox"; -import { Check } from "lucide-react"; - -import { cn } from "@/lib/utils"; - -const Checkbox = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - - - - - -)); -Checkbox.displayName = CheckboxPrimitive.Root.displayName; - -export { Checkbox }; diff --git a/web/src/components/ui/command.tsx b/web/src/components/ui/command.tsx deleted file mode 100644 index 0e82ac9..0000000 --- a/web/src/components/ui/command.tsx +++ /dev/null @@ -1,153 +0,0 @@ -import * as React from "react"; -import { type DialogProps } from "@radix-ui/react-dialog"; -import { Command as CommandPrimitive } from "cmdk"; -import { Search } from "lucide-react"; - -import { cn } from "@/lib/utils"; -import { Dialog, DialogContent } from "@/components/ui/dialog"; - -const Command = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); -Command.displayName = CommandPrimitive.displayName; - -interface CommandDialogProps extends DialogProps {} - -const CommandDialog = ({ children, ...props }: CommandDialogProps) => { - return ( - - - - {children} - - - - ); -}; - -const CommandInput = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( -
- - -
-)); - -CommandInput.displayName = CommandPrimitive.Input.displayName; - -const CommandList = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); - -CommandList.displayName = CommandPrimitive.List.displayName; - -const CommandEmpty = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->((props, ref) => ( - -)); - -CommandEmpty.displayName = CommandPrimitive.Empty.displayName; - -const CommandGroup = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); - -CommandGroup.displayName = CommandPrimitive.Group.displayName; - -const CommandSeparator = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); -CommandSeparator.displayName = CommandPrimitive.Separator.displayName; - -const CommandItem = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); - -CommandItem.displayName = CommandPrimitive.Item.displayName; - -const CommandShortcut = ({ - className, - ...props -}: React.HTMLAttributes) => { - return ( - - ); -}; -CommandShortcut.displayName = "CommandShortcut"; - -export { - Command, - CommandDialog, - CommandInput, - CommandList, - CommandEmpty, - CommandGroup, - CommandItem, - CommandShortcut, - CommandSeparator, -}; diff --git a/web/src/components/ui/dialog.tsx b/web/src/components/ui/dialog.tsx deleted file mode 100644 index 7191ede..0000000 --- a/web/src/components/ui/dialog.tsx +++ /dev/null @@ -1,120 +0,0 @@ -import * as React from "react"; -import * as DialogPrimitive from "@radix-ui/react-dialog"; -import { X } from "lucide-react"; - -import { cn } from "@/lib/utils"; - -const Dialog = DialogPrimitive.Root; - -const DialogTrigger = DialogPrimitive.Trigger; - -const DialogPortal = DialogPrimitive.Portal; - -const DialogClose = DialogPrimitive.Close; - -const DialogOverlay = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); -DialogOverlay.displayName = DialogPrimitive.Overlay.displayName; - -const DialogContent = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, children, ...props }, ref) => ( - - - - {children} - - - Close - - - -)); -DialogContent.displayName = DialogPrimitive.Content.displayName; - -const DialogHeader = ({ - className, - ...props -}: React.HTMLAttributes) => ( -
-); -DialogHeader.displayName = "DialogHeader"; - -const DialogFooter = ({ - className, - ...props -}: React.HTMLAttributes) => ( -
-); -DialogFooter.displayName = "DialogFooter"; - -const DialogTitle = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); -DialogTitle.displayName = DialogPrimitive.Title.displayName; - -const DialogDescription = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); -DialogDescription.displayName = DialogPrimitive.Description.displayName; - -export { - Dialog, - DialogPortal, - DialogOverlay, - DialogClose, - DialogTrigger, - DialogContent, - DialogHeader, - DialogFooter, - DialogTitle, - DialogDescription, -}; diff --git a/web/src/components/ui/dropdown-menu.tsx b/web/src/components/ui/dropdown-menu.tsx deleted file mode 100644 index 3a0c7fe..0000000 --- a/web/src/components/ui/dropdown-menu.tsx +++ /dev/null @@ -1,200 +0,0 @@ -"use client"; - -import * as React from "react"; -import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"; -import { Check, ChevronRight, Circle } from "lucide-react"; - -import { cn } from "@/lib/utils"; - -const DropdownMenu = DropdownMenuPrimitive.Root; - -const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger; - -const DropdownMenuGroup = DropdownMenuPrimitive.Group; - -const DropdownMenuPortal = DropdownMenuPrimitive.Portal; - -const DropdownMenuSub = DropdownMenuPrimitive.Sub; - -const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup; - -const DropdownMenuSubTrigger = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef & { - inset?: boolean; - } ->(({ className, inset, children, ...props }, ref) => ( - - {children} - - -)); -DropdownMenuSubTrigger.displayName = - DropdownMenuPrimitive.SubTrigger.displayName; - -const DropdownMenuSubContent = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); -DropdownMenuSubContent.displayName = - DropdownMenuPrimitive.SubContent.displayName; - -const DropdownMenuContent = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, sideOffset = 4, ...props }, ref) => ( - - - -)); -DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName; - -const DropdownMenuItem = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef & { - inset?: boolean; - } ->(({ className, inset, ...props }, ref) => ( - -)); -DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName; - -const DropdownMenuCheckboxItem = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, children, checked, ...props }, ref) => ( - - - - - - - {children} - -)); -DropdownMenuCheckboxItem.displayName = - DropdownMenuPrimitive.CheckboxItem.displayName; - -const DropdownMenuRadioItem = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, children, ...props }, ref) => ( - - - - - - - {children} - -)); -DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName; - -const DropdownMenuLabel = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef & { - inset?: boolean; - } ->(({ className, inset, ...props }, ref) => ( - -)); -DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName; - -const DropdownMenuSeparator = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); -DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName; - -const DropdownMenuShortcut = ({ - className, - ...props -}: React.HTMLAttributes) => { - return ( - - ); -}; -DropdownMenuShortcut.displayName = "DropdownMenuShortcut"; - -export { - DropdownMenu, - DropdownMenuTrigger, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuCheckboxItem, - DropdownMenuRadioItem, - DropdownMenuLabel, - DropdownMenuSeparator, - DropdownMenuShortcut, - DropdownMenuGroup, - DropdownMenuPortal, - DropdownMenuSub, - DropdownMenuSubContent, - DropdownMenuSubTrigger, - DropdownMenuRadioGroup, -}; diff --git a/web/src/components/ui/input.tsx b/web/src/components/ui/input.tsx deleted file mode 100644 index 9d631e7..0000000 --- a/web/src/components/ui/input.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import * as React from "react"; - -import { cn } from "@/lib/utils"; - -export interface InputProps - extends React.InputHTMLAttributes {} - -const Input = React.forwardRef( - ({ className, type, ...props }, ref) => { - return ( - - ); - }, -); -Input.displayName = "Input"; - -export { Input }; diff --git a/web/src/components/ui/label.tsx b/web/src/components/ui/label.tsx deleted file mode 100644 index 84f8b0c..0000000 --- a/web/src/components/ui/label.tsx +++ /dev/null @@ -1,26 +0,0 @@ -"use client"; - -import * as React from "react"; -import * as LabelPrimitive from "@radix-ui/react-label"; -import { cva, type VariantProps } from "class-variance-authority"; - -import { cn } from "@/lib/utils"; - -const labelVariants = cva( - "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70", -); - -const Label = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef & - VariantProps ->(({ className, ...props }, ref) => ( - -)); -Label.displayName = LabelPrimitive.Root.displayName; - -export { Label }; diff --git a/web/src/components/ui/outline-button.tsx b/web/src/components/ui/outline-button.tsx deleted file mode 100644 index b050118..0000000 --- a/web/src/components/ui/outline-button.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import * as React from "react"; - -import { cn } from "@/lib/utils"; - -export interface ButtonProps - extends React.ButtonHTMLAttributes {} - -const OutlineButton = React.forwardRef( - ({ className, ...props }, ref) => { - return ( -
- -
- ); - }, -); -OutlineButton.displayName = "Button"; - -export { OutlineButton }; diff --git a/web/src/components/ui/popover.tsx b/web/src/components/ui/popover.tsx deleted file mode 100644 index 73be7bb..0000000 --- a/web/src/components/ui/popover.tsx +++ /dev/null @@ -1,31 +0,0 @@ -"use client"; - -import * as React from "react"; -import * as PopoverPrimitive from "@radix-ui/react-popover"; - -import { cn } from "@/lib/utils"; - -const Popover = PopoverPrimitive.Root; - -const PopoverTrigger = PopoverPrimitive.Trigger; - -const PopoverContent = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, align = "center", sideOffset = 4, ...props }, ref) => ( - - - -)); -PopoverContent.displayName = PopoverPrimitive.Content.displayName; - -export { Popover, PopoverTrigger, PopoverContent }; diff --git a/web/src/components/ui/progress.tsx b/web/src/components/ui/progress.tsx deleted file mode 100644 index 8221536..0000000 --- a/web/src/components/ui/progress.tsx +++ /dev/null @@ -1,28 +0,0 @@ -"use client"; - -import * as React from "react"; -import * as ProgressPrimitive from "@radix-ui/react-progress"; - -import { cn } from "@/lib/utils"; - -const Progress = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, value, ...props }, ref) => ( - - - -)); -Progress.displayName = ProgressPrimitive.Root.displayName; - -export { Progress }; diff --git a/web/src/components/ui/scroll-area.tsx b/web/src/components/ui/scroll-area.tsx deleted file mode 100644 index b1f1127..0000000 --- a/web/src/components/ui/scroll-area.tsx +++ /dev/null @@ -1,48 +0,0 @@ -"use client"; - -import * as React from "react"; -import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"; - -import { cn } from "@/lib/utils"; - -const ScrollArea = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, children, ...props }, ref) => ( - - - {children} - - - - -)); -ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName; - -const ScrollBar = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, orientation = "vertical", ...props }, ref) => ( - - - -)); -ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName; - -export { ScrollArea, ScrollBar }; diff --git a/web/src/components/ui/select.tsx b/web/src/components/ui/select.tsx deleted file mode 100644 index 9218f68..0000000 --- a/web/src/components/ui/select.tsx +++ /dev/null @@ -1,160 +0,0 @@ -"use client"; - -import * as React from "react"; -import * as SelectPrimitive from "@radix-ui/react-select"; -import { Check, ChevronDown, ChevronUp } from "lucide-react"; - -import { cn } from "@/lib/utils"; - -const Select = SelectPrimitive.Root; - -const SelectGroup = SelectPrimitive.Group; - -const SelectValue = SelectPrimitive.Value; - -const SelectTrigger = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, children, ...props }, ref) => ( - span]:line-clamp-1", - className, - )} - {...props} - > - {children} - - - - -)); -SelectTrigger.displayName = SelectPrimitive.Trigger.displayName; - -const SelectScrollUpButton = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - - - -)); -SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName; - -const SelectScrollDownButton = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - - - -)); -SelectScrollDownButton.displayName = - SelectPrimitive.ScrollDownButton.displayName; - -const SelectContent = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, children, position = "popper", ...props }, ref) => ( - - - - - {children} - - - - -)); -SelectContent.displayName = SelectPrimitive.Content.displayName; - -const SelectLabel = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); -SelectLabel.displayName = SelectPrimitive.Label.displayName; - -const SelectItem = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, children, ...props }, ref) => ( - - - - - - - - {children} - -)); -SelectItem.displayName = SelectPrimitive.Item.displayName; - -const SelectSeparator = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); -SelectSeparator.displayName = SelectPrimitive.Separator.displayName; - -export { - Select, - SelectGroup, - SelectValue, - SelectTrigger, - SelectContent, - SelectLabel, - SelectItem, - SelectSeparator, - SelectScrollUpButton, - SelectScrollDownButton, -}; diff --git a/web/src/components/ui/separator.tsx b/web/src/components/ui/separator.tsx deleted file mode 100644 index 9ac3b95..0000000 --- a/web/src/components/ui/separator.tsx +++ /dev/null @@ -1,31 +0,0 @@ -"use client"; - -import * as React from "react"; -import * as SeparatorPrimitive from "@radix-ui/react-separator"; - -import { cn } from "@/lib/utils"; - -const Separator = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->( - ( - { className, orientation = "horizontal", decorative = true, ...props }, - ref, - ) => ( - - ), -); -Separator.displayName = SeparatorPrimitive.Root.displayName; - -export { Separator }; diff --git a/web/src/components/ui/sonner.tsx b/web/src/components/ui/sonner.tsx deleted file mode 100644 index 549cf84..0000000 --- a/web/src/components/ui/sonner.tsx +++ /dev/null @@ -1,31 +0,0 @@ -"use client"; - -import { useTheme } from "next-themes"; -import { Toaster as Sonner } from "sonner"; - -type ToasterProps = React.ComponentProps; - -const Toaster = ({ ...props }: ToasterProps) => { - const { theme = "system" } = useTheme(); - - return ( - - ); -}; - -export { Toaster }; diff --git a/web/src/components/ui/switch.tsx b/web/src/components/ui/switch.tsx deleted file mode 100644 index b73edee..0000000 --- a/web/src/components/ui/switch.tsx +++ /dev/null @@ -1,29 +0,0 @@ -"use client"; - -import * as React from "react"; -import * as SwitchPrimitives from "@radix-ui/react-switch"; - -import { cn } from "@/lib/utils"; - -const Switch = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - - - -)); -Switch.displayName = SwitchPrimitives.Root.displayName; - -export { Switch }; diff --git a/web/src/components/ui/toast.tsx b/web/src/components/ui/toast.tsx deleted file mode 100644 index 2bc23c1..0000000 --- a/web/src/components/ui/toast.tsx +++ /dev/null @@ -1,127 +0,0 @@ -import * as React from "react"; -import * as ToastPrimitives from "@radix-ui/react-toast"; -import { cva, type VariantProps } from "class-variance-authority"; -import { X } from "lucide-react"; - -import { cn } from "@/lib/utils"; - -const ToastProvider = ToastPrimitives.Provider; - -const ToastViewport = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); -ToastViewport.displayName = ToastPrimitives.Viewport.displayName; - -const toastVariants = cva( - "group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full", - { - variants: { - variant: { - default: "border bg-background text-foreground", - destructive: - "destructive group border-destructive bg-destructive text-destructive-foreground", - }, - }, - defaultVariants: { - variant: "default", - }, - }, -); - -const Toast = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef & - VariantProps ->(({ className, variant, ...props }, ref) => { - return ( - - ); -}); -Toast.displayName = ToastPrimitives.Root.displayName; - -const ToastAction = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); -ToastAction.displayName = ToastPrimitives.Action.displayName; - -const ToastClose = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - - - -)); -ToastClose.displayName = ToastPrimitives.Close.displayName; - -const ToastTitle = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); -ToastTitle.displayName = ToastPrimitives.Title.displayName; - -const ToastDescription = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); -ToastDescription.displayName = ToastPrimitives.Description.displayName; - -type ToastProps = React.ComponentPropsWithoutRef; - -type ToastActionElement = React.ReactElement; - -export { - type ToastProps, - type ToastActionElement, - ToastProvider, - ToastViewport, - Toast, - ToastTitle, - ToastDescription, - ToastClose, - ToastAction, -}; diff --git a/web/src/components/ui/toaster.tsx b/web/src/components/ui/toaster.tsx deleted file mode 100644 index f664d71..0000000 --- a/web/src/components/ui/toaster.tsx +++ /dev/null @@ -1,35 +0,0 @@ -"use client"; - -import { - Toast, - ToastClose, - ToastDescription, - ToastProvider, - ToastTitle, - ToastViewport, -} from "@/components/ui/toast.tsx"; -import { useToast } from "@/components/ui/use-toast.tsx"; - -export function Toaster() { - const { toasts } = useToast(); - - return ( - - {toasts.map(function ({ id, title, description, action, ...props }) { - return ( - -
- {title && {title}} - {description && ( - {description} - )} -
- {action} - -
- ); - })} - -
- ); -} diff --git a/web/src/components/ui/tooltip.tsx b/web/src/components/ui/tooltip.tsx deleted file mode 100644 index 2c42a2c..0000000 --- a/web/src/components/ui/tooltip.tsx +++ /dev/null @@ -1,30 +0,0 @@ -"use client"; - -import * as React from "react"; -import * as TooltipPrimitive from "@radix-ui/react-tooltip"; - -import { cn } from "@/lib/utils"; - -const TooltipProvider = TooltipPrimitive.Provider; - -const Tooltip = TooltipPrimitive.Root; - -const TooltipTrigger = TooltipPrimitive.Trigger; - -const TooltipContent = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, sideOffset = 4, ...props }, ref) => ( - -)); -TooltipContent.displayName = TooltipPrimitive.Content.displayName; - -export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }; diff --git a/web/src/components/ui/use-toast.tsx b/web/src/components/ui/use-toast.tsx deleted file mode 100644 index 592daf0..0000000 --- a/web/src/components/ui/use-toast.tsx +++ /dev/null @@ -1,189 +0,0 @@ -// Inspired by react-hot-toast library -import * as React from "react"; - -import type { ToastActionElement, ToastProps } from "@/components/ui/toast.tsx"; - -const TOAST_LIMIT = 1; -const TOAST_REMOVE_DELAY = 1000000; - -type ToasterToast = ToastProps & { - id: string; - title?: React.ReactNode; - description?: React.ReactNode; - action?: ToastActionElement; -}; - -const actionTypes = { - ADD_TOAST: "ADD_TOAST", - UPDATE_TOAST: "UPDATE_TOAST", - DISMISS_TOAST: "DISMISS_TOAST", - REMOVE_TOAST: "REMOVE_TOAST", -} as const; - -let count = 0; - -function genId() { - count = (count + 1) % Number.MAX_SAFE_INTEGER; - return count.toString(); -} - -type ActionType = typeof actionTypes; - -type Action = - | { - type: ActionType["ADD_TOAST"]; - toast: ToasterToast; - } - | { - type: ActionType["UPDATE_TOAST"]; - toast: Partial; - } - | { - type: ActionType["DISMISS_TOAST"]; - toastId?: ToasterToast["id"]; - } - | { - type: ActionType["REMOVE_TOAST"]; - toastId?: ToasterToast["id"]; - }; - -interface State { - toasts: ToasterToast[]; -} - -const toastTimeouts = new Map>(); - -const addToRemoveQueue = (toastId: string) => { - if (toastTimeouts.has(toastId)) { - return; - } - - const timeout = setTimeout(() => { - toastTimeouts.delete(toastId); - dispatch({ - type: "REMOVE_TOAST", - toastId: toastId, - }); - }, TOAST_REMOVE_DELAY); - - toastTimeouts.set(toastId, timeout); -}; - -export const reducer = (state: State, action: Action): State => { - switch (action.type) { - case "ADD_TOAST": - return { - ...state, - toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT), - }; - - case "UPDATE_TOAST": - return { - ...state, - toasts: state.toasts.map((t) => - t.id === action.toast.id ? { ...t, ...action.toast } : t, - ), - }; - - case "DISMISS_TOAST": { - const { toastId } = action; - - // ! Side effects ! - This could be extracted into a dismissToast() action, - // but I'll keep it here for simplicity - if (toastId) { - addToRemoveQueue(toastId); - } else { - state.toasts.forEach((toast) => { - addToRemoveQueue(toast.id); - }); - } - - return { - ...state, - toasts: state.toasts.map((t) => - t.id === toastId || toastId === undefined - ? { - ...t, - open: false, - } - : t, - ), - }; - } - case "REMOVE_TOAST": - if (action.toastId === undefined) { - return { - ...state, - toasts: [], - }; - } - return { - ...state, - toasts: state.toasts.filter((t) => t.id !== action.toastId), - }; - } -}; - -const listeners: Array<(state: State) => void> = []; - -let memoryState: State = { toasts: [] }; - -function dispatch(action: Action) { - memoryState = reducer(memoryState, action); - listeners.forEach((listener) => { - listener(memoryState); - }); -} - -type Toast = Omit; - -function toast({ ...props }: Toast) { - const id = genId(); - - const update = (props: ToasterToast) => - dispatch({ - type: "UPDATE_TOAST", - toast: { ...props, id }, - }); - const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id }); - - dispatch({ - type: "ADD_TOAST", - toast: { - ...props, - id, - open: true, - onOpenChange: (open) => { - if (!open) dismiss(); - }, - }, - }); - - return { - id: id, - dismiss, - update, - }; -} - -function useToast() { - const [state, setState] = React.useState(memoryState); - - React.useEffect(() => { - listeners.push(setState); - return () => { - const index = listeners.indexOf(setState); - if (index > -1) { - listeners.splice(index, 1); - } - }; - }, [state]); - - return { - ...state, - toast, - dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }), - }; -} - -export { useToast, toast }; diff --git a/web/src/consts/config.tsx b/web/src/consts/config.tsx deleted file mode 100644 index 9c86ff5..0000000 --- a/web/src/consts/config.tsx +++ /dev/null @@ -1,20 +0,0 @@ -export const SENTINEL_API_URL = - import.meta.env.VITE_SENTINEL_API_URL ?? "https://sso.gauchoracing.com/api"; - -export const DISCORD_CLIENT_ID = "1204930904913481840"; -export const DISCORD_OAUTH_BASE_URL = - "https://discord.com/api/oauth2/authorize"; -export const DISCORD_SERVER_INVITE_URL = "https://discord.gg/tvYFre2m4F"; - -export const SHARED_DRIVE_URL = - "https://drive.google.com/drive/u/0/folders/0ADMP93ZBlor_Uk9PVA"; -export const GITHUB_ORG_URL = "https://github.com/Gaucho-Racing"; -export const WIKI_URL = "https://wiki.gauchoracing.com"; - -export const SOCIAL_LINKS = { - github: "https://github.com/gaucho-racing/sentinel", - instagram: "https://instagram.com/gauchoracingucsb", - twitter: "https://twitter.com/gaucho_racing", - linkedin: - "https://www.linkedin.com/company/gaucho-racing-at-uc-santa-barbara", -}; diff --git a/web/src/index.css b/web/src/index.css deleted file mode 100644 index b012034..0000000 --- a/web/src/index.css +++ /dev/null @@ -1,74 +0,0 @@ -@tailwind base; -@tailwind components; -@tailwind utilities; - -@layer base { - :root { - --background: 0 0% 0%; - --foreground: 0 0% 98%; - --card: 240 10% 3.9%; - --card-foreground: 0 0% 98%; - --popover: 240 10% 3.9%; - --popover-foreground: 0 0% 98%; - --primary: 0 0% 98%; - --primary-foreground: 240 5.9% 10%; - --secondary: 240 3.7% 15.9%; - --secondary-foreground: 0 0% 98%; - --muted: 240 3.7% 15.9%; - --muted-foreground: 240 5% 64.9%; - --accent: 240 3.7% 15.9%; - --accent-foreground: 0 0% 98%; - --destructive: 0 84% 60%; - --destructive-foreground: 0 0% 98%; - --border: 240 3.7% 15.9%; - --input: 240 3.7% 15.9%; - --ring: 240 4.9% 83.9%; - --radius: 0.75rem; - } - - .dark { - --background: 0 0% 0%; - --foreground: 0 0% 98%; - --card: 240 10% 3.9%; - --card-foreground: 0 0% 98%; - --popover: 240 10% 3.9%; - --popover-foreground: 0 0% 98%; - --primary: 0 0% 98%; - --primary-foreground: 240 5.9% 10%; - --secondary: 240 3.7% 15.9%; - --secondary-foreground: 0 0% 98%; - --muted: 240 3.7% 15.9%; - --muted-foreground: 240 5% 64.9%; - --accent: 240 3.7% 15.9%; - --accent-foreground: 0 0% 98%; - --destructive: 0 84% 60%; - --destructive-foreground: 0 0% 98%; - --border: 240 3.7% 15.9%; - --input: 240 3.7% 15.9%; - --ring: 240 4.9% 83.9%; - } -} - -@layer base { - * { - @apply border-border; - } - body { - @apply bg-background text-foreground; - } - h1 { - @apply text-4xl font-extrabold tracking-tight; - } - h2 { - @apply text-3xl font-bold tracking-tight; - } - h3 { - @apply text-2xl font-semibold tracking-tight; - } - h4 { - @apply text-xl font-semibold tracking-tight; - } - p { - @apply text-base; - } -} diff --git a/web/src/lib/auth.ts b/web/src/lib/auth.ts deleted file mode 100644 index d25811f..0000000 --- a/web/src/lib/auth.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { SENTINEL_API_URL } from "@/consts/config"; -import { initUser } from "@/models/user"; -import { getUser, setUser } from "@/lib/store"; -import axios from "axios"; - -export const checkCredentials = async (): Promise => { - const currentUser = getUser(); - if (localStorage.getItem("sentinel_access_token") == null) { - return 1; - } else if (currentUser.id == "") { - try { - const response = await axios.get(`${SENTINEL_API_URL}/users/@me`, { - headers: { - Authorization: `Bearer ${localStorage.getItem("sentinel_access_token")}`, - }, - }); - if (response.status == 200) { - setUser(response.data); - return 0; - } - } catch (error) { - if ((await refreshAccessToken()) == 0) { - return checkCredentials(); - } - return 1; - } - } - return 0; -}; - -const refreshAccessToken = async (): Promise => { - const refreshToken = localStorage.getItem("sentinel_refresh_token"); - if (refreshToken == null) { - return 1; - } - try { - const response = await axios.post( - `${SENTINEL_API_URL}/oauth/token`, - new URLSearchParams({ - grant_type: "refresh_token", - refresh_token: refreshToken, - }), - { - headers: { - "Content-Type": "application/x-www-form-urlencoded", - }, - }, - ); - if (response.status == 200) { - saveAccessToken(response.data.access_token); - saveRefreshToken(response.data.refresh_token); - return 0; - } - } catch (error) { - return 1; - } - return 1; -}; - -export const logout = () => { - localStorage.removeItem("sentinel_access_token"); - localStorage.removeItem("sentinel_refresh_token"); - // Remove all cookies that start with sentinel_ - document.cookie.split(";").forEach((cookie) => { - const trimmedCookie = cookie.trim(); - if (trimmedCookie.startsWith("sentinel_")) { - const cookieName = trimmedCookie.split("=")[0]; - document.cookie = `${cookieName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/; domain=.gauchoracing.com; secure; samesite=lax`; - } - }); - setUser(initUser); -}; - -export const saveAccessToken = (accessToken: string) => { - localStorage.setItem("sentinel_access_token", accessToken); - document.cookie = `sentinel_access_token=${accessToken}; domain=.gauchoracing.com; path=/; secure; samesite=lax`; -}; - -export const saveRefreshToken = (refreshToken: string) => { - localStorage.setItem("sentinel_refresh_token", refreshToken); - document.cookie = `sentinel_refresh_token=${refreshToken}; domain=.gauchoracing.com; path=/; secure; samesite=lax`; -}; diff --git a/web/src/lib/axios-error-handler.ts b/web/src/lib/axios-error-handler.ts deleted file mode 100644 index 4961588..0000000 --- a/web/src/lib/axios-error-handler.ts +++ /dev/null @@ -1,18 +0,0 @@ -function getAxiosErrorCode(error: any) { - if (error.response == null) { - return 0; - } - return error.response.status; -} - -function getAxiosErrorMessage(error: any) { - if (error.response == null) { - return error.message; - } else if (error.response.data.message != null) { - return error.response.data.message; - } else { - return "Failed with status code " + error.response.status; - } -} - -export { getAxiosErrorCode, getAxiosErrorMessage }; diff --git a/web/src/lib/notify.tsx b/web/src/lib/notify.tsx deleted file mode 100644 index c82eb50..0000000 --- a/web/src/lib/notify.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { - faCheckCircle, - faWarning, - faXmarkCircle, -} from "@fortawesome/free-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { toast } from "sonner"; - -interface Notify { - info(message: string, description?: string): void; - success(message: string, description?: string): void; - warning(message: string, description?: string): void; - error(message: string, description?: string): void; -} - -export const notify: Notify = { - info: (title: string, description?: string) => { - toast(title, { - description: description, - }); - }, - success: (title: string, description?: string) => { - toast(title, { - icon: , - description: description, - }); - }, - warning: (title: string, description?: string) => { - toast(title, { - icon: , - description: description, - }); - }, - error: (title: string, description?: string) => { - toast(title, { - icon: , - description: description, - }); - }, -}; diff --git a/web/src/lib/store.ts b/web/src/lib/store.ts deleted file mode 100644 index c528150..0000000 --- a/web/src/lib/store.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { initUser } from "@/models/user"; -import createStore from "react-superstore"; - -export const [useUser, setUser, getUser] = createStore(initUser); diff --git a/web/src/lib/utils.ts b/web/src/lib/utils.ts deleted file mode 100644 index a5ef193..0000000 --- a/web/src/lib/utils.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { clsx, type ClassValue } from "clsx"; -import { twMerge } from "tailwind-merge"; - -export function cn(...inputs: ClassValue[]) { - return twMerge(clsx(inputs)); -} diff --git a/web/src/main.tsx b/web/src/main.tsx deleted file mode 100644 index d55f535..0000000 --- a/web/src/main.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import React from "react"; -import ReactDOM from "react-dom/client"; -import { createBrowserRouter, RouterProvider } from "react-router-dom"; -import "./index.css"; -import "/node_modules/react-grid-layout/css/styles.css"; -import "/node_modules/react-resizable/css/styles.css"; -import "mapbox-gl/dist/mapbox-gl.css"; -import { Toaster } from "./components/ui/sonner.tsx"; -import App from "./App.tsx"; -import LoginDiscordPage from "@/pages/auth/LoginDiscordPage.tsx"; -import LoginPage from "@/pages/auth/LoginPage.tsx"; -import EditUserPage from "@/pages/users/EditUserPage.tsx"; -import ApplicationsPage from "@/pages/applications/ApplicationsPage.tsx"; -import AuthorizePage from "@/pages/oauth/AuthorizePage.tsx"; -import UsersPage from "@/pages/users/UsersPage.tsx"; -import UserProfilePage from "@/pages/users/UserProfilePage.tsx"; -import AnalyticsPage from "@/pages/analytics/AnalyticsPage.tsx"; - -const router = createBrowserRouter([ - { - path: "/", - element: , - }, - { - path: "/auth/login", - element: , - }, - { - path: "/auth/login/discord", - element: , - }, - { - path: "/oauth/authorize", - element: , - }, - { - path: "/users", - element: , - }, - { - path: "/users/:id", - element: , - }, - { - path: "/users/:id/edit", - element: , - }, - { - path: "/analytics", - element: , - }, - { - path: "/applications", - element: , - }, - { - path: "/applications/:id", - element: , - }, -]); - -ReactDOM.createRoot(document.getElementById("root")!).render( - - - - , -); diff --git a/web/src/models/application.tsx b/web/src/models/application.tsx deleted file mode 100644 index ced3f54..0000000 --- a/web/src/models/application.tsx +++ /dev/null @@ -1,33 +0,0 @@ -export interface ClientApplication { - id: string; - user_id: string; - secret: string; - name: string; - redirect_uris: string[]; - updated_at: Date; - created_at: Date; -} - -export const initClientApplication: ClientApplication = { - id: "", - user_id: "", - secret: "", - name: "", - redirect_uris: [], - updated_at: new Date(), - created_at: new Date(), -}; - -/* -{ - "id": "eqHTFAzg1vro", - "user_id": "348220961155448833", - "secret": "xaPME4afDUM0QOjkNfPq0Xw5ukm4lbdm", - "name": "Test Application", - "redirect_uris": [ - "http://localhost:8080/auth/login" - ], - "updated_at": "2024-06-30T20:40:28.06707-07:00", - "created_at": "2024-06-30T20:39:31.836686-07:00" - } -*/ diff --git a/web/src/models/user.tsx b/web/src/models/user.tsx deleted file mode 100644 index 98324de..0000000 --- a/web/src/models/user.tsx +++ /dev/null @@ -1,127 +0,0 @@ -export interface User { - id: string; - username: string; - first_name: string; - last_name: string; - email: string; - phone_number: string; - gender: string; - birthday: string; - graduate_level: string; - graduation_year: number; - major: string; - shirt_size: string; - jacket_size: string; - sae_registration_number: string; - avatar_url: string; - verified: boolean; - subteams: Subteam[]; - roles: string[]; - updated_at: string; - created_at: string; -} - -export const isInnerCircle = (user: User): boolean => { - return ( - user.roles.includes("d_admin") || - user.roles.includes("d_officer") || - user.roles.includes("d_lead") || - user.roles.includes("d_special_advisor") - ); -}; - -export interface Subteam { - id: string; - name: string; - created_at: string; -} - -export const initUser: User = { - id: "", - username: "", - first_name: "", - last_name: "", - email: "", - phone_number: "", - gender: "", - birthday: "", - graduate_level: "", - graduation_year: 0, - major: "", - shirt_size: "", - jacket_size: "", - sae_registration_number: "", - avatar_url: "", - verified: false, - subteams: [], - roles: [], - updated_at: "", - created_at: "", -}; - -// export const setUser = (user: User, data: User) => { -// user.id = data.id; -// user.first_name = data.first_name; -// user.last_name = data.last_name; -// user.email = data.email; -// user.phone_number = data.phone_number; -// user.gender = data.gender; -// user.birthday = data.birthday; -// user.graduate_level = data.graduate_level; -// user.graduation_year = data.graduation_year; -// user.major = data.major; -// user.shirt_size = data.shirt_size; -// user.jacket_size = data.jacket_size; -// user.sae_registration_number = data.sae_registration_number; -// user.avatar_url = data.avatar_url; -// user.verified = data.verified; -// user.subteams = data.subteams; -// user.roles = data.roles; -// user.updated_at = data.updated_at; -// user.created_at = data.created_at; -// }; - -/* -{ - "id": "348220961155448833", - "username": "bk1031", - "first_name": "Bharat", - "last_name": "Kathi", - "email": "bkathi@ucsb.edu", - "phone_number": "", - "graduate_level": "", - "graduation_year": 0, - "major": "", - "shirt_size": "", - "jacket_size": "", - "sae_registration_number": "", - "avatar_url": "https://cdn.discordapp.com/avatars/348220961155448833/1bedb626ddb6b5a712ee3b172e442eff.png?size=256", - "verified": false, - "subteams": [ - { - "id": "761116347865890816", - "name": "Electronics", - "created_at": "2024-06-27T18:23:22.099813-07:00" - }, - { - "id": "761331962563919874", - "name": "Business", - "created_at": "2024-06-27T18:23:22.104944-07:00" - }, - { - "id": "1254572624307290202", - "name": "Data", - "created_at": "2024-06-27T18:23:22.11023-07:00" - } - ], - "roles": [ - "d_lead", - "d_admin", - "d_officer", - "d_member", - "github_BK1031" - ], - "updated_at": "2024-06-27T00:34:12.749266-07:00", - "created_at": "2024-06-27T00:34:12.771085-07:00" -} -*/ diff --git a/web/src/pages/analytics/AnalyticsPage.tsx b/web/src/pages/analytics/AnalyticsPage.tsx deleted file mode 100644 index a7052d7..0000000 --- a/web/src/pages/analytics/AnalyticsPage.tsx +++ /dev/null @@ -1,387 +0,0 @@ -import React from "react"; -import axios from "axios"; -import { useNavigate } from "react-router-dom"; -import { SENTINEL_API_URL } from "@/consts/config"; -import { checkCredentials } from "@/lib/auth"; -import { getAxiosErrorMessage } from "@/lib/axios-error-handler"; -import { notify } from "@/lib/notify"; -import { User } from "@/models/user"; -import Footer from "@/components/Footer"; -import { AuthLoading } from "@/components/AuthLoading"; -import { Button } from "@/components/ui/button"; -import { Card } from "@/components/ui/card"; -import { Separator } from "@/components/ui/separator"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { - faArrowLeft, - faChartBar, - faChartLine, - faChartPie, -} from "@fortawesome/free-solid-svg-icons"; -import { Loader2 } from "lucide-react"; -import { - PieChart, - Pie, - Cell, - Tooltip as ReTooltip, - ResponsiveContainer, - BarChart, - Bar, - XAxis, - YAxis, - CartesianGrid, - LineChart, - Line, -} from "recharts"; - -type UserLogin = { - id: string; - user_id: string; - destination: string; - scope: string; - ip_address: string; - login_type: string; - created_at: string; -}; - -const COLORS = [ - "#f472b6", - "#60a5fa", - "#34d399", - "#f59e0b", - "#a78bfa", - "#f87171", - "#22d3ee", - "#e879f9", -]; // on-brand - -function AnalyticsPage() { - const navigate = useNavigate(); - - const [authCheckLoading, setAuthCheckLoading] = React.useState(false); - const [usersLoading, setUsersLoading] = React.useState(false); - const [loginsLoading, setLoginsLoading] = React.useState(false); - const [users, setUsers] = React.useState([]); - const [logins, setLogins] = React.useState([]); - - React.useEffect(() => { - checkAuth().then(() => { - getUsers(); - getLogins(); - }); - }, []); - - const checkAuth = async () => { - setAuthCheckLoading(true); - const currentRoute = window.location.pathname + window.location.search; - const status = await checkCredentials(); - if (status != 0) { - navigate(`/auth/login?route=${encodeURIComponent(currentRoute)}`); - } else { - setAuthCheckLoading(false); - } - }; - - const getUsers = async () => { - setUsersLoading(true); - try { - const response = await axios.get(`${SENTINEL_API_URL}/users`, { - headers: { - Authorization: `Bearer ${localStorage.getItem("sentinel_access_token")}`, - }, - }); - if (response.status == 200) setUsers(response.data); - } catch (error: any) { - notify.error(getAxiosErrorMessage(error)); - } - setUsersLoading(false); - }; - - const getLogins = async () => { - setLoginsLoading(true); - try { - const response = await axios.get(`${SENTINEL_API_URL}/logins`, { - headers: { - Authorization: `Bearer ${localStorage.getItem("sentinel_access_token")}`, - }, - }); - if (response.status == 200) setLogins(response.data); - } catch (error: any) { - notify.error(getAxiosErrorMessage(error)); - } - setLoginsLoading(false); - }; - - const genderData = React.useMemo(() => { - const map: Record = {}; - users.forEach((u) => { - const g = (u.gender || "Unknown").trim() || "Unknown"; - map[g] = (map[g] || 0) + 1; - }); - return Object.entries(map).map(([name, value]) => ({ name, value })); - }, [users]); - - const gradYearData = React.useMemo(() => { - const map: Record = {}; - users.forEach((u) => { - const y = u.graduation_year ? u.graduation_year.toString() : "Unknown"; - map[y] = (map[y] || 0) + 1; - }); - return Object.entries(map) - .sort((a, b) => - a[0] === "Unknown" - ? 1 - : b[0] === "Unknown" - ? -1 - : parseInt(a[0]) - parseInt(b[0]), - ) - .map(([year, count]) => ({ year, count })); - }, [users]); - - const subteamData = React.useMemo(() => { - const map: Record = {}; - users.forEach((u) => - u.subteams.forEach((s) => (map[s.name] = (map[s.name] || 0) + 1)), - ); - return Object.entries(map) - .sort((a, b) => b[1] - a[1]) - .map(([name, count]) => ({ name, count })); - }, [users]); - - const rolesData = React.useMemo(() => { - const map: Record = {}; - users.forEach((u) => - u.roles - .filter((r) => r.startsWith("d_")) - .forEach((r) => (map[r] = (map[r] || 0) + 1)), - ); - return Object.entries(map).map(([name, value]) => ({ name, value })); - }, [users]); - - const loginSeries = React.useMemo(() => { - const buckets: Record = {}; - const now = new Date(); - for (let i = 29; i >= 0; i--) { - const d = new Date(now); - d.setDate(now.getDate() - i); - const key = d.toISOString().slice(0, 10); - buckets[key] = 0; - } - logins.forEach((l) => { - const key = new Date(l.created_at).toISOString().slice(0, 10); - if (buckets[key] !== undefined) buckets[key] += 1; - }); - return Object.entries(buckets).map(([date, count]) => ({ date, count })); - }, [logins]); - - return ( - <> - {authCheckLoading ? ( - - ) : ( -
-
-
- -
-

Analytics

- {(usersLoading || loginsLoading) && ( -
- -
- )} -
- -
- -

Gender Distribution

-
- -
- - - - {genderData.map((_, index) => ( - - ))} - - - - -
-
- - -
- -

Graduation Year

-
- -
- - - - - - - - - -
-
- - -
- -

Subteam Sizes

-
- -
- - - - - - - - - -
-
- - -
- -

Logins (Last 30 Days)

-
- -
- - - - - - - - - -
-
- - -
- -

Roles

-
- -
- - - - {rolesData.map((_, index) => ( - - ))} - - - - -
-
-
-
-
-
- )} - - ); -} - -export default AnalyticsPage; diff --git a/web/src/pages/applications/ApplicationsPage.tsx b/web/src/pages/applications/ApplicationsPage.tsx deleted file mode 100644 index a22ab9d..0000000 --- a/web/src/pages/applications/ApplicationsPage.tsx +++ /dev/null @@ -1,631 +0,0 @@ -import React from "react"; -import axios from "axios"; -import { SENTINEL_API_URL } from "@/consts/config"; -import { Button } from "@/components/ui/button"; -import { Card } from "@/components/ui/card"; -import { Loader2 } from "lucide-react"; -import { getAxiosErrorMessage } from "@/lib/axios-error-handler"; -import { useNavigate, useParams } from "react-router-dom"; -import { checkCredentials } from "@/lib/auth"; -import Footer from "@/components/Footer"; -import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar"; -import { ClientApplication, initClientApplication } from "@/models/application"; -import { OutlineButton } from "@/components/ui/outline-button"; -import { AuthLoading } from "@/components/AuthLoading"; -import { User, initUser, isInnerCircle } from "@/models/user"; -import { Input } from "@/components/ui/input"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { faArrowLeft, faTrash } from "@fortawesome/free-solid-svg-icons"; -import { - AlertDialog, - AlertDialogAction, - AlertDialogCancel, - AlertDialogContent, - AlertDialogDescription, - AlertDialogFooter, - AlertDialogHeader, - AlertDialogTitle, - AlertDialogTrigger, -} from "@/components/ui/alert-dialog"; -import { faAppStore } from "@fortawesome/free-brands-svg-icons"; -import { notify } from "@/lib/notify"; -import { useUser } from "@/lib/store"; - -function ApplicationsPage() { - const navigate = useNavigate(); - const { id } = useParams(); - const currentUser = useUser(); - - const [authCheckLoading, setAuthCheckLoading] = React.useState(false); - - const [applicationsLoading, setApplicationsLoading] = React.useState(false); - const [applications, setApplications] = React.useState( - [], - ); - const [selectedApplication, setSelectedApplication] = - React.useState(initClientApplication); - const [selectedApplicationLoading, setSelectedApplicationLoading] = - React.useState(false); - const [selectedOwner, setSelectedOwner] = React.useState(initUser); - const [canEdit, setCanEdit] = React.useState(false); - - const [creatingApplication, setCreatingApplication] = React.useState(false); - - const [scopes, setScopes] = React.useState<{ [key: string]: string }>({}); - - React.useEffect(() => { - checkAuth().then(async () => { - await getApplications(); - await getScopes(); - init(); - }); - }, [id]); - - const checkAuth = async () => { - setAuthCheckLoading(true); - const currentRoute = window.location.pathname + window.location.search; - const status = await checkCredentials(); - if (status != 0) { - navigate(`/auth/login?route=${encodeURIComponent(currentRoute)}`); - } else { - setAuthCheckLoading(false); - } - }; - - const init = async () => { - if (id) { - if (id == "new") { - setSelectedApplication(initClientApplication); - setSelectedOwner(currentUser); - setCreatingApplication(true); - setCanEdit(true); - } else { - await getSelectedApplication(id); - } - } - }; - - const getScopes = async () => { - try { - const response = await axios.get(`${SENTINEL_API_URL}/oauth/scopes`, { - headers: { - Authorization: `Bearer ${localStorage.getItem("sentinel_access_token")}`, - }, - }); - if (response.status == 200) { - setScopes(response.data); - } - } catch (error: any) { - notify.error(getAxiosErrorMessage(error)); - } - }; - - const getSelectedApplication = async (applicationId: string) => { - setSelectedApplicationLoading(true); - setCanEdit(false); - setCreatingApplication(false); - try { - const response = await axios.get( - `${SENTINEL_API_URL}/applications/${applicationId}`, - { - headers: { - Authorization: `Bearer ${localStorage.getItem("sentinel_access_token")}`, - }, - }, - ); - setSelectedApplication(response.data); - getUser(response.data.user_id); - if ( - response.data.user_id == currentUser.id || - isInnerCircle(currentUser) - ) { - setCanEdit(true); - } else { - setCanEdit(false); - } - } catch (error: any) { - notify.error(getAxiosErrorMessage(error)); - navigate("/applications"); - } - setSelectedApplicationLoading(false); - }; - - const getApplications = async () => { - if (applications.length == 0) { - setApplicationsLoading(true); - } - try { - const response = await axios.get(`${SENTINEL_API_URL}/applications`, { - headers: { - Authorization: `Bearer ${localStorage.getItem("sentinel_access_token")}`, - }, - }); - setApplications(response.data); - } catch (error: any) { - notify.error(getAxiosErrorMessage(error)); - } - setApplicationsLoading(false); - }; - - const getUser = async (userId: string) => { - setSelectedOwner(initUser); - setSelectedApplicationLoading(true); - try { - const response = await axios.get(`${SENTINEL_API_URL}/users/${userId}`, { - headers: { - Authorization: `Bearer ${localStorage.getItem("sentinel_access_token")}`, - }, - }); - setSelectedOwner(response.data); - } catch (error: any) { - notify.error(getAxiosErrorMessage(error)); - } - setSelectedApplicationLoading(false); - }; - - const createApplication = async () => { - if (selectedApplication.name.trim() == "") { - notify.error("You must specify a name for your application."); - return; - } - const cleanedApplication = { - ...selectedApplication, - user_id: selectedApplication.user_id || currentUser.id, - redirect_uris: selectedApplication.redirect_uris.filter( - (uri) => uri.trim() !== "", - ), - }; - setSelectedApplication(cleanedApplication); - try { - const response = await axios.post( - `${SENTINEL_API_URL}/applications`, - cleanedApplication, - { - headers: { - Authorization: `Bearer ${localStorage.getItem("sentinel_access_token")}`, - }, - }, - ); - if (response.status == 200) { - navigate(`/applications/${response.data.id}`); - notify.success( - "Changes saved", - "Your application has successfully been updated.", - ); - } - } catch (error: any) { - notify.error(getAxiosErrorMessage(error)); - } - getApplications(); - }; - - const deleteApplication = async () => { - try { - const response = await axios.delete( - `${SENTINEL_API_URL}/applications/${selectedApplication.id}`, - { - headers: { - Authorization: `Bearer ${localStorage.getItem("sentinel_access_token")}`, - }, - }, - ); - if (response.status == 200) { - navigate("/applications"); - } - } catch (error: any) { - notify.error(getAxiosErrorMessage(error)); - } - setSelectedApplication(initClientApplication); - getApplications(); - }; - - const ApplicationListItem = (props: { application: ClientApplication }) => { - return ( - navigate(`/applications/${props.application.id}`)} - > -
-
-
{props.application.name}
-
- Client ID: {props.application.id} -
-
-
-
- ); - }; - - const NoApplicationsCard = () => { - return ( - -
- -

No Applications

-

- You don't have any applications yet. Create a new application to get - started. -

- navigate("/applications/new")} - className="mt-4" - > - New Application - -
-
- ); - }; - - const NoApplicationSelectedCard = () => { - return ( - -
- -

No Application Selected

-

- Select an application from the list to view or edit its details. -

-
-
- ); - }; - - const ApplicationLoadingCard = () => { - return ( - -
- -
-
- ); - }; - - return ( - <> - {authCheckLoading ? ( - - ) : ( -
-
-
- -
-

Applications

-

- Want to create a new team application? Use the Sentinel API to - easily authenticate Gaucho Racing members. Check out our API - documentation{" "} - - window.open( - "https://wiki.gauchoracing.com/books/sentinel/page/api-documentation", - "_blank", - ) - } - > - here - - . -

- {applications.length == 0 && - !creatingApplication && - !applicationsLoading ? ( - - ) : ( - <> -
-
- {applicationsLoading ? ( -
- -
- ) : ( -
- {applications.map((application) => ( -
- -
- ))} - navigate("/applications/new")} - className="mt-2 w-full" - > - New Application - -
- )} -
-
- {selectedApplicationLoading ? ( - - ) : selectedApplication.id == "" && !creatingApplication ? ( - - ) : ( - -
- {canEdit ? ( -
- { - setSelectedApplication({ - ...selectedApplication, - name: e.target.value, - }); - }} - /> - {!creatingApplication ? ( - - - - - - - - Are you absolutely sure? - - - This action cannot be undone. This will - permanently delete your application. - - - - - Cancel - - - Delete - - - - - ) : ( - <> - )} -
- ) : ( -

- {selectedApplication.name} -

- )} -

- {creatingApplication ? ( - <> - ) : ( -

-
- Client ID: -
- -
- )} - {creatingApplication ? ( - <> - ) : ( -
-
- Client Secret: -
- -
- )} -
-
- Owner: -
-
- - - CN - -
-
- {selectedOwner.first_name}{" "} - {selectedOwner.last_name} -
-
- {selectedOwner.email} -
-
-
-
-
-
Redirect URIs:
-

- You must specify at least one URI for - authentication to work. If you pass a URI in an - OAuth request, it must exactly match one of the - URIs you enter here. -

- {selectedApplication.redirect_uris.map( - (uri, index) => ( -
-
- URI {index + 1}: -
- { - const newUris = - selectedApplication.redirect_uris; - newUris[index] = e.target.value; - setSelectedApplication({ - ...selectedApplication, - redirect_uris: newUris, - }); - }} - /> - {canEdit ? ( - - ) : ( - <> - )} -
- ), - )} - {canEdit && ( -
- -
- )} -
- {!creatingApplication && ( -
-
Scopes:
-

- You must specify one or more valid scopes when - making an authorization request. -

-
- {Object.entries(scopes) - .filter(([scope]) => scope !== "sentinel:all") - .map(([scope, description]) => ( -
-
- - - -

- {description} -

-
-
- ))} -
-
- )} - {canEdit && ( -
-
- - { - createApplication(); - }} - > - Save Changes - -
-
- )} -
-
- )} -
{" "} -
- - )} -
-
-
- )} - - ); -} - -export default ApplicationsPage; diff --git a/web/src/pages/auth/LoginDiscordPage.tsx b/web/src/pages/auth/LoginDiscordPage.tsx deleted file mode 100644 index c49af6c..0000000 --- a/web/src/pages/auth/LoginDiscordPage.tsx +++ /dev/null @@ -1,197 +0,0 @@ -import React from "react"; -import axios from "axios"; -import { - DISCORD_CLIENT_ID, - DISCORD_OAUTH_BASE_URL, - DISCORD_SERVER_INVITE_URL, - SENTINEL_API_URL, -} from "@/consts/config"; -import { Card } from "@/components/ui/card"; -import { Loader2 } from "lucide-react"; -import { getAxiosErrorMessage } from "@/lib/axios-error-handler"; -import { useNavigate, useSearchParams } from "react-router-dom"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { faDiscord } from "@fortawesome/free-brands-svg-icons"; -import { - checkCredentials, - saveAccessToken, - saveRefreshToken, -} from "@/lib/auth"; -import { notify } from "@/lib/notify"; - -function LoginDiscordPage() { - const navigate = useNavigate(); - const [queryParameters] = useSearchParams(); - - const [sentinelMsg, setSentinelMsg] = React.useState(""); - const [loginLoading, setLoginLoading] = React.useState(true); - const [accountExists, setAccountExists] = React.useState(true); - - React.useEffect(() => { - ping(); - login(); - }, []); - - const ping = async () => { - try { - const response = await axios.get(`${SENTINEL_API_URL}/ping`); - console.log(response.data); - setSentinelMsg(response.data.message); - } catch (error: any) { - notify.error(getAxiosErrorMessage(error)); - } - }; - - const checkAuth = async () => { - const status = await checkCredentials(); - if (status == 0) { - handleRedirect(); - } - }; - - const login = async () => { - const code = queryParameters.get("code"); - if (!code) { - navigate("/"); - return; - } - try { - const response = await axios.post( - `${SENTINEL_API_URL}/auth/login/discord?code=${code}`, - ); - if (response.status == 200) { - saveAccessToken(response.data.access_token); - saveRefreshToken(response.data.refresh_token); - checkAuth(); - } - } catch (error: any) { - notify.error(getAxiosErrorMessage(error)); - setLoginLoading(false); - if (getAxiosErrorMessage(error).includes("No account with this")) { - setAccountExists(false); - } - } - }; - - const handleRedirect = () => { - const route = queryParameters.get("state"); - if (route) { - navigate(route); - } else { - navigate("/"); - } - }; - - const LoadingCard = () => { - return ( - -
- Gaucho Racing - -
-
- ); - }; - - const InvalidCodeCard = () => { - return ( - -
- Gaucho Racing -

- Discord OAuth Error -

-

Invalid or expired code. Please try again.

- -
-
- ); - }; - - const NoAccountCard = () => { - return ( - -
- Gaucho Racing -

- No Account Found -

-

- No Sentinel account found. Make sure that you have joined the Gaucho - Racing Discord server and verified your account. -

-

- You can verify your account using the !verify command - in the #verification channel. -
-
- Example: {`!verify `} -

- -
-
- ); - }; - - return ( - <> -
-
-
- {loginLoading ? ( - - ) : accountExists ? ( - - ) : ( - - )} -
-
-

{sentinelMsg}

-
-
- - ); -} - -export default LoginDiscordPage; diff --git a/web/src/pages/auth/LoginPage.tsx b/web/src/pages/auth/LoginPage.tsx deleted file mode 100644 index b72be68..0000000 --- a/web/src/pages/auth/LoginPage.tsx +++ /dev/null @@ -1,173 +0,0 @@ -import React from "react"; -import axios from "axios"; -import { - DISCORD_CLIENT_ID, - DISCORD_OAUTH_BASE_URL, - SENTINEL_API_URL, -} from "@/consts/config"; -import { Input } from "@/components/ui/input"; -import { Card } from "@/components/ui/card"; -import { Loader2 } from "lucide-react"; -import { getAxiosErrorMessage } from "@/lib/axios-error-handler"; -import { useNavigate, useSearchParams } from "react-router-dom"; -import { Separator } from "@/components/ui/separator"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { faDiscord } from "@fortawesome/free-brands-svg-icons"; -import { - checkCredentials, - saveAccessToken, - saveRefreshToken, -} from "@/lib/auth"; -import { OutlineButton } from "@/components/ui/outline-button"; -import { notify } from "@/lib/notify"; - -function LoginPage() { - const navigate = useNavigate(); - const [queryParameters] = useSearchParams(); - - const [sentinelMsg, setSentinelMsg] = React.useState(""); - - const [loginLoading, setLoginLoading] = React.useState(false); - const [loginEmail, setLoginEmail] = React.useState(""); - const [loginPassword, setLoginPassword] = React.useState(""); - - React.useEffect(() => { - ping(); - checkAuth(); - }, []); - - const ping = async () => { - try { - const response = await axios.get(`${SENTINEL_API_URL}/ping`); - console.log(response.data); - setSentinelMsg(response.data.message); - } catch (error: any) { - notify.error(getAxiosErrorMessage(error)); - } - }; - - const checkAuth = async () => { - const status = await checkCredentials(); - console.log(status); - if (status == 0) { - handleRedirect(); - } - }; - - const login = async () => { - setLoginLoading(true); - try { - const response = await axios.post(`${SENTINEL_API_URL}/auth/login`, { - email: loginEmail, - password: loginPassword, - }); - if (response.status == 200) { - saveAccessToken(response.data.access_token); - saveRefreshToken(response.data.refresh_token); - checkAuth(); - } - } catch (error: any) { - notify.error(getAxiosErrorMessage(error)); - } - setLoginLoading(false); - }; - - const handleRedirect = () => { - const route = queryParameters.get("route"); - if (route) { - navigate(route); - } else { - navigate("/"); - } - }; - - return ( - <> -
-
-
- -
- Gaucho Racing -

- Sentinel Sign On -

- { - setLoginEmail(e.target.value); - }} - /> - { - setLoginPassword(e.target.value); - }} - onKeyDown={(e) => { - if (e.key === "Enter") { - login(); - } - }} - /> - - {loginLoading && } - Sign In with Email - -
- -

OR

- -
- -
-
-
-
-

{sentinelMsg}

-
-
- - ); -} - -export default LoginPage; diff --git a/web/src/pages/oauth/AuthorizePage.tsx b/web/src/pages/oauth/AuthorizePage.tsx deleted file mode 100644 index af01676..0000000 --- a/web/src/pages/oauth/AuthorizePage.tsx +++ /dev/null @@ -1,286 +0,0 @@ -import React from "react"; -import axios from "axios"; -import { SENTINEL_API_URL } from "@/consts/config"; -import { Card } from "@/components/ui/card"; -import { Loader2 } from "lucide-react"; -import { getAxiosErrorMessage } from "@/lib/axios-error-handler"; -import { useNavigate, useSearchParams } from "react-router-dom"; -import { checkCredentials } from "@/lib/auth"; -import { ClientApplication, initClientApplication } from "@/models/application"; -import { OutlineButton } from "@/components/ui/outline-button"; -import { Button } from "@/components/ui/button"; -import { notify } from "@/lib/notify"; -import { - Tooltip, - TooltipContent, - TooltipProvider, - TooltipTrigger, -} from "@/components/ui/tooltip"; - -function AuthorizePage() { - const navigate = useNavigate(); - const [queryParameters] = useSearchParams(); - - const [sentinelMsg, setSentinelMsg] = React.useState(""); - const [loginLoading, setLoginLoading] = React.useState(true); - const [errorMsg, setErrorMsg] = React.useState(""); - const [promptRequired, setPromptRequired] = React.useState(false); - - const [application, setApplication] = React.useState( - initClientApplication, - ); - - const [scopes, setScopes] = React.useState<{ [key: string]: string }>({}); - - React.useEffect(() => { - checkAuth().then(() => { - ping(); - getScopes(); - validate(); - }); - }, []); - - const ping = async () => { - try { - const response = await axios.get(`${SENTINEL_API_URL}/ping`); - console.log(response.data); - setSentinelMsg(response.data.message); - } catch (error: any) { - notify.error(getAxiosErrorMessage(error)); - } - }; - - const checkAuth = async () => { - const currentRoute = window.location.pathname + window.location.search; - const status = await checkCredentials(); - if (status != 0) { - navigate(`/auth/login?route=${encodeURIComponent(currentRoute)}`); - } - }; - - const getScopes = async () => { - try { - const response = await axios.get(`${SENTINEL_API_URL}/oauth/scopes`, { - headers: { - Authorization: `Bearer ${localStorage.getItem("sentinel_access_token")}`, - }, - }); - if (response.status == 200) { - setScopes(response.data); - } - } catch (error: any) { - notify.error(getAxiosErrorMessage(error)); - } - }; - - const validate = async () => { - setLoginLoading(true); - const url = window.location.href; - try { - const response = await axios.get( - `${SENTINEL_API_URL}/oauth/authorize${url.split("oauth/authorize")[1]}`, - { - headers: { - Authorization: `Bearer ${localStorage.getItem("sentinel_access_token")}`, - }, - }, - ); - if (response.status == 200) { - setErrorMsg(""); - getApplication(response.data.client_id); - if (response.data.prompt == "consent") { - setLoginLoading(false); - setPromptRequired(true); - } else if (response.data.prompt == "none") { - setPromptRequired(false); - authorize(); - } - } - } catch (error: any) { - notify.error(getAxiosErrorMessage(error)); - setLoginLoading(false); - setErrorMsg(error.response.data.message); - } - }; - - const authorize = async () => { - setLoginLoading(true); - const url = window.location.href; - try { - const response = await axios.post( - `${SENTINEL_API_URL}/oauth/authorize${url.split("oauth/authorize")[1]}`, - {}, - { - headers: { - Authorization: `Bearer ${localStorage.getItem("sentinel_access_token")}`, - }, - }, - ); - if (response.status == 200) { - handleRedirect(response.data.code); - } - } catch (error: any) { - notify.error(getAxiosErrorMessage(error)); - setLoginLoading(false); - setErrorMsg(error.response.data.message); - } - }; - - const getApplication = async (clientId: string) => { - try { - const response = await axios.get( - `${SENTINEL_API_URL}/applications/${clientId}`, - { - headers: { - Authorization: `Bearer ${localStorage.getItem("sentinel_access_token")}`, - }, - }, - ); - setApplication(response.data); - } catch (error: any) { - notify.error(getAxiosErrorMessage(error)); - } - }; - - const handleRedirect = (code: string) => { - const state = queryParameters.get("state"); - const redirectUri = queryParameters.get("redirect_uri"); - // Save authorize time for client in cookies - const clientId = queryParameters.get("client_id"); - if (clientId) { - const currentMillis = new Date().getTime(); - document.cookie = `sentinel_${clientId}=${currentMillis}; domain=.gauchoracing.com; path=/; secure; samesite=lax`; - } - window.location.href = redirectUri + `?code=${code}&state=${state}`; - }; - - const LoadingCard = () => { - return ( - -
- Gaucho Racing - -
-
- ); - }; - - const InvalidCodeCard = () => { - return ( - -
- Gaucho Racing -

- Sentinel OAuth Error -

-

{errorMsg}

-
-
- ); - }; - - const PromptCard = () => { - return ( - -
- Gaucho Racing -

- Login to {application.name} -

-

- {application.name} would like to access your Sentinel account. -

-

Requested Scopes:

-
- {queryParameters - .get("scope") - ?.split(" ") - .map((scope) => ( -
- - - - - {scope} - - - -
- - - -

- {scopes[scope]} -

-
-
-
-
-
- ))} -
-
- - { - authorize(); - }} - > - Authorize - -
-
-
- ); - }; - - return ( - <> -
-
-
- {loginLoading ? ( - - ) : promptRequired ? ( - - ) : errorMsg != "" ? ( - - ) : ( - <> - )} -
-
-

{sentinelMsg}

-
-
- - ); -} - -export default AuthorizePage; diff --git a/web/src/pages/users/EditUserPage.tsx b/web/src/pages/users/EditUserPage.tsx deleted file mode 100644 index 17b839b..0000000 --- a/web/src/pages/users/EditUserPage.tsx +++ /dev/null @@ -1,587 +0,0 @@ -import React from "react"; -import axios from "axios"; -import { SENTINEL_API_URL } from "@/consts/config"; -import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; -import { Card } from "@/components/ui/card"; -import { getAxiosErrorMessage } from "@/lib/axios-error-handler"; -import { useNavigate, useParams } from "react-router-dom"; -import { Separator } from "@/components/ui/separator"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { faArrowLeft, faLock, faUser } from "@fortawesome/free-solid-svg-icons"; -import { checkCredentials } from "@/lib/auth"; -import Footer from "@/components/Footer"; -import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar"; -import { User, initUser, isInnerCircle } from "@/models/user"; -import { setUser, useUser, getUser as getCurrentUser } from "@/lib/store"; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuRadioGroup, - DropdownMenuRadioItem, - DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu"; -import { OutlineButton } from "@/components/ui/outline-button"; -import { AuthLoading } from "@/components/AuthLoading"; -import { cn } from "@/lib/utils"; -import { format } from "date-fns"; -import { Calendar } from "@/components/ui/calendar"; -import { - Popover, - PopoverContent, - PopoverTrigger, -} from "@/components/ui/popover"; -import { CalendarIcon, Loader2 } from "lucide-react"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; -import { notify } from "@/lib/notify"; - -function EditUserPage() { - const navigate = useNavigate(); - const { id } = useParams(); - const currentUser = useUser(); - - const [authCheckLoading, setAuthCheckLoading] = React.useState(false); - - const [canEdit, setCanEdit] = React.useState(false); - const [editUser, setEditUser] = React.useState(initUser); - const [userLoading, setUserLoading] = React.useState(false); - - const [date, setDate] = React.useState(); - - React.useEffect(() => { - checkAuth().then(() => { - checkEditPermissions(); - }); - }, [id]); - - const checkAuth = async () => { - setAuthCheckLoading(true); - const currentRoute = window.location.pathname + window.location.search; - const status = await checkCredentials(); - if (status != 0) { - navigate(`/auth/login?route=${encodeURIComponent(currentRoute)}`); - } else { - setAuthCheckLoading(false); - } - }; - - const checkEditPermissions = async () => { - const currentUser = getCurrentUser(); - if (isInnerCircle(currentUser) || currentUser.id == id) { - getUser(); - setCanEdit(true); - } else { - setCanEdit(false); - } - }; - - const getUser = async () => { - setUserLoading(true); - try { - const response = await axios.get(`${SENTINEL_API_URL}/users/${id}`, { - headers: { - Authorization: `Bearer ${localStorage.getItem("sentinel_access_token")}`, - }, - }); - if (response.status == 200) { - setEditUser(response.data); - if (response.data.birthday) { - const date = new Date(response.data.birthday); - setDate(date); - } - } - } catch (error: any) { - notify.error(getAxiosErrorMessage(error)); - setEditUser(initUser); - } - setUserLoading(false); - }; - - const saveUser = async () => { - setUserLoading(true); - if (date) { - const dateString = date.toLocaleDateString("en-US", { - year: "numeric", - month: "long", - day: "numeric", - }); - editUser.birthday = dateString; - } - try { - const response = await axios.post( - `${SENTINEL_API_URL}/users/${id}`, - editUser, - { - headers: { - Authorization: `Bearer ${localStorage.getItem("sentinel_access_token")}`, - }, - }, - ); - if (response.status == 200) { - setEditUser(response.data); - } - notify.success( - "Changes saved", - "Your profile has successfully been updated.", - ); - } catch (error: any) { - notify.error(getAxiosErrorMessage(error)); - } - setUserLoading(false); - }; - - const ProfileField = (props: { label: string; value: string }) => { - return ( -
-
{props.label}:
-
- {props.value != "" ? props.value : "Not set"} -
-
- ); - }; - - const InsufficientPermissionsCard = () => { - return ( - -
- -

Insufficient Permissions

-
- -

- You do not have permission to edit this user's profile. If you believe - this is an error, please contact an administrator. -

-
- ); - }; - - return ( - <> - {authCheckLoading ? ( - - ) : ( -
-
-
- -
-

Editing {editUser.first_name}

-
- {canEdit ? ( - -
-
- -

Profile

-
- { - await saveUser(); - if (currentUser.id == id) { - setUser(editUser); - } - }} - > - {userLoading && } - Save Changes - -
- -
- - - CN - -
-

- {editUser.first_name} {editUser.last_name} -

-

{editUser.email}

-
-
- - -
-
First Name:
- { - setEditUser({ - ...editUser, - first_name: e.target.value, - }); - }} - /> -
-
-
Last Name:
- { - setEditUser({ - ...editUser, - last_name: e.target.value, - }); - }} - /> -
-
-
- Phone Number: -
- { - setEditUser({ - ...editUser, - phone_number: e.target.value, - }); - }} - /> -
-
-
Gender:
- - - - - - { - setEditUser({ - ...editUser, - gender: value, - }); - }} - > - - Male - - - Female - - - Other - - - - -
-
-
Birthday:
- - - - - -
-
- -
-
- -
-
- -
-
-
-
-
- Graduate Level: -
- - - - - - { - setEditUser({ - ...editUser, - graduate_level: value, - }); - }} - > - - Undergraduate - - - Graduate - - - - -
-
-
- Graduation Year: -
- { - const parsedGraduationYear = - parseInt(e.target.value) || 0; - setEditUser({ - ...editUser, - graduation_year: parsedGraduationYear, - }); - }} - /> -
-
-
Major:
- { - setEditUser({ - ...editUser, - major: e.target.value, - }); - }} - /> -
-
-
Shirt Size:
- - - - - - { - setEditUser({ - ...editUser, - shirt_size: value, - }); - }} - > - - XS - - - S - - - M - - - L - - - XL - - - - -
-
-
Jacket Size:
- - - - - - { - setEditUser({ - ...editUser, - jacket_size: value, - }); - }} - > - - XS - - - S - - - M - - - L - - - XL - - - - -
-
-
- SAE Member ID: -
- { - setEditUser({ - ...editUser, - sae_registration_number: e.target.value, - }); - }} - /> -
- - subteam.name) - .join(", ")} - /> -
-
Roles:
-
- {editUser.roles.map((role) => ( -
- - {role} - -
- ))} -
-
- - -
- ) : ( - - )} -
-
-
-
- )} - - ); -} - -export default EditUserPage; diff --git a/web/src/pages/users/UserProfilePage.tsx b/web/src/pages/users/UserProfilePage.tsx deleted file mode 100644 index 453318f..0000000 --- a/web/src/pages/users/UserProfilePage.tsx +++ /dev/null @@ -1,519 +0,0 @@ -import React from "react"; -import axios from "axios"; -import { useNavigate, useParams } from "react-router-dom"; -import { SENTINEL_API_URL } from "@/consts/config"; -import { checkCredentials } from "@/lib/auth"; -import { getAxiosErrorMessage } from "@/lib/axios-error-handler"; -import { notify } from "@/lib/notify"; -import { useUser } from "@/lib/store"; -import { User, initUser, isInnerCircle } from "@/models/user"; -import Footer from "@/components/Footer"; -import { AuthLoading } from "@/components/AuthLoading"; -import { Button } from "@/components/ui/button"; -import { OutlineButton } from "@/components/ui/outline-button"; -import { Card } from "@/components/ui/card"; -import { Separator } from "@/components/ui/separator"; -import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; -import { Loader2 } from "lucide-react"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { - faArrowLeft, - faChartLine, - faClockRotateLeft, - faUser, - faMessage, - faFaceSmile, -} from "@fortawesome/free-solid-svg-icons"; -import { - LineChart, - Line, - XAxis, - YAxis, - CartesianGrid, - Tooltip, - ResponsiveContainer, -} from "recharts"; - -type UserLogin = { - id: string; - user_id: string; - destination: string; - scope: string; - ip_address: string; - login_type: string; - created_at: string; -}; - -type ActivityCount = { - date: string; - action: string; - count: number; -}; - -function UserProfilePage() { - const navigate = useNavigate(); - const { id } = useParams(); - const currentUser = useUser(); - - const [authCheckLoading, setAuthCheckLoading] = React.useState(false); - const [userLoading, setUserLoading] = React.useState(false); - const [user, setUser] = React.useState(initUser); - const [loginsLoading, setLoginsLoading] = React.useState(false); - const [logins, setLogins] = React.useState([]); - const [activitiesLoading, setActivitiesLoading] = React.useState(false); - const [activityStats, setActivityStats] = React.useState([]); - - React.useEffect(() => { - checkAuth().then(() => { - getUser(); - getLogins(); - getActivityStats(); - }); - }, [id]); - - const checkAuth = async () => { - setAuthCheckLoading(true); - const currentRoute = window.location.pathname + window.location.search; - const status = await checkCredentials(); - if (status != 0) { - navigate(`/auth/login?route=${encodeURIComponent(currentRoute)}`); - } else { - setAuthCheckLoading(false); - } - }; - - const getActivityStats = async () => { - setActivitiesLoading(true); - try { - const response = await axios.get( - `${SENTINEL_API_URL}/users/${id}/activity-stats`, - { - headers: { - Authorization: `Bearer ${localStorage.getItem("sentinel_access_token")}`, - }, - }, - ); - if (response.status == 200) setActivityStats(response.data); - } catch (error: any) { - notify.error(getAxiosErrorMessage(error)); - setActivityStats([]); - } - setActivitiesLoading(false); - }; - - const getUser = async () => { - setUserLoading(true); - try { - const response = await axios.get(`${SENTINEL_API_URL}/users/${id}`, { - headers: { - Authorization: `Bearer ${localStorage.getItem("sentinel_access_token")}`, - }, - }); - if (response.status == 200) { - setUser(response.data); - } - } catch (error: any) { - notify.error(getAxiosErrorMessage(error)); - setUser(initUser); - } - setUserLoading(false); - }; - - const getLogins = async () => { - setLoginsLoading(true); - try { - const response = await axios.get( - `${SENTINEL_API_URL}/users/${id}/logins`, - { - headers: { - Authorization: `Bearer ${localStorage.getItem("sentinel_access_token")}`, - }, - }, - ); - if (response.status == 200) { - setLogins(response.data); - } - } catch (error: any) { - // If not authorized, just show none - if (!getAxiosErrorMessage(error).toLowerCase().includes("unauthorized")) { - notify.error(getAxiosErrorMessage(error)); - } - setLogins([]); - } - setLoginsLoading(false); - }; - - const canEdit = React.useMemo(() => { - return isInnerCircle(currentUser) || currentUser.id == id; - }, [currentUser, id]); - - const lastLogin = React.useMemo(() => { - if (logins.length === 0) return undefined; - return [...logins].sort( - (a, b) => - new Date(b.created_at).getTime() - new Date(a.created_at).getTime(), - )[0]; - }, [logins]); - - const loginSeries = React.useMemo(() => { - const buckets: Record = {}; - const now = new Date(); - for (let i = 29; i >= 0; i--) { - const d = new Date(now); - d.setDate(now.getDate() - i); - const key = d.toISOString().slice(0, 10); - buckets[key] = 0; - } - logins.forEach((l) => { - const key = new Date(l.created_at).toISOString().slice(0, 10); - if (buckets[key] !== undefined) buckets[key] += 1; - }); - return Object.entries(buckets).map(([date, count]) => ({ date, count })); - }, [logins]); - - const messageSeries = React.useMemo(() => { - const map: Record = {}; - activityStats - .filter((a) => a.action === "message") - .forEach((a) => (map[a.date] = a.count)); - return Object.entries(map).map(([date, count]) => ({ date, count })); - }, [activityStats]); - - const reactionSeries = React.useMemo(() => { - const map: Record = {}; - activityStats - .filter((a) => a.action === "reaction") - .forEach((a) => (map[a.date] = a.count)); - return Object.entries(map).map(([date, count]) => ({ date, count })); - }, [activityStats]); - - const ProfileField = (props: { label: string; value: string }) => { - return ( -
-
{props.label}:
-
- {props.value != "" ? props.value : "Not set"} -
-
- ); - }; - - return ( - <> - {authCheckLoading ? ( - - ) : ( -
-
-
- -
-

- {user.first_name} {user.last_name} -

-
- -
-
- -

Profile

-
- {canEdit && ( - navigate(`/users/${user.id}/edit`)} - > - Edit - - )} -
- - {userLoading ? ( -
- -
- ) : ( - <> -
- - - CN - -
-

- {user.first_name} {user.last_name} -

-

{user.email}

-
-
- - - - - - - - - - - s.name).join(", ")} - /> -
-
Roles:
-
- {user.roles.map((role) => ( -
- - {role} - -
- ))} -
-
- - - - )} -
- -
- -
-
- -

Login History

-
-
- - {loginsLoading ? ( -
- -
- ) : ( -
-
-
- Total Logins:{" "} - {logins.length} -
-
- Last Login:{" "} - {lastLogin - ? new Date(lastLogin.created_at).toLocaleString() - : "—"} -
-
-
- - - - - - - - - -
-
- {logins.slice(0, 50).map((l) => ( -
-
- - {l.destination} - {" "} - • {l.login_type} -
-
- {new Date(l.created_at).toLocaleString()} -
-
- ))} -
-
- )} -
- - -
-
- -

Discord Activity (Last 90 Days)

-
-
- - {activitiesLoading ? ( -
- -
- ) : ( -
-
-
- {" "} - Messages per day -
- - - - - - - - - -
-
-
- {" "} - Reactions per day -
- - - - - - - - - -
-
- )} -
-
-
-
-
-
- )} - - ); -} - -export default UserProfilePage; diff --git a/web/src/pages/users/UsersPage.tsx b/web/src/pages/users/UsersPage.tsx deleted file mode 100644 index f186af1..0000000 --- a/web/src/pages/users/UsersPage.tsx +++ /dev/null @@ -1,412 +0,0 @@ -import React from "react"; -import axios from "axios"; -import { SENTINEL_API_URL } from "@/consts/config"; -import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; -import { Card } from "@/components/ui/card"; -import { getAxiosErrorMessage } from "@/lib/axios-error-handler"; -import { useNavigate } from "react-router-dom"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { - faArrowLeft, - faChevronRight, - faEnvelope, -} from "@fortawesome/free-solid-svg-icons"; -import { checkCredentials } from "@/lib/auth"; -import Footer from "@/components/Footer"; -import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar"; -import { User } from "@/models/user"; -import { useUser } from "@/lib/store"; -import { AuthLoading } from "@/components/AuthLoading"; -import Fuse from "fuse.js"; -import { faDiscord } from "@fortawesome/free-brands-svg-icons"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; -import { notify } from "@/lib/notify"; -import { Loader2 } from "lucide-react"; -import { faSearch } from "@fortawesome/free-solid-svg-icons"; -import { Switch } from "@/components/ui/switch"; -import { Label } from "@/components/ui/label"; - -function UsersPage() { - const navigate = useNavigate(); - const currentUser = useUser(); - - const [authCheckLoading, setAuthCheckLoading] = React.useState(false); - - const [displayUsers, setDisplayUsers] = React.useState([]); - const [users, setUsers] = React.useState([]); - const [userLoading, setUserLoading] = React.useState(false); - - const [searchTerm, setSearchTerm] = React.useState(""); - const [selectedSubteam, setSelectedSubteam] = React.useState("all"); - const [selectedRole, setSelectedRole] = React.useState("all"); - - const [tableView, setTableView] = React.useState(false); - - React.useEffect(() => { - checkAuth().then(() => { - getUsers(); - }); - }, [currentUser]); - - const checkAuth = async () => { - setAuthCheckLoading(true); - const currentRoute = window.location.pathname + window.location.search; - const status = await checkCredentials(); - if (status != 0) { - navigate(`/auth/login?route=${encodeURIComponent(currentRoute)}`); - } else { - setAuthCheckLoading(false); - } - }; - - const getUsers = async () => { - setUserLoading(true); - try { - const response = await axios.get(`${SENTINEL_API_URL}/users`, { - headers: { - Authorization: `Bearer ${localStorage.getItem("sentinel_access_token")}`, - }, - }); - if (response.status == 200) { - const sortedUsers = response.data.sort((a: User, b: User) => - a.first_name.localeCompare(b.first_name), - ); - setUsers(sortedUsers); - setDisplayUsers(sortedUsers); - } - } catch (error: any) { - notify.error(getAxiosErrorMessage(error)); - } - setUserLoading(false); - }; - - const filterUsers = () => { - let filteredUsers = users; - - if (searchTerm) { - const fuse = new Fuse(filteredUsers, { - keys: ["first_name", "last_name", "email", "username"], - threshold: 0.3, - }); - filteredUsers = fuse.search(searchTerm).map((result) => result.item); - } - - if (selectedSubteam && selectedSubteam !== "all") { - filteredUsers = filteredUsers.filter((user) => - user.subteams.some((subteam) => subteam.name === selectedSubteam), - ); - } - - if (selectedRole && selectedRole !== "all") { - filteredUsers = filteredUsers.filter((user) => - user.roles.includes(selectedRole), - ); - } - - setDisplayUsers(filteredUsers); - }; - - React.useEffect(() => { - filterUsers(); - }, [searchTerm, selectedSubteam, selectedRole, users]); - - const allSubteams = React.useMemo(() => { - const subteamSet = new Set(); - users.forEach((user) => - user.subteams.forEach((subteam) => subteamSet.add(subteam.name)), - ); - return Array.from(subteamSet); - }, [users]); - - const allRoles = React.useMemo(() => { - const roleSet = new Set(); - users.forEach((user) => - user.roles - .filter((role) => role.startsWith("d_")) - .forEach((role) => roleSet.add(role)), - ); - return Array.from(roleSet); - }, [users]); - - const formatRoleName = (role: string) => { - return role - .slice(2) - .split("_") - .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) - .join(" "); - }; - - const UserCard = ({ user }: { user: User }) => { - return ( - navigate(`/users/${user.id}`)} - > -
-
-
- - - - {user.first_name[0]} - {user.last_name[0]} - - -

- {user.first_name} {user.last_name} -

-
-
-
-
- -

{user.email}

-
-
- -

{user.username}

-
-
-
-
Subteams:
-
- {user.subteams.map((subteam) => ( -
- - {subteam.name} - -
- ))} -
-
-
-
Roles:
-
- {user.roles - .filter((role) => role.startsWith("d_")) - .map((role) => ( -
- - {formatRoleName(role)} - -
- ))} -
-
-
-
- -
-
- ); - }; - - const UsersTable = ({ users }: { users: User[] }) => { - return ( - - - - - - - - - - - - - {users.map((user) => ( - navigate(`/users/${user.id}`)} - > - - - - - - - ))} - -
NameEmailUsernameSubteamsRoles
-
- - - - {user.first_name[0]} - {user.last_name[0]} - - -
-
- {user.first_name} {user.last_name} -
-
-
-
{user.email}{user.username} - {user.subteams.map((s) => s.name).join(", ")} - - {user.roles - .filter((r) => r.startsWith("d_")) - .map((r) => formatRoleName(r)) - .join(", ")} -
-
- ); - }; - - return ( - <> - {authCheckLoading ? ( - - ) : ( -
-
-
- -
-

Users

-
- -
- {userLoading ? ( -
- -
- ) : ( -
- {tableView ? ( - - ) : ( -
- {displayUsers.map((user) => ( - - ))} -
- )} -
- )} -
-
-
- )} - - ); -} - -export default UsersPage; - -interface SearchAndFilterComponentProps { - tableView: boolean; - setTableView: (value: boolean) => void; - searchTerm: string; - setSearchTerm: (value: string) => void; - selectedSubteam: string; - setSelectedSubteam: (value: string) => void; - selectedRole: string; - setSelectedRole: (value: string) => void; - allSubteams: string[]; - allRoles: string[]; - formatRoleName: (role: string) => string; -} - -const SearchAndFilterComponent: React.FC = ({ - tableView, - setTableView, - searchTerm, - setSearchTerm, - selectedSubteam, - setSelectedSubteam, - selectedRole, - setSelectedRole, - allSubteams, - allRoles, - formatRoleName, -}) => { - const [isInputFocused, setIsInputFocused] = React.useState(false); - - return ( -
-
- setSearchTerm(e.target.value)} - onFocus={() => setIsInputFocused(true)} - onBlur={() => setIsInputFocused(false)} - className="w-full pl-10" - /> - -
- - -
- - -
-
- ); -}; diff --git a/web/src/vite-env.d.ts b/web/src/vite-env.d.ts deleted file mode 100644 index 11f02fe..0000000 --- a/web/src/vite-env.d.ts +++ /dev/null @@ -1 +0,0 @@ -/// diff --git a/web/tailwind.config.js b/web/tailwind.config.js deleted file mode 100644 index d4b351b..0000000 --- a/web/tailwind.config.js +++ /dev/null @@ -1,80 +0,0 @@ -/** @type {import('tailwindcss').Config} */ -module.exports = { - darkMode: ["class"], - content: [ - "./pages/**/*.{ts,tsx}", - "./components/**/*.{ts,tsx}", - "./app/**/*.{ts,tsx}", - "./src/**/*.{ts,tsx}", - ], - prefix: "", - theme: { - container: { - center: true, - padding: "2rem", - screens: { - "2xl": "1400px", - }, - }, - extend: { - colors: { - "gr-pink": "#e105a3", - "gr-purple": "#8412fc", - "discord-blurple": "#7289da", - border: "hsl(var(--border))", - input: "hsl(var(--input))", - ring: "hsl(var(--ring))", - background: "hsl(var(--background))", - foreground: "hsl(var(--foreground))", - primary: { - DEFAULT: "hsl(var(--primary))", - foreground: "hsl(var(--primary-foreground))", - }, - secondary: { - DEFAULT: "hsl(var(--secondary))", - foreground: "hsl(var(--secondary-foreground))", - }, - destructive: { - DEFAULT: "hsl(var(--destructive))", - foreground: "hsl(var(--destructive-foreground))", - }, - muted: { - DEFAULT: "hsl(var(--muted))", - foreground: "hsl(var(--muted-foreground))", - }, - accent: { - DEFAULT: "hsl(var(--accent))", - foreground: "hsl(var(--accent-foreground))", - }, - popover: { - DEFAULT: "hsl(var(--popover))", - foreground: "hsl(var(--popover-foreground))", - }, - card: { - DEFAULT: "hsl(var(--card))", - foreground: "hsl(var(--card-foreground))", - }, - }, - borderRadius: { - lg: "var(--radius)", - md: "calc(var(--radius) - 2px)", - sm: "calc(var(--radius) - 4px)", - }, - keyframes: { - "accordion-down": { - from: { height: "0" }, - to: { height: "var(--radix-accordion-content-height)" }, - }, - "accordion-up": { - from: { height: "var(--radix-accordion-content-height)" }, - to: { height: "0" }, - }, - }, - animation: { - "accordion-down": "accordion-down 0.2s ease-out", - "accordion-up": "accordion-up 0.2s ease-out", - }, - }, - }, - plugins: [require("tailwindcss-animate")], -}; diff --git a/web/tsconfig.json b/web/tsconfig.json deleted file mode 100644 index 78b856f..0000000 --- a/web/tsconfig.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2020", - "useDefineForClassFields": true, - "lib": ["ES2020", "DOM", "DOM.Iterable"], - "module": "ESNext", - "skipLibCheck": true, - "baseUrl": ".", - "paths": { - "@/*": ["./src/*"], - }, - /* Bundler mode */ - "moduleResolution": "bundler", - "allowImportingTsExtensions": true, - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "jsx": "react-jsx", - - /* Linting */ - "strict": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true, - }, - "include": ["src"], - "references": [{ "path": "./tsconfig.node.json" }], -} diff --git a/web/tsconfig.node.json b/web/tsconfig.node.json deleted file mode 100644 index 42872c5..0000000 --- a/web/tsconfig.node.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "compilerOptions": { - "composite": true, - "skipLibCheck": true, - "module": "ESNext", - "moduleResolution": "bundler", - "allowSyntheticDefaultImports": true - }, - "include": ["vite.config.ts"] -} diff --git a/web/vite.config.ts b/web/vite.config.ts deleted file mode 100644 index 72d9596..0000000 --- a/web/vite.config.ts +++ /dev/null @@ -1,13 +0,0 @@ -import path from "path"; -import { defineConfig } from "vite"; -import react from "@vitejs/plugin-react-swc"; - -// https://vitejs.dev/config/ -export default defineConfig({ - plugins: [react()], - resolve: { - alias: { - "@": path.resolve(__dirname, "./src"), - }, - }, -});