diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml deleted file mode 100644 index 00f2fc9..0000000 --- a/.github/workflows/build.yml +++ /dev/null @@ -1,44 +0,0 @@ -name: build -run-name: Triggered by ${{ github.event_name }} to ${{ github.ref }} by @${{ github.actor }} - -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: - runs-on: ubuntu-latest - name: sentinel - defaults: - run: - shell: bash - steps: - - uses: actions/checkout@v4 - with: - submodules: true - - name: Setup Go - uses: actions/setup-go@v4 - with: - go-version: '1.22.0' - - name: Sentinel - run: | - go get . - go build \ No newline at end of file diff --git a/.github/workflows/core.yml b/.github/workflows/core.yml new file mode 100644 index 0000000..eebd016 --- /dev/null +++ b/.github/workflows/core.yml @@ -0,0 +1,153 @@ +name: core +run-name: Triggered by ${{ github.event_name }} to ${{ github.ref }} by @${{ github.actor }} + +on: + push: + branches: + - "**" + tags: + - "**" + +jobs: + build: + runs-on: ${{ matrix.runner }} + name: Build ${{ matrix.platform }} + strategy: + fail-fast: false + matrix: + include: + - platform: linux/amd64 + runner: ubuntu-24.04 + - platform: linux/arm64 + runner: ubuntu-24.04-arm + + permissions: + contents: read + packages: write + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Generate platform pair + id: platform + run: | + platform=${{ matrix.platform }} + echo "pair=${platform//\//-}" >> $GITHUB_OUTPUT + + - name: Build and push by digest + id: build + uses: docker/build-push-action@v5 + with: + context: core + platforms: ${{ matrix.platform }} + outputs: type=image,name=ghcr.io/gaucho-racing/sentinel-core,push-by-digest=true,name-canonical=true,push=true + cache-from: type=gha,scope=build-${{ steps.platform.outputs.pair }} + cache-to: type=gha,scope=build-${{ steps.platform.outputs.pair }},mode=max + + - name: Export digest + run: | + mkdir -p /tmp/digests + digest="${{ steps.build.outputs.digest }}" + touch "/tmp/digests/${digest#sha256:}" + + - name: Upload digest + uses: actions/upload-artifact@v4 + with: + name: digests-${{ steps.platform.outputs.pair }} + path: /tmp/digests/* + if-no-files-found: error + retention-days: 1 + + merge: + runs-on: ubuntu-latest + name: Merge manifests + needs: build + + permissions: + contents: read + packages: write + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + fetch-tags: true + + - name: Download digests + uses: actions/download-artifact@v4 + with: + path: /tmp/digests + pattern: digests-* + merge-multiple: true + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Check if this commit has a release tag + id: release + run: | + tag=$(git tag --points-at HEAD | grep '^v' | head -n1) + if [ -n "$tag" ]; then + echo "Found tag: $tag" + if gh release view "$tag" --json tagName > /dev/null 2>&1; then + echo "release_tag=$tag" >> $GITHUB_OUTPUT + echo "is_release=true" >> $GITHUB_OUTPUT + exit 0 + fi + fi + echo "is_release=false" >> $GITHUB_OUTPUT + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Generate tag list + id: tags + shell: bash + run: | + TAGS="type=sha" + + if [ "${GITHUB_REF_TYPE}" = "branch" ] && [ "${GITHUB_REF_NAME}" = "main" ]; then + TAGS="${TAGS}\ntype=raw,value=latest" + fi + + if [ "${{ steps.release.outputs.is_release }}" = "true" ]; then + CLEAN_TAG=$(echo "${{ steps.release.outputs.release_tag }}" | sed 's/^v//') + TAGS="${TAGS}\ntype=raw,value=${CLEAN_TAG}" + fi + + echo -e "tags<> $GITHUB_OUTPUT + + - name: Extract image metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ghcr.io/gaucho-racing/sentinel-core + tags: ${{ steps.tags.outputs.tags }} + + - name: Create manifest list and push + working-directory: /tmp/digests + run: | + docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ + $(printf 'ghcr.io/gaucho-racing/sentinel-core@sha256:%s ' *) + + - name: Inspect image + run: | + docker buildx imagetools inspect ghcr.io/gaucho-racing/sentinel-core:${{ steps.meta.outputs.version }} diff --git a/.github/workflows/discord.yml b/.github/workflows/discord.yml new file mode 100644 index 0000000..716d449 --- /dev/null +++ b/.github/workflows/discord.yml @@ -0,0 +1,153 @@ +name: discord +run-name: Triggered by ${{ github.event_name }} to ${{ github.ref }} by @${{ github.actor }} + +on: + push: + branches: + - "**" + tags: + - "**" + +jobs: + build: + runs-on: ${{ matrix.runner }} + name: Build ${{ matrix.platform }} + strategy: + fail-fast: false + matrix: + include: + - platform: linux/amd64 + runner: ubuntu-24.04 + - platform: linux/arm64 + runner: ubuntu-24.04-arm + + permissions: + contents: read + packages: write + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Generate platform pair + id: platform + run: | + platform=${{ matrix.platform }} + echo "pair=${platform//\//-}" >> $GITHUB_OUTPUT + + - name: Build and push by digest + id: build + uses: docker/build-push-action@v5 + with: + context: discord + platforms: ${{ matrix.platform }} + outputs: type=image,name=ghcr.io/gaucho-racing/sentinel-discord,push-by-digest=true,name-canonical=true,push=true + cache-from: type=gha,scope=build-${{ steps.platform.outputs.pair }} + cache-to: type=gha,scope=build-${{ steps.platform.outputs.pair }},mode=max + + - name: Export digest + run: | + mkdir -p /tmp/digests + digest="${{ steps.build.outputs.digest }}" + touch "/tmp/digests/${digest#sha256:}" + + - name: Upload digest + uses: actions/upload-artifact@v4 + with: + name: digests-${{ steps.platform.outputs.pair }} + path: /tmp/digests/* + if-no-files-found: error + retention-days: 1 + + merge: + runs-on: ubuntu-latest + name: Merge manifests + needs: build + + permissions: + contents: read + packages: write + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + fetch-tags: true + + - name: Download digests + uses: actions/download-artifact@v4 + with: + path: /tmp/digests + pattern: digests-* + merge-multiple: true + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Check if this commit has a release tag + id: release + run: | + tag=$(git tag --points-at HEAD | grep '^v' | head -n1) + if [ -n "$tag" ]; then + echo "Found tag: $tag" + if gh release view "$tag" --json tagName > /dev/null 2>&1; then + echo "release_tag=$tag" >> $GITHUB_OUTPUT + echo "is_release=true" >> $GITHUB_OUTPUT + exit 0 + fi + fi + echo "is_release=false" >> $GITHUB_OUTPUT + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Generate tag list + id: tags + shell: bash + run: | + TAGS="type=sha" + + if [ "${GITHUB_REF_TYPE}" = "branch" ] && [ "${GITHUB_REF_NAME}" = "main" ]; then + TAGS="${TAGS}\ntype=raw,value=latest" + fi + + if [ "${{ steps.release.outputs.is_release }}" = "true" ]; then + CLEAN_TAG=$(echo "${{ steps.release.outputs.release_tag }}" | sed 's/^v//') + TAGS="${TAGS}\ntype=raw,value=${CLEAN_TAG}" + fi + + echo -e "tags<> $GITHUB_OUTPUT + + - name: Extract image metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ghcr.io/gaucho-racing/sentinel-discord + tags: ${{ steps.tags.outputs.tags }} + + - name: Create manifest list and push + working-directory: /tmp/digests + run: | + docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ + $(printf 'ghcr.io/gaucho-racing/sentinel-discord@sha256:%s ' *) + + - name: Inspect image + run: | + docker buildx imagetools inspect ghcr.io/gaucho-racing/sentinel-discord:${{ steps.meta.outputs.version }} diff --git a/.github/workflows/oauth.yml b/.github/workflows/oauth.yml new file mode 100644 index 0000000..56ca4ca --- /dev/null +++ b/.github/workflows/oauth.yml @@ -0,0 +1,153 @@ +name: oauth +run-name: Triggered by ${{ github.event_name }} to ${{ github.ref }} by @${{ github.actor }} + +on: + push: + branches: + - "**" + tags: + - "**" + +jobs: + build: + runs-on: ${{ matrix.runner }} + name: Build ${{ matrix.platform }} + strategy: + fail-fast: false + matrix: + include: + - platform: linux/amd64 + runner: ubuntu-24.04 + - platform: linux/arm64 + runner: ubuntu-24.04-arm + + permissions: + contents: read + packages: write + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Generate platform pair + id: platform + run: | + platform=${{ matrix.platform }} + echo "pair=${platform//\//-}" >> $GITHUB_OUTPUT + + - name: Build and push by digest + id: build + uses: docker/build-push-action@v5 + with: + context: oauth + platforms: ${{ matrix.platform }} + outputs: type=image,name=ghcr.io/gaucho-racing/sentinel-oauth,push-by-digest=true,name-canonical=true,push=true + cache-from: type=gha,scope=build-${{ steps.platform.outputs.pair }} + cache-to: type=gha,scope=build-${{ steps.platform.outputs.pair }},mode=max + + - name: Export digest + run: | + mkdir -p /tmp/digests + digest="${{ steps.build.outputs.digest }}" + touch "/tmp/digests/${digest#sha256:}" + + - name: Upload digest + uses: actions/upload-artifact@v4 + with: + name: digests-${{ steps.platform.outputs.pair }} + path: /tmp/digests/* + if-no-files-found: error + retention-days: 1 + + merge: + runs-on: ubuntu-latest + name: Merge manifests + needs: build + + permissions: + contents: read + packages: write + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + fetch-tags: true + + - name: Download digests + uses: actions/download-artifact@v4 + with: + path: /tmp/digests + pattern: digests-* + merge-multiple: true + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Check if this commit has a release tag + id: release + run: | + tag=$(git tag --points-at HEAD | grep '^v' | head -n1) + if [ -n "$tag" ]; then + echo "Found tag: $tag" + if gh release view "$tag" --json tagName > /dev/null 2>&1; then + echo "release_tag=$tag" >> $GITHUB_OUTPUT + echo "is_release=true" >> $GITHUB_OUTPUT + exit 0 + fi + fi + echo "is_release=false" >> $GITHUB_OUTPUT + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Generate tag list + id: tags + shell: bash + run: | + TAGS="type=sha" + + if [ "${GITHUB_REF_TYPE}" = "branch" ] && [ "${GITHUB_REF_NAME}" = "main" ]; then + TAGS="${TAGS}\ntype=raw,value=latest" + fi + + if [ "${{ steps.release.outputs.is_release }}" = "true" ]; then + CLEAN_TAG=$(echo "${{ steps.release.outputs.release_tag }}" | sed 's/^v//') + TAGS="${TAGS}\ntype=raw,value=${CLEAN_TAG}" + fi + + echo -e "tags<> $GITHUB_OUTPUT + + - name: Extract image metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ghcr.io/gaucho-racing/sentinel-oauth + tags: ${{ steps.tags.outputs.tags }} + + - name: Create manifest list and push + working-directory: /tmp/digests + run: | + docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ + $(printf 'ghcr.io/gaucho-racing/sentinel-oauth@sha256:%s ' *) + + - name: Inspect image + run: | + docker buildx imagetools inspect ghcr.io/gaucho-racing/sentinel-oauth:${{ steps.meta.outputs.version }} 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/.gitignore b/.gitignore index aa20365..35878e2 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,9 @@ drive-service-account.json # Go workspace file go.work +# Air hot reload +tmp/ + coverage.html jwtRS256.key 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..0c76a3c 100644 --- a/Makefile +++ b/Makefile @@ -6,9 +6,17 @@ 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 + +run-discord: + chmod +x scripts/run-discord.sh + ./scripts/run-discord.sh + +run-oauth: + chmod +x scripts/run-oauth.sh + ./scripts/run-oauth.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/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/.air.toml b/core/.air.toml new file mode 100644 index 0000000..3d5c83f --- /dev/null +++ b/core/.air.toml @@ -0,0 +1,21 @@ +root = "." +tmp_dir = "tmp" + +[build] + bin = "./tmp/main" + cmd = "go mod tidy && go build -o ./tmp/main ." + delay = 1000 + exclude_dir = ["tmp", "vendor"] + exclude_regex = ["_test.go"] + include_ext = ["go", "toml"] + kill_delay = "0s" + send_interrupt = false + poll = true + poll_interval = 500 + stop_on_error = true + +[log] + time = false + +[misc] + clean_on_exit = true diff --git a/web/src/App.css b/core/.gitignore similarity index 100% rename from web/src/App.css rename to core/.gitignore diff --git a/Dockerfile b/core/Dockerfile similarity index 62% rename from Dockerfile rename to core/Dockerfile index 60e8f79..01107bc 100644 --- a/Dockerfile +++ b/core/Dockerfile @@ -1,4 +1,4 @@ -FROM --platform=$BUILDPLATFORM golang:1.22-alpine3.19 AS builder +FROM --platform=$BUILDPLATFORM golang:1.26-alpine AS builder RUN apk --no-cache add ca-certificates RUN apk add --no-cache tzdata @@ -12,7 +12,7 @@ RUN go mod download COPY . ./ ARG TARGETOS ARG TARGETARCH -RUN GOOS=$TARGETOS GOARCH=$TARGETARCH go build -o /sentinel +RUN GOOS=$TARGETOS GOARCH=$TARGETARCH go build -o /app ## ## Deploy @@ -21,9 +21,9 @@ FROM alpine:3.19 WORKDIR / -COPY --from=builder /sentinel /sentinel +COPY --from=builder /app /app COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo ENV TZ=America/Los_Angeles -ENTRYPOINT ["/sentinel"] \ No newline at end of file +ENTRYPOINT ["/app"] \ No newline at end of file diff --git a/core/api/api.go b/core/api/api.go new file mode 100644 index 0000000..dd97388 --- /dev/null +++ b/core/api/api.go @@ -0,0 +1,262 @@ +package api + +import ( + "net/http" + "strings" + "time" + + "github.com/gaucho-racing/sentinel/core/config" + "github.com/gaucho-racing/sentinel/core/pkg/logger" + "github.com/gaucho-racing/sentinel/core/service" + "github.com/gin-contrib/cors" + "github.com/gin-gonic/gin" +) + +func Run() { + api := InitializeRouter() + InitializeRoutes(api) + err := api.Run(":" + config.Port) + if err != nil { + logger.SugarLogger.Fatalf("Failed to start server: %v", err) + } +} + +func InitializeRouter() *gin.Engine { + if config.IsProduction() { + 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(AuthChecker()) + r.Use(UnauthorizedPanicHandler()) + return r +} + +func InitializeRoutes(router *gin.Engine) { + router.GET("/core/ping", Ping) + router.GET("/core/keys", JWKS) + router.POST("/core/token", GenerateToken) + router.POST("/core/token/validate", ValidateToken) + router.DELETE("/core/token/:id", RevokeToken) + + router.GET("/core/entity/external/:provider/:externalID", GetEntityByExternalAuth) + router.POST("/core/entity/logins", CreateEntityLogin) + router.POST("/core/entity", CreateEntity) + router.GET("/core/entity/:entityID", GetEntityByID) + router.GET("/core/entity/:entityID/groups", GetEntityGroups) + router.GET("/core/entity/:entityID/logins", GetEntityLogins) + router.POST("/core/entity/:entityID/email-auth", CreateEntityEmailAuth) + router.POST("/core/entity/:entityID/phone-auth", CreateEntityPhoneAuth) + router.POST("/core/entity/:entityID/external-auth", CreateEntityExternalAuth) + router.POST("/core/users", CreateOrUpdateUser) + + router.POST("/core/applications/verify", VerifyClientCredentials) + router.POST("/core/login/email-password", LoginEmailPassword) + + router.GET("/entities/@me", GetMe) + router.GET("/entities/:id", GetEntity) + + router.GET("/users", GetAllUsers) + router.GET("/users/check-username", CheckUsername) + router.GET("/users/:id", GetUserByID) + router.POST("/users", CreateOrUpdateUser) + router.DELETE("/users/:id", DeleteUser) + router.GET("/users/:id/groups", GetUserGroups) + router.GET("/users/:id/logins", GetUserLogins) + router.GET("/users/:id/recent-applications", GetUserRecentApplications) + + router.GET("/applications", GetAllApplications) + router.GET("/applications/:id", GetApplicationByID) + router.GET("/applications/client/:clientID", GetApplicationByClientID) + router.POST("/applications", CreateApplication) + router.PUT("/applications/:id", UpdateApplication) + router.DELETE("/applications/:id", DeleteApplication) + router.GET("/applications/:id/secret", GetApplicationSecret) + router.GET("/applications/:id/groups", GetApplicationGroups) + router.POST("/applications/:id/groups", AddApplicationGroup) + router.DELETE("/applications/:id/groups/:groupID", RemoveApplicationGroup) + router.GET("/applications/:id/redirect-uris", GetApplicationRedirectURIs) + router.POST("/applications/:id/redirect-uris", AddApplicationRedirectURI) + router.DELETE("/applications/:id/redirect-uris", RemoveApplicationRedirectURI) + + router.GET("/groups", GetAllGroups) + router.GET("/groups/:id", GetGroupByID) + router.POST("/groups", CreateOrUpdateGroup) + router.DELETE("/groups/:id", DeleteGroup) + + router.GET("/groups/:id/members", GetGroupMembers) + router.POST("/groups/:id/members", AddGroupMember) + router.DELETE("/groups/:id/members/:entityID", RemoveGroupMember) + + router.GET("/groups/:id/owners", GetGroupOwners) + router.POST("/groups/:id/owners", AddGroupOwner) + router.DELETE("/groups/:id/owners/:entityID", RemoveGroupOwner) + + router.GET("/groups/:id/requests", GetGroupJoinRequests) + router.GET("/groups/:id/requests/:requestID", GetGroupJoinRequest) + router.POST("/groups/:id/requests", CreateGroupJoinRequest) + router.POST("/groups/:id/requests/:requestID/approve", ApproveGroupJoinRequest) + router.POST("/groups/:id/requests/:requestID/reject", RejectGroupJoinRequest) + router.DELETE("/groups/:id/requests/:requestID", DeleteGroupJoinRequest) + + router.POST("/groups/:id/requests/:requestID/comments", CreateJoinRequestComment) + router.DELETE("/groups/:id/requests/:requestID/comments/:commentID", DeleteJoinRequestComment) +} + +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.ValidateToken(strings.Split(authHeader, "Bearer ")[1]) + if err != nil { + logger.SugarLogger.Errorln("Failed to validate token: " + err.Error()) + c.AbortWithStatusJSON(401, gin.H{"error": err.Error()}) + } else { + logger.SugarLogger.Infof("Decoded token: %s (%s)", claims.ID, claims.Subject) + logger.SugarLogger.Infof("↳ Client ID: %s", claims.Audience[0]) + logger.SugarLogger.Infof("↳ Issued at: %s", claims.IssuedAt.String()) + logger.SugarLogger.Infof("↳ Expires at: %s", claims.ExpiresAt.String()) + c.Set("Auth-Token", strings.Split(authHeader, "Bearer ")[1]) + c.Set("Auth-EntityID", claims.Subject) + c.Set("Auth-Audience", claims.Audience[0]) + c.Set("Auth-Scope", claims.Scope) + c.Set("Auth-Claims", claims.CustomClaims) + } + } + } + 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{"error": "you are not authorized to access this resource"}) + } else { + // Handle other panics + logger.SugarLogger.Errorf("Unexpected panic: %v", err) + c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": 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 RequestTokenExists(c *gin.Context) bool { + _, exists := c.Get("Auth-Token") + return exists +} + +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 RequestTokenHasEntityID(c *gin.Context, entityID string) bool { + return GetRequestTokenEntityID(c) == entityID +} + +func GetRequestToken(c *gin.Context) string { + token, exists := c.Get("Auth-Token") + if !exists { + return "" + } + return token.(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 GetRequestTokenClaims(c *gin.Context) map[string]interface{} { + claims, exists := c.Get("Auth-Claims") + if !exists { + return nil + } + return claims.(map[string]interface{}) +} + +// GetRequestTokenEntityID returns the subject (entity_id) of the bearer that +// AuthChecker resolved, or "" if no valid bearer was presented. +func GetRequestTokenEntityID(c *gin.Context) string { + id, exists := c.Get("Auth-EntityID") + if !exists { + return "" + } + return id.(string) +} + +// GetRequestTokenUserID returns the user_id custom claim from the bearer, or +// "" if the bearer represents a non-user (service account) or no bearer. +func GetRequestTokenUserID(c *gin.Context) string { + claims := GetRequestTokenClaims(c) + if claims == nil { + return "" + } + id, _ := claims["user_id"].(string) + return id +} + +// RequestTokenHasUserID returns true when the bearer's user_id claim equals +// the given userID. +func RequestTokenHasUserID(c *gin.Context, userID string) bool { + return GetRequestTokenUserID(c) == userID +} diff --git a/core/api/application.go b/core/api/application.go new file mode 100644 index 0000000..52c17a1 --- /dev/null +++ b/core/api/application.go @@ -0,0 +1,341 @@ +package api + +import ( + "net/http" + + "github.com/gaucho-racing/sentinel/core/model" + "github.com/gaucho-racing/sentinel/core/service" + "github.com/gin-gonic/gin" + "gorm.io/gorm" +) + +func GetAllApplications(c *gin.Context) { + Require(c, Any( + RequestTokenHasAudience(c, "sentinel"), + RequestTokenHasScope(c, "applications:read"), + )) + applications, err := service.GetAllApplications() + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, applications) +} + +func GetApplicationByID(c *gin.Context) { + Require(c, Any( + RequestTokenHasAudience(c, "sentinel"), + RequestTokenHasScope(c, "applications:read"), + )) + id := c.Param("id") + app, err := service.GetApplicationByID(id) + if err != nil { + if err == gorm.ErrRecordNotFound { + c.JSON(http.StatusNotFound, gin.H{"error": "application not found"}) + return + } + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, app) +} + +func GetApplicationByClientID(c *gin.Context) { + clientID := c.Param("clientID") + app, err := service.GetApplicationByClientID(clientID) + if err != nil { + if err == gorm.ErrRecordNotFound { + c.JSON(http.StatusNotFound, gin.H{"error": "application not found"}) + return + } + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, app) +} + +type verifyClientRequest struct { + ClientID string `json:"client_id" binding:"required"` + ClientSecret string `json:"client_secret" binding:"required"` +} + +func VerifyClientCredentials(c *gin.Context) { + var req verifyClientRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + app, err := service.GetApplicationByClientID(req.ClientID) + if err != nil { + c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid client credentials"}) + return + } + if app.ClientSecret != req.ClientSecret { + c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid client credentials"}) + return + } + c.JSON(http.StatusOK, gin.H{"message": "valid"}) +} + +type createApplicationRequest struct { + Name string `json:"name" binding:"required"` + Description string `json:"description"` + IconURL string `json:"icon_url"` + LaunchURL string `json:"launch_url"` +} + +// createdApplicationResponse exposes the freshly minted client_secret +// (model.Application JSON-skips it on subsequent reads). +type createdApplicationResponse struct { + model.Application + Secret string `json:"client_secret"` +} + +func CreateApplication(c *gin.Context) { + Require(c, Any( + RequestTokenHasAudience(c, "sentinel"), + RequestTokenHasScope(c, "applications:write"), + )) + + var req createApplicationRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + app, err := service.CreateApplication(model.Application{ + Name: req.Name, + Description: req.Description, + IconURL: req.IconURL, + LaunchURL: req.LaunchURL, + OwnerID: GetRequestTokenEntityID(c), + }) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, createdApplicationResponse{ + Application: app, + Secret: app.ClientSecret, + }) +} + +type updateApplicationRequest struct { + Name string `json:"name"` + Description string `json:"description"` + IconURL string `json:"icon_url"` + LaunchURL string `json:"launch_url"` +} + +func UpdateApplication(c *gin.Context) { + id := c.Param("id") + existing, err := service.GetApplicationByID(id) + if err != nil { + if err == gorm.ErrRecordNotFound { + c.JSON(http.StatusNotFound, gin.H{"error": "application not found"}) + return + } + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + Require(c, ApplicationWriteAuthorized(c, existing)) + var req updateApplicationRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + existing.Name = req.Name + existing.Description = req.Description + existing.IconURL = req.IconURL + existing.LaunchURL = req.LaunchURL + updated, err := service.UpdateApplication(existing) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, updated) +} + +// GetApplicationSecret returns just the client_secret. Separated from the +// main GET handler so the secret doesn't leak through list/by-id reads. +// Gating tightens later when ownership lands; for now any first-party +// bearer can see it. +func GetApplicationSecret(c *gin.Context) { + id := c.Param("id") + app, err := service.GetApplicationByID(id) + if err != nil { + if err == gorm.ErrRecordNotFound { + c.JSON(http.StatusNotFound, gin.H{"error": "application not found"}) + return + } + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + Require(c, Any( + RequestTokenHasScope(c, "sentinel:all"), + RequestTokenHasAudience(c, "sentinel") && RequestTokenHasEntityID(c, app.OwnerID), + )) + c.JSON(http.StatusOK, gin.H{"client_secret": app.ClientSecret}) +} + +func DeleteApplication(c *gin.Context) { + id := c.Param("id") + existing, err := service.GetApplicationByID(id) + if err != nil { + if err == gorm.ErrRecordNotFound { + c.JSON(http.StatusNotFound, gin.H{"error": "application not found"}) + return + } + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + Require(c, ApplicationWriteAuthorized(c, existing)) + if err := service.DeleteApplication(id); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, gin.H{"message": "application deleted"}) +} + +func GetApplicationGroups(c *gin.Context) { + Require(c, Any( + RequestTokenHasAudience(c, "sentinel"), + RequestTokenHasScope(c, "applications:read"), + )) + id := c.Param("id") + groups, err := service.GetGroupsForApplication(id) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, groups) +} + +type addApplicationGroupRequest struct { + GroupID string `json:"group_id" binding:"required"` +} + +func AddApplicationGroup(c *gin.Context) { + id := c.Param("id") + existing, err := service.GetApplicationByID(id) + if err != nil { + if err == gorm.ErrRecordNotFound { + c.JSON(http.StatusNotFound, gin.H{"error": "application not found"}) + return + } + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + Require(c, ApplicationWriteAuthorized(c, existing)) + var req addApplicationGroupRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + ag, err := service.CreateApplicationGroup(model.ApplicationGroup{ + ApplicationID: id, + GroupID: req.GroupID, + }) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, ag) +} + +func RemoveApplicationGroup(c *gin.Context) { + id := c.Param("id") + existing, err := service.GetApplicationByID(id) + if err != nil { + if err == gorm.ErrRecordNotFound { + c.JSON(http.StatusNotFound, gin.H{"error": "application not found"}) + return + } + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + Require(c, ApplicationWriteAuthorized(c, existing)) + groupID := c.Param("groupID") + if err := service.DeleteApplicationGroup(id, groupID); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, gin.H{"message": "group removed from application"}) +} + +func GetApplicationRedirectURIs(c *gin.Context) { + Require(c, Any( + RequestTokenHasAudience(c, "sentinel"), + RequestTokenHasScope(c, "applications:read"), + )) + id := c.Param("id") + uris, err := service.GetRedirectURIsForApplication(id) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, uris) +} + +type addRedirectURIRequest struct { + RedirectURI string `json:"redirect_uri" binding:"required"` +} + +func AddApplicationRedirectURI(c *gin.Context) { + id := c.Param("id") + existing, err := service.GetApplicationByID(id) + if err != nil { + if err == gorm.ErrRecordNotFound { + c.JSON(http.StatusNotFound, gin.H{"error": "application not found"}) + return + } + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + Require(c, ApplicationWriteAuthorized(c, existing)) + var req addRedirectURIRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + uri, err := service.CreateApplicationRedirectURI(id, req.RedirectURI) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, uri) +} + +func RemoveApplicationRedirectURI(c *gin.Context) { + id := c.Param("id") + existing, err := service.GetApplicationByID(id) + if err != nil { + if err == gorm.ErrRecordNotFound { + c.JSON(http.StatusNotFound, gin.H{"error": "application not found"}) + return + } + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + Require(c, ApplicationWriteAuthorized(c, existing)) + uri := c.Query("uri") + if uri == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "uri query parameter is required"}) + return + } + if err := service.DeleteApplicationRedirectURI(id, uri); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, gin.H{"message": "redirect uri removed from application"}) +} + +// ApplicationWriteAuthorized returns true when the bearer can mutate the +// given application: admin scope, first-party UI used by the owner, or a +// third-party with explicit applications:write granted by the owner. +func ApplicationWriteAuthorized(c *gin.Context, app model.Application) bool { + return Any( + RequestTokenHasScope(c, "sentinel:all"), + RequestTokenHasAudience(c, "sentinel") && RequestTokenHasEntityID(c, app.OwnerID), + RequestTokenHasScope(c, "applications:write") && RequestTokenHasEntityID(c, app.OwnerID), + ) +} diff --git a/core/api/entity.go b/core/api/entity.go new file mode 100644 index 0000000..076e790 --- /dev/null +++ b/core/api/entity.go @@ -0,0 +1,114 @@ +package api + +import ( + "net/http" + + "github.com/gaucho-racing/sentinel/core/model" + "github.com/gaucho-racing/sentinel/core/service" + "github.com/gin-gonic/gin" + "gorm.io/gorm" +) + +func GetMe(c *gin.Context) { + Require(c, RequestTokenHasScope(c, "user:read")) + id := GetRequestTokenEntityID(c) + + entity, err := service.GetEntityByID(id) + if err != nil { + if err == gorm.ErrRecordNotFound { + c.JSON(http.StatusNotFound, gin.H{"error": "entity not found"}) + return + } + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, entity) +} + +func GetEntity(c *gin.Context) { + id := c.Param("id") + Require(c, Any( + RequestTokenHasAudience(c, "sentinel"), + RequestTokenHasScope(c, "user:read") && RequestTokenHasEntityID(c, id), + )) + + entity, err := service.GetEntityByID(id) + if err != nil { + if err == gorm.ErrRecordNotFound { + c.JSON(http.StatusNotFound, gin.H{"error": "entity not found"}) + return + } + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, entity) +} + +func GetEntityByID(c *gin.Context) { + entityID := c.Param("entityID") + entity, err := service.GetEntityByID(entityID) + if err != nil { + if err == gorm.ErrRecordNotFound { + c.JSON(http.StatusNotFound, gin.H{"error": "entity not found"}) + return + } + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, entity) +} + +func GetEntityGroups(c *gin.Context) { + entityID := c.Param("entityID") + groups, err := service.GetGroupsForEntity(entityID) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, groups) +} + +func GetEntityByExternalAuth(c *gin.Context) { + provider := c.Param("provider") + externalID := c.Param("externalID") + entity, err := service.GetEntityByExternalAuth(provider, externalID) + if err != nil { + if err == gorm.ErrRecordNotFound { + c.JSON(http.StatusNotFound, gin.H{"error": "entity not found"}) + return + } + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, entity) +} + +func CreateEntityLogin(c *gin.Context) { + var login model.EntityLogin + if err := c.ShouldBindJSON(&login); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + login, err := service.CreateEntityLogin(login) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, login) +} + +func GetEntityLogins(c *gin.Context) { + logins, err := service.GetEntityLogins(service.EntityLoginsFilter{ + EntityID: c.Param("entityID"), + ClientID: c.Query("client_id"), + Scope: c.Query("scope"), + Before: c.Query("before"), + After: c.Query("after"), + Limit: c.Query("limit"), + }) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, logins) +} diff --git a/core/api/group.go b/core/api/group.go new file mode 100644 index 0000000..c84a690 --- /dev/null +++ b/core/api/group.go @@ -0,0 +1,336 @@ +package api + +import ( + "net/http" + "time" + + "github.com/gaucho-racing/sentinel/core/model" + "github.com/gaucho-racing/sentinel/core/service" + "github.com/gin-gonic/gin" + "gorm.io/gorm" +) + +func GetAllGroups(c *gin.Context) { + groups, err := service.GetAllGroups() + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, groups) +} + +func GetGroupByID(c *gin.Context) { + id := c.Param("id") + group, err := service.GetGroupByID(id) + if err != nil { + if err == gorm.ErrRecordNotFound { + c.JSON(http.StatusNotFound, gin.H{"error": "group not found"}) + return + } + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, group) +} + +func CreateOrUpdateGroup(c *gin.Context) { + var group model.Group + if err := c.ShouldBindJSON(&group); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + existing, err := service.GetGroupByID(group.ID) + if err != nil && err != gorm.ErrRecordNotFound { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + if existing.ID != "" { + group, err = service.UpdateGroup(group) + } else { + group, err = service.CreateGroup(group) + } + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, group) +} + +func DeleteGroup(c *gin.Context) { + id := c.Param("id") + if err := service.DeleteGroup(id); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, gin.H{"message": "group deleted"}) +} + +// Members + +func GetGroupMembers(c *gin.Context) { + id := c.Param("id") + members, err := service.GetMembersForGroup(id) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, members) +} + +type addGroupMemberRequest struct { + EntityID string `json:"entity_id" binding:"required"` + Source string `json:"source"` + AddedBy string `json:"added_by"` + HasExpiration bool `json:"has_expiration"` + ExpiresAt time.Time `json:"expires_at"` +} + +func AddGroupMember(c *gin.Context) { + id := c.Param("id") + var req addGroupMemberRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + member, err := service.CreateGroupMember(model.GroupMember{ + GroupID: id, + EntityID: req.EntityID, + Source: req.Source, + AddedBy: req.AddedBy, + HasExpiration: req.HasExpiration, + ExpiresAt: req.ExpiresAt, + }) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, member) +} + +func RemoveGroupMember(c *gin.Context) { + id := c.Param("id") + entityID := c.Param("entityID") + if err := service.DeleteGroupMember(id, entityID); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, gin.H{"message": "member removed from group"}) +} + +// Owners + +func GetGroupOwners(c *gin.Context) { + id := c.Param("id") + owners, err := service.GetOwnersForGroup(id) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, owners) +} + +type addGroupOwnerRequest struct { + EntityID string `json:"entity_id" binding:"required"` + AddedBy string `json:"added_by"` +} + +func AddGroupOwner(c *gin.Context) { + id := c.Param("id") + var req addGroupOwnerRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + owner, err := service.CreateGroupOwner(model.GroupOwner{ + GroupID: id, + EntityID: req.EntityID, + AddedBy: req.AddedBy, + }) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, owner) +} + +func RemoveGroupOwner(c *gin.Context) { + id := c.Param("id") + entityID := c.Param("entityID") + if err := service.DeleteGroupOwner(id, entityID); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, gin.H{"message": "owner removed from group"}) +} + +// Join Requests + +func GetGroupJoinRequests(c *gin.Context) { + id := c.Param("id") + requests, err := service.GetJoinRequestsByGroup(id) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, requests) +} + +func GetGroupJoinRequest(c *gin.Context) { + requestID := c.Param("requestID") + request, err := service.GetJoinRequestByID(requestID) + if err != nil { + if err == gorm.ErrRecordNotFound { + c.JSON(http.StatusNotFound, gin.H{"error": "join request not found"}) + return + } + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, request) +} + +type createJoinRequestRequest struct { + EntityID string `json:"entity_id" binding:"required"` + HasExpiration bool `json:"has_expiration"` + ExpiresAt time.Time `json:"expires_at"` +} + +func CreateGroupJoinRequest(c *gin.Context) { + id := c.Param("id") + var req createJoinRequestRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + _, err := service.GetGroupMember(id, req.EntityID) + if err == nil { + c.JSON(http.StatusConflict, gin.H{"error": "entity is already a member of this group"}) + return + } + request, err := service.CreateJoinRequest(model.GroupJoinRequest{ + GroupID: id, + EntityID: req.EntityID, + Status: string(model.GroupJoinRequestStatusPending), + HasExpiration: req.HasExpiration, + ExpiresAt: req.ExpiresAt, + }) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, request) +} + +type reviewJoinRequestRequest struct { + ReviewedBy string `json:"reviewed_by" binding:"required"` +} + +func ApproveGroupJoinRequest(c *gin.Context) { + requestID := c.Param("requestID") + var req reviewJoinRequestRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + request, err := service.GetJoinRequestByID(requestID) + if err != nil { + if err == gorm.ErrRecordNotFound { + c.JSON(http.StatusNotFound, gin.H{"error": "join request not found"}) + return + } + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + request.Status = string(model.GroupJoinRequestStatusApproved) + request.ReviewedBy = req.ReviewedBy + request.ReviewedAt = time.Now() + request, err = service.UpdateJoinRequest(request) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + _, err = service.CreateGroupMember(model.GroupMember{ + GroupID: request.GroupID, + EntityID: request.EntityID, + Source: string(model.GroupMemberSourceDirect), + AddedBy: req.ReviewedBy, + HasExpiration: request.HasExpiration, + ExpiresAt: request.ExpiresAt, + }) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, request) +} + +func RejectGroupJoinRequest(c *gin.Context) { + requestID := c.Param("requestID") + var req reviewJoinRequestRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + request, err := service.GetJoinRequestByID(requestID) + if err != nil { + if err == gorm.ErrRecordNotFound { + c.JSON(http.StatusNotFound, gin.H{"error": "join request not found"}) + return + } + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + request.Status = string(model.GroupJoinRequestStatusRejected) + request.ReviewedBy = req.ReviewedBy + request.ReviewedAt = time.Now() + request, err = service.UpdateJoinRequest(request) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, request) +} + +func DeleteGroupJoinRequest(c *gin.Context) { + requestID := c.Param("requestID") + if err := service.DeleteJoinRequest(requestID); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, gin.H{"message": "join request deleted"}) +} + +// Join Request Comments + +type createJoinRequestCommentRequest struct { + EntityID string `json:"entity_id" binding:"required"` + Comment string `json:"comment" binding:"required"` +} + +func CreateJoinRequestComment(c *gin.Context) { + requestID := c.Param("requestID") + var req createJoinRequestCommentRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + comment, err := service.CreateJoinRequestComment(model.GroupJoinRequestComment{ + RequestID: requestID, + EntityID: req.EntityID, + Comment: req.Comment, + }) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, comment) +} + +func DeleteJoinRequestComment(c *gin.Context) { + commentID := c.Param("commentID") + if err := service.DeleteJoinRequestComment(commentID); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, gin.H{"message": "comment deleted"}) +} diff --git a/core/api/jwt.go b/core/api/jwt.go new file mode 100644 index 0000000..c7ffced --- /dev/null +++ b/core/api/jwt.go @@ -0,0 +1,62 @@ +package api + +import ( + "net/http" + + "github.com/gaucho-racing/sentinel/core/config" + "github.com/gaucho-racing/sentinel/core/service" + "github.com/gin-gonic/gin" +) + +func JWKS(c *gin.Context) { + c.JSON(http.StatusOK, config.RsaPublicKeyJWKS) +} + +type generateTokenRequest struct { + EntityID string `json:"entity_id" binding:"required"` + ClientID string `json:"client_id" binding:"required"` + Scope string `json:"scope" binding:"required"` + ExpiresIn int `json:"expires_in" binding:"required"` + Claims map[string]interface{} `json:"claims"` +} + +func GenerateToken(c *gin.Context) { + var req generateTokenRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + token, tokenID, err := service.GenerateToken(req.EntityID, req.ClientID, req.Scope, req.ExpiresIn, req.Claims) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, gin.H{"token": token, "token_id": tokenID}) +} + +type validateTokenRequest struct { + Token string `json:"token" binding:"required"` +} + +func ValidateToken(c *gin.Context) { + var req validateTokenRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + claims, err := service.ValidateToken(req.Token) + if err != nil { + c.JSON(http.StatusUnauthorized, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, claims) +} + +func RevokeToken(c *gin.Context) { + id := c.Param("id") + if err := service.RevokeToken(id); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, gin.H{"message": "token revoked"}) +} diff --git a/core/api/login.go b/core/api/login.go new file mode 100644 index 0000000..4afd81a --- /dev/null +++ b/core/api/login.go @@ -0,0 +1,30 @@ +package api + +import ( + "net/http" + + "github.com/gaucho-racing/sentinel/core/service" + "github.com/gin-gonic/gin" +) + +type loginEmailPasswordRequest struct { + Email string `json:"email" binding:"required"` + Password string `json:"password" binding:"required"` +} + +// LoginEmailPassword verifies an email + password against the stored auth. +// Internal: called by the oauth service from /auth/login/email-password. +// Does not mint tokens — that's oauth's job. +func LoginEmailPassword(c *gin.Context) { + var req loginEmailPasswordRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + entity, err := service.LoginEmailPassword(req.Email, req.Password) + if err != nil { + c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid credentials"}) + return + } + c.JSON(http.StatusOK, gin.H{"entity_id": entity.ID}) +} diff --git a/core/api/onboarding.go b/core/api/onboarding.go new file mode 100644 index 0000000..7285323 --- /dev/null +++ b/core/api/onboarding.go @@ -0,0 +1,115 @@ +package api + +import ( + "net/http" + + "github.com/gaucho-racing/sentinel/core/model" + "github.com/gaucho-racing/sentinel/core/service" + "github.com/gin-gonic/gin" + "gorm.io/gorm" +) + +type createEntityRequest struct { + Type model.EntityType `json:"type" binding:"required"` +} + +func CreateEntity(c *gin.Context) { + var req createEntityRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + entity, err := service.CreateEntity(model.Entity{Type: req.Type}) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, entity) +} + +type createEmailAuthRequest struct { + Email string `json:"email" binding:"required"` + Password string `json:"password" binding:"required"` +} + +// CreateEntityEmailAuth upserts an entity's email auth — creates a fresh +// row on onboarding, replaces email + password on subsequent calls +// (password reset, email change). Picks the service-layer function based +// on whether a row already exists. +func CreateEntityEmailAuth(c *gin.Context) { + entityID := c.Param("entityID") + var req createEmailAuthRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + if err := service.ValidatePassword(req.Password); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + hashed, err := service.HashPassword(req.Password) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + existing, err := service.GetEmailAuthForEntity(entityID) + var auth model.EntityEmail + switch { + case err == gorm.ErrRecordNotFound || existing.EntityID == "": + auth, err = service.CreateEmailAuthForEntity(entityID, req.Email, hashed) + case err != nil: + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + default: + auth, err = service.UpdateEmailAuthForEntity(entityID, req.Email, hashed) + } + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, auth) +} + +type createPhoneAuthRequest struct { + PhoneNumber string `json:"phone_number" binding:"required"` +} + +func CreateEntityPhoneAuth(c *gin.Context) { + entityID := c.Param("entityID") + var req createPhoneAuthRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + auth, err := service.CreatePhoneAuthForEntity(entityID, req.PhoneNumber) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, auth) +} + +type createExternalAuthRequest struct { + Provider model.ExternalAuthProvider `json:"provider" binding:"required"` + ExternalID string `json:"external_id" binding:"required"` +} + +func CreateEntityExternalAuth(c *gin.Context) { + entityID := c.Param("entityID") + var req createExternalAuthRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + auth, err := service.CreateExternalAuthForEntity(model.EntityExternalAuth{ + EntityID: entityID, + Provider: req.Provider, + ExternalID: req.ExternalID, + }) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, auth) +} diff --git a/core/api/ping.go b/core/api/ping.go new file mode 100644 index 0000000..f7ffa39 --- /dev/null +++ b/core/api/ping.go @@ -0,0 +1,12 @@ +package api + +import ( + "net/http" + + "github.com/gaucho-racing/sentinel/core/config" + "github.com/gin-gonic/gin" +) + +func Ping(c *gin.Context) { + c.JSON(http.StatusOK, gin.H{"message": config.Service.FormattedNameWithVersion() + " is online!"}) +} diff --git a/core/api/user.go b/core/api/user.go new file mode 100644 index 0000000..9a5c21d --- /dev/null +++ b/core/api/user.go @@ -0,0 +1,159 @@ +package api + +import ( + "net/http" + "strconv" + + "github.com/gaucho-racing/sentinel/core/model" + "github.com/gaucho-racing/sentinel/core/service" + "github.com/gin-gonic/gin" + "gorm.io/gorm" +) + +func GetAllUsers(c *gin.Context) { + users, err := service.GetAllUsers() + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, users) +} + +func CheckUsername(c *gin.Context) { + c.Header("Cache-Control", "no-store") + username := c.Query("username") + if username == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "username query param is required"}) + return + } + available, err := service.IsUsernameAvailable(username) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, gin.H{"available": available}) +} + +func GetUserByID(c *gin.Context) { + id := c.Param("id") + user, err := service.GetUserByID(id) + if err != nil { + if err == gorm.ErrRecordNotFound { + c.JSON(http.StatusNotFound, gin.H{"error": "user not found"}) + return + } + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, user) +} + +func CreateOrUpdateUser(c *gin.Context) { + var user model.User + if err := c.ShouldBindJSON(&user); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + existing, err := service.GetUserByID(user.ID) + if err != nil && err != gorm.ErrRecordNotFound { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + if existing.ID != "" { + user, err = service.UpdateUser(user) + } else { + user, err = service.CreateUser(user) + } + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, user) +} + +func DeleteUser(c *gin.Context) { + id := c.Param("id") + if err := service.DeleteUser(id); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, gin.H{"message": "user deleted"}) +} + +func GetUserGroups(c *gin.Context) { + id := c.Param("id") + user, err := service.GetUserByID(id) + if err != nil { + if err == gorm.ErrRecordNotFound { + c.JSON(http.StatusNotFound, gin.H{"error": "user not found"}) + return + } + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, user.Groups) +} + +func GetUserRecentApplications(c *gin.Context) { + id := c.Param("id") + Require(c, Any( + RequestTokenHasAudience(c, "sentinel"), + RequestTokenHasScope(c, "user:read") && RequestTokenHasUserID(c, id), + )) + + user, err := service.GetUserByID(id) + if err != nil { + if err == gorm.ErrRecordNotFound { + c.JSON(http.StatusNotFound, gin.H{"error": "user not found"}) + return + } + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + limit := 0 + if raw := c.Query("limit"); raw != "" { + if n, err := strconv.Atoi(raw); err == nil && n > 0 { + limit = n + } + } + + apps, err := service.GetAccessedApplicationsForEntity(user.EntityID, limit) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, apps) +} + +func GetUserLogins(c *gin.Context) { + id := c.Param("id") + Require(c, Any( + RequestTokenHasAudience(c, "sentinel"), + RequestTokenHasScope(c, "user:read") && RequestTokenHasUserID(c, id), + )) + + user, err := service.GetUserByID(id) + if err != nil { + if err == gorm.ErrRecordNotFound { + c.JSON(http.StatusNotFound, gin.H{"error": "user not found"}) + return + } + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + logins, err := service.GetEntityLogins(service.EntityLoginsFilter{ + EntityID: user.EntityID, + ClientID: c.Query("client_id"), + Scope: c.Query("scope"), + Before: c.Query("before"), + After: c.Query("after"), + Limit: c.Query("limit"), + }) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, logins) +} diff --git a/config/banner.go b/core/config/banner.go similarity index 82% rename from config/banner.go rename to core/config/banner.go index 5758f05..812bdcd 100644 --- a/config/banner.go +++ b/core/config/banner.go @@ -1,8 +1,6 @@ package config -import ( - "github.com/fatih/color" -) +import "github.com/fatih/color" var Banner = ` ███████╗███████╗███╗ ██╗████████╗██╗███╗ ██╗███████╗██╗ @@ -14,9 +12,9 @@ var Banner = ` ` func PrintStartupBanner() { - banner := color.New(color.Bold, color.FgHiMagenta).PrintlnFunc() + banner := color.New(color.Bold, color.FgHiYellow).PrintlnFunc() banner(Banner) - version := color.New(color.Bold, color.FgMagenta).PrintlnFunc() - version("Running Sentinel v" + Version + " [ENV: " + Env + "]") + version := color.New(color.Bold, color.FgYellow).PrintlnFunc() + version("Running " + Service.FormattedNameWithVersion() + " [ENV: " + Env + "]") println() } diff --git a/core/config/config.go b/core/config/config.go new file mode 100644 index 0000000..de7889d --- /dev/null +++ b/core/config/config.go @@ -0,0 +1,55 @@ +package config + +import ( + "crypto/rsa" + "os" + + "github.com/bk1031/rincon-go/v2" +) + +var Service rincon.Service = rincon.Service{ + Name: "Sentinel Core", + Version: "5.0.0", + Endpoint: os.Getenv("SERVICE_ENDPOINT"), + HealthCheck: os.Getenv("SERVICE_HEALTH_CHECK"), +} + +var Routes = []rincon.Route{ + { + Route: "/core/**", + Method: "*", + }, + { + Route: "/users/**", + Method: "*", + }, + { + Route: "/applications/**", + Method: "*", + }, + { + Route: "/groups/**", + Method: "*", + }, + { + Route: "/entities/**", + Method: "*", + }, +} + +var Env = os.Getenv("ENV") +var Port = os.Getenv("PORT") + +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") + +func IsProduction() bool { + return Env == "PROD" +} + +var RsaPublicKey *rsa.PublicKey +var RsaPrivateKey *rsa.PrivateKey +var RsaPublicKeyJWKS map[string]interface{} diff --git a/core/config/verify.go b/core/config/verify.go new file mode 100644 index 0000000..c2a2e03 --- /dev/null +++ b/core/config/verify.go @@ -0,0 +1,34 @@ +package config + +import "github.com/gaucho-racing/sentinel/core/pkg/logger" + +func Verify() { + if Env == "" { + Env = "PROD" + logger.SugarLogger.Infof("ENV is not set, defaulting to %s", Env) + } + if Port == "" { + Port = "9999" + logger.SugarLogger.Infof("PORT is not set, defaulting to %s", Port) + } + if DatabaseHost == "" { + DatabaseHost = "localhost" + logger.SugarLogger.Infof("DATABASE_HOST is not set, defaulting to %s", DatabaseHost) + } + if DatabasePort == "" { + DatabasePort = "5432" + logger.SugarLogger.Infof("DATABASE_PORT is not set, defaulting to %s", DatabasePort) + } + if DatabaseUser == "" { + DatabaseUser = "postgres" + logger.SugarLogger.Infof("DATABASE_USER is not set, defaulting to %s", DatabaseUser) + } + if DatabasePassword == "" { + DatabasePassword = "password" + logger.SugarLogger.Infof("DATABASE_PASSWORD is not set, defaulting to %s", DatabasePassword) + } + if DatabaseName == "" { + DatabaseName = "sentinel" + logger.SugarLogger.Infof("DATABASE_NAME is not set, defaulting to %s", DatabaseName) + } +} diff --git a/core/database/db.go b/core/database/db.go new file mode 100644 index 0000000..ea48d60 --- /dev/null +++ b/core/database/db.go @@ -0,0 +1,56 @@ +package database + +import ( + "fmt" + "time" + + "github.com/gaucho-racing/sentinel/core/config" + "github.com/gaucho-racing/sentinel/core/model" + "github.com/gaucho-racing/sentinel/core/pkg/logger" + "gorm.io/driver/postgres" + "gorm.io/gorm" +) + +var DB *gorm.DB + +var dbRetries = 0 + +func Init() { + dsn := fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%s sslmode=disable TimeZone=UTC", config.DatabaseHost, config.DatabaseUser, config.DatabasePassword, config.DatabaseName, config.DatabasePort) + db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{}) + if err != nil { + if dbRetries < 5 { + dbRetries++ + logger.SugarLogger.Errorln("failed to connect database, retrying in 5s... ") + time.Sleep(time.Second * 5) + Init() + } else { + logger.SugarLogger.Fatalf("failed to connect database after 5 attempts") + } + } else { + logger.SugarLogger.Infoln("Connected to database") + db.AutoMigrate( + &model.Entity{}, + &model.EntityEmail{}, + &model.EntityPhone{}, + &model.EntityExternalAuth{}, + &model.PhoneLoginCode{}, + &model.EmailLoginCode{}, + &model.Token{}, + &model.User{}, + &model.Application{}, + &model.ApplicationGroup{}, + &model.ApplicationRedirectURI{}, + &model.EntityLogin{}, + &model.ServiceAccount{}, + &model.Group{}, + &model.GroupMember{}, + &model.GroupJoinRequest{}, + &model.GroupJoinRequestComment{}, + &model.GroupOwner{}, + &model.SigningKey{}, + ) + logger.SugarLogger.Infoln("AutoMigration complete") + DB = db + } +} diff --git a/core/go.mod b/core/go.mod new file mode 100644 index 0000000..b73cf60 --- /dev/null +++ b/core/go.mod @@ -0,0 +1,58 @@ +module github.com/gaucho-racing/sentinel/core + +go 1.26 + +require ( + github.com/bk1031/rincon-go/v2 v2.0.0 + github.com/fatih/color v1.18.0 + github.com/gaucho-racing/ulid-go v1.1.0 + github.com/gin-contrib/cors v1.7.6 + github.com/gin-gonic/gin v1.11.0 + github.com/go-resty/resty/v2 v2.17.2 + github.com/golang-jwt/jwt/v5 v5.3.1 + go.uber.org/zap v1.27.1 + golang.org/x/crypto v0.41.0 + gorm.io/driver/postgres v1.6.0 + gorm.io/gorm v1.31.1 +) + +require ( + github.com/bytedance/sonic v1.14.0 // indirect + github.com/bytedance/sonic/loader v0.3.0 // indirect + github.com/cloudwego/base64x v0.1.6 // indirect + github.com/gabriel-vasile/mimetype v1.4.9 // indirect + github.com/gin-contrib/sse v1.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.27.0 // indirect + github.com/goccy/go-json v0.10.5 // indirect + github.com/goccy/go-yaml v1.18.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + github.com/jackc/pgx/v5 v5.6.0 // indirect + github.com/jackc/puddle/v2 v2.2.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.3.0 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/mattn/go-colorable v0.1.13 // 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.4 // indirect + github.com/quic-go/qpack v0.5.1 // indirect + github.com/quic-go/quic-go v0.54.0 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.3.0 // indirect + go.uber.org/mock v0.5.0 // indirect + go.uber.org/multierr v1.10.0 // indirect + golang.org/x/arch v0.20.0 // indirect + golang.org/x/mod v0.26.0 // indirect + golang.org/x/net v0.43.0 // indirect + golang.org/x/sync v0.16.0 // indirect + golang.org/x/sys v0.35.0 // indirect + golang.org/x/text v0.28.0 // indirect + golang.org/x/tools v0.35.0 // indirect + google.golang.org/protobuf v1.36.9 // indirect +) diff --git a/core/go.sum b/core/go.sum new file mode 100644 index 0000000..b22155b --- /dev/null +++ b/core/go.sum @@ -0,0 +1,130 @@ +github.com/bk1031/rincon-go/v2 v2.0.0 h1:nmDHQNZI/AFfW+ZGTGoxpNPrv3OYXQ09anX+fCoiQsQ= +github.com/bk1031/rincon-go/v2 v2.0.0/go.mod h1:287Zc8PvUNnJuAwpt9XVuYUL8k4wrXg3Fa3L0KEmAB4= +github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ= +github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA= +github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA= +github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= +github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= +github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= +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/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= +github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY= +github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok= +github.com/gaucho-racing/ulid-go v1.1.0 h1:x00XM8EjlegfhlLYIob+U8ba5iX0gDRUr8mgBsjCunk= +github.com/gaucho-racing/ulid-go v1.1.0/go.mod h1:HwqoC27UtvXHrmhTO7K2GnXZ1VAeR6tg6EjrSEP5JUU= +github.com/gin-contrib/cors v1.7.6 h1:3gQ8GMzs1Ylpf70y8bMw4fVpycXIeX1ZemuSQIsnQQY= +github.com/gin-contrib/cors v1.7.6/go.mod h1:Ulcl+xN4jel9t1Ry8vqph23a60FwH9xVLd+3ykmTjOk= +github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= +github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= +github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk= +github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls= +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.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4= +github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= +github.com/go-resty/resty/v2 v2.17.2 h1:FQW5oHYcIlkCNrMD2lloGScxcHJ0gkjshV3qcQAyHQk= +github.com/go-resty/resty/v2 v2.17.2/go.mod h1:kCKZ3wWmwJaNc7S29BRtUhJwy7iqmn+2mLtQrOyQlVA= +github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= +github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= +github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= +github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY= +github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY= +github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw= +github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= +github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +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.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= +github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +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/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.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= +github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +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/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= +github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= +github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg= +github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY= +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/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.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +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.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA= +github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= +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/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= +go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= +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.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= +go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c= +golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk= +golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= +golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= +golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg= +golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ= +golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= +golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= +golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= +golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= +golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= +golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= +golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= +golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= +golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0= +golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw= +google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw= +google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +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/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4= +gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo= +gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg= +gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs= diff --git a/core/jobs/init.go b/core/jobs/init.go new file mode 100644 index 0000000..0b4397d --- /dev/null +++ b/core/jobs/init.go @@ -0,0 +1,94 @@ +package jobs + +import ( + "github.com/gaucho-racing/sentinel/core/model" + "github.com/gaucho-racing/sentinel/core/pkg/logger" + "github.com/gaucho-racing/sentinel/core/service" + "gorm.io/gorm" +) + +const SentinelClientID = "sentinel" +const SentinelApplicationID = "app_01kpy5f8263c4rqnhn9v2akdvf" + +const SentinelCoreEntityID = "ent_01kpy5f8263c4rqnhn9y920fkn" +const SentinelCoreServiceAccountID = "sa_01kpy5f8263c4rqnhn9zejxyhk" + +func InitializeCore() { + initializeDefaultApplications() + initializeDefaultEntities() + initializeDefaultServiceAccounts() + logger.SugarLogger.Infoln("Finished initializing sentinel-core") +} + +func initializeDefaultApplications() { + _, err := service.GetApplicationByID(SentinelApplicationID) + if err == gorm.ErrRecordNotFound { + app, err := service.CreateApplication(model.Application{ + ID: SentinelApplicationID, + Name: "Sentinel", + Description: "Gaucho Racing's authentication service", + ClientID: SentinelClientID, + LaunchURL: "https://sso.gauchoracing.com", + OwnerID: SentinelCoreEntityID, + }) + if err != nil { + logger.SugarLogger.Fatalf("Failed to create Sentinel application: %v", err) + return + } + logger.SugarLogger.Infof("Created Sentinel application (id=%s, client_id=%s)", app.ID, app.ClientID) + logger.SugarLogger.Infof("Sentinel client secret: %s", app.ClientSecret) + + defaultRedirectURIs := []string{ + "http://localhost:3000/auth/callback", + } + for _, uri := range defaultRedirectURIs { + service.CreateApplicationRedirectURI(app.ID, uri) + } + logger.SugarLogger.Infof("Added %d default redirect URIs", len(defaultRedirectURIs)) + } else if err != nil { + logger.SugarLogger.Fatalf("Failed to check for Sentinel application: %v", err) + } else { + logger.SugarLogger.Infoln("Sentinel application already exists") + } +} + +func initializeDefaultEntities() { + _, err := service.GetEntityByID(SentinelCoreEntityID) + if err == gorm.ErrRecordNotFound { + entity, err := service.CreateEntity(model.Entity{ + ID: SentinelCoreEntityID, + Type: model.EntityTypeServiceAccount, + }) + if err != nil { + logger.SugarLogger.Fatalf("Failed to create Sentinel core entity: %v", err) + return + } + logger.SugarLogger.Infof("Created Sentinel core entity (id=%s)", entity.ID) + } else if err != nil { + logger.SugarLogger.Fatalf("Failed to check for Sentinel core entity: %v", err) + } else { + logger.SugarLogger.Infoln("Sentinel core entity already exists") + } +} + +func initializeDefaultServiceAccounts() { + _, err := service.GetServiceAccountByID(SentinelCoreServiceAccountID) + if err == gorm.ErrRecordNotFound { + serviceAccount, err := service.CreateServiceAccount(model.ServiceAccount{ + ID: SentinelCoreServiceAccountID, + EntityID: SentinelCoreEntityID, + ApplicationID: SentinelApplicationID, + Name: "Sentinel Core", + CreatedBy: SentinelCoreServiceAccountID, + }) + if err != nil { + logger.SugarLogger.Fatalf("Failed to create Sentinel core service account: %v", err) + return + } + logger.SugarLogger.Infof("Created Sentinel core service account (id=%s)", serviceAccount.ID) + } else if err != nil { + logger.SugarLogger.Fatalf("Failed to check for Sentinel core service account: %v", err) + } else { + logger.SugarLogger.Infoln("Sentinel core service account already exists") + } +} diff --git a/core/main.go b/core/main.go new file mode 100644 index 0000000..6c5bdb2 --- /dev/null +++ b/core/main.go @@ -0,0 +1,25 @@ +package main + +import ( + "github.com/gaucho-racing/sentinel/core/api" + "github.com/gaucho-racing/sentinel/core/config" + "github.com/gaucho-racing/sentinel/core/database" + "github.com/gaucho-racing/sentinel/core/jobs" + "github.com/gaucho-racing/sentinel/core/pkg/logger" + "github.com/gaucho-racing/sentinel/core/pkg/rincon" + "github.com/gaucho-racing/sentinel/core/service" +) + +func main() { + logger.Init(config.IsProduction()) + defer logger.Logger.Sync() + + config.Verify() + config.PrintStartupBanner() + rincon.Init(&config.Service, &config.Routes) + database.Init() + service.InitializeKeys() + jobs.InitializeCore() + + api.Run() +} diff --git a/core/model/application.go b/core/model/application.go new file mode 100644 index 0000000..52612c3 --- /dev/null +++ b/core/model/application.go @@ -0,0 +1,40 @@ +package model + +import "time" + +type Application struct { + ID string `json:"id" gorm:"primaryKey"` + OwnerID string `json:"owner_id" gorm:"index"` + Name string `json:"name"` + Description string `json:"description"` + ClientID string `json:"client_id" gorm:"uniqueIndex"` + ClientSecret string `json:"-"` + IconURL string `json:"icon_url"` + LaunchURL string `json:"launch_url"` + RedirectURIs []string `json:"redirect_uris" gorm:"-"` + UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"` + CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"` +} + +func (Application) TableName() string { + return "application" +} + +type ApplicationRedirectURI struct { + ApplicationID string `json:"application_id" gorm:"primaryKey"` + RedirectURI string `json:"redirect_uri" gorm:"primaryKey"` +} + +func (ApplicationRedirectURI) TableName() string { + return "application_redirect_uri" +} + +type ApplicationGroup struct { + ApplicationID string `json:"application_id" gorm:"primaryKey"` + GroupID string `json:"group_id" gorm:"primaryKey"` + CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"` +} + +func (ApplicationGroup) TableName() string { + return "application_group" +} diff --git a/core/model/email.go b/core/model/email.go new file mode 100644 index 0000000..8320cf4 --- /dev/null +++ b/core/model/email.go @@ -0,0 +1,16 @@ +package model + +import "time" + +type EmailLoginCode struct { + Email string `json:"email" gorm:"primaryKey"` + Code int `json:"code" gorm:"primaryKey"` + ExpiresAt time.Time `json:"expires_at"` + Verified bool `json:"verified"` + VerifiedAt time.Time `json:"verified_at"` + CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"` +} + +func (EmailLoginCode) TableName() string { + return "auth_email_login_code" +} diff --git a/core/model/entity.go b/core/model/entity.go new file mode 100644 index 0000000..bd06fd8 --- /dev/null +++ b/core/model/entity.go @@ -0,0 +1,70 @@ +package model + +import "time" + +type EntityType string + +const ( + EntityTypeUser EntityType = "USER" + EntityTypeServiceAccount EntityType = "SERVICE_ACCOUNT" +) + +type ExternalAuthProvider string + +const ( + ExternalAuthProviderGoogle ExternalAuthProvider = "GOOGLE" + ExternalAuthProviderGitHub ExternalAuthProvider = "GITHUB" + ExternalAuthProviderDiscord ExternalAuthProvider = "DISCORD" +) + +type Entity struct { + ID string `json:"id" gorm:"primaryKey"` + Type EntityType `json:"type"` + CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"` + + EmailAuth EntityEmail `json:"email_auth" gorm:"-"` + PhoneAuth EntityPhone `json:"phone_auth" gorm:"-"` + ExternalAuths []EntityExternalAuth `json:"external_auths" gorm:"-"` + User *User `json:"user,omitempty" gorm:"-"` + ServiceAccount *ServiceAccount `json:"service_account,omitempty" gorm:"-"` +} + +func (Entity) TableName() string { + return "auth_entity" +} + +type EntityEmail struct { + EntityID string `json:"entity_id" gorm:"primaryKey"` + Email string `json:"email" gorm:"index"` + Password string `json:"-"` + CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"` +} + +func (EntityEmail) TableName() string { + return "auth_entity_email" +} + +type EntityPhone struct { + EntityID string `json:"entity_id" gorm:"primaryKey"` + PhoneNumber string `json:"phone_number" gorm:"index"` + CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"` +} + +func (EntityPhone) TableName() string { + return "auth_entity_phone" +} + +type EntityExternalAuth struct { + EntityID string `json:"entity_id" gorm:"primaryKey"` + ExternalID string `json:"external_id"` + Provider ExternalAuthProvider `json:"provider" gorm:"primaryKey"` + AccessToken string `json:"access_token"` + RefreshToken string `json:"refresh_token"` + ExpiresAt time.Time `json:"expires_at"` + CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"` +} + +func (EntityExternalAuth) TableName() string { + return "auth_entity_external_auth" +} + diff --git a/core/model/entity_login.go b/core/model/entity_login.go new file mode 100644 index 0000000..9e76ea8 --- /dev/null +++ b/core/model/entity_login.go @@ -0,0 +1,18 @@ +package model + +import "time" + +type EntityLogin struct { + ID string `json:"id" gorm:"primaryKey"` + EntityID string `json:"entity_id" gorm:"index"` + ClientID string `json:"client_id" gorm:"index"` + Scope string `json:"scope"` + AccessTokenID string `json:"access_token_id"` + RefreshTokenID string `json:"refresh_token_id"` + IPAddress string `json:"ip_address"` + CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"` +} + +func (EntityLogin) TableName() string { + return "entity_login" +} diff --git a/core/model/group.go b/core/model/group.go new file mode 100644 index 0000000..8d8796e --- /dev/null +++ b/core/model/group.go @@ -0,0 +1,109 @@ +package model + +import ( + "database/sql/driver" + "encoding/json" + "fmt" + "time" +) + +type StringSlice []string + +func (s StringSlice) Value() (driver.Value, error) { + b, err := json.Marshal(s) + return string(b), err +} + +func (s *StringSlice) Scan(value interface{}) error { + switch v := value.(type) { + case string: + return json.Unmarshal([]byte(v), s) + case []byte: + return json.Unmarshal(v, s) + default: + return fmt.Errorf("unsupported type: %T", value) + } +} + +type GroupMemberSource string + +const ( + GroupMemberSourceDirect GroupMemberSource = "DIRECT" + GroupMemberSourceConditional GroupMemberSource = "CONDITIONAL" + GroupMemberSourceDiscord GroupMemberSource = "DISCORD" +) + +type GroupJoinRequestStatus string + +const ( + GroupJoinRequestStatusPending GroupJoinRequestStatus = "PENDING" + GroupJoinRequestStatusApproved GroupJoinRequestStatus = "APPROVED" + GroupJoinRequestStatusRejected GroupJoinRequestStatus = "REJECTED" +) + +type Group struct { + ID string `json:"id" gorm:"primaryKey"` + Name string `json:"name"` + Description string `json:"description"` + AllowedSources StringSlice `json:"allowed_sources" gorm:"type:jsonb"` + UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"` + CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"` +} + +func (Group) TableName() string { + return "group" +} + +type GroupMember struct { + GroupID string `json:"group_id" gorm:"primaryKey"` + EntityID string `json:"entity_id" gorm:"primaryKey"` + Source string `json:"source"` + AddedBy string `json:"added_by"` + HasExpiration bool `json:"has_expiration"` + ExpiresAt time.Time `json:"expires_at"` + JoinedAt time.Time `json:"joined_at" gorm:"autoCreateTime"` +} + +func (GroupMember) TableName() string { + return "group_member" +} + +type GroupOwner struct { + GroupID string `json:"group_id" gorm:"primaryKey"` + EntityID string `json:"entity_id" gorm:"primaryKey"` + AddedBy string `json:"added_by"` + CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"` +} + +func (GroupOwner) TableName() string { + return "group_owner" +} + +type GroupJoinRequest struct { + ID string `json:"id" gorm:"primaryKey"` + GroupID string `json:"group_id"` + EntityID string `json:"entity_id"` + Status string `json:"status"` + ReviewedBy string `json:"reviewed_by"` + ReviewedAt time.Time `json:"reviewed_at"` + HasExpiration bool `json:"has_expiration"` + ExpiresAt time.Time `json:"expires_at"` + CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"` + Comments []GroupJoinRequestComment `json:"comments" gorm:"-"` +} + +func (GroupJoinRequest) TableName() string { + return "group_join_request" +} + +type GroupJoinRequestComment struct { + ID string `json:"id" gorm:"primaryKey"` + RequestID string `json:"request_id"` + EntityID string `json:"entity_id"` + Comment string `json:"comment"` + CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"` +} + +func (GroupJoinRequestComment) TableName() string { + return "group_join_request_comment" +} diff --git a/core/model/jwt.go b/core/model/jwt.go new file mode 100644 index 0000000..c458df8 --- /dev/null +++ b/core/model/jwt.go @@ -0,0 +1,85 @@ +package model + +import ( + "encoding/json" + "time" + + "github.com/golang-jwt/jwt/v5" +) + +type TokenClaims struct { + Scope string `json:"scope"` + CustomClaims map[string]interface{} `json:"-"` + jwt.RegisteredClaims +} + +var registeredKeys = map[string]bool{ + "jti": true, "sub": true, "iss": true, + "aud": true, "exp": true, "iat": true, "nbf": true, + "scope": true, +} + +func (tc TokenClaims) MarshalJSON() ([]byte, error) { + m := make(map[string]interface{}) + for k, v := range tc.CustomClaims { + m[k] = v + } + m["scope"] = tc.Scope + if tc.ID != "" { + m["jti"] = tc.ID + } + if tc.Subject != "" { + m["sub"] = tc.Subject + } + if tc.Issuer != "" { + m["iss"] = tc.Issuer + } + if len(tc.Audience) > 0 { + m["aud"] = tc.Audience + } + if tc.ExpiresAt != nil { + m["exp"] = tc.ExpiresAt + } + if tc.IssuedAt != nil { + m["iat"] = tc.IssuedAt + } + if tc.NotBefore != nil { + m["nbf"] = tc.NotBefore + } + return json.Marshal(m) +} + +func (tc *TokenClaims) UnmarshalJSON(data []byte) error { + if err := json.Unmarshal(data, &tc.RegisteredClaims); err != nil { + return err + } + var raw map[string]json.RawMessage + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + if scopeRaw, ok := raw["scope"]; ok { + json.Unmarshal(scopeRaw, &tc.Scope) + } + tc.CustomClaims = make(map[string]interface{}) + for k, v := range raw { + if !registeredKeys[k] { + var val interface{} + json.Unmarshal(v, &val) + tc.CustomClaims[k] = val + } + } + return nil +} + +type Token struct { + ID string `json:"id" gorm:"primaryKey"` + EntityID string `json:"entity_id"` + ClientID string `json:"client_id"` + Scope string `json:"scope"` + ExpiresAt time.Time `json:"expires_at"` + CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"` +} + +func (Token) TableName() string { + return "auth_token" +} diff --git a/core/model/phone.go b/core/model/phone.go new file mode 100644 index 0000000..1b410ad --- /dev/null +++ b/core/model/phone.go @@ -0,0 +1,16 @@ +package model + +import "time" + +type PhoneLoginCode struct { + PhoneNumber string `json:"phone_number" gorm:"primaryKey"` + Code int `json:"code" gorm:"primaryKey"` + ExpiresAt time.Time `json:"expires_at"` + Verified bool `json:"verified"` + VerifiedAt time.Time `json:"verified_at"` + CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"` +} + +func (PhoneLoginCode) TableName() string { + return "auth_phone_login_code" +} diff --git a/core/model/service_account.go b/core/model/service_account.go new file mode 100644 index 0000000..4f55d7a --- /dev/null +++ b/core/model/service_account.go @@ -0,0 +1,17 @@ +package model + +import "time" + +type ServiceAccount struct { + ID string `json:"id" gorm:"primaryKey"` + EntityID string `json:"entity_id" gorm:"index"` + ApplicationID string `json:"application_id" gorm:"index"` + Name string `json:"name"` + CreatedBy string `json:"created_by"` + Groups []Group `json:"groups" gorm:"-"` + CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"` +} + +func (ServiceAccount) TableName() string { + return "service_account" +} diff --git a/core/model/signing_key.go b/core/model/signing_key.go new file mode 100644 index 0000000..b4b0bb0 --- /dev/null +++ b/core/model/signing_key.go @@ -0,0 +1,16 @@ +package model + +import "time" + +type SigningKey struct { + ID string `json:"id" gorm:"primaryKey"` + Algorithm string `json:"algorithm"` + PrivateKeyPEM string `json:"-"` + PublicKeyPEM string `json:"public_key_pem"` + Active bool `json:"active" gorm:"index"` + CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"` +} + +func (SigningKey) TableName() string { + return "signing_key" +} diff --git a/core/model/user.go b/core/model/user.go new file mode 100644 index 0000000..a29d6bc --- /dev/null +++ b/core/model/user.go @@ -0,0 +1,30 @@ +package model + +import "time" + +type User struct { + ID string `json:"id" gorm:"primaryKey"` + EntityID string `json:"entity_id" gorm:"index"` + Username string `json:"username" gorm:"uniqueIndex"` + FirstName string `json:"first_name"` + LastName string `json:"last_name"` + Email string `json:"email" gorm:"-"` + PhoneNumber string `json:"phone_number" gorm:"-"` + Gender string `json:"gender"` + Birthday time.Time `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"` + InitialRole string `json:"initial_role"` + Groups []Group `json:"groups" gorm:"-"` + UpdatedAt time.Time `json:"updated_at"` + CreatedAt time.Time `json:"created_at"` +} + +func (User) TableName() string { + return "user" +} diff --git a/utils/logger.go b/core/pkg/logger/logger.go similarity index 51% rename from utils/logger.go rename to core/pkg/logger/logger.go index e3f99bd..2722d06 100644 --- a/utils/logger.go +++ b/core/pkg/logger/logger.go @@ -1,17 +1,16 @@ -package utils +package logger import ( "go.uber.org/zap" - "sentinel/config" ) var Logger *zap.Logger var SugarLogger *zap.SugaredLogger -func InitializeLogger() { +func Init(production bool) { Logger = zap.Must(zap.NewProduction()) - if config.Env == "DEV" { - Logger = zap.Must(zap.NewDevelopment()) + if !production { + Logger = zap.Must(zap.NewDevelopment(zap.AddCaller(), zap.AddStacktrace(zap.ErrorLevel))) } SugarLogger = Logger.Sugar() } diff --git a/core/pkg/rincon/rincon.go b/core/pkg/rincon/rincon.go new file mode 100644 index 0000000..7966c02 --- /dev/null +++ b/core/pkg/rincon/rincon.go @@ -0,0 +1,60 @@ +package rincon + +import ( + "os" + "time" + + "github.com/bk1031/rincon-go/v2" + "github.com/gaucho-racing/sentinel/core/pkg/logger" +) + +var RinconClient *rincon.Client +var RinconUser = os.Getenv("RINCON_USER") +var RinconPassword = os.Getenv("RINCON_PASSWORD") +var RinconEndpoint = os.Getenv("RINCON_ENDPOINT") + +func Init(service *rincon.Service, routes *[]rincon.Route) { + if RinconUser == "" || RinconPassword == "" || RinconEndpoint == "" { + logger.SugarLogger.Errorln("Rincon user, password, or endpoint is not set") + return + } + client := createClient(RinconEndpoint, RinconUser, RinconPassword) + if client == nil { + return + } + id, err := client.Register(*service, *routes) + if err != nil { + logger.SugarLogger.Fatalf("Failed to register service with Rincon: %v", err) + return + } + logger.SugarLogger.Infof("Registered service with ID: %d", id) + RinconClient = client + *service = *client.Service() +} + +func createClient(endpoint string, user string, password string) *rincon.Client { + rinconRetries := 0 + for rinconRetries < 5 { + client, err := rincon.NewClient(rincon.Config{ + BaseURL: endpoint, + HeartbeatMode: rincon.ServerHeartbeat, + HeartbeatInterval: 60, + AuthUser: user, + AuthPassword: password, + }) + if err != nil { + if rinconRetries < 5 { + logger.SugarLogger.Errorf("Failed to create Rincon client with %s: %v, retrying in 5s...", endpoint, err) + rinconRetries++ + time.Sleep(time.Second * 5) + } else { + logger.SugarLogger.Errorln("Failed to create Rincon client after 5 attempts") + return nil + } + } else { + logger.SugarLogger.Infof("Created Rincon client with endpoint %s", endpoint) + return client + } + } + return nil +} diff --git a/core/pkg/sentinel/sentinel.go b/core/pkg/sentinel/sentinel.go new file mode 100644 index 0000000..5dd0add --- /dev/null +++ b/core/pkg/sentinel/sentinel.go @@ -0,0 +1,122 @@ +package sentinel + +import ( + "fmt" + + "github.com/gaucho-racing/sentinel/core/pkg/logger" + "github.com/gaucho-racing/sentinel/core/pkg/rincon" + "github.com/go-resty/resty/v2" +) + +var client = resty.New() + +func resolveURL(route string, method string) (string, error) { + if rincon.RinconClient == nil { + return "", fmt.Errorf("rincon client is not initialized") + } + service, err := rincon.RinconClient.MatchRoute(route, method) + if err != nil { + return "", fmt.Errorf("failed to resolve route %s: %w", route, err) + } + return service.Endpoint + route, nil +} + +func Get(route string, result interface{}, headers ...map[string]string) error { + url, err := resolveURL(route, "GET") + if err != nil { + return err + } + req := client.R().SetResult(result) + if len(headers) > 0 { + req.SetHeaders(headers[0]) + } + resp, err := req.Get(url) + if err != nil { + return err + } + if resp.IsError() { + logger.SugarLogger.Errorf("GET %s returned %d: %s", route, resp.StatusCode(), resp.String()) + return fmt.Errorf("GET %s returned %d", route, resp.StatusCode()) + } + return nil +} + +func Post(route string, body interface{}, result interface{}, headers ...map[string]string) error { + url, err := resolveURL(route, "POST") + if err != nil { + return err + } + req := client.R().SetBody(body).SetResult(result) + if len(headers) > 0 { + req.SetHeaders(headers[0]) + } + resp, err := req.Post(url) + if err != nil { + return err + } + if resp.IsError() { + logger.SugarLogger.Errorf("POST %s returned %d: %s", route, resp.StatusCode(), resp.String()) + return fmt.Errorf("POST %s returned %d", route, resp.StatusCode()) + } + return nil +} + +func Put(route string, body interface{}, result interface{}, headers ...map[string]string) error { + url, err := resolveURL(route, "PUT") + if err != nil { + return err + } + req := client.R().SetBody(body).SetResult(result) + if len(headers) > 0 { + req.SetHeaders(headers[0]) + } + resp, err := req.Put(url) + if err != nil { + return err + } + if resp.IsError() { + logger.SugarLogger.Errorf("PUT %s returned %d: %s", route, resp.StatusCode(), resp.String()) + return fmt.Errorf("PUT %s returned %d", route, resp.StatusCode()) + } + return nil +} + +func Patch(route string, body interface{}, result interface{}, headers ...map[string]string) error { + url, err := resolveURL(route, "PATCH") + if err != nil { + return err + } + req := client.R().SetBody(body).SetResult(result) + if len(headers) > 0 { + req.SetHeaders(headers[0]) + } + resp, err := req.Patch(url) + if err != nil { + return err + } + if resp.IsError() { + logger.SugarLogger.Errorf("PATCH %s returned %d: %s", route, resp.StatusCode(), resp.String()) + return fmt.Errorf("PATCH %s returned %d", route, resp.StatusCode()) + } + return nil +} + +func Delete(route string, result interface{}, headers ...map[string]string) error { + url, err := resolveURL(route, "DELETE") + if err != nil { + return err + } + req := client.R().SetResult(result) + if len(headers) > 0 { + req.SetHeaders(headers[0]) + } + resp, err := req.Delete(url) + if err != nil { + return err + } + if resp.IsError() { + logger.SugarLogger.Errorf("DELETE %s returned %d: %s", route, resp.StatusCode(), resp.String()) + return fmt.Errorf("DELETE %s returned %d", route, resp.StatusCode()) + } + return nil +} diff --git a/core/service/application.go b/core/service/application.go new file mode 100644 index 0000000..36ba480 --- /dev/null +++ b/core/service/application.go @@ -0,0 +1,232 @@ +package service + +import ( + "crypto/rand" + "time" + + "github.com/gaucho-racing/sentinel/core/database" + "github.com/gaucho-racing/sentinel/core/model" + "github.com/gaucho-racing/sentinel/core/pkg/logger" + "github.com/gaucho-racing/ulid-go" +) + +// AccessedApplication is an Application plus the last time the requesting +// entity signed into it. Used by GetAccessedApplicationsForEntity for the +// "recently accessed" dashboard surface. +type AccessedApplication struct { + model.Application + LastAccessedAt time.Time `json:"last_accessed_at"` +} + +// GetAccessedApplicationsForEntity returns the applications the entity has +// signed into, deduplicated by client_id, ordered by most-recent access. +// Server-side dedupe so users with lopsided login distributions (many logins +// for one app, few for others) still see all distinct apps. limit=0 means +// unlimited. +func GetAccessedApplicationsForEntity(entityID string, limit int) ([]AccessedApplication, error) { + apps := []AccessedApplication{} + sql := ` + SELECT a.*, l.last_accessed_at + FROM application a + INNER JOIN ( + SELECT client_id, MAX(created_at) AS last_accessed_at + FROM entity_login + WHERE entity_id = ? + GROUP BY client_id + ) l ON l.client_id = a.client_id + ORDER BY l.last_accessed_at DESC + ` + args := []interface{}{entityID} + if limit > 0 { + sql += " LIMIT ?" + args = append(args, limit) + } + if err := database.DB.Raw(sql, args...).Scan(&apps).Error; err != nil { + return []AccessedApplication{}, err + } + for i := range apps { + PopulateApplication(&apps[i].Application) + } + return apps, nil +} + +func GetAllApplications() ([]model.Application, error) { + applications := []model.Application{} + if err := database.DB.Find(&applications).Error; err != nil { + return []model.Application{}, err + } + for i := range applications { + PopulateApplication(&applications[i]) + } + return applications, nil +} + +func GetApplicationByID(id string) (model.Application, error) { + var app model.Application + if err := database.DB.Where("id = ?", id).First(&app).Error; err != nil { + return model.Application{}, err + } + PopulateApplication(&app) + return app, nil +} + +func GetApplicationByClientID(clientID string) (model.Application, error) { + var app model.Application + if err := database.DB.Where("client_id = ?", clientID).First(&app).Error; err != nil { + return model.Application{}, err + } + PopulateApplication(&app) + return app, nil +} + +func GetApplicationsByOwnerID(ownerID string) ([]model.Application, error) { + applications := []model.Application{} + if err := database.DB.Where("owner_id = ?", ownerID).Find(&applications).Error; err != nil { + return []model.Application{}, err + } + for i := range applications { + PopulateApplication(&applications[i]) + } + return applications, nil +} + +func CreateApplication(app model.Application) (model.Application, error) { + if app.ID == "" { + app.ID = ulid.Make().Prefixed("app") + } + if app.ClientID == "" { + app.ClientID = generateSecret(12) + } + if app.ClientSecret == "" { + app.ClientSecret = generateSecret(64) + } + if err := database.DB.Create(&app).Error; err != nil { + return model.Application{}, err + } + PopulateApplication(&app) + return app, nil +} + +func UpdateApplication(app model.Application) (model.Application, error) { + if err := database.DB.Save(&app).Error; err != nil { + return model.Application{}, err + } + PopulateApplication(&app) + return app, nil +} + +func PopulateApplication(app *model.Application) { + uris, err := GetRedirectURIsForApplication(app.ID) + if err != nil { + logger.SugarLogger.Errorf("Failed to get redirect URIs for application %s: %v", app.ID, err) + } + app.RedirectURIs = uris +} + +func DeleteApplication(id string) error { + if err := database.DB.Where("id = ?", id).Delete(&model.Application{}).Error; err != nil { + return err + } + return nil +} + +func GetGroupsForApplication(applicationID string) ([]model.Group, error) { + appGroups := []model.ApplicationGroup{} + if err := database.DB.Where("application_id = ?", applicationID).Find(&appGroups).Error; err != nil { + return []model.Group{}, err + } + groups := []model.Group{} + for _, ag := range appGroups { + var group model.Group + if err := database.DB.Where("id = ?", ag.GroupID).First(&group).Error; err != nil { + logger.SugarLogger.Errorf("Failed to get group %s for application %s: %v", ag.GroupID, applicationID, err) + continue + } + groups = append(groups, group) + } + return groups, nil +} + +func GetApplicationsForGroup(groupID string) ([]model.Application, error) { + appGroups := []model.ApplicationGroup{} + if err := database.DB.Where("group_id = ?", groupID).Find(&appGroups).Error; err != nil { + return []model.Application{}, err + } + applications := []model.Application{} + for _, ag := range appGroups { + var app model.Application + if err := database.DB.Where("id = ?", ag.ApplicationID).First(&app).Error; err != nil { + logger.SugarLogger.Errorf("Failed to get application %s for group %s: %v", ag.ApplicationID, groupID, err) + continue + } + applications = append(applications, app) + } + return applications, nil +} + +func CreateApplicationGroup(ag model.ApplicationGroup) (model.ApplicationGroup, error) { + if err := database.DB.Create(&ag).Error; err != nil { + return model.ApplicationGroup{}, err + } + return ag, nil +} + +func DeleteApplicationGroup(applicationID string, groupID string) error { + if err := database.DB.Where("application_id = ? AND group_id = ?", applicationID, groupID).Delete(&model.ApplicationGroup{}).Error; err != nil { + return err + } + return nil +} + +func GetRedirectURIsForApplication(applicationID string) ([]string, error) { + uris := []model.ApplicationRedirectURI{} + if err := database.DB.Where("application_id = ?", applicationID).Find(&uris).Error; err != nil { + return []string{}, err + } + result := make([]string, len(uris)) + for i, uri := range uris { + result[i] = uri.RedirectURI + } + return result, nil +} + +func CreateApplicationRedirectURI(applicationID string, redirectURI string) (model.ApplicationRedirectURI, error) { + uri := model.ApplicationRedirectURI{ + ApplicationID: applicationID, + RedirectURI: redirectURI, + } + if err := database.DB.Create(&uri).Error; err != nil { + return model.ApplicationRedirectURI{}, err + } + return uri, nil +} + +func DeleteApplicationRedirectURI(applicationID string, redirectURI string) error { + if err := database.DB.Where("application_id = ? AND redirect_uri = ?", applicationID, redirectURI).Delete(&model.ApplicationRedirectURI{}).Error; err != nil { + return err + } + return nil +} + +func ValidateRedirectURI(applicationID string, redirectURI string) (bool, error) { + uris, err := GetRedirectURIsForApplication(applicationID) + if err != nil { + return false, err + } + for _, uri := range uris { + if uri == redirectURI { + return true, nil + } + } + return false, nil +} + +func generateSecret(length int) string { + const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + b := make([]byte, length) + rand.Read(b) + for i := range b { + b[i] = charset[int(b[i])%len(charset)] + } + return string(b) +} diff --git a/core/service/email.go b/core/service/email.go new file mode 100644 index 0000000..57d7846 --- /dev/null +++ b/core/service/email.go @@ -0,0 +1,102 @@ +package service + +import ( + "fmt" + "unicode" + + "github.com/gaucho-racing/sentinel/core/model" + "github.com/gaucho-racing/sentinel/core/pkg/logger" + "golang.org/x/crypto/bcrypt" +) + +// LoginEmailPassword logs in a user with an email and password by comparing +// the password with the hashed password in the database for that email. +// If the entity or email auth is not found, an error is returned. +// If the password is invalid, an error is returned. +// If the login is successful, the entity is returned. +func LoginEmailPassword(email string, password string) (model.Entity, error) { + entity, _ := GetEntityByEmail(email) + if entity.ID == "" { + logger.SugarLogger.Errorf("No entity found for email %s", email) + return model.Entity{}, fmt.Errorf("no entity found for email %s", email) + } + auth, _ := GetEmailAuthForEntity(entity.ID) + if auth.EntityID == "" { + logger.SugarLogger.Errorf("No email auth found for entity %s", entity.ID) + return model.Entity{}, fmt.Errorf("no email auth found for entity %s", entity.ID) + } + err := bcrypt.CompareHashAndPassword([]byte(auth.Password), []byte(password)) + if err != nil { + logger.SugarLogger.Errorf("Invalid password for email %s: %v", email, err) + return model.Entity{}, fmt.Errorf("invalid password for email %s: %v", email, err) + } + return entity, nil +} + +// RegisterEmailPassword registers a new user with an email and password by +// hashing the password and creating a new entity and email auth for that entity. +// If the password is invalid, an error is returned. +// If the registration is successful, the entity is returned. +// Note that this function will overwrite the email auth for the entity if it already exists. +func RegisterEmailPassword(email string, password string) (model.Entity, error) { + if err := ValidatePassword(password); err != nil { + logger.SugarLogger.Errorf("Invalid password: %v", err) + return model.Entity{}, err + } + hashedPassword, err := HashPassword(password) + if err != nil { + logger.SugarLogger.Errorf("Failed to hash password: %v", err) + return model.Entity{}, err + } + entity, _ := GetEntityByEmail(email) + if entity.ID == "" { + logger.SugarLogger.Errorf("No entity found for email %s", email) + return model.Entity{}, fmt.Errorf("no entity found for email %s, please register first", email) + } + auth, err := CreateEmailAuthForEntity(entity.ID, email, hashedPassword) + if err != nil { + logger.SugarLogger.Errorf("Failed to add email auth for entity %s: %v", entity.ID, err) + return model.Entity{}, err + } + PopulateEntity(&entity) + logger.SugarLogger.Infof("Added email auth for entity %s: %v", entity.ID, auth) + return entity, nil +} + +// HashPassword hashes a password using bcrypt +// Returns the hashed password if successful, otherwise an error +func HashPassword(password string) (string, error) { + hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) + if err != nil { + return "", err + } + return string(hash), nil +} + +// ValidatePassword checks if a password follows some basic rules. +// Returns an error if the password is invalid. +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 +} diff --git a/core/service/entity.go b/core/service/entity.go new file mode 100644 index 0000000..0e21d9d --- /dev/null +++ b/core/service/entity.go @@ -0,0 +1,153 @@ +package service + +import ( + "github.com/gaucho-racing/sentinel/core/database" + "github.com/gaucho-racing/sentinel/core/model" + "github.com/gaucho-racing/sentinel/core/pkg/logger" + "github.com/gaucho-racing/ulid-go" + "gorm.io/gorm" +) + +const SentinelServiceAccountName = "sentinel-core" + +func GetEntityByID(id string) (model.Entity, error) { + var entity model.Entity + if err := database.DB.Where("id = ?", id).First(&entity).Error; err != nil { + return model.Entity{}, err + } + PopulateEntity(&entity) + return entity, nil +} + +func CreateEntity(entity model.Entity) (model.Entity, error) { + if entity.ID == "" { + entity.ID = ulid.Make().Prefixed("ent") + } + if err := database.DB.Create(&entity).Error; err != nil { + return model.Entity{}, err + } + PopulateEntity(&entity) + return entity, nil +} + +func PopulateEntity(entity *model.Entity) { + var err error + entity.EmailAuth, err = GetEmailAuthForEntity(entity.ID) + if err != nil && err != gorm.ErrRecordNotFound { + logger.SugarLogger.Errorf("Failed to get email auth for entity %s: %v", entity.ID, err) + } + entity.PhoneAuth, err = GetPhoneAuthForEntity(entity.ID) + if err != nil && err != gorm.ErrRecordNotFound { + logger.SugarLogger.Errorf("Failed to get phone auth for entity %s: %v", entity.ID, err) + } + entity.ExternalAuths, err = GetExternalAuthForEntity(entity.ID) + if err != nil && err != gorm.ErrRecordNotFound { + logger.SugarLogger.Errorf("Failed to get external auths for entity %s: %v", entity.ID, err) + } + if entity.Type == model.EntityTypeUser { + user, err := GetUserByEntityID(entity.ID) + if err != nil && err != gorm.ErrRecordNotFound { + logger.SugarLogger.Errorf("Failed to get user for entity %s: %v", entity.ID, err) + } + if user.ID != "" { + entity.User = &user + } + } + if entity.Type == model.EntityTypeServiceAccount { + sa, err := GetServiceAccountByEntityID(entity.ID) + if err != nil && err != gorm.ErrRecordNotFound { + logger.SugarLogger.Errorf("Failed to get service account for entity %s: %v", entity.ID, err) + } + if sa.ID != "" { + entity.ServiceAccount = &sa + } + } +} + +func GetEmailAuthForEntity(entityID string) (model.EntityEmail, error) { + var auth model.EntityEmail + if err := database.DB.Where("entity_id = ?", entityID).First(&auth).Error; err != nil { + return model.EntityEmail{}, err + } + return auth, nil +} + +func CreateEmailAuthForEntity(entityID string, email string, password string) (model.EntityEmail, error) { + auth := model.EntityEmail{ + EntityID: entityID, + Email: email, + Password: password, + } + if err := database.DB.Create(&auth).Error; err != nil { + return model.EntityEmail{}, err + } + return auth, nil +} + +func UpdateEmailAuthForEntity(entityID string, email string, password string) (model.EntityEmail, error) { + var auth model.EntityEmail + if err := database.DB.Where("entity_id = ?", entityID).First(&auth).Error; err != nil { + return model.EntityEmail{}, err + } + auth.Email = email + auth.Password = password + if err := database.DB.Save(&auth).Error; err != nil { + return model.EntityEmail{}, err + } + return auth, nil +} + +func GetEntityByEmail(email string) (model.Entity, error) { + var entityEmail model.EntityEmail + if err := database.DB.Where("email = ?", email).First(&entityEmail).Error; err != nil { + return model.Entity{}, err + } + entity, err := GetEntityByID(entityEmail.EntityID) + if err != nil { + return model.Entity{}, err + } + PopulateEntity(&entity) + return entity, nil +} + +func GetPhoneAuthForEntity(entityID string) (model.EntityPhone, error) { + var auth model.EntityPhone + if err := database.DB.Where("entity_id = ?", entityID).First(&auth).Error; err != nil { + return model.EntityPhone{}, err + } + return auth, nil +} + +func CreatePhoneAuthForEntity(entityID string, phoneNumber string) (model.EntityPhone, error) { + auth := model.EntityPhone{ + EntityID: entityID, + PhoneNumber: phoneNumber, + } + if err := database.DB.Create(&auth).Error; err != nil { + return model.EntityPhone{}, err + } + return auth, nil +} + +func GetEntityByExternalAuth(provider string, externalID string) (model.Entity, error) { + var auth model.EntityExternalAuth + if err := database.DB.Where("UPPER(provider) = UPPER(?) AND external_id = ?", provider, externalID).First(&auth).Error; err != nil { + return model.Entity{}, err + } + return GetEntityByID(auth.EntityID) +} + +func GetExternalAuthForEntity(entityID string) ([]model.EntityExternalAuth, error) { + auths := []model.EntityExternalAuth{} + if err := database.DB.Where("entity_id = ?", entityID).Find(&auths).Error; err != nil { + return []model.EntityExternalAuth{}, err + } + return auths, nil +} + +func CreateExternalAuthForEntity(auth model.EntityExternalAuth) (model.EntityExternalAuth, error) { + if err := database.DB.Create(&auth).Error; err != nil { + return model.EntityExternalAuth{}, err + } + return auth, nil +} diff --git a/core/service/entity_login.go b/core/service/entity_login.go new file mode 100644 index 0000000..475a728 --- /dev/null +++ b/core/service/entity_login.go @@ -0,0 +1,62 @@ +package service + +import ( + "strconv" + "time" + + "github.com/gaucho-racing/sentinel/core/database" + "github.com/gaucho-racing/sentinel/core/model" + "github.com/gaucho-racing/ulid-go" +) + +func CreateEntityLogin(login model.EntityLogin) (model.EntityLogin, error) { + if login.ID == "" { + login.ID = ulid.Make().Prefixed("elog") + } + if err := database.DB.Create(&login).Error; err != nil { + return model.EntityLogin{}, err + } + return login, nil +} + +// EntityLoginsFilter holds the query params accepted by GetEntityLogins. +// All string fields are optional; empty strings are ignored. +type EntityLoginsFilter struct { + EntityID string + ClientID string + Scope string + Before string // RFC3339; matches logins with created_at < Before + After string // RFC3339; matches logins with created_at > After + Limit string // integer string; 0 or unset means unlimited +} + +func GetEntityLogins(filter EntityLoginsFilter) ([]model.EntityLogin, error) { + logins := []model.EntityLogin{} + query := database.DB.Where("entity_id = ?", filter.EntityID) + if filter.ClientID != "" { + query = query.Where("client_id = ?", filter.ClientID) + } + if filter.Scope != "" { + query = query.Where("scope = ?", filter.Scope) + } + if filter.Before != "" { + if t, err := time.Parse(time.RFC3339, filter.Before); err == nil { + query = query.Where("created_at < ?", t) + } + } + if filter.After != "" { + if t, err := time.Parse(time.RFC3339, filter.After); err == nil { + query = query.Where("created_at > ?", t) + } + } + query = query.Order("created_at desc") + if filter.Limit != "" { + if n, err := strconv.Atoi(filter.Limit); err == nil && n > 0 { + query = query.Limit(n) + } + } + if err := query.Find(&logins).Error; err != nil { + return []model.EntityLogin{}, err + } + return logins, nil +} diff --git a/core/service/group.go b/core/service/group.go new file mode 100644 index 0000000..215a9b9 --- /dev/null +++ b/core/service/group.go @@ -0,0 +1,205 @@ +package service + +import ( + "github.com/gaucho-racing/sentinel/core/database" + "github.com/gaucho-racing/sentinel/core/model" + "github.com/gaucho-racing/sentinel/core/pkg/logger" + "github.com/gaucho-racing/ulid-go" +) + +func GetAllGroups() ([]model.Group, error) { + groups := []model.Group{} + if err := database.DB.Find(&groups).Error; err != nil { + return []model.Group{}, err + } + return groups, nil +} + +func GetGroupByID(id string) (model.Group, error) { + var group model.Group + if err := database.DB.Where("id = ?", id).First(&group).Error; err != nil { + return model.Group{}, err + } + return group, nil +} + +func CreateGroup(group model.Group) (model.Group, error) { + if group.ID == "" { + group.ID = ulid.Make().Prefixed("grp") + } + if err := database.DB.Create(&group).Error; err != nil { + return model.Group{}, err + } + return group, nil +} + +func UpdateGroup(group model.Group) (model.Group, error) { + if err := database.DB.Save(&group).Error; err != nil { + return model.Group{}, err + } + return group, nil +} + +func DeleteGroup(id string) error { + if err := database.DB.Where("id = ?", id).Delete(&model.Group{}).Error; err != nil { + return err + } + return nil +} + +func GetMembersForGroup(groupID string) ([]model.GroupMember, error) { + members := []model.GroupMember{} + if err := database.DB.Where("group_id = ?", groupID).Find(&members).Error; err != nil { + return []model.GroupMember{}, err + } + return members, nil +} + +func GetGroupMember(groupID string, entityID string) (model.GroupMember, error) { + var member model.GroupMember + if err := database.DB.Where("group_id = ? AND entity_id = ?", groupID, entityID).First(&member).Error; err != nil { + return model.GroupMember{}, err + } + return member, nil +} + +func CreateGroupMember(member model.GroupMember) (model.GroupMember, error) { + if err := database.DB.Create(&member).Error; err != nil { + return model.GroupMember{}, err + } + return member, nil +} + +func UpdateGroupMember(member model.GroupMember) (model.GroupMember, error) { + if err := database.DB.Save(&member).Error; err != nil { + return model.GroupMember{}, err + } + return member, nil +} + +func DeleteGroupMember(groupID string, entityID string) error { + if err := database.DB.Where("group_id = ? AND entity_id = ?", groupID, entityID).Delete(&model.GroupMember{}).Error; err != nil { + return err + } + return nil +} + +func GetOwnersForGroup(groupID string) ([]model.GroupOwner, error) { + owners := []model.GroupOwner{} + if err := database.DB.Where("group_id = ?", groupID).Find(&owners).Error; err != nil { + return []model.GroupOwner{}, err + } + return owners, nil +} + +func GetGroupOwner(groupID string, entityID string) (model.GroupOwner, error) { + var owner model.GroupOwner + if err := database.DB.Where("group_id = ? AND entity_id = ?", groupID, entityID).First(&owner).Error; err != nil { + return model.GroupOwner{}, err + } + return owner, nil +} + +func CreateGroupOwner(owner model.GroupOwner) (model.GroupOwner, error) { + if err := database.DB.Create(&owner).Error; err != nil { + return model.GroupOwner{}, err + } + return owner, nil +} + +func DeleteGroupOwner(groupID string, entityID string) error { + if err := database.DB.Where("group_id = ? AND entity_id = ?", groupID, entityID).Delete(&model.GroupOwner{}).Error; err != nil { + return err + } + return nil +} + +func GetJoinRequestsByGroup(groupID string) ([]model.GroupJoinRequest, error) { + requests := []model.GroupJoinRequest{} + if err := database.DB.Where("group_id = ?", groupID).Find(&requests).Error; err != nil { + return []model.GroupJoinRequest{}, err + } + for i := range requests { + PopulateJoinRequest(&requests[i]) + } + return requests, nil +} + +func GetJoinRequestsByEntity(entityID string) ([]model.GroupJoinRequest, error) { + requests := []model.GroupJoinRequest{} + if err := database.DB.Where("entity_id = ?", entityID).Find(&requests).Error; err != nil { + return []model.GroupJoinRequest{}, err + } + for i := range requests { + PopulateJoinRequest(&requests[i]) + } + return requests, nil +} + +func GetJoinRequestByID(id string) (model.GroupJoinRequest, error) { + var request model.GroupJoinRequest + if err := database.DB.Where("id = ?", id).First(&request).Error; err != nil { + return model.GroupJoinRequest{}, err + } + PopulateJoinRequest(&request) + return request, nil +} + +func CreateJoinRequest(request model.GroupJoinRequest) (model.GroupJoinRequest, error) { + if request.ID == "" { + request.ID = ulid.Make().Prefixed("gjr") + } + if err := database.DB.Create(&request).Error; err != nil { + return model.GroupJoinRequest{}, err + } + PopulateJoinRequest(&request) + return request, nil +} + +func UpdateJoinRequest(request model.GroupJoinRequest) (model.GroupJoinRequest, error) { + if err := database.DB.Save(&request).Error; err != nil { + return model.GroupJoinRequest{}, err + } + PopulateJoinRequest(&request) + return request, nil +} + +func DeleteJoinRequest(id string) error { + if err := database.DB.Where("id = ?", id).Delete(&model.GroupJoinRequest{}).Error; err != nil { + return err + } + return nil +} + +func PopulateJoinRequest(request *model.GroupJoinRequest) { + comments, err := GetCommentsForJoinRequest(request.ID) + if err != nil { + logger.SugarLogger.Errorf("Failed to get comments for join request %s: %v", request.ID, err) + } + request.Comments = comments +} + +func GetCommentsForJoinRequest(requestID string) ([]model.GroupJoinRequestComment, error) { + comments := []model.GroupJoinRequestComment{} + if err := database.DB.Where("request_id = ?", requestID).Find(&comments).Error; err != nil { + return []model.GroupJoinRequestComment{}, err + } + return comments, nil +} + +func CreateJoinRequestComment(comment model.GroupJoinRequestComment) (model.GroupJoinRequestComment, error) { + if comment.ID == "" { + comment.ID = ulid.Make().Prefixed("gjrc") + } + if err := database.DB.Create(&comment).Error; err != nil { + return model.GroupJoinRequestComment{}, err + } + return comment, nil +} + +func DeleteJoinRequestComment(id string) error { + if err := database.DB.Where("id = ?", id).Delete(&model.GroupJoinRequestComment{}).Error; err != nil { + return err + } + return nil +} diff --git a/core/service/jwt.go b/core/service/jwt.go new file mode 100644 index 0000000..fc89b1f --- /dev/null +++ b/core/service/jwt.go @@ -0,0 +1,193 @@ +package service + +import ( + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "encoding/base64" + "encoding/pem" + "errors" + "fmt" + "math/big" + "time" + + "github.com/gaucho-racing/sentinel/core/config" + "github.com/gaucho-racing/sentinel/core/database" + "github.com/gaucho-racing/sentinel/core/model" + "github.com/gaucho-racing/sentinel/core/pkg/logger" + "github.com/gaucho-racing/ulid-go" + "github.com/golang-jwt/jwt/v5" + "gorm.io/gorm" +) + +// InitializeKeys loads the active RSA signing key from the signing_key +// table, or generates a fresh keypair and persists it if no active key +// exists. Persistence keeps sessions valid across core restarts. +func InitializeKeys() { + var stored model.SigningKey + err := database.DB.Where("active = ?", true).First(&stored).Error + if err == nil { + priv, perr := parsePrivateKeyPEM(stored.PrivateKeyPEM) + if perr != nil { + logger.SugarLogger.Fatalf("Failed to parse stored signing key %s: %v", stored.ID, perr) + return + } + applyKey(priv) + logger.SugarLogger.Infof("Loaded signing key %s from db", stored.ID) + return + } + if !errors.Is(err, gorm.ErrRecordNotFound) { + logger.SugarLogger.Fatalf("Failed to load signing key: %v", err) + return + } + + priv, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + logger.SugarLogger.Fatalf("Failed to generate RSA private key: %v", err) + return + } + privPEM := encodePrivateKeyPEM(priv) + pubPEM, err := encodePublicKeyPEM(&priv.PublicKey) + if err != nil { + logger.SugarLogger.Fatalf("Failed to encode public key: %v", err) + return + } + fresh := model.SigningKey{ + ID: ulid.Make().Prefixed("sig"), + Algorithm: "RS256", + PrivateKeyPEM: privPEM, + PublicKeyPEM: pubPEM, + Active: true, + } + if err := database.DB.Create(&fresh).Error; err != nil { + logger.SugarLogger.Fatalf("Failed to persist signing key: %v", err) + return + } + applyKey(priv) + logger.SugarLogger.Infof("Generated and persisted new signing key %s", fresh.ID) +} + +func applyKey(priv *rsa.PrivateKey) { + config.RsaPrivateKey = priv + config.RsaPublicKey = &priv.PublicKey + config.RsaPublicKeyJWKS = PublicKeyToJWKS(&priv.PublicKey) +} + +func encodePrivateKeyPEM(priv *rsa.PrivateKey) string { + return string(pem.EncodeToMemory(&pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: x509.MarshalPKCS1PrivateKey(priv), + })) +} + +func encodePublicKeyPEM(pub *rsa.PublicKey) (string, error) { + der, err := x509.MarshalPKIXPublicKey(pub) + if err != nil { + return "", err + } + return string(pem.EncodeToMemory(&pem.Block{ + Type: "PUBLIC KEY", + Bytes: der, + })), nil +} + +func parsePrivateKeyPEM(s string) (*rsa.PrivateKey, error) { + block, _ := pem.Decode([]byte(s)) + if block == nil { + return nil, fmt.Errorf("invalid PEM block") + } + return x509.ParsePKCS1PrivateKey(block.Bytes) +} + +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 GenerateToken(entityID string, clientID string, scope string, expiresIn int, claims map[string]interface{}) (string, string, error) { + expirationTime := time.Now().Add(time.Duration(expiresIn) * time.Second) + + tokenID := ulid.Make().Prefixed("jwt") + tokenClaims := &model.TokenClaims{ + Scope: scope, + CustomClaims: claims, + RegisteredClaims: jwt.RegisteredClaims{ + ID: tokenID, + Subject: entityID, + Issuer: "https://sso.gauchoracing.org", + Audience: jwt.ClaimStrings{clientID}, + IssuedAt: jwt.NewNumericDate(time.Now()), + ExpiresAt: jwt.NewNumericDate(expirationTime), + }, + } + + token := jwt.NewWithClaims(jwt.SigningMethodRS256, tokenClaims) + signedToken, err := token.SignedString(config.RsaPrivateKey) + if err != nil { + logger.SugarLogger.Errorf("Failed to generate token: %v", err) + return "", "", err + } + + dbToken := &model.Token{ + ID: tokenID, + EntityID: entityID, + ClientID: clientID, + Scope: scope, + ExpiresAt: expirationTime, + } + if err := database.DB.Create(dbToken).Error; err != nil { + logger.SugarLogger.Errorf("Failed to save token: %v", err) + return "", "", err + } + + return signedToken, tokenID, nil +} + +func ValidateToken(token string) (*model.TokenClaims, error) { + claims := &model.TokenClaims{} + + parsedToken, err := jwt.ParseWithClaims(token, claims, func(token *jwt.Token) (interface{}, error) { + return config.RsaPublicKey, nil + }, jwt.WithValidMethods([]string{"RS256"})) + if err != nil { + logger.SugarLogger.Errorf("Failed to parse token: %v", err) + return nil, err + } + + if !parsedToken.Valid { + return nil, fmt.Errorf("token is invalid") + } + if len(claims.Audience) == 0 { + return nil, fmt.Errorf("token has invalid audience") + } + + dbToken := &model.Token{} + result := database.DB.Where("id = ?", claims.ID).First(&dbToken) + if result.Error != nil { + return nil, fmt.Errorf("token has been revoked") + } + + return claims, nil +} + +func RevokeToken(id string) error { + result := database.DB.Where("id = ?", id).Delete(&model.Token{}) + if result.Error != nil { + logger.SugarLogger.Errorf("Failed to revoke token: %v", result.Error) + return result.Error + } + return nil +} diff --git a/core/service/service_account.go b/core/service/service_account.go new file mode 100644 index 0000000..ab6eb96 --- /dev/null +++ b/core/service/service_account.go @@ -0,0 +1,91 @@ +package service + +import ( + "github.com/gaucho-racing/sentinel/core/database" + "github.com/gaucho-racing/sentinel/core/model" + "github.com/gaucho-racing/sentinel/core/pkg/logger" + "github.com/gaucho-racing/ulid-go" +) + +func GetAllServiceAccounts() ([]model.ServiceAccount, error) { + var serviceAccounts []model.ServiceAccount + if err := database.DB.Find(&serviceAccounts).Error; err != nil { + return []model.ServiceAccount{}, err + } + for i := range serviceAccounts { + PopulateServiceAccount(&serviceAccounts[i]) + } + return serviceAccounts, nil +} + +func GetServiceAccountByID(id string) (model.ServiceAccount, error) { + var sa model.ServiceAccount + if err := database.DB.Where("id = ?", id).First(&sa).Error; err != nil { + return model.ServiceAccount{}, err + } + PopulateServiceAccount(&sa) + return sa, nil +} + +func GetServiceAccountByEntityID(entityID string) (model.ServiceAccount, error) { + var sa model.ServiceAccount + if err := database.DB.Where("entity_id = ?", entityID).First(&sa).Error; err != nil { + return model.ServiceAccount{}, err + } + PopulateServiceAccount(&sa) + return sa, nil +} + +func GetServiceAccountByName(name string) (model.ServiceAccount, error) { + var sa model.ServiceAccount + if err := database.DB.Where("name = ?", name).First(&sa).Error; err != nil { + return model.ServiceAccount{}, err + } + PopulateServiceAccount(&sa) + return sa, nil +} + +func GetServiceAccountsByApplicationID(applicationID string) ([]model.ServiceAccount, error) { + var serviceAccounts []model.ServiceAccount + if err := database.DB.Where("application_id = ?", applicationID).Find(&serviceAccounts).Error; err != nil { + return []model.ServiceAccount{}, err + } + for i := range serviceAccounts { + PopulateServiceAccount(&serviceAccounts[i]) + } + return serviceAccounts, nil +} + +func CreateServiceAccount(sa model.ServiceAccount) (model.ServiceAccount, error) { + if sa.ID == "" { + sa.ID = ulid.Make().Prefixed("sa") + } + if err := database.DB.Create(&sa).Error; err != nil { + return model.ServiceAccount{}, err + } + PopulateServiceAccount(&sa) + return sa, nil +} + +func UpdateServiceAccount(sa model.ServiceAccount) (model.ServiceAccount, error) { + if err := database.DB.Save(&sa).Error; err != nil { + return model.ServiceAccount{}, err + } + PopulateServiceAccount(&sa) + return sa, nil +} + +func DeleteServiceAccount(id string) error { + if err := database.DB.Where("id = ?", id).Delete(&model.ServiceAccount{}).Error; err != nil { + return err + } + return nil +} + +func PopulateServiceAccount(sa *model.ServiceAccount) { + groups, err := GetGroupsForEntity(sa.EntityID) + if err != nil { + logger.SugarLogger.Errorf("Failed to get groups for service account %s: %v", sa.ID, err) + } + sa.Groups = groups +} diff --git a/core/service/user.go b/core/service/user.go new file mode 100644 index 0000000..dfc61e7 --- /dev/null +++ b/core/service/user.go @@ -0,0 +1,110 @@ +package service + +import ( + "github.com/gaucho-racing/sentinel/core/database" + "github.com/gaucho-racing/sentinel/core/model" + "github.com/gaucho-racing/sentinel/core/pkg/logger" + "github.com/gaucho-racing/ulid-go" +) + +func GetAllUsers() ([]model.User, error) { + users := []model.User{} + if err := database.DB.Find(&users).Error; err != nil { + return []model.User{}, err + } + for i := range users { + PopulateUser(&users[i]) + } + return users, nil +} + +func GetUserByID(id string) (model.User, error) { + var user model.User + if err := database.DB.Where("id = ?", id).First(&user).Error; err != nil { + return model.User{}, err + } + PopulateUser(&user) + return user, nil +} + +func GetUserByEntityID(entityID string) (model.User, error) { + var user model.User + if err := database.DB.Where("entity_id = ?", entityID).First(&user).Error; err != nil { + return model.User{}, err + } + PopulateUser(&user) + return user, nil +} + +// IsUsernameAvailable returns true when no user has the given username, +// matched case-insensitively. +func IsUsernameAvailable(username string) (bool, error) { + var count int64 + if err := database.DB.Model(&model.User{}). + Where("LOWER(username) = LOWER(?)", username). + Count(&count).Error; err != nil { + return false, err + } + return count == 0, nil +} + +func CreateUser(user model.User) (model.User, error) { + if user.ID == "" { + user.ID = ulid.Make().Prefixed("usr") + } + if err := database.DB.Create(&user).Error; err != nil { + return model.User{}, err + } + PopulateUser(&user) + return user, nil +} + +func UpdateUser(user model.User) (model.User, error) { + if err := database.DB.Save(&user).Error; err != nil { + return model.User{}, err + } + PopulateUser(&user) + return user, nil +} + +func DeleteUser(id string) error { + if err := database.DB.Where("id = ?", id).Delete(&model.User{}).Error; err != nil { + return err + } + return nil +} + +func PopulateUser(user *model.User) { + groups, err := GetGroupsForEntity(user.EntityID) + if err != nil { + logger.SugarLogger.Errorf("Failed to get groups for user %s: %v", user.ID, err) + } + user.Groups = groups + + emailAuth, err := GetEmailAuthForEntity(user.EntityID) + if err == nil { + user.Email = emailAuth.Email + } + + phoneAuth, err := GetPhoneAuthForEntity(user.EntityID) + if err == nil { + user.PhoneNumber = phoneAuth.PhoneNumber + } +} + +func GetGroupsForEntity(entityID string) ([]model.Group, error) { + var members []model.GroupMember + if err := database.DB.Where("entity_id = ?", entityID).Find(&members).Error; err != nil { + return []model.Group{}, err + } + groups := []model.Group{} + for _, member := range members { + var group model.Group + if err := database.DB.Where("id = ?", member.GroupID).First(&group).Error; err != nil { + logger.SugarLogger.Errorf("Failed to get group %s for entity %s: %v", member.GroupID, entityID, err) + continue + } + groups = append(groups, group) + } + return groups, nil +} 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/discord/.air.toml b/discord/.air.toml new file mode 100644 index 0000000..3d5c83f --- /dev/null +++ b/discord/.air.toml @@ -0,0 +1,21 @@ +root = "." +tmp_dir = "tmp" + +[build] + bin = "./tmp/main" + cmd = "go mod tidy && go build -o ./tmp/main ." + delay = 1000 + exclude_dir = ["tmp", "vendor"] + exclude_regex = ["_test.go"] + include_ext = ["go", "toml"] + kill_delay = "0s" + send_interrupt = false + poll = true + poll_interval = 500 + stop_on_error = true + +[log] + time = false + +[misc] + clean_on_exit = true diff --git a/discord/.gitignore b/discord/.gitignore new file mode 100644 index 0000000..3fec32c --- /dev/null +++ b/discord/.gitignore @@ -0,0 +1 @@ +tmp/ diff --git a/discord/Dockerfile b/discord/Dockerfile new file mode 100644 index 0000000..512b60e --- /dev/null +++ b/discord/Dockerfile @@ -0,0 +1,29 @@ +FROM --platform=$BUILDPLATFORM golang:1.26-alpine AS builder + +RUN apk --no-cache add ca-certificates +RUN apk add --no-cache tzdata + +WORKDIR /app + +COPY go.mod ./ +COPY go.sum ./ +RUN go mod download + +COPY . ./ +ARG TARGETOS +ARG TARGETARCH +RUN GOOS=$TARGETOS GOARCH=$TARGETARCH go build -o /app + +## +## Deploy +## +FROM alpine:3.19 + +WORKDIR / + +COPY --from=builder /app /app + +COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo +ENV TZ=America/Los_Angeles + +ENTRYPOINT ["/app"] diff --git a/discord/api/api.go b/discord/api/api.go new file mode 100644 index 0000000..bc330bf --- /dev/null +++ b/discord/api/api.go @@ -0,0 +1,40 @@ +package api + +import ( + "time" + + "github.com/gaucho-racing/sentinel/discord/config" + "github.com/gaucho-racing/sentinel/discord/pkg/logger" + "github.com/gin-contrib/cors" + "github.com/gin-gonic/gin" +) + +func Run() { + api := InitializeRouter() + InitializeRoutes(api) + err := api.Run(":" + config.Port) + if err != nil { + logger.SugarLogger.Fatalf("Failed to start server: %v", err) + } +} + +func InitializeRouter() *gin.Engine { + if config.IsProduction() { + 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, + })) + return r +} + +func InitializeRoutes(router *gin.Engine) { + router.GET("/discord/ping", Ping) + router.GET("/discord/onboarding-tokens/:id", GetOnboardingToken) + router.POST("/discord/onboarding-tokens/:id/consume", ConsumeOnboardingToken) +} diff --git a/discord/api/onboarding_token.go b/discord/api/onboarding_token.go new file mode 100644 index 0000000..cb232c5 --- /dev/null +++ b/discord/api/onboarding_token.go @@ -0,0 +1,129 @@ +package api + +import ( + "errors" + "net/http" + "strings" + "time" + + "github.com/gaucho-racing/sentinel/discord/pkg/logger" + "github.com/gaucho-racing/sentinel/discord/service" + "github.com/gin-gonic/gin" +) + +type onboardingTokenInfo struct { + DiscordID string `json:"discord_id"` + DiscordUsername string `json:"discord_username"` + DiscordGlobalName string `json:"discord_global_name"` + DiscordAvatarURL string `json:"discord_avatar_url"` +} + +func GetOnboardingToken(c *gin.Context) { + c.Header("Cache-Control", "no-store") + id := c.Param("id") + token, err := service.GetOnboardingTokenByID(id) + switch { + case errors.Is(err, service.ErrOnboardingTokenNotFound): + c.JSON(http.StatusNotFound, gin.H{"error": "onboarding token not found"}) + return + case errors.Is(err, service.ErrOnboardingTokenInvalid): + c.JSON(http.StatusGone, gin.H{"error": "onboarding token expired or already used"}) + return + case err != nil: + logger.SugarLogger.Errorf("Failed to fetch onboarding token %s: %v", id, err) + c.JSON(http.StatusInternalServerError, gin.H{"error": "internal error"}) + return + } + + c.JSON(http.StatusOK, onboardingTokenInfo{ + DiscordID: token.DiscordID, + DiscordUsername: token.DiscordUsername, + DiscordGlobalName: token.DiscordGlobalName, + DiscordAvatarURL: token.DiscordAvatarURL, + }) +} + +type consumeRequest struct { + Email string `json:"email" binding:"required"` + Password string `json:"password" binding:"required"` + Username string `json:"username" binding:"required"` + FirstName string `json:"first_name" binding:"required"` + LastName string `json:"last_name" binding:"required"` + Gender string `json:"gender" binding:"required"` + Birthday string `json:"birthday" binding:"required"` + PhoneNumber string `json:"phone_number" binding:"required"` + GraduateLevel string `json:"graduate_level" binding:"required"` + GraduationYear int `json:"graduation_year"` + Major string `json:"major"` + ShirtSize string `json:"shirt_size" binding:"required"` + JacketSize string `json:"jacket_size" binding:"required"` + SAERegistrationNumber string `json:"sae_registration_number"` + InitialRole string `json:"initial_role" binding:"required"` +} + +var validInitialRoles = map[string]bool{ + "member": true, + "alumni": true, + "mentor": true, + "sponsor": true, + "other": true, +} + +func ConsumeOnboardingToken(c *gin.Context) { + c.Header("Cache-Control", "no-store") + id := c.Param("id") + + var req consumeRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + if !validInitialRoles[req.InitialRole] { + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid initial_role"}) + return + } + + if req.GraduationYear > 0 && req.GraduationYear < time.Now().Year() { + parts := strings.SplitN(req.Email, "@", 2) + if len(parts) == 2 && strings.EqualFold(parts[1], "ucsb.edu") { + c.JSON(http.StatusBadRequest, gin.H{ + "error": "UCSB emails expire after graduation. Update your graduation year or use a personal email.", + }) + return + } + } + + entityID, err := service.ConsumeOnboardingToken(id, service.OnboardingConsumePayload{ + Email: req.Email, + Password: req.Password, + Username: req.Username, + FirstName: req.FirstName, + LastName: req.LastName, + Gender: req.Gender, + Birthday: req.Birthday, + PhoneNumber: req.PhoneNumber, + GraduateLevel: req.GraduateLevel, + GraduationYear: req.GraduationYear, + Major: req.Major, + ShirtSize: req.ShirtSize, + JacketSize: req.JacketSize, + SAERegistrationNumber: req.SAERegistrationNumber, + InitialRole: req.InitialRole, + }) + + switch { + case errors.Is(err, service.ErrOnboardingTokenNotFound): + c.JSON(http.StatusNotFound, gin.H{"error": err.Error()}) + return + case errors.Is(err, service.ErrOnboardingTokenInvalid): + c.JSON(http.StatusGone, gin.H{"error": err.Error()}) + return + case err != nil: + logger.SugarLogger.Errorf("Failed to consume onboarding token %s: %v", id, err) + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, gin.H{"entity_id": entityID}) +} diff --git a/discord/api/ping.go b/discord/api/ping.go new file mode 100644 index 0000000..857f17d --- /dev/null +++ b/discord/api/ping.go @@ -0,0 +1,12 @@ +package api + +import ( + "net/http" + + "github.com/gaucho-racing/sentinel/discord/config" + "github.com/gin-gonic/gin" +) + +func Ping(c *gin.Context) { + c.JSON(http.StatusOK, gin.H{"message": config.Service.FormattedNameWithVersion() + " is online!"}) +} diff --git a/discord/commands/handler.go b/discord/commands/handler.go new file mode 100644 index 0000000..b069839 --- /dev/null +++ b/discord/commands/handler.go @@ -0,0 +1,87 @@ +package commands + +import ( + "strings" + + "github.com/bwmarrin/discordgo" + "github.com/gaucho-racing/sentinel/discord/config" + "github.com/gaucho-racing/sentinel/discord/model" + "github.com/gaucho-racing/sentinel/discord/pkg/logger" + "github.com/gaucho-racing/sentinel/discord/service" +) + +func InitializeBot() { + if service.Discord == nil { + logger.SugarLogger.Errorln("Discord session is not connected") + return + } + service.Discord.AddHandler(OnDiscordMessage) + service.Discord.AddHandler(OnDiscordReaction) + service.Discord.Identify.Intents = discordgo.MakeIntent(discordgo.IntentsAll) + err := service.Discord.Open() + if err != nil { + logger.SugarLogger.Errorln("Error opening Discord connection:", err) + return + } + logger.SugarLogger.Infof("Discord Bot is now running! [Prefix = %s]", config.DiscordPrefix) +} + +func OnDiscordMessage(s *discordgo.Session, m *discordgo.MessageCreate) { + if m.Author.Bot { + return + } + + channelName := service.GetChannelName(m.ChannelID) + + logger.SugarLogger.Infof("Message from %s in #%s: %s", m.Author.ID, channelName, m.Content) + + _, err := service.CreateDiscordMessage(model.DiscordMessage{ + DiscordUserID: m.Author.ID, + ChannelID: m.ChannelID, + ChannelName: channelName, + MessageID: m.ID, + Content: m.Content, + }) + if err != nil { + logger.SugarLogger.Errorf("Failed to persist discord message: %v", err) + } + + if !strings.HasPrefix(m.Content, config.DiscordPrefix) { + return + } + parts := strings.Fields(m.Content[len(config.DiscordPrefix):]) + if len(parts) == 0 { + return + } + command := parts[0] + args := parts[1:] + switch command { + case "ping": + Ping(args, s, m) + case "verify": + Verify(args, s, m) + default: + logger.SugarLogger.Infof("Unknown command: %s", command) + } +} + +func OnDiscordReaction(s *discordgo.Session, r *discordgo.MessageReactionAdd) { + if r.UserID == s.State.User.ID { + return + } + + channelName := service.GetChannelName(r.ChannelID) + + logger.SugarLogger.Infof("Reaction from %s in #%s: %s", r.UserID, channelName, r.Emoji.Name) + + _, err := service.CreateDiscordReaction(model.DiscordReaction{ + DiscordUserID: r.UserID, + ChannelID: r.ChannelID, + ChannelName: channelName, + MessageID: r.MessageID, + Emoji: r.Emoji.Name, + }) + if err != nil { + logger.SugarLogger.Errorf("Failed to persist discord reaction: %v", err) + } +} diff --git a/discord/commands/ping.go b/discord/commands/ping.go new file mode 100644 index 0000000..4c4e6a2 --- /dev/null +++ b/discord/commands/ping.go @@ -0,0 +1,16 @@ +package commands + +import ( + "strconv" + + "github.com/bwmarrin/discordgo" + "github.com/gaucho-racing/sentinel/discord/config" +) + +func Ping(args []string, s *discordgo.Session, m *discordgo.MessageCreate) { + message, err := s.ChannelMessageSend(m.ChannelID, "Pong from "+"Sentinel v"+config.Service.Version+"!") + if err == nil { + delay := message.Timestamp.Sub(m.Timestamp).Milliseconds() + s.ChannelMessageEdit(m.ChannelID, message.ID, "Pong from "+"Sentinel v"+config.Service.Version+"! (**"+strconv.FormatInt(delay, 10)+"ms**)") + } +} diff --git a/discord/commands/verify.go b/discord/commands/verify.go new file mode 100644 index 0000000..910734d --- /dev/null +++ b/discord/commands/verify.go @@ -0,0 +1,47 @@ +package commands + +import ( + "fmt" + "time" + + "github.com/bwmarrin/discordgo" + "github.com/gaucho-racing/sentinel/discord/config" + "github.com/gaucho-racing/sentinel/discord/pkg/logger" + "github.com/gaucho-racing/sentinel/discord/service" +) + +const verifyReplyTTL = 5 * time.Second + +func Verify(args []string, s *discordgo.Session, m *discordgo.MessageCreate) { + defer s.ChannelMessageDelete(m.ChannelID, m.ID) + + if entityID := service.GetEntityIDForDiscordUser(m.Author.ID); entityID != "" { + logger.SugarLogger.Infof("Discord user %s is already onboarded as %s", m.Author.ID, entityID) + _ = service.SendDirectMessage(m.Author.ID, fmt.Sprintf("You're already onboarded! Sign in at %s/auth/login", config.WebBaseURL)) + service.SendDisappearingMessage(m.ChannelID, fmt.Sprintf("<@%s> you're already onboarded — sent you a DM with the login link.", m.Author.ID), verifyReplyTTL) + return + } + + token, err := service.CreateOnboardingTokenForDiscordUser( + m.Author.ID, + m.Author.Username, + m.Author.GlobalName, + m.Author.AvatarURL(""), + ) + if err != nil { + logger.SugarLogger.Errorf("Failed to mint onboarding token for %s: %v", m.Author.ID, err) + service.SendDisappearingMessage(m.ChannelID, fmt.Sprintf("<@%s> something went wrong, try again in a minute.", m.Author.ID), verifyReplyTTL) + return + } + + link := fmt.Sprintf("%s/onboard?token=%s", config.WebBaseURL, token.ID) + body := fmt.Sprintf("Welcome to Gaucho Racing! Click here to set up your Sentinel account:\n%s\n\nThis link expires in %s.", link, config.OnboardingTokenTTL) + if err := service.SendDirectMessage(m.Author.ID, body); err != nil { + logger.SugarLogger.Errorf("Failed to DM onboarding link to %s: %v", m.Author.ID, err) + service.SendDisappearingMessage(m.ChannelID, fmt.Sprintf("<@%s> I couldn't DM you — enable DMs from server members and run `%sverify` again.", m.Author.ID, config.DiscordPrefix), verifyReplyTTL) + return + } + + logger.SugarLogger.Infof("Issued onboarding token %s to Discord user %s", token.ID, m.Author.ID) + service.SendDisappearingMessage(m.ChannelID, fmt.Sprintf("<@%s> 📬 Check your DMs for the verification link.", m.Author.ID), verifyReplyTTL) +} diff --git a/discord/config/banner.go b/discord/config/banner.go new file mode 100644 index 0000000..81f26c5 --- /dev/null +++ b/discord/config/banner.go @@ -0,0 +1,20 @@ +package config + +import "github.com/fatih/color" + +var Banner = ` +███████╗███████╗███╗ ██╗████████╗██╗███╗ ██╗███████╗██╗ +██╔════╝██╔════╝████╗ ██║╚══██╔══╝██║████╗ ██║██╔════╝██║ +███████╗█████╗ ██╔██╗ ██║ ██║ ██║██╔██╗ ██║█████╗ ██║ +╚════██║██╔══╝ ██║╚██╗██║ ██║ ██║██║╚██╗██║██╔══╝ ██║ +███████║███████╗██║ ╚████║ ██║ ██║██║ ╚████║███████╗███████╗ +╚══════╝╚══════╝╚═╝ ╚═══╝ ╚═╝ ╚═╝╚═╝ ╚═══╝╚══════╝╚══════╝ +` + +func PrintStartupBanner() { + banner := color.New(color.Bold, color.FgHiCyan).PrintlnFunc() + banner(Banner) + version := color.New(color.Bold, color.FgCyan).PrintlnFunc() + version("Running " + Service.FormattedNameWithVersion() + " [ENV: " + Env + "]") + println() +} diff --git a/discord/config/config.go b/discord/config/config.go new file mode 100644 index 0000000..9485520 --- /dev/null +++ b/discord/config/config.go @@ -0,0 +1,43 @@ +package config + +import ( + "os" + "time" + + "github.com/bk1031/rincon-go/v2" +) + +var Service rincon.Service = rincon.Service{ + Name: "Sentinel Discord", + Version: "5.0.0", + Endpoint: os.Getenv("SERVICE_ENDPOINT"), + HealthCheck: os.Getenv("SERVICE_HEALTH_CHECK"), +} + +var Routes = []rincon.Route{ + { + Route: "/discord/**", + Method: "*", + }, +} + +var Env = os.Getenv("ENV") +var Port = os.Getenv("PORT") + +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 DiscordPrefix = os.Getenv("DISCORD_PREFIX") + +var WebBaseURL = os.Getenv("WEB_BASE_URL") + +var OnboardingTokenTTL = 15 * time.Minute + +func IsProduction() bool { + return Env == "PROD" +} diff --git a/discord/config/verify.go b/discord/config/verify.go new file mode 100644 index 0000000..88faf66 --- /dev/null +++ b/discord/config/verify.go @@ -0,0 +1,50 @@ +package config + +import ( + "github.com/gaucho-racing/sentinel/discord/pkg/logger" +) + +func Verify() { + if Env == "" { + Env = "PROD" + logger.SugarLogger.Infof("ENV is not set, defaulting to %s", Env) + } + if Port == "" { + Port = "9998" + logger.SugarLogger.Infof("PORT is not set, defaulting to %s", Port) + } + if DatabaseHost == "" { + DatabaseHost = "localhost" + logger.SugarLogger.Infof("DATABASE_HOST is not set, defaulting to %s", DatabaseHost) + } + if DatabasePort == "" { + DatabasePort = "5432" + logger.SugarLogger.Infof("DATABASE_PORT is not set, defaulting to %s", DatabasePort) + } + if DatabaseUser == "" { + DatabaseUser = "postgres" + logger.SugarLogger.Infof("DATABASE_USER is not set, defaulting to %s", DatabaseUser) + } + if DatabasePassword == "" { + DatabasePassword = "password" + logger.SugarLogger.Infof("DATABASE_PASSWORD is not set, defaulting to %s", DatabasePassword) + } + if DatabaseName == "" { + DatabaseName = "sentinel" + logger.SugarLogger.Infof("DATABASE_NAME is not set, defaulting to %s", DatabaseName) + } + if DiscordToken == "" { + logger.SugarLogger.Warnln("DISCORD_TOKEN is not set") + } + if DiscordGuild == "" { + logger.SugarLogger.Warnln("DISCORD_GUILD is not set") + } + if DiscordPrefix == "" { + DiscordPrefix = "d!" + logger.SugarLogger.Infof("DISCORD_PREFIX is not set, defaulting to %s", DiscordPrefix) + } + if WebBaseURL == "" { + WebBaseURL = "http://localhost:10310" + logger.SugarLogger.Infof("WEB_BASE_URL is not set, defaulting to %s", WebBaseURL) + } +} diff --git a/discord/database/db.go b/discord/database/db.go new file mode 100644 index 0000000..3a35c87 --- /dev/null +++ b/discord/database/db.go @@ -0,0 +1,40 @@ +package database + +import ( + "fmt" + "time" + + "github.com/gaucho-racing/sentinel/discord/config" + "github.com/gaucho-racing/sentinel/discord/model" + "github.com/gaucho-racing/sentinel/discord/pkg/logger" + "gorm.io/driver/postgres" + "gorm.io/gorm" +) + +var DB *gorm.DB + +var dbRetries = 0 + +func Init() { + dsn := fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%s sslmode=disable TimeZone=UTC", config.DatabaseHost, config.DatabaseUser, config.DatabasePassword, config.DatabaseName, config.DatabasePort) + db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{}) + if err != nil { + if dbRetries < 5 { + dbRetries++ + logger.SugarLogger.Errorln("failed to connect database, retrying in 5s... ") + time.Sleep(time.Second * 5) + Init() + } else { + logger.SugarLogger.Fatalf("failed to connect database after 5 attempts") + } + } else { + logger.SugarLogger.Infoln("Connected to database") + db.AutoMigrate( + &model.DiscordMessage{}, + &model.DiscordReaction{}, + &model.OnboardingToken{}, + ) + logger.SugarLogger.Infoln("AutoMigration complete") + DB = db + } +} diff --git a/discord/go.mod b/discord/go.mod new file mode 100644 index 0000000..6b2ff59 --- /dev/null +++ b/discord/go.mod @@ -0,0 +1,58 @@ +module github.com/gaucho-racing/sentinel/discord + +go 1.25.6 + +require ( + github.com/bk1031/rincon-go/v2 v2.0.0 + github.com/bwmarrin/discordgo v0.29.0 + github.com/fatih/color v1.19.0 + github.com/gaucho-racing/ulid-go v1.1.0 + github.com/gin-contrib/cors v1.7.7 + github.com/gin-gonic/gin v1.12.0 + github.com/go-resty/resty/v2 v2.17.2 + go.uber.org/zap v1.27.1 + gorm.io/driver/postgres v1.6.0 + gorm.io/gorm v1.31.1 +) + +require ( + github.com/bytedance/gopkg v0.1.3 // indirect + github.com/bytedance/sonic v1.15.0 // indirect + github.com/bytedance/sonic/loader v0.5.0 // indirect + github.com/cloudwego/base64x v0.1.6 // indirect + github.com/gabriel-vasile/mimetype v1.4.12 // indirect + github.com/gin-contrib/sse v1.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.30.1 // indirect + github.com/goccy/go-json v0.10.5 // indirect + github.com/goccy/go-yaml v1.19.2 // indirect + github.com/gorilla/websocket v1.4.2 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + github.com/jackc/pgx/v5 v5.6.0 // indirect + github.com/jackc/puddle/v2 v2.2.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.3.0 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/mattn/go-colorable v0.1.14 // 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.4 // indirect + github.com/quic-go/qpack v0.6.0 // indirect + github.com/quic-go/quic-go v0.59.0 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.3.1 // indirect + go.mongodb.org/mongo-driver/v2 v2.5.0 // indirect + go.uber.org/multierr v1.10.0 // indirect + golang.org/x/arch v0.23.0 // indirect + golang.org/x/crypto v0.48.0 // indirect + golang.org/x/net v0.51.0 // indirect + golang.org/x/sync v0.20.0 // indirect + golang.org/x/sys v0.42.0 // indirect + golang.org/x/text v0.35.0 // indirect + google.golang.org/protobuf v1.36.10 // indirect +) diff --git a/discord/go.sum b/discord/go.sum new file mode 100644 index 0000000..9ece67f --- /dev/null +++ b/discord/go.sum @@ -0,0 +1,138 @@ +github.com/bk1031/rincon-go/v2 v2.0.0 h1:nmDHQNZI/AFfW+ZGTGoxpNPrv3OYXQ09anX+fCoiQsQ= +github.com/bk1031/rincon-go/v2 v2.0.0/go.mod h1:287Zc8PvUNnJuAwpt9XVuYUL8k4wrXg3Fa3L0KEmAB4= +github.com/bwmarrin/discordgo v0.29.0 h1:FmWeXFaKUwrcL3Cx65c20bTRW+vOb6k8AnaP+EgjDno= +github.com/bwmarrin/discordgo v0.29.0/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY= +github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M= +github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM= +github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE= +github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k= +github.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE= +github.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo= +github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= +github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= +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/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w= +github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE= +github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw= +github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= +github.com/gaucho-racing/ulid-go v1.1.0 h1:x00XM8EjlegfhlLYIob+U8ba5iX0gDRUr8mgBsjCunk= +github.com/gaucho-racing/ulid-go v1.1.0/go.mod h1:HwqoC27UtvXHrmhTO7K2GnXZ1VAeR6tg6EjrSEP5JUU= +github.com/gin-contrib/cors v1.7.7 h1:Oh9joP463x7Mw72vhvJ61YQm8ODh9b04YR7vsOErD0Q= +github.com/gin-contrib/cors v1.7.7/go.mod h1:K5tW0RkzJtWSiOdikXloy8VEZlgdVNpHNw8FpjUPNrE= +github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= +github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= +github.com/gin-gonic/gin v1.12.0 h1:b3YAbrZtnf8N//yjKeU2+MQsh2mY5htkZidOM7O0wG8= +github.com/gin-gonic/gin v1.12.0/go.mod h1:VxccKfsSllpKshkBWgVgRniFFAzFb9csfngsqANjnLc= +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.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w= +github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM= +github.com/go-resty/resty/v2 v2.17.2 h1:FQW5oHYcIlkCNrMD2lloGScxcHJ0gkjshV3qcQAyHQk= +github.com/go-resty/resty/v2 v2.17.2/go.mod h1:kCKZ3wWmwJaNc7S29BRtUhJwy7iqmn+2mLtQrOyQlVA= +github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= +github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM= +github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +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/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY= +github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw= +github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= +github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +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.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= +github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +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/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +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.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= +github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +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/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8= +github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII= +github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw= +github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU= +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.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +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.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY= +github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= +go.mongodb.org/mongo-driver/v2 v2.5.0 h1:yXUhImUjjAInNcpTcAlPHiT7bIXhshCTL3jVBkF3xaE= +go.mongodb.org/mongo-driver/v2 v2.5.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0= +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/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= +go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= +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.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= +go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +golang.org/x/arch v0.23.0 h1:lKF64A2jF6Zd8L0knGltUnegD62JMFBiCPBmQpToHhg= +golang.org/x/arch v0.23.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= +golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo= +golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= +golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= +golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA= +golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= +golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= +google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +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/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4= +gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo= +gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg= +gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs= diff --git a/discord/main.go b/discord/main.go new file mode 100644 index 0000000..bfa7833 --- /dev/null +++ b/discord/main.go @@ -0,0 +1,25 @@ +package main + +import ( + "github.com/gaucho-racing/sentinel/discord/api" + "github.com/gaucho-racing/sentinel/discord/commands" + "github.com/gaucho-racing/sentinel/discord/config" + "github.com/gaucho-racing/sentinel/discord/database" + "github.com/gaucho-racing/sentinel/discord/pkg/logger" + "github.com/gaucho-racing/sentinel/discord/pkg/rincon" + "github.com/gaucho-racing/sentinel/discord/service" +) + +func main() { + logger.Init(config.IsProduction()) + defer logger.Logger.Sync() + + config.Verify() + config.PrintStartupBanner() + rincon.Init(&config.Service, &config.Routes) + database.Init() + service.ConnectDiscord() + commands.InitializeBot() + + api.Run() +} diff --git a/discord/model/discord_message.go b/discord/model/discord_message.go new file mode 100644 index 0000000..e068da1 --- /dev/null +++ b/discord/model/discord_message.go @@ -0,0 +1,18 @@ +package model + +import "time" + +type DiscordMessage struct { + ID string `json:"id" gorm:"primaryKey"` + EntityID string `json:"entity_id"` + DiscordUserID string `json:"discord_user_id" gorm:"index"` + ChannelID string `json:"channel_id"` + ChannelName string `json:"channel_name"` + MessageID string `json:"message_id"` + Content string `json:"content"` + CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"` +} + +func (DiscordMessage) TableName() string { + return "discord_message" +} diff --git a/discord/model/discord_reaction.go b/discord/model/discord_reaction.go new file mode 100644 index 0000000..65e1618 --- /dev/null +++ b/discord/model/discord_reaction.go @@ -0,0 +1,18 @@ +package model + +import "time" + +type DiscordReaction struct { + ID string `json:"id" gorm:"primaryKey"` + EntityID string `json:"entity_id"` + DiscordUserID string `json:"discord_user_id" gorm:"index"` + ChannelID string `json:"channel_id"` + ChannelName string `json:"channel_name"` + MessageID string `json:"message_id"` + Emoji string `json:"emoji"` + CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"` +} + +func (DiscordReaction) TableName() string { + return "discord_reaction" +} diff --git a/discord/model/onboarding_token.go b/discord/model/onboarding_token.go new file mode 100644 index 0000000..5a87a6f --- /dev/null +++ b/discord/model/onboarding_token.go @@ -0,0 +1,19 @@ +package model + +import "time" + +type OnboardingToken struct { + ID string `json:"id" gorm:"primaryKey"` + DiscordID string `json:"discord_id" gorm:"index"` + DiscordUsername string `json:"discord_username"` + DiscordGlobalName string `json:"discord_global_name"` + DiscordAvatarURL string `json:"discord_avatar_url"` + EntityID string `json:"entity_id" gorm:"index"` + UsedAt *time.Time `json:"used_at"` + ExpiresAt time.Time `json:"expires_at"` + CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"` +} + +func (OnboardingToken) TableName() string { + return "onboarding_token" +} diff --git a/discord/pkg/logger/logger.go b/discord/pkg/logger/logger.go new file mode 100644 index 0000000..2722d06 --- /dev/null +++ b/discord/pkg/logger/logger.go @@ -0,0 +1,16 @@ +package logger + +import ( + "go.uber.org/zap" +) + +var Logger *zap.Logger +var SugarLogger *zap.SugaredLogger + +func Init(production bool) { + Logger = zap.Must(zap.NewProduction()) + if !production { + Logger = zap.Must(zap.NewDevelopment(zap.AddCaller(), zap.AddStacktrace(zap.ErrorLevel))) + } + SugarLogger = Logger.Sugar() +} diff --git a/discord/pkg/rincon/rincon.go b/discord/pkg/rincon/rincon.go new file mode 100644 index 0000000..69d4a8d --- /dev/null +++ b/discord/pkg/rincon/rincon.go @@ -0,0 +1,60 @@ +package rincon + +import ( + "os" + "time" + + "github.com/bk1031/rincon-go/v2" + "github.com/gaucho-racing/sentinel/discord/pkg/logger" +) + +var RinconClient *rincon.Client +var RinconUser = os.Getenv("RINCON_USER") +var RinconPassword = os.Getenv("RINCON_PASSWORD") +var RinconEndpoint = os.Getenv("RINCON_ENDPOINT") + +func Init(service *rincon.Service, routes *[]rincon.Route) { + if RinconUser == "" || RinconPassword == "" || RinconEndpoint == "" { + logger.SugarLogger.Errorln("Rincon user, password, or endpoint is not set") + return + } + client := createClient(RinconEndpoint, RinconUser, RinconPassword) + if client == nil { + return + } + id, err := client.Register(*service, *routes) + if err != nil { + logger.SugarLogger.Fatalf("Failed to register service with Rincon: %v", err) + return + } + logger.SugarLogger.Infof("Registered service with ID: %d", id) + RinconClient = client + *service = *client.Service() +} + +func createClient(endpoint string, user string, password string) *rincon.Client { + rinconRetries := 0 + for rinconRetries < 5 { + client, err := rincon.NewClient(rincon.Config{ + BaseURL: endpoint, + HeartbeatMode: rincon.ServerHeartbeat, + HeartbeatInterval: 60, + AuthUser: user, + AuthPassword: password, + }) + if err != nil { + if rinconRetries < 5 { + logger.SugarLogger.Errorf("Failed to create Rincon client with %s: %v, retrying in 5s...", endpoint, err) + rinconRetries++ + time.Sleep(time.Second * 5) + } else { + logger.SugarLogger.Errorln("Failed to create Rincon client after 5 attempts") + return nil + } + } else { + logger.SugarLogger.Infof("Created Rincon client with endpoint %s", endpoint) + return client + } + } + return nil +} diff --git a/discord/pkg/sentinel/sentinel.go b/discord/pkg/sentinel/sentinel.go new file mode 100644 index 0000000..f78b395 --- /dev/null +++ b/discord/pkg/sentinel/sentinel.go @@ -0,0 +1,119 @@ +package sentinel + +import ( + "encoding/json" + "errors" + "fmt" + + "github.com/gaucho-racing/sentinel/discord/pkg/logger" + "github.com/gaucho-racing/sentinel/discord/pkg/rincon" + "github.com/go-resty/resty/v2" +) + +// Sentinel-side error categories — wrapped into APIError.Err so callers +// can errors.Is on them and pick the right user-facing message. +var ( + ErrRinconUninitialized = errors.New("rincon client not initialized") + ErrRouteResolution = errors.New("rincon could not resolve route") +) + +var client = resty.New() + +// APIError is returned by every method in this package. Status == 0 means no +// HTTP response was received (route resolution failure or transport error). +// Status > 0 means the upstream replied with that status code. Callers should +// use errors.As to inspect it and decide how to surface to their own +// response — most importantly, a 4xx from upstream should NOT collapse to a +// generic "service unavailable" on the user-facing side. +type APIError struct { + Method string + Route string + Status int // 0 when no HTTP response was received + Body string // raw response body + Message string // parsed "error" field from a JSON body, when present + Err error // underlying transport or resolution error +} + +func (e *APIError) Error() string { + if e.Status == 0 { + if e.Err != nil { + return fmt.Sprintf("%s %s: %v", e.Method, e.Route, e.Err) + } + return fmt.Sprintf("%s %s: no response", e.Method, e.Route) + } + if e.Message != "" { + return fmt.Sprintf("%s %s returned %d: %s", e.Method, e.Route, e.Status, e.Message) + } + return fmt.Sprintf("%s %s returned %d", e.Method, e.Route, e.Status) +} + +func (e *APIError) Unwrap() error { return e.Err } + +func resolveURL(route string, method string) (string, error) { + if rincon.RinconClient == nil { + return "", ErrRinconUninitialized + } + service, err := rincon.RinconClient.MatchRoute(route, method) + if err != nil { + return "", fmt.Errorf("%w: %s: %v", ErrRouteResolution, route, err) + } + return service.Endpoint + route, nil +} + +func do(method, route string, body, result interface{}, headers []map[string]string) error { + url, err := resolveURL(route, method) + if err != nil { + return &APIError{Method: method, Route: route, Err: err} + } + req := client.R() + if body != nil { + req = req.SetBody(body) + } + if result != nil { + req = req.SetResult(result) + } + if len(headers) > 0 { + req = req.SetHeaders(headers[0]) + } + resp, err := req.Execute(method, url) + if err != nil { + return &APIError{Method: method, Route: route, Err: err} + } + if resp.IsError() { + ae := &APIError{ + Method: method, + Route: route, + Status: resp.StatusCode(), + Body: resp.String(), + } + var parsed struct { + Error string `json:"error"` + } + if json.Unmarshal(resp.Body(), &parsed) == nil && parsed.Error != "" { + ae.Message = parsed.Error + } + logger.SugarLogger.Errorf("%s %s returned %d: %s", method, route, resp.StatusCode(), resp.String()) + return ae + } + return nil +} + +func Get(route string, result interface{}, headers ...map[string]string) error { + return do("GET", route, nil, result, headers) +} + +func Post(route string, body interface{}, result interface{}, headers ...map[string]string) error { + return do("POST", route, body, result, headers) +} + +func Put(route string, body interface{}, result interface{}, headers ...map[string]string) error { + return do("PUT", route, body, result, headers) +} + +func Patch(route string, body interface{}, result interface{}, headers ...map[string]string) error { + return do("PATCH", route, body, result, headers) +} + +func Delete(route string, result interface{}, headers ...map[string]string) error { + return do("DELETE", route, nil, result, headers) +} diff --git a/discord/service/discord.go b/discord/service/discord.go new file mode 100644 index 0000000..4097b15 --- /dev/null +++ b/discord/service/discord.go @@ -0,0 +1,58 @@ +package service + +import ( + "time" + + "github.com/bwmarrin/discordgo" + "github.com/gaucho-racing/sentinel/discord/config" + "github.com/gaucho-racing/sentinel/discord/pkg/logger" +) + +var Discord *discordgo.Session + +func ConnectDiscord() { + dg, err := discordgo.New("Bot " + config.DiscordToken) + if err != nil { + logger.SugarLogger.Errorln("Error creating Discord session:", err) + return + } + Discord = dg + logger.SugarLogger.Infoln("Created Discord session") +} + +func GetChannelName(channelID string) string { + channel, err := Discord.Channel(channelID) + if err != nil { + logger.SugarLogger.Errorf("Failed to get channel %s: %v", channelID, err) + return "" + } + return channel.Name +} + +// SendDisappearingMessage posts a channel message and schedules its deletion +// after the given delay. Returns immediately; deletion happens in a goroutine. +func SendDisappearingMessage(channelID, content string, delay time.Duration) { + msg, err := Discord.ChannelMessageSend(channelID, content) + if err != nil { + logger.SugarLogger.Errorf("Failed to send disappearing message in %s: %v", channelID, err) + return + } + go delayedMessageDelete(channelID, msg.ID, delay) +} + +func delayedMessageDelete(channelID, messageID string, delay time.Duration) { + time.Sleep(delay) + if err := Discord.ChannelMessageDelete(channelID, messageID); err != nil { + logger.SugarLogger.Errorf("Failed to delete message %s in %s: %v", messageID, channelID, err) + } +} + +// SendDirectMessage opens a DM channel with the user and posts the content. +func SendDirectMessage(userID, content string) error { + channel, err := Discord.UserChannelCreate(userID) + if err != nil { + return err + } + _, err = Discord.ChannelMessageSend(channel.ID, content) + return err +} diff --git a/discord/service/discord_message.go b/discord/service/discord_message.go new file mode 100644 index 0000000..d20b06f --- /dev/null +++ b/discord/service/discord_message.go @@ -0,0 +1,57 @@ +package service + +import ( + "github.com/gaucho-racing/sentinel/discord/database" + "github.com/gaucho-racing/sentinel/discord/model" + "github.com/gaucho-racing/ulid-go" +) + +func GetAllDiscordMessages() ([]model.DiscordMessage, error) { + var messages []model.DiscordMessage + if err := database.DB.Find(&messages).Error; err != nil { + return []model.DiscordMessage{}, err + } + return messages, nil +} + +func GetDiscordMessageByID(id string) (model.DiscordMessage, error) { + var message model.DiscordMessage + if err := database.DB.Where("id = ?", id).First(&message).Error; err != nil { + return model.DiscordMessage{}, err + } + return message, nil +} + +func GetDiscordMessagesByDiscordUserID(discordUserID string) ([]model.DiscordMessage, error) { + var messages []model.DiscordMessage + if err := database.DB.Where("discord_user_id = ?", discordUserID).Find(&messages).Error; err != nil { + return []model.DiscordMessage{}, err + } + return messages, nil +} + +func GetDiscordMessagesByEntityID(entityID string) ([]model.DiscordMessage, error) { + var messages []model.DiscordMessage + if err := database.DB.Where("entity_id = ?", entityID).Find(&messages).Error; err != nil { + return []model.DiscordMessage{}, err + } + return messages, nil +} + +func CreateDiscordMessage(message model.DiscordMessage) (model.DiscordMessage, error) { + if message.ID == "" { + message.ID = ulid.Make().Prefixed("dmsg") + } + message.EntityID = GetEntityIDForDiscordUser(message.DiscordUserID) + if err := database.DB.Create(&message).Error; err != nil { + return model.DiscordMessage{}, err + } + return message, nil +} + +func DeleteDiscordMessage(id string) error { + if err := database.DB.Where("id = ?", id).Delete(&model.DiscordMessage{}).Error; err != nil { + return err + } + return nil +} diff --git a/discord/service/discord_reaction.go b/discord/service/discord_reaction.go new file mode 100644 index 0000000..7c717eb --- /dev/null +++ b/discord/service/discord_reaction.go @@ -0,0 +1,57 @@ +package service + +import ( + "github.com/gaucho-racing/sentinel/discord/database" + "github.com/gaucho-racing/sentinel/discord/model" + "github.com/gaucho-racing/ulid-go" +) + +func GetAllDiscordReactions() ([]model.DiscordReaction, error) { + var reactions []model.DiscordReaction + if err := database.DB.Find(&reactions).Error; err != nil { + return []model.DiscordReaction{}, err + } + return reactions, nil +} + +func GetDiscordReactionByID(id string) (model.DiscordReaction, error) { + var reaction model.DiscordReaction + if err := database.DB.Where("id = ?", id).First(&reaction).Error; err != nil { + return model.DiscordReaction{}, err + } + return reaction, nil +} + +func GetDiscordReactionsByDiscordUserID(discordUserID string) ([]model.DiscordReaction, error) { + var reactions []model.DiscordReaction + if err := database.DB.Where("discord_user_id = ?", discordUserID).Find(&reactions).Error; err != nil { + return []model.DiscordReaction{}, err + } + return reactions, nil +} + +func GetDiscordReactionsByEntityID(entityID string) ([]model.DiscordReaction, error) { + var reactions []model.DiscordReaction + if err := database.DB.Where("entity_id = ?", entityID).Find(&reactions).Error; err != nil { + return []model.DiscordReaction{}, err + } + return reactions, nil +} + +func CreateDiscordReaction(reaction model.DiscordReaction) (model.DiscordReaction, error) { + if reaction.ID == "" { + reaction.ID = ulid.Make().Prefixed("drxn") + } + reaction.EntityID = GetEntityIDForDiscordUser(reaction.DiscordUserID) + if err := database.DB.Create(&reaction).Error; err != nil { + return model.DiscordReaction{}, err + } + return reaction, nil +} + +func DeleteDiscordReaction(id string) error { + if err := database.DB.Where("id = ?", id).Delete(&model.DiscordReaction{}).Error; err != nil { + return err + } + return nil +} diff --git a/discord/service/entity.go b/discord/service/entity.go new file mode 100644 index 0000000..f773246 --- /dev/null +++ b/discord/service/entity.go @@ -0,0 +1,24 @@ +package service + +import ( + "github.com/gaucho-racing/sentinel/discord/pkg/logger" + "github.com/gaucho-racing/sentinel/discord/pkg/sentinel" +) + +type entityResponse struct { + ID string `json:"id"` + Type string `json:"type"` +} + +// GetEntityIDForDiscordUser resolves a Discord user ID to a Sentinel entity ID. +// Returns "" if no mapping is found, allowing callers to persist records +// with an empty entity_id that can be backfilled later. +func GetEntityIDForDiscordUser(discordUserID string) string { + var entity entityResponse + err := sentinel.Get("/core/entity/external/DISCORD/"+discordUserID, &entity) + if err != nil { + logger.SugarLogger.Debugf("No entity found for Discord user %s: %v", discordUserID, err) + return "" + } + return entity.ID +} diff --git a/discord/service/onboarding_token.go b/discord/service/onboarding_token.go new file mode 100644 index 0000000..28fe119 --- /dev/null +++ b/discord/service/onboarding_token.go @@ -0,0 +1,157 @@ +package service + +import ( + "errors" + "fmt" + "time" + + "github.com/gaucho-racing/sentinel/discord/config" + "github.com/gaucho-racing/sentinel/discord/database" + "github.com/gaucho-racing/sentinel/discord/model" + "github.com/gaucho-racing/sentinel/discord/pkg/logger" + "github.com/gaucho-racing/sentinel/discord/pkg/sentinel" + "github.com/gaucho-racing/ulid-go" + "gorm.io/gorm" +) + +var ( + ErrOnboardingTokenNotFound = errors.New("onboarding token not found") + ErrOnboardingTokenInvalid = errors.New("onboarding token expired or already used") +) + +// CreateOnboardingTokenForDiscordUser invalidates any unused tokens for the +// Discord user and mints a fresh one. Caller is expected to have already +// verified that no Entity exists for this Discord ID. +func CreateOnboardingTokenForDiscordUser(discordID, username, globalName, avatarURL string) (model.OnboardingToken, error) { + now := time.Now() + if err := database.DB.Model(&model.OnboardingToken{}). + Where("discord_id = ? AND used_at IS NULL AND expires_at > ?", discordID, now). + Update("expires_at", now).Error; err != nil { + logger.SugarLogger.Errorf("Failed to invalidate prior onboarding tokens for %s: %v", discordID, err) + } + + token := model.OnboardingToken{ + ID: ulid.Make().Prefixed("ont"), + DiscordID: discordID, + DiscordUsername: username, + DiscordGlobalName: globalName, + DiscordAvatarURL: avatarURL, + ExpiresAt: now.Add(config.OnboardingTokenTTL), + } + if err := database.DB.Create(&token).Error; err != nil { + return model.OnboardingToken{}, err + } + return token, nil +} + +// GetOnboardingTokenByID returns the token row if it exists and is still +// usable. ErrOnboardingTokenNotFound for a missing row, ErrOnboardingTokenInvalid +// for a row that is expired or already consumed. +func GetOnboardingTokenByID(id string) (model.OnboardingToken, error) { + var token model.OnboardingToken + if err := database.DB.Where("id = ?", id).First(&token).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return model.OnboardingToken{}, ErrOnboardingTokenNotFound + } + return model.OnboardingToken{}, err + } + if token.UsedAt != nil || time.Now().After(token.ExpiresAt) { + return token, ErrOnboardingTokenInvalid + } + return token, nil +} + +// OnboardingConsumePayload is the form data the user submits at the end of +// the onboarding flow. +type OnboardingConsumePayload struct { + Email string + Password string + Username string + FirstName string + LastName string + Gender string + Birthday string + PhoneNumber string + GraduateLevel string + GraduationYear int + Major string + ShirtSize string + JacketSize string + SAERegistrationNumber string + InitialRole string +} + +// ConsumeOnboardingToken validates the token, fans out 5 calls to core to +// create Entity + User + email/phone/external auth, then marks the token used. +// Best-effort: a mid-flight failure leaves an orphan Entity that a cleanup +// job can sweep later. Returns the new entity ID. +func ConsumeOnboardingToken(id string, p OnboardingConsumePayload) (string, error) { + token, err := GetOnboardingTokenByID(id) + if err != nil { + return "", err + } + + birthday, err := time.Parse("2006-01-02", p.Birthday) + if err != nil { + return "", fmt.Errorf("invalid birthday format (want YYYY-MM-DD): %w", err) + } + + var entityResp struct { + ID string `json:"id"` + } + if err := sentinel.Post("/core/entity", map[string]string{"type": "USER"}, &entityResp); err != nil { + return "", fmt.Errorf("create entity: %w", err) + } + + var ignored map[string]any + + userBody := map[string]any{ + "entity_id": entityResp.ID, + "username": p.Username, + "first_name": p.FirstName, + "last_name": p.LastName, + "gender": p.Gender, + "birthday": birthday, + "graduate_level": p.GraduateLevel, + "graduation_year": p.GraduationYear, + "major": p.Major, + "shirt_size": p.ShirtSize, + "jacket_size": p.JacketSize, + "sae_registration_number": p.SAERegistrationNumber, + "avatar_url": token.DiscordAvatarURL, + "initial_role": p.InitialRole, + } + if err := sentinel.Post("/core/users", userBody, &ignored); err != nil { + return "", fmt.Errorf("create user: %w", err) + } + + if err := sentinel.Post(fmt.Sprintf("/core/entity/%s/email-auth", entityResp.ID), map[string]string{ + "email": p.Email, + "password": p.Password, + }, &ignored); err != nil { + return "", fmt.Errorf("create email auth: %w", err) + } + + if err := sentinel.Post(fmt.Sprintf("/core/entity/%s/phone-auth", entityResp.ID), map[string]string{ + "phone_number": p.PhoneNumber, + }, &ignored); err != nil { + return "", fmt.Errorf("create phone auth: %w", err) + } + + if err := sentinel.Post(fmt.Sprintf("/core/entity/%s/external-auth", entityResp.ID), map[string]string{ + "provider": "DISCORD", + "external_id": token.DiscordID, + }, &ignored); err != nil { + return "", fmt.Errorf("create external auth: %w", err) + } + + now := time.Now() + if err := database.DB.Model(&model.OnboardingToken{}). + Where("id = ?", id). + Updates(map[string]any{"used_at": now, "entity_id": entityResp.ID}). + Error; err != nil { + logger.SugarLogger.Errorf("Failed to mark onboarding token %s used: %v", id, err) + } + + return entityResp.ID, nil +} diff --git a/docker-compose.yml b/docker-compose.yml index 73aaa9a..027772e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,51 +1,156 @@ name: sentinel services: - db: - container_name: db - image: ghcr.io/singlestore-labs/singlestoredb-dev:latest - platform: linux/amd64 - restart: unless-stopped + kerbecs: + container_name: sentinel-kerbecs + image: bk1031/kerbecs:latest + restart: always + ports: + - "10310:10310" + - "10300:10300" volumes: - - s2data:/data - - ./init.sql:/init.sql + - ./kerbecs.yaml:/etc/kerbecs/kerbecs.yaml:ro + environment: + KERBECS_CONFIG: /etc/kerbecs/kerbecs.yaml + depends_on: + - core + - oauth + - discord + - web + + rincon: + container_name: sentinel-rincon + image: bk1031/rincon:latest + restart: always ports: - - "3306:3306" - - "8080:8080" - - "9000:9000" + - "10311:10311" + depends_on: + - db + environment: + STORAGE_MODE: sql + DB_DRIVER: postgres + DB_HOST: db + DB_PORT: 5432 + DB_USER: postgres + DB_PASSWORD: ${POSTGRES_PASSWORD} + DB_NAME: sentinel + + core: + container_name: sentinel-core + image: golang:1.26-alpine + restart: always + working_dir: /app + command: > + sh -c "go install github.com/air-verse/air@latest && air" + volumes: + - ./core:/app + - core_gopath:/go + depends_on: + - db + - rincon + environment: + ENV: DEV + PORT: 9999 + DATABASE_HOST: db + DATABASE_PORT: 5432 + DATABASE_USER: postgres + DATABASE_PASSWORD: ${POSTGRES_PASSWORD} + DATABASE_NAME: sentinel + RINCON_ENDPOINT: http://rincon:10311 + RINCON_USER: admin + RINCON_PASSWORD: admin + SERVICE_ENDPOINT: http://core:9999 + SERVICE_HEALTH_CHECK: http://core:9999/core/ping + + discord: + container_name: sentinel-discord + image: golang:1.26-alpine + restart: always + working_dir: /app + command: > + sh -c "go install github.com/air-verse/air@latest && air" + volumes: + - ./discord:/app + - discord_gopath:/go + depends_on: + - db + - rincon environment: - ROOT_PASSWORD: "password" + ENV: DEV + PORT: 9998 + DATABASE_HOST: db + DATABASE_PORT: 5432 + DATABASE_USER: postgres + DATABASE_PASSWORD: ${POSTGRES_PASSWORD} + DATABASE_NAME: sentinel + RINCON_ENDPOINT: http://rincon:10311 + RINCON_USER: admin + RINCON_PASSWORD: admin + SERVICE_ENDPOINT: http://discord:9998 + SERVICE_HEALTH_CHECK: http://discord:9998/discord/ping + DISCORD_TOKEN: ${DISCORD_TOKEN} + DISCORD_GUILD: ${DISCORD_GUILD} + DISCORD_PREFIX: d! + WEB_BASE_URL: ${WEB_BASE_URL:-http://localhost:10310} - sentinel: - container_name: sentinel + oauth: + container_name: sentinel-oauth + image: golang:1.26-alpine + restart: always + working_dir: /app + command: > + sh -c "go install github.com/air-verse/air@latest && air" + volumes: + - ./oauth:/app + - oauth_gopath:/go depends_on: - db - image: gauchoracing/sentinel:latest - restart: unless-stopped + - rincon + environment: + ENV: DEV + PORT: 9997 + DATABASE_HOST: db + DATABASE_PORT: 5432 + DATABASE_USER: postgres + DATABASE_PASSWORD: ${POSTGRES_PASSWORD} + DATABASE_NAME: sentinel + RINCON_ENDPOINT: http://rincon:10311 + RINCON_USER: admin + RINCON_PASSWORD: admin + SERVICE_ENDPOINT: http://oauth:9997 + SERVICE_HEALTH_CHECK: http://oauth:9997/oauth/ping + + web: + container_name: sentinel-web + image: node:22-alpine + restart: always + working_dir: /app + command: > + sh -c "npm install && npm run dev -- --host 0.0.0.0" + volumes: + - ./web:/app + - web_node_modules:/app/node_modules ports: - - "${PORT}:${PORT}" + - "5173:5173" + environment: + VITE_API_URL: http://localhost:10310 + + db: + container_name: sentinel-db + image: postgres:18-alpine + restart: always 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: ${POSTGRES_PASSWORD} + POSTGRES_DB: sentinel + ports: + - "127.0.0.1:5432:5432" + volumes: + - pgdata:/var/lib/postgresql volumes: - s2data: \ No newline at end of file + pgdata: + core_gopath: + discord_gopath: + oauth_gopath: + web_node_modules: 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/kerbecs.yaml b/kerbecs.yaml new file mode 100644 index 0000000..7568c87 --- /dev/null +++ b/kerbecs.yaml @@ -0,0 +1,123 @@ +gateway: + name: sentinel-gateway + version: 0.1.0 + env: ${ENV:DEV} + timeouts: + dial: 5s + headers: 30s + idle: 50s + overall: 0 + +listeners: + gateway: + port: "10310" + cors: + enabled: true + allowed_origins: ["*"] + allow_credentials: false + admin: + port: "10300" + auth: + type: basic + username: ${KERBECS_USER:admin} + password: ${KERBECS_PASSWORD:admin} + +providers: + static: + watch: false + +upstreams: + core: + name: sentinel-core + version: 0.1.0 + instances: + - http://core:9999 + + oauth: + name: sentinel-oauth + version: 0.1.0 + instances: + - http://oauth:9997 + + discord: + name: sentinel-discord + version: 0.1.0 + instances: + - http://discord:9998 + + web: + name: sentinel-web + version: 0.1.0 + instances: + - http://web:5173 + +routes: + - name: core-internal + match: + path: /api/core/* + upstream: core + rewrite: + strip_prefix: /api + envelope: passthrough + + - name: core-users + match: + path: /api/users/* + upstream: core + rewrite: + strip_prefix: /api + envelope: passthrough + + - name: core-applications + match: + path: /api/applications/* + upstream: core + rewrite: + strip_prefix: /api + envelope: passthrough + + - name: core-groups + match: + path: /api/groups/* + upstream: core + rewrite: + strip_prefix: /api + envelope: passthrough + + - name: core-entities + match: + path: /api/entities/* + upstream: core + rewrite: + strip_prefix: /api + envelope: passthrough + + - name: oauth + match: + path: /api/oauth/* + upstream: oauth + rewrite: + strip_prefix: /api + envelope: passthrough + + - name: auth + match: + path: /api/auth/* + upstream: oauth + rewrite: + strip_prefix: /api + envelope: passthrough + + - name: discord + match: + path: /api/discord/* + upstream: discord + rewrite: + strip_prefix: /api + envelope: passthrough + + - name: web-frontend + match: + path: /* + upstream: web + envelope: passthrough 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/oauth/.air.toml b/oauth/.air.toml new file mode 100644 index 0000000..3d5c83f --- /dev/null +++ b/oauth/.air.toml @@ -0,0 +1,21 @@ +root = "." +tmp_dir = "tmp" + +[build] + bin = "./tmp/main" + cmd = "go mod tidy && go build -o ./tmp/main ." + delay = 1000 + exclude_dir = ["tmp", "vendor"] + exclude_regex = ["_test.go"] + include_ext = ["go", "toml"] + kill_delay = "0s" + send_interrupt = false + poll = true + poll_interval = 500 + stop_on_error = true + +[log] + time = false + +[misc] + clean_on_exit = true diff --git a/oauth/.gitignore b/oauth/.gitignore new file mode 100644 index 0000000..3fec32c --- /dev/null +++ b/oauth/.gitignore @@ -0,0 +1 @@ +tmp/ diff --git a/oauth/Dockerfile b/oauth/Dockerfile new file mode 100644 index 0000000..512b60e --- /dev/null +++ b/oauth/Dockerfile @@ -0,0 +1,29 @@ +FROM --platform=$BUILDPLATFORM golang:1.26-alpine AS builder + +RUN apk --no-cache add ca-certificates +RUN apk add --no-cache tzdata + +WORKDIR /app + +COPY go.mod ./ +COPY go.sum ./ +RUN go mod download + +COPY . ./ +ARG TARGETOS +ARG TARGETARCH +RUN GOOS=$TARGETOS GOARCH=$TARGETARCH go build -o /app + +## +## Deploy +## +FROM alpine:3.19 + +WORKDIR / + +COPY --from=builder /app /app + +COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo +ENV TZ=America/Los_Angeles + +ENTRYPOINT ["/app"] diff --git a/oauth/api/api.go b/oauth/api/api.go new file mode 100644 index 0000000..663fbf1 --- /dev/null +++ b/oauth/api/api.go @@ -0,0 +1,55 @@ +package api + +import ( + "time" + + "github.com/gaucho-racing/sentinel/oauth/config" + "github.com/gaucho-racing/sentinel/oauth/pkg/logger" + "github.com/gin-contrib/cors" + "github.com/gin-gonic/gin" +) + +func Run() { + api := InitializeRouter() + InitializeRoutes(api) + err := api.Run(":" + config.Port) + if err != nil { + logger.SugarLogger.Fatalf("Failed to start server: %v", err) + } +} + +func InitializeRouter() *gin.Engine { + if config.IsProduction() { + 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, + })) + return r +} + +func InitializeRoutes(router *gin.Engine) { + router.GET("/oauth/ping", Ping) + router.GET("/oauth/authorize", ValidateAuthorize) + router.POST("/oauth/authorize", Authorize) + router.POST("/oauth/token", ExchangeToken) + + router.POST("/auth/login/email-password", LoginEmailPassword) + router.POST("/auth/refresh", RefreshSession) +} + +// GetClientIP returns the originating client IP. Prefers Cloudflare's +// CF-Connecting-IP (set by CF, overrides any client-supplied value, and +// unspoofable as long as the origin only accepts traffic from CF's IP +// ranges). Falls back to gin's c.ClientIP() in dev or non-CF deployments. +func GetClientIP(c *gin.Context) string { + if ip := c.GetHeader("CF-Connecting-IP"); ip != "" { + return ip + } + return c.ClientIP() +} diff --git a/oauth/api/authorize.go b/oauth/api/authorize.go new file mode 100644 index 0000000..6001ef9 --- /dev/null +++ b/oauth/api/authorize.go @@ -0,0 +1,152 @@ +package api + +import ( + "fmt" + "net/http" + "time" + + "github.com/gaucho-racing/sentinel/oauth/pkg/logger" + "github.com/gaucho-racing/sentinel/oauth/pkg/sentinel" + "github.com/gaucho-racing/sentinel/oauth/service" + "github.com/gin-gonic/gin" +) + +type applicationResponse struct { + ID string `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + ClientID string `json:"client_id"` + IconURL string `json:"icon_url"` + RedirectURIs []string `json:"redirect_uris"` +} + +type validateAuthorizeResponse struct { + ClientID string `json:"client_id"` + RedirectURI string `json:"redirect_uri"` + Scope string `json:"scope"` + Prompt string `json:"prompt"` + AppName string `json:"app_name"` + AppIconURL string `json:"app_icon_url"` +} + +// ValidateAuthorize validates the OAuth authorize request parameters +// and returns application info for the frontend consent screen. +func ValidateAuthorize(c *gin.Context) { + clientID := c.Query("client_id") + if clientID == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "client_id is required"}) + return + } + + redirectURI := c.Query("redirect_uri") + if redirectURI == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "redirect_uri is required"}) + return + } + + scope := c.Query("scope") + if scope == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "scope is required"}) + return + } + + if !service.ValidateScopes(scope) { + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid scope"}) + return + } + + if service.ScopesContain(scope, "sentinel:all") { + c.JSON(http.StatusBadRequest, gin.H{"error": "sentinel:all scope cannot be requested by client applications"}) + return + } + + var app applicationResponse + err := sentinel.Get("/applications/client/"+clientID, &app) + if err != nil { + logger.SugarLogger.Errorf("Failed to get application for client_id %s: %v", clientID, err) + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid client_id"}) + return + } + + validURI := false + for _, uri := range app.RedirectURIs { + if uri == redirectURI { + validURI = true + break + } + } + if !validURI { + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid redirect_uri"}) + return + } + + prompt := c.Query("prompt") + entityID := c.Query("entity_id") + if prompt == "none" && entityID != "" { + var logins []map[string]interface{} + err = sentinel.Get(fmt.Sprintf("/core/entity/%s/logins?client_id=%s&scope=%s&limit=1", entityID, clientID, scope), &logins) + if err == nil && len(logins) > 0 { + if createdAt, ok := logins[0]["created_at"].(string); ok { + if t, err := time.Parse(time.RFC3339, createdAt); err == nil && time.Since(t) < 7*24*time.Hour { + prompt = "none" + } else { + prompt = "consent" + } + } else { + prompt = "consent" + } + } else { + prompt = "consent" + } + } else { + prompt = "consent" + } + + c.JSON(http.StatusOK, validateAuthorizeResponse{ + ClientID: clientID, + RedirectURI: redirectURI, + Scope: scope, + Prompt: prompt, + AppName: app.Name, + AppIconURL: app.IconURL, + }) +} + +type authorizeRequest struct { + EntityID string `json:"entity_id" binding:"required"` +} + +// Authorize generates an authorization code after the user approves consent. +// The frontend sends the entity_id of the authenticated user. +func Authorize(c *gin.Context) { + clientID := c.Query("client_id") + redirectURI := c.Query("redirect_uri") + scope := c.Query("scope") + + if clientID == "" || redirectURI == "" || scope == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "client_id, redirect_uri, and scope are required"}) + return + } + + var req authorizeRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + if !service.ValidateScopes(scope) || service.ScopesContain(scope, "sentinel:all") { + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid scope"}) + return + } + + authCode, err := service.GenerateAuthorizationCode(req.EntityID, clientID, scope, redirectURI) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, gin.H{ + "code": authCode.Code, + "redirect_uri": redirectURI, + }) +} diff --git a/oauth/api/login.go b/oauth/api/login.go new file mode 100644 index 0000000..64cb315 --- /dev/null +++ b/oauth/api/login.go @@ -0,0 +1,130 @@ +package api + +import ( + "errors" + "net/http" + + "github.com/gaucho-racing/sentinel/oauth/config" + "github.com/gaucho-racing/sentinel/oauth/pkg/logger" + "github.com/gaucho-racing/sentinel/oauth/pkg/sentinel" + "github.com/gaucho-racing/sentinel/oauth/service" + "github.com/gin-gonic/gin" +) + +type sessionResponse struct { + AccessToken string `json:"access_token"` + RefreshToken string `json:"refresh_token"` + ExpiresIn int `json:"expires_in"` + EntityID string `json:"entity_id"` +} + +type emailPasswordLoginRequest struct { + Email string `json:"email" binding:"required"` + Password string `json:"password" binding:"required"` +} + +func LoginEmailPassword(c *gin.Context) { + c.Header("Cache-Control", "no-store") + var req emailPasswordLoginRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + var verify struct { + EntityID string `json:"entity_id"` + } + if err := sentinel.Post("/core/login/email-password", req, &verify); err != nil { + logger.SugarLogger.Errorf("login: upstream failure: %v", err) + var apiErr *sentinel.APIError + if errors.As(err, &apiErr) && apiErr.Status == http.StatusUnauthorized { + c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid credentials"}) + return + } + c.JSON(http.StatusBadGateway, gin.H{"error": err.Error()}) + return + } + + resp, err := mintFirstPartySession(c, verify.EntityID) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, resp) +} + +type refreshSessionRequest struct { + RefreshToken string `json:"refresh_token" binding:"required"` +} + +func RefreshSession(c *gin.Context) { + c.Header("Cache-Control", "no-store") + var req refreshSessionRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + var claims map[string]interface{} + if err := sentinel.Post("/core/token/validate", map[string]string{"token": req.RefreshToken}, &claims); err != nil { + c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid or expired refresh token"}) + return + } + + entityID, _ := claims["sub"].(string) + scope, _ := claims["scope"].(string) + if entityID == "" || !service.ScopesContain(scope, "refresh_token") { + c.JSON(http.StatusUnauthorized, gin.H{"error": "not a refresh token"}) + return + } + + if tokenID, ok := claims["jti"].(string); ok { + sentinel.Delete("/core/token/"+tokenID, nil) + } + + resp, err := mintFirstPartySession(c, entityID) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, resp) +} + +// First-party tokens are scoped to the Sentinel app itself. +const firstPartyClientID = "sentinel" +const firstPartyAccessScope = "user:read user:write groups:read applications:read" +const firstPartyRefreshScope = firstPartyAccessScope + " refresh_token" + +// mintFirstPartySession builds claims, mints access + refresh JWTs, and +// records an entity login for audit. Used by /auth/login and /auth/refresh. +func mintFirstPartySession(c *gin.Context, entityID string) (sessionResponse, error) { + claims := service.BuildTokenClaims(entityID, firstPartyClientID) + + accessToken, accessTokenID, err := generateToken(entityID, firstPartyClientID, firstPartyAccessScope, config.AccessTokenTTL, claims) + if err != nil { + return sessionResponse{}, errors.New("failed to generate access token") + } + + refreshToken, refreshTokenID, err := generateToken(entityID, firstPartyClientID, firstPartyRefreshScope, config.RefreshTokenTTL, claims) + if err != nil { + logger.SugarLogger.Errorf("Failed to generate refresh token for %s: %v", entityID, err) + refreshToken = "" + refreshTokenID = "" + } + + sentinel.Post("/core/entity/logins", map[string]string{ + "entity_id": entityID, + "client_id": firstPartyClientID, + "scope": firstPartyAccessScope, + "access_token_id": accessTokenID, + "refresh_token_id": refreshTokenID, + "ip_address": GetClientIP(c), + }, nil) + + return sessionResponse{ + AccessToken: accessToken, + RefreshToken: refreshToken, + ExpiresIn: config.AccessTokenTTL, + EntityID: entityID, + }, nil +} diff --git a/oauth/api/ping.go b/oauth/api/ping.go new file mode 100644 index 0000000..3502126 --- /dev/null +++ b/oauth/api/ping.go @@ -0,0 +1,12 @@ +package api + +import ( + "net/http" + + "github.com/gaucho-racing/sentinel/oauth/config" + "github.com/gin-gonic/gin" +) + +func Ping(c *gin.Context) { + c.JSON(http.StatusOK, gin.H{"message": config.Service.FormattedNameWithVersion() + " is online!"}) +} diff --git a/oauth/api/token.go b/oauth/api/token.go new file mode 100644 index 0000000..022a8f2 --- /dev/null +++ b/oauth/api/token.go @@ -0,0 +1,238 @@ +package api + +import ( + "net/http" + + "github.com/gaucho-racing/sentinel/oauth/config" + "github.com/gaucho-racing/sentinel/oauth/pkg/logger" + "github.com/gaucho-racing/sentinel/oauth/pkg/sentinel" + "github.com/gaucho-racing/sentinel/oauth/service" + "github.com/gin-gonic/gin" +) + +type tokenRequest struct { + EntityID string `json:"entity_id"` + ClientID string `json:"client_id"` + Scope string `json:"scope"` + ExpiresIn int `json:"expires_in"` + Claims map[string]interface{} `json:"claims"` +} + +type tokenResponse struct { + Token string `json:"token"` + TokenID string `json:"token_id"` +} + +type exchangeTokenResponse struct { + AccessToken string `json:"access_token"` + RefreshToken string `json:"refresh_token,omitempty"` + TokenType string `json:"token_type"` + ExpiresIn int `json:"expires_in"` + Scope string `json:"scope"` +} + +// ExchangeToken handles the OAuth token exchange. +// Supports grant_type=authorization_code and grant_type=refresh_token. +func ExchangeToken(c *gin.Context) { + grantType := c.PostForm("grant_type") + switch grantType { + case "authorization_code": + handleAuthorizationCodeExchange(c) + case "refresh_token": + handleRefreshTokenExchange(c) + default: + c.JSON(http.StatusBadRequest, gin.H{"error": "unsupported grant_type"}) + } +} + +func handleAuthorizationCodeExchange(c *gin.Context) { + code := c.PostForm("code") + if code == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "code is required"}) + return + } + + clientID, clientSecret, hasAuth := c.Request.BasicAuth() + if !hasAuth { + clientID = c.PostForm("client_id") + clientSecret = c.PostForm("client_secret") + } + if clientID == "" || clientSecret == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "client credentials are required"}) + return + } + + redirectURI := c.PostForm("redirect_uri") + if redirectURI == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "redirect_uri is required"}) + return + } + + // Validate client credentials via core + var app applicationResponse + err := sentinel.Get("/applications/client/"+clientID, &app) + if err != nil { + c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid client credentials"}) + return + } + // We need the secret to validate — fetch via internal endpoint + if !validateClientSecret(clientID, clientSecret) { + c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid client credentials"}) + return + } + + authCode, err := service.VerifyAuthorizationCode(code) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + if authCode.ClientID != clientID { + c.JSON(http.StatusBadRequest, gin.H{"error": "client_id mismatch"}) + return + } + if authCode.RedirectURI != redirectURI { + c.JSON(http.StatusBadRequest, gin.H{"error": "redirect_uri mismatch"}) + return + } + + claims := service.BuildTokenClaims(authCode.EntityID, clientID) + + // Generate access token via core + accessToken, accessTokenID, err := generateToken(authCode.EntityID, clientID, authCode.Scope, config.AccessTokenTTL, claims) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to generate access token"}) + return + } + + // Generate refresh token via core + refreshToken, refreshTokenID, err := generateToken(authCode.EntityID, clientID, authCode.Scope+" refresh_token", config.RefreshTokenTTL, claims) + if err != nil { + logger.SugarLogger.Errorf("Failed to generate refresh token: %v", err) + refreshToken = "" + refreshTokenID = "" + } + + sentinel.Post("/core/entity/logins", map[string]string{ + "entity_id": authCode.EntityID, + "client_id": clientID, + "scope": authCode.Scope, + "access_token_id": accessTokenID, + "refresh_token_id": refreshTokenID, + "ip_address": GetClientIP(c), + }, nil) + + c.JSON(http.StatusOK, exchangeTokenResponse{ + AccessToken: accessToken, + RefreshToken: refreshToken, + TokenType: "Bearer", + ExpiresIn: config.AccessTokenTTL, + Scope: authCode.Scope, + }) +} + +func handleRefreshTokenExchange(c *gin.Context) { + refreshToken := c.PostForm("refresh_token") + if refreshToken == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "refresh_token is required"}) + return + } + + clientID, clientSecret, hasAuth := c.Request.BasicAuth() + if !hasAuth { + clientID = c.PostForm("client_id") + clientSecret = c.PostForm("client_secret") + } + if clientID == "" || clientSecret == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "client credentials are required"}) + return + } + + if !validateClientSecret(clientID, clientSecret) { + c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid client credentials"}) + return + } + + // Validate the refresh token via core + var claims map[string]interface{} + err := sentinel.Post("/core/token/validate", map[string]string{"token": refreshToken}, &claims) + if err != nil { + c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid or expired refresh token"}) + return + } + + entityID, _ := claims["sub"].(string) + scope, _ := claims["scope"].(string) + + if !service.ScopesContain(scope, "refresh_token") { + c.JSON(http.StatusUnauthorized, gin.H{"error": "provided token is not a refresh token"}) + return + } + + // Revoke the old refresh token + if tokenID, ok := claims["jti"].(string); ok { + sentinel.Delete("/core/token/"+tokenID, nil) + } + + // Strip refresh_token from scope for the access token + accessScope := service.RemoveScope(scope, "refresh_token") + + newClaims := service.BuildTokenClaims(entityID, clientID) + + // Generate new access token + accessToken, accessTokenID, err := generateToken(entityID, clientID, accessScope, config.AccessTokenTTL, newClaims) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to generate access token"}) + return + } + + // Generate new refresh token (keep refresh_token in scope) + newRefreshToken, newRefreshTokenID, err := generateToken(entityID, clientID, scope, config.RefreshTokenTTL, newClaims) + if err != nil { + logger.SugarLogger.Errorf("Failed to generate refresh token: %v", err) + newRefreshToken = "" + newRefreshTokenID = "" + } + + sentinel.Post("/core/entity/logins", map[string]string{ + "entity_id": entityID, + "client_id": clientID, + "scope": accessScope, + "access_token_id": accessTokenID, + "refresh_token_id": newRefreshTokenID, + "ip_address": GetClientIP(c), + }, nil) + + c.JSON(http.StatusOK, exchangeTokenResponse{ + AccessToken: accessToken, + RefreshToken: newRefreshToken, + TokenType: "Bearer", + ExpiresIn: config.AccessTokenTTL, + Scope: accessScope, + }) +} + +func generateToken(entityID string, clientID string, scope string, expiresIn int, claims map[string]interface{}) (string, string, error) { + var result tokenResponse + err := sentinel.Post("/core/token", tokenRequest{ + EntityID: entityID, + ClientID: clientID, + Scope: scope, + ExpiresIn: expiresIn, + Claims: claims, + }, &result) + if err != nil { + return "", "", err + } + return result.Token, result.TokenID, nil +} + + +func validateClientSecret(clientID string, clientSecret string) bool { + var result map[string]interface{} + err := sentinel.Post("/core/applications/verify", map[string]string{ + "client_id": clientID, + "client_secret": clientSecret, + }, &result) + return err == nil +} diff --git a/oauth/config/banner.go b/oauth/config/banner.go new file mode 100644 index 0000000..e2896d7 --- /dev/null +++ b/oauth/config/banner.go @@ -0,0 +1,20 @@ +package config + +import "github.com/fatih/color" + +var Banner = ` +███████╗███████╗███╗ ██╗████████╗██╗███╗ ██╗███████╗██╗ +██╔════╝██╔════╝████╗ ██║╚══██╔══╝██║████╗ ██║██╔════╝██║ +███████╗█████╗ ██╔██╗ ██║ ██║ ██║██╔██╗ ██║█████╗ ██║ +╚════██║██╔══╝ ██║╚██╗██║ ██║ ██║██║╚██╗██║██╔══╝ ██║ +███████║███████╗██║ ╚████║ ██║ ██║██║ ╚████║███████╗███████╗ +╚══════╝╚══════╝╚═╝ ╚═══╝ ╚═╝ ╚═╝╚═╝ ╚═══╝╚══════╝╚══════╝ +` + +func PrintStartupBanner() { + banner := color.New(color.Bold, color.FgHiGreen).PrintlnFunc() + banner(Banner) + version := color.New(color.Bold, color.FgGreen).PrintlnFunc() + version("Running " + Service.FormattedNameWithVersion() + " [ENV: " + Env + "]") + println() +} diff --git a/oauth/config/config.go b/oauth/config/config.go new file mode 100644 index 0000000..f061c9d --- /dev/null +++ b/oauth/config/config.go @@ -0,0 +1,45 @@ +package config + +import ( + "os" + + "github.com/bk1031/rincon-go/v2" +) + +var Service rincon.Service = rincon.Service{ + Name: "Sentinel OAuth", + Version: "5.0.0", + Endpoint: os.Getenv("SERVICE_ENDPOINT"), + HealthCheck: os.Getenv("SERVICE_HEALTH_CHECK"), +} + +var Routes = []rincon.Route{ + { + Route: "/oauth/**", + Method: "*", + }, + { + Route: "/auth/**", + Method: "*", + }, + { + Route: "/.well-known/**", + Method: "GET", + }, +} + +var Env = os.Getenv("ENV") +var Port = os.Getenv("PORT") + +var AccessTokenTTL int +var RefreshTokenTTL int + +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") + +func IsProduction() bool { + return Env == "PROD" +} diff --git a/oauth/config/verify.go b/oauth/config/verify.go new file mode 100644 index 0000000..39aebb9 --- /dev/null +++ b/oauth/config/verify.go @@ -0,0 +1,55 @@ +package config + +import ( + "os" + "strconv" + + "github.com/gaucho-racing/sentinel/oauth/pkg/logger" +) + +func Verify() { + if Env == "" { + Env = "PROD" + logger.SugarLogger.Infof("ENV is not set, defaulting to %s", Env) + } + if Port == "" { + Port = "9997" + logger.SugarLogger.Infof("PORT is not set, defaulting to %s", Port) + } + if DatabaseHost == "" { + DatabaseHost = "localhost" + logger.SugarLogger.Infof("DATABASE_HOST is not set, defaulting to %s", DatabaseHost) + } + if DatabasePort == "" { + DatabasePort = "5432" + logger.SugarLogger.Infof("DATABASE_PORT is not set, defaulting to %s", DatabasePort) + } + if DatabaseUser == "" { + DatabaseUser = "postgres" + logger.SugarLogger.Infof("DATABASE_USER is not set, defaulting to %s", DatabaseUser) + } + if DatabasePassword == "" { + DatabasePassword = "password" + logger.SugarLogger.Infof("DATABASE_PASSWORD is not set, defaulting to %s", DatabasePassword) + } + if DatabaseName == "" { + DatabaseName = "sentinel" + logger.SugarLogger.Infof("DATABASE_NAME is not set, defaulting to %s", DatabaseName) + } + AccessTokenTTL = parseIntEnv("ACCESS_TOKEN_TTL", 30*60) + RefreshTokenTTL = parseIntEnv("REFRESH_TOKEN_TTL", 7*24*60*60) +} + +func parseIntEnv(key string, fallback int) int { + raw := os.Getenv(key) + if raw == "" { + logger.SugarLogger.Infof("%s is not set, defaulting to %d", key, fallback) + return fallback + } + n, err := strconv.Atoi(raw) + if err != nil { + logger.SugarLogger.Warnf("%s is not a valid integer (%q), defaulting to %d", key, raw, fallback) + return fallback + } + return n +} diff --git a/oauth/database/db.go b/oauth/database/db.go new file mode 100644 index 0000000..ad24133 --- /dev/null +++ b/oauth/database/db.go @@ -0,0 +1,36 @@ +package database + +import ( + "fmt" + "time" + + "github.com/gaucho-racing/sentinel/oauth/config" + "github.com/gaucho-racing/sentinel/oauth/model" + "github.com/gaucho-racing/sentinel/oauth/pkg/logger" + "gorm.io/driver/postgres" + "gorm.io/gorm" +) + +var DB *gorm.DB + +var dbRetries = 0 + +func Init() { + dsn := fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%s sslmode=disable TimeZone=UTC", config.DatabaseHost, config.DatabaseUser, config.DatabasePassword, config.DatabaseName, config.DatabasePort) + db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{}) + if err != nil { + if dbRetries < 5 { + dbRetries++ + logger.SugarLogger.Errorln("failed to connect database, retrying in 5s... ") + time.Sleep(time.Second * 5) + Init() + } else { + logger.SugarLogger.Fatalf("failed to connect database after 5 attempts") + } + } else { + logger.SugarLogger.Infoln("Connected to database") + db.AutoMigrate(&model.AuthorizationCode{}) + logger.SugarLogger.Infoln("AutoMigration complete") + DB = db + } +} diff --git a/oauth/go.mod b/oauth/go.mod new file mode 100644 index 0000000..000b584 --- /dev/null +++ b/oauth/go.mod @@ -0,0 +1,55 @@ +module github.com/gaucho-racing/sentinel/oauth + +go 1.25.6 + +require ( + github.com/bk1031/rincon-go/v2 v2.0.0 + github.com/fatih/color v1.19.0 + github.com/gin-contrib/cors v1.7.7 + github.com/gin-gonic/gin v1.12.0 + github.com/go-resty/resty/v2 v2.17.2 + go.uber.org/zap v1.27.1 + gorm.io/driver/postgres v1.6.0 + gorm.io/gorm v1.31.1 +) + +require ( + github.com/bytedance/gopkg v0.1.3 // indirect + github.com/bytedance/sonic v1.15.0 // indirect + github.com/bytedance/sonic/loader v0.5.0 // indirect + github.com/cloudwego/base64x v0.1.6 // indirect + github.com/gabriel-vasile/mimetype v1.4.12 // indirect + github.com/gin-contrib/sse v1.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.30.1 // indirect + github.com/goccy/go-json v0.10.5 // indirect + github.com/goccy/go-yaml v1.19.2 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + github.com/jackc/pgx/v5 v5.6.0 // indirect + github.com/jackc/puddle/v2 v2.2.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.3.0 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/mattn/go-colorable v0.1.14 // 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.4 // indirect + github.com/quic-go/qpack v0.6.0 // indirect + github.com/quic-go/quic-go v0.59.0 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.3.1 // indirect + go.mongodb.org/mongo-driver/v2 v2.5.0 // indirect + go.uber.org/multierr v1.10.0 // indirect + golang.org/x/arch v0.23.0 // indirect + golang.org/x/crypto v0.48.0 // indirect + golang.org/x/net v0.51.0 // indirect + golang.org/x/sync v0.20.0 // indirect + golang.org/x/sys v0.42.0 // indirect + golang.org/x/text v0.35.0 // indirect + google.golang.org/protobuf v1.36.10 // indirect +) diff --git a/oauth/go.sum b/oauth/go.sum new file mode 100644 index 0000000..5012b82 --- /dev/null +++ b/oauth/go.sum @@ -0,0 +1,126 @@ +github.com/bk1031/rincon-go/v2 v2.0.0 h1:nmDHQNZI/AFfW+ZGTGoxpNPrv3OYXQ09anX+fCoiQsQ= +github.com/bk1031/rincon-go/v2 v2.0.0/go.mod h1:287Zc8PvUNnJuAwpt9XVuYUL8k4wrXg3Fa3L0KEmAB4= +github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M= +github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM= +github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE= +github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k= +github.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE= +github.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo= +github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= +github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= +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/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w= +github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE= +github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw= +github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= +github.com/gin-contrib/cors v1.7.7 h1:Oh9joP463x7Mw72vhvJ61YQm8ODh9b04YR7vsOErD0Q= +github.com/gin-contrib/cors v1.7.7/go.mod h1:K5tW0RkzJtWSiOdikXloy8VEZlgdVNpHNw8FpjUPNrE= +github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= +github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= +github.com/gin-gonic/gin v1.12.0 h1:b3YAbrZtnf8N//yjKeU2+MQsh2mY5htkZidOM7O0wG8= +github.com/gin-gonic/gin v1.12.0/go.mod h1:VxccKfsSllpKshkBWgVgRniFFAzFb9csfngsqANjnLc= +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.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w= +github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM= +github.com/go-resty/resty/v2 v2.17.2 h1:FQW5oHYcIlkCNrMD2lloGScxcHJ0gkjshV3qcQAyHQk= +github.com/go-resty/resty/v2 v2.17.2/go.mod h1:kCKZ3wWmwJaNc7S29BRtUhJwy7iqmn+2mLtQrOyQlVA= +github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= +github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM= +github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY= +github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw= +github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= +github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +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.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= +github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +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/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +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.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= +github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +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/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8= +github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII= +github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw= +github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU= +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.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +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.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY= +github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= +go.mongodb.org/mongo-driver/v2 v2.5.0 h1:yXUhImUjjAInNcpTcAlPHiT7bIXhshCTL3jVBkF3xaE= +go.mongodb.org/mongo-driver/v2 v2.5.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0= +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/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= +go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= +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.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= +go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +golang.org/x/arch v0.23.0 h1:lKF64A2jF6Zd8L0knGltUnegD62JMFBiCPBmQpToHhg= +golang.org/x/arch v0.23.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A= +golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= +golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= +golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo= +golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= +golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= +golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA= +golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= +golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= +google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= +google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +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/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4= +gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo= +gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg= +gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs= diff --git a/oauth/main.go b/oauth/main.go new file mode 100644 index 0000000..1c7e670 --- /dev/null +++ b/oauth/main.go @@ -0,0 +1,21 @@ +package main + +import ( + "github.com/gaucho-racing/sentinel/oauth/api" + "github.com/gaucho-racing/sentinel/oauth/config" + "github.com/gaucho-racing/sentinel/oauth/database" + "github.com/gaucho-racing/sentinel/oauth/pkg/logger" + "github.com/gaucho-racing/sentinel/oauth/pkg/rincon" +) + +func main() { + logger.Init(config.IsProduction()) + defer logger.Logger.Sync() + + config.Verify() + config.PrintStartupBanner() + rincon.Init(&config.Service, &config.Routes) + database.Init() + + api.Run() +} diff --git a/oauth/model/authorization_code.go b/oauth/model/authorization_code.go new file mode 100644 index 0000000..86ced2a --- /dev/null +++ b/oauth/model/authorization_code.go @@ -0,0 +1,17 @@ +package model + +import "time" + +type AuthorizationCode struct { + Code string `json:"code" gorm:"primaryKey"` + EntityID string `json:"entity_id"` + ClientID string `json:"client_id"` + Scope string `json:"scope"` + RedirectURI string `json:"redirect_uri"` + ExpiresAt time.Time `json:"expires_at"` + CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"` +} + +func (AuthorizationCode) TableName() string { + return "authorization_code" +} diff --git a/oauth/model/scope.go b/oauth/model/scope.go new file mode 100644 index 0000000..d184755 --- /dev/null +++ b/oauth/model/scope.go @@ -0,0 +1,10 @@ +package model + +var ValidScopes = map[string]string{ + "user:read": "Read user and entity profile information", + "user:write": "Update user profile information", + "groups:read": "Read group memberships", + "applications:read": "Read application details", + "applications:write": "Manage applications", + "sentinel:all": "Full internal access (not available to third-party apps)", +} diff --git a/oauth/pkg/logger/logger.go b/oauth/pkg/logger/logger.go new file mode 100644 index 0000000..2722d06 --- /dev/null +++ b/oauth/pkg/logger/logger.go @@ -0,0 +1,16 @@ +package logger + +import ( + "go.uber.org/zap" +) + +var Logger *zap.Logger +var SugarLogger *zap.SugaredLogger + +func Init(production bool) { + Logger = zap.Must(zap.NewProduction()) + if !production { + Logger = zap.Must(zap.NewDevelopment(zap.AddCaller(), zap.AddStacktrace(zap.ErrorLevel))) + } + SugarLogger = Logger.Sugar() +} diff --git a/oauth/pkg/rincon/rincon.go b/oauth/pkg/rincon/rincon.go new file mode 100644 index 0000000..fe5e959 --- /dev/null +++ b/oauth/pkg/rincon/rincon.go @@ -0,0 +1,60 @@ +package rincon + +import ( + "os" + "time" + + "github.com/bk1031/rincon-go/v2" + "github.com/gaucho-racing/sentinel/oauth/pkg/logger" +) + +var RinconClient *rincon.Client +var RinconUser = os.Getenv("RINCON_USER") +var RinconPassword = os.Getenv("RINCON_PASSWORD") +var RinconEndpoint = os.Getenv("RINCON_ENDPOINT") + +func Init(service *rincon.Service, routes *[]rincon.Route) { + if RinconUser == "" || RinconPassword == "" || RinconEndpoint == "" { + logger.SugarLogger.Errorln("Rincon user, password, or endpoint is not set") + return + } + client := createClient(RinconEndpoint, RinconUser, RinconPassword) + if client == nil { + return + } + id, err := client.Register(*service, *routes) + if err != nil { + logger.SugarLogger.Fatalf("Failed to register service with Rincon: %v", err) + return + } + logger.SugarLogger.Infof("Registered service with ID: %d", id) + RinconClient = client + *service = *client.Service() +} + +func createClient(endpoint string, user string, password string) *rincon.Client { + rinconRetries := 0 + for rinconRetries < 5 { + client, err := rincon.NewClient(rincon.Config{ + BaseURL: endpoint, + HeartbeatMode: rincon.ServerHeartbeat, + HeartbeatInterval: 60, + AuthUser: user, + AuthPassword: password, + }) + if err != nil { + if rinconRetries < 5 { + logger.SugarLogger.Errorf("Failed to create Rincon client with %s: %v, retrying in 5s...", endpoint, err) + rinconRetries++ + time.Sleep(time.Second * 5) + } else { + logger.SugarLogger.Errorln("Failed to create Rincon client after 5 attempts") + return nil + } + } else { + logger.SugarLogger.Infof("Created Rincon client with endpoint %s", endpoint) + return client + } + } + return nil +} diff --git a/oauth/pkg/sentinel/sentinel.go b/oauth/pkg/sentinel/sentinel.go new file mode 100644 index 0000000..963d794 --- /dev/null +++ b/oauth/pkg/sentinel/sentinel.go @@ -0,0 +1,121 @@ +package sentinel + +import ( + "encoding/json" + "errors" + "fmt" + + "github.com/gaucho-racing/sentinel/oauth/pkg/logger" + "github.com/gaucho-racing/sentinel/oauth/pkg/rincon" + "github.com/go-resty/resty/v2" +) + +// Sentinel-side error categories — wrapped into APIError.Err so callers +// can errors.Is on them and pick the right user-facing message. +var ( + ErrRinconUninitialized = errors.New("rincon client not initialized") + ErrRouteResolution = errors.New("rincon could not resolve route") +) + +var client = resty.New() + +// APIError is returned by every method in this package. Status == 0 means no +// HTTP response was received (route resolution failure or transport error). +// Status > 0 means the upstream replied with that status code. Callers should +// use errors.As to inspect it and decide how to surface to their own +// response — most importantly, a 4xx from upstream should NOT collapse to a +// generic "service unavailable" on the user-facing side. +type APIError struct { + Method string + Route string + Status int // 0 when no HTTP response was received + Body string // raw response body + Message string // parsed "error" field from a JSON body, when present + Err error // underlying transport or resolution error +} + +func (e *APIError) Error() string { + if e.Status == 0 { + if e.Err != nil { + return fmt.Sprintf("%s %s: %v", e.Method, e.Route, e.Err) + } + return fmt.Sprintf("%s %s: no response", e.Method, e.Route) + } + if e.Message != "" { + return fmt.Sprintf("%s %s returned %d: %s", e.Method, e.Route, e.Status, e.Message) + } + return fmt.Sprintf("%s %s returned %d", e.Method, e.Route, e.Status) +} + +func (e *APIError) Unwrap() error { return e.Err } + +func resolveURL(route string, method string) (string, error) { + if rincon.RinconClient == nil { + return "", ErrRinconUninitialized + } + service, err := rincon.RinconClient.MatchRoute(route, method) + if err != nil { + return "", fmt.Errorf("%w: %s: %v", ErrRouteResolution, route, err) + } + return service.Endpoint + route, nil +} + +// do executes the request and converts any failure path into an *APIError. +// success returns nil; resty unmarshals the response body into result for us. +func do(method, route string, body, result interface{}, headers []map[string]string) error { + url, err := resolveURL(route, method) + if err != nil { + return &APIError{Method: method, Route: route, Err: err} + } + req := client.R() + if body != nil { + req = req.SetBody(body) + } + if result != nil { + req = req.SetResult(result) + } + if len(headers) > 0 { + req = req.SetHeaders(headers[0]) + } + resp, err := req.Execute(method, url) + if err != nil { + return &APIError{Method: method, Route: route, Err: err} + } + if resp.IsError() { + ae := &APIError{ + Method: method, + Route: route, + Status: resp.StatusCode(), + Body: resp.String(), + } + var parsed struct { + Error string `json:"error"` + } + if json.Unmarshal(resp.Body(), &parsed) == nil && parsed.Error != "" { + ae.Message = parsed.Error + } + logger.SugarLogger.Errorf("%s %s returned %d: %s", method, route, resp.StatusCode(), resp.String()) + return ae + } + return nil +} + +func Get(route string, result interface{}, headers ...map[string]string) error { + return do("GET", route, nil, result, headers) +} + +func Post(route string, body interface{}, result interface{}, headers ...map[string]string) error { + return do("POST", route, body, result, headers) +} + +func Put(route string, body interface{}, result interface{}, headers ...map[string]string) error { + return do("PUT", route, body, result, headers) +} + +func Patch(route string, body interface{}, result interface{}, headers ...map[string]string) error { + return do("PATCH", route, body, result, headers) +} + +func Delete(route string, result interface{}, headers ...map[string]string) error { + return do("DELETE", route, nil, result, headers) +} diff --git a/oauth/service/authorization_code.go b/oauth/service/authorization_code.go new file mode 100644 index 0000000..4bfa9d9 --- /dev/null +++ b/oauth/service/authorization_code.go @@ -0,0 +1,48 @@ +package service + +import ( + "crypto/rand" + "fmt" + "time" + + "github.com/gaucho-racing/sentinel/oauth/database" + "github.com/gaucho-racing/sentinel/oauth/model" +) + +func GenerateAuthorizationCode(entityID string, clientID string, scope string, redirectURI string) (model.AuthorizationCode, error) { + code := generateCryptoString(32) + authCode := model.AuthorizationCode{ + Code: code, + EntityID: entityID, + ClientID: clientID, + Scope: scope, + RedirectURI: redirectURI, + ExpiresAt: time.Now().Add(5 * time.Minute), + } + if err := database.DB.Create(&authCode).Error; err != nil { + return model.AuthorizationCode{}, err + } + return authCode, nil +} + +func VerifyAuthorizationCode(code string) (model.AuthorizationCode, error) { + var authCode model.AuthorizationCode + if err := database.DB.Where("code = ?", code).First(&authCode).Error; err != nil { + return model.AuthorizationCode{}, fmt.Errorf("invalid authorization code") + } + defer database.DB.Where("code = ?", code).Delete(&model.AuthorizationCode{}) + if time.Now().After(authCode.ExpiresAt) { + return model.AuthorizationCode{}, fmt.Errorf("authorization code expired") + } + return authCode, nil +} + +func generateCryptoString(length int) string { + const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + b := make([]byte, length) + rand.Read(b) + for i := range b { + b[i] = charset[int(b[i])%len(charset)] + } + return string(b) +} diff --git a/oauth/service/scope.go b/oauth/service/scope.go new file mode 100644 index 0000000..84ba6bf --- /dev/null +++ b/oauth/service/scope.go @@ -0,0 +1,35 @@ +package service + +import ( + "strings" + + "github.com/gaucho-racing/sentinel/oauth/model" +) + +func ValidateScopes(scopes string) bool { + for _, scope := range strings.Fields(scopes) { + if _, ok := model.ValidScopes[scope]; !ok { + return false + } + } + return true +} + +func ScopesContain(scopes string, target string) bool { + for _, scope := range strings.Fields(scopes) { + if scope == target { + return true + } + } + return false +} + +func RemoveScope(scopes string, target string) string { + var result []string + for _, scope := range strings.Fields(scopes) { + if scope != target { + result = append(result, scope) + } + } + return strings.Join(result, " ") +} diff --git a/oauth/service/token_claims.go b/oauth/service/token_claims.go new file mode 100644 index 0000000..1e08b26 --- /dev/null +++ b/oauth/service/token_claims.go @@ -0,0 +1,52 @@ +package service + +import ( + "github.com/gaucho-racing/sentinel/oauth/pkg/logger" + "github.com/gaucho-racing/sentinel/oauth/pkg/sentinel" +) + +type entityResponse struct { + ID string `json:"id"` + Type string `json:"type"` + User *idHolder `json:"user"` + ServiceAccount *idHolder `json:"service_account"` +} + +type idHolder struct { + ID string `json:"id"` +} + +type groupResponse struct { + ID string `json:"id"` +} + +// BuildTokenClaims resolves identity and group-membership claims for an entity +// by querying core. This is where future per-app / per-entity overrides will +// be applied before a token is signed. +func BuildTokenClaims(entityID string, clientID string) map[string]interface{} { + claims := map[string]interface{}{} + + var entity entityResponse + if err := sentinel.Get("/core/entity/"+entityID, &entity); err != nil { + logger.SugarLogger.Errorf("Failed to load entity %s for token claims: %v", entityID, err) + return claims + } + claims["entity_type"] = entity.Type + if entity.User != nil { + claims["user_id"] = entity.User.ID + } + if entity.ServiceAccount != nil { + claims["service_account_id"] = entity.ServiceAccount.ID + } + + groupIDs := []string{} + var groups []groupResponse + if err := sentinel.Get("/core/entity/"+entityID+"/groups", &groups); err == nil { + for _, g := range groups { + groupIDs = append(groupIDs, g.ID) + } + } + claims["groups"] = groupIDs + + return claims +} 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/scripts/run-discord.sh b/scripts/run-discord.sh new file mode 100755 index 0000000..87d224c --- /dev/null +++ b/scripts/run-discord.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +# check if go.mod exists in current directory +if [ ! -f discord/go.mod ]; then + echo "discord/go.mod not found" + echo "Please make sure you are in the root sentinel directory" + exit 1 +fi + +# check if .env exists in current directory +if [ ! -f .env ]; then + echo ".env not found" + echo "Please make sure the .env file is present in the current directory" + exit 1 +fi + + +set -a +. .env +cd discord +go get . +go run main.go diff --git a/scripts/run-oauth.sh b/scripts/run-oauth.sh new file mode 100755 index 0000000..11fed1b --- /dev/null +++ b/scripts/run-oauth.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +# check if go.mod exists in current directory +if [ ! -f oauth/go.mod ]; then + echo "oauth/go.mod not found" + echo "Please make sure you are in the root sentinel directory" + exit 1 +fi + +# check if .env exists in current directory +if [ ! -f .env ]; then + echo ".env not found" + echo "Please make sure the .env file is present in the current directory" + exit 1 +fi + + +set -a +. .env +cd oauth +go get . +go run main.go 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/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/.env.example b/web/.env.example new file mode 100644 index 0000000..e9832e4 --- /dev/null +++ b/web/.env.example @@ -0,0 +1 @@ +VITE_API_URL=http://localhost:10310 \ No newline at end of file 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/.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 index bb15685..7dbf7eb 100644 --- a/web/README.md +++ b/web/README.md @@ -4,27 +4,70 @@ This template provides a minimal setup to get React working in Vite with HMR and 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 +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Oxc](https://oxc.rs) +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) -## Expanding the ESLint configuration +## React Compiler + +The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation). -If you are developing a production application, we recommend updating the configuration to enable type aware lint rules: +## Expanding the ESLint configuration -- Configure the top-level `parserOptions` property like this: +If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules: ```js -export default { - // other rules... - parserOptions: { - ecmaVersion: "latest", - sourceType: "module", - project: ["./tsconfig.json", "./tsconfig.node.json"], - tsconfigRootDir: __dirname, +export default defineConfig([ + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + // Other configs... + + // Remove tseslint.configs.recommended and replace with this + tseslint.configs.recommendedTypeChecked, + // Alternatively, use this for stricter rules + tseslint.configs.strictTypeChecked, + // Optionally, add this for stylistic rules + tseslint.configs.stylisticTypeChecked, + + // Other configs... + ], + languageOptions: { + parserOptions: { + project: ['./tsconfig.node.json', './tsconfig.app.json'], + tsconfigRootDir: import.meta.dirname, + }, + // other options... + }, }, -}; +]) ``` -- 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 +You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules: + +```js +// eslint.config.js +import reactX from 'eslint-plugin-react-x' +import reactDom from 'eslint-plugin-react-dom' + +export default defineConfig([ + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + // Other configs... + // Enable lint rules for React + reactX.configs['recommended-typescript'], + // Enable lint rules for React DOM + reactDom.configs.recommended, + ], + languageOptions: { + parserOptions: { + project: ['./tsconfig.node.json', './tsconfig.app.json'], + tsconfigRootDir: import.meta.dirname, + }, + // other options... + }, + }, +]) +``` diff --git a/web/components.json b/web/components.json index 73e99a6..5c23ec4 100644 --- a/web/components.json +++ b/web/components.json @@ -1,17 +1,25 @@ { "$schema": "https://ui.shadcn.com/schema.json", - "style": "default", + "style": "radix-nova", "rsc": false, "tsx": true, "tailwind": { - "config": "tailwind.config.js", + "config": "", "css": "src/index.css", - "baseColor": "slate", - "cssVariables": false, + "baseColor": "neutral", + "cssVariables": true, "prefix": "" }, + "iconLibrary": "lucide", + "rtl": false, "aliases": { "components": "@/components", - "utils": "@/lib/utils" - } + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "menuColor": "default", + "menuAccent": "subtle", + "registries": {} } diff --git a/web/eslint.config.js b/web/eslint.config.js new file mode 100644 index 0000000..ef614d2 --- /dev/null +++ b/web/eslint.config.js @@ -0,0 +1,22 @@ +import js from '@eslint/js' +import globals from 'globals' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' +import tseslint from 'typescript-eslint' +import { defineConfig, globalIgnores } from 'eslint/config' + +export default defineConfig([ + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + js.configs.recommended, + tseslint.configs.recommended, + reactHooks.configs.flat.recommended, + reactRefresh.configs.vite, + ], + languageOptions: { + globals: globals.browser, + }, + }, +]) diff --git a/web/index.html b/web/index.html index 5f2119a..3aad7eb 100644 --- a/web/index.html +++ b/web/index.html @@ -1,11 +1,10 @@ - + - - + - GR Sentinel + 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 index 5f601e9..dc5143f 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -1,1201 +1,1271 @@ { - "name": "sentinel", + "name": "web", "version": "0.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "sentinel", + "name": "web", "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" + "@fontsource-variable/geist": "^5.2.8", + "@fontsource-variable/inter": "^5.2.8", + "@tailwindcss/vite": "^4.2.4", + "@tanstack/react-query": "^5.100.10", + "axios": "^1.15.2", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "cmdk": "^1.1.1", + "lucide-react": "^1.11.0", + "motion": "^12.38.0", + "next-themes": "^0.4.6", + "radix-ui": "^1.4.3", + "react": "^19.2.5", + "react-dom": "^19.2.5", + "react-international-phone": "^4.8.0", + "react-router-dom": "^7.14.2", + "shadcn": "^4.5.0", + "sonner": "^2.0.7", + "tailwind-merge": "^3.5.0", + "tailwindcss": "^4.2.4", + "tw-animate-css": "^1.4.0" }, "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, + "@eslint/js": "^10.0.1", + "@types/node": "^24.12.2", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "eslint": "^10.2.1", + "eslint-plugin-react-hooks": "^7.1.1", + "eslint-plugin-react-refresh": "^0.5.2", + "globals": "^17.5.0", + "typescript": "~6.0.2", + "typescript-eslint": "^8.58.2", + "vite": "^8.0.10" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, "engines": { - "node": ">=0.10.0" + "node": ">=6.9.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==", + "node_modules/@babel/compat-data": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "license": "MIT", "engines": { - "node": ">=10" + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "opencollective", + "url": "https://opencollective.com/babel" } }, - "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==", + "node_modules/@babel/generator": { + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "license": "MIT", "dependencies": { - "regenerator-runtime": "^0.14.0" + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" }, "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" - ], + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", + "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.3" + }, "engines": { - "node": ">=12" + "node": ">=6.9.0" } }, - "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" - ], + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, "engines": { - "node": ">=12" + "node": ">=6.9.0" } }, - "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" - ], + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.6.tgz", + "integrity": "sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-member-expression-to-functions": "^7.28.5", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/helper-replace-supers": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/traverse": "^7.28.6", + "semver": "^6.3.1" + }, "engines": { - "node": ">=12" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "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" - ], + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "license": "MIT", "engines": { - "node": ">=12" + "node": ">=6.9.0" } }, - "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" - ], + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.28.5.tgz", + "integrity": "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5" + }, "engines": { - "node": ">=12" + "node": ">=6.9.0" } }, - "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" - ], + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, "engines": { - "node": ">=12" + "node": ">=6.9.0" } }, - "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" - ], + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, "engines": { - "node": ">=12" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "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" - ], + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", + "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.1" + }, "engines": { - "node": ">=12" + "node": ">=6.9.0" } }, - "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" - ], + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "license": "MIT", "engines": { - "node": ">=12" + "node": ">=6.9.0" } }, - "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" - ], + "node_modules/@babel/helper-replace-supers": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.28.6.tgz", + "integrity": "sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==", + "license": "MIT", + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.28.5", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/traverse": "^7.28.6" + }, "engines": { - "node": ">=12" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "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" - ], + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", + "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, "engines": { - "node": ">=12" + "node": ">=6.9.0" } }, - "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" - ], + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "license": "MIT", "engines": { - "node": ">=12" + "node": ">=6.9.0" } }, - "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" - ], + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "license": "MIT", "engines": { - "node": ">=12" + "node": ">=6.9.0" } }, - "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" - ], + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "license": "MIT", "engines": { - "node": ">=12" + "node": ">=6.9.0" } }, - "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" - ], + "node_modules/@babel/helpers": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", + "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0" + }, "engines": { - "node": ">=12" + "node": ">=6.9.0" } }, - "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" - ], + "node_modules/@babel/parser": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, "engines": { - "node": ">=12" + "node": ">=6.0.0" } }, - "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" - ], + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz", + "integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, "engines": { - "node": ">=12" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "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" - ], + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz", + "integrity": "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, "engines": { - "node": ">=12" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "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" - ], + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.28.6.tgz", + "integrity": "sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" + }, "engines": { - "node": ">=12" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "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" - ], + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.6.tgz", + "integrity": "sha512-0YWL2RFxOqEm9Efk5PvreamxPME8OyY0wM5wh5lHjF+VtVhdneCWGzZeSqzOfiobVqQaNCd2z0tQvnI9DaPWPw==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/plugin-syntax-typescript": "^7.28.6" + }, "engines": { - "node": ">=12" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "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" - ], + "node_modules/@babel/preset-typescript": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.28.5.tgz", + "integrity": "sha512-+bQy5WOI2V6LJZpPVxY+yp66XdZ2yifu0Mc1aP5CQKgjn4QM5IN2i5fAZ4xKop47pr8rpVhiAeu+nDQa12C8+g==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.27.1", + "@babel/plugin-transform-typescript": "^7.28.5" + }, "engines": { - "node": ">=12" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "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" - ], + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, "engines": { - "node": ">=12" + "node": ">=6.9.0" } }, - "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, + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "license": "MIT", "dependencies": { - "eslint-visitor-keys": "^3.3.0" + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + "node": ">=6.9.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, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + "node": ">=6.9.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, + "node_modules/@dotenvx/dotenvx": { + "version": "1.63.0", + "resolved": "https://registry.npmjs.org/@dotenvx/dotenvx/-/dotenvx-1.63.0.tgz", + "integrity": "sha512-jjkmzIRu19uH78AjFInqfcALehbDCZZ7M09hurVawyqNxtOXEg2LR73L59y4QnzfYDEzjbhVzGAd2uDHu0D1aQ==", + "license": "BSD-3-Clause", "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" + "commander": "^11.1.0", + "dotenv": "^17.2.1", + "eciesjs": "^0.4.10", + "execa": "^5.1.1", + "fdir": "^6.2.0", + "ignore": "^5.3.0", + "object-treeify": "1.1.33", + "picomatch": "^4.0.4", + "which": "^4.0.0", + "yocto-spinner": "^1.1.0" }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "bin": { + "dotenvx": "src/cli/dotenvx.js" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://dotenvx.com" } }, - "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/@dotenvx/dotenvx/node_modules/commander": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "license": "MIT", + "engines": { + "node": ">=16" } }, - "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, + "node_modules/@dotenvx/dotenvx/node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "license": "MIT", "dependencies": { - "brace-expansion": "^1.1.7" + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" }, "engines": { - "node": "*" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "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, + "node_modules/@dotenvx/dotenvx/node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "license": "MIT", "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": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "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/@dotenvx/dotenvx/node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.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" + "node_modules/@dotenvx/dotenvx/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "license": "MIT", + "engines": { + "node": ">=8" }, - "peerDependencies": { - "react": ">=16.8.0", - "react-dom": ">=16.8.0" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "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, + "node_modules/@dotenvx/dotenvx/node_modules/isexe": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.5.tgz", + "integrity": "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==", + "license": "BlueOak-1.0.0", "engines": { - "node": ">=6" + "node": ">=18" } }, - "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, + "node_modules/@dotenvx/dotenvx/node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "license": "MIT", "dependencies": { - "@fortawesome/fontawesome-common-types": "6.5.1" + "path-key": "^3.0.0" }, "engines": { - "node": ">=6" + "node": ">=8" } }, - "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, + "node_modules/@dotenvx/dotenvx/node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "license": "MIT", "dependencies": { - "@fortawesome/fontawesome-common-types": "6.5.1" + "mimic-fn": "^2.1.0" }, "engines": { "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "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" - }, + "node_modules/@dotenvx/dotenvx/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "license": "ISC" + }, + "node_modules/@dotenvx/dotenvx/node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "license": "MIT", "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, + "node_modules/@dotenvx/dotenvx/node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "license": "ISC", "dependencies": { - "@fortawesome/fontawesome-common-types": "6.5.1" + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" }, "engines": { - "node": ">=6" + "node": "^16.13.0 || >=18.0.0" } }, - "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" + "node_modules/@ecies/ciphers": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/@ecies/ciphers/-/ciphers-0.2.6.tgz", + "integrity": "sha512-patgsRPKGkhhoBjETV4XxD0En4ui5fbX0hzayqI3M8tvNMGUoUvmyYAIWwlxBc1KX5cturfqByYdj5bYGRpN9g==", + "license": "MIT", + "engines": { + "bun": ">=1", + "deno": ">=2.7.10", + "node": ">=16" }, "peerDependencies": { - "@fortawesome/fontawesome-svg-core": "~1 || ~6", - "react": ">=16.3" + "@noble/ciphers": "^1.0.0" } }, - "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, + "node_modules/@emnapi/core": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "license": "MIT", + "optional": true, "dependencies": { - "@humanwhocodes/object-schema": "^2.0.2", - "debug": "^4.3.1", - "minimatch": "^3.0.5" - }, - "engines": { - "node": ">=10.10.0" + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.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, + "node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "license": "MIT", + "optional": true, "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "tslib": "^2.4.0" } }, - "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==", + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", "dev": true, + "license": "MIT", "dependencies": { - "brace-expansion": "^1.1.7" + "eslint-visitor-keys": "^3.4.3" }, "engines": { - "node": "*" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, - "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==", + "node_modules/@eslint-community/eslint-utils/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, + "license": "Apache-2.0", "engines": { - "node": ">=12.22" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" + "url": "https://opencollective.com/eslint" } }, - "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" - }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=12" + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, - "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" + "node_modules/@eslint/config-array": { + "version": "0.23.5", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.5.tgz", + "integrity": "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^3.0.5", + "debug": "^4.3.1", + "minimatch": "^10.2.4" }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" } }, - "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==", + "node_modules/@eslint/config-helpers": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.5.5.tgz", + "integrity": "sha512-eIJYKTCECbP/nsKaaruF6LW967mtbQbsw4JTtSVkUQc9MneSkbrgPJAbKl9nWr0ZeowV8BfsarBmPpBzGelA2w==", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "ansi-regex": "^6.0.1" + "@eslint/core": "^1.2.1" }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, - "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==", + "node_modules/@eslint/core": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.1.tgz", + "integrity": "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" + "@types/json-schema": "^7.0.15" }, "engines": { - "node": ">=6.0.0" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, - "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==", + "node_modules/@eslint/js": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz", + "integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=6.0.0" + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "eslint": "^10.0.0" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } } }, - "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==", + "node_modules/@eslint/object-schema": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz", + "integrity": "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==", + "dev": true, + "license": "Apache-2.0", "engines": { - "node": ">=6.0.0" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, - "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==", + "node_modules/@eslint/plugin-kit": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.1.tgz", + "integrity": "sha512-rZAP3aVgB9ds9KOeUSL+zZ21hPmo8dh6fnIFwRQj5EAZl9gzR7wxYbYXYysAM8CTqGmUGyp2S4kUdV17MnGuWQ==", + "dev": true, + "license": "Apache-2.0", "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==", + "@eslint/core": "^1.2.1", + "levn": "^0.4.1" + }, "engines": { - "node": ">= 0.6" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, - "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==", + "node_modules/@floating-ui/core": { + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.5.tgz", + "integrity": "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==", + "license": "MIT", "dependencies": { - "@mapbox/point-geometry": "~0.1.0" + "@floating-ui/utils": "^0.2.11" } }, - "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/@floating-ui/dom": { + "version": "1.7.6", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.6.tgz", + "integrity": "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.7.5", + "@floating-ui/utils": "^0.2.11" } }, - "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/@floating-ui/react-dom": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.8.tgz", + "integrity": "sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.7.6" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" } }, - "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/@floating-ui/utils": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.11.tgz", + "integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==", + "license": "MIT" }, - "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/@fontsource-variable/geist": { + "version": "5.2.8", + "resolved": "https://registry.npmjs.org/@fontsource-variable/geist/-/geist-5.2.8.tgz", + "integrity": "sha512-cJ6m9e+8MQ5dCYJsLylfZrgBh6KkG4bOLckB35Tr9J/EqdkEM6QllH5PxqP1dhTvFup+HtMRPuz9xOjxXJggxw==", + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" } }, - "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/@fontsource-variable/inter": { + "version": "5.2.8", + "resolved": "https://registry.npmjs.org/@fontsource-variable/inter/-/inter-5.2.8.tgz", + "integrity": "sha512-kOfP2D+ykbcX/P3IFnokOhVRNoTozo5/JxhAIVYLpea/UBmCQ/YWPBfWIDuBImXX/15KH+eKh4xpEUyS2sQQGQ==", + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" } }, - "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, + "node_modules/@hono/node-server": { + "version": "1.19.14", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz", + "integrity": "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==", + "license": "MIT", "engines": { - "node": ">= 10" + "node": ">=18.14.1" + }, + "peerDependencies": { + "hono": "^4" } }, - "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, + "node_modules/@humanfs/core": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, "engines": { - "node": ">= 10" + "node": ">=18.18.0" } }, - "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, + "node_modules/@humanfs/node": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" + }, "engines": { - "node": ">= 10" + "node": ">=18.18.0" } }, - "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, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", "engines": { - "node": ">= 10" + "node": ">=18.18.0" } }, - "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, + "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, + "license": "Apache-2.0", "engines": { - "node": ">= 10" + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" } }, - "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" - }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", "engines": { - "node": ">= 8" + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@nodelib/fs.stat": { + "node_modules/@inquirer/ansi": { "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-2.0.5.tgz", + "integrity": "sha512-doc2sWgJpbFQ64UflSVd17ibMGDuxO1yKgOgLMwavzESnXjFWJqUeG8saYosqKpHp4kWiM5x1nXvEjbpx90gzw==", + "license": "MIT", "engines": { - "node": ">= 8" + "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" } }, - "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==", + "node_modules/@inquirer/confirm": { + "version": "6.0.12", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-6.0.12.tgz", + "integrity": "sha512-h9FgGun3QwVYNj5TWIZZ+slii73bMoBFjPfVIGtnFuL4t8gBiNDV9PcSfIzkuxvgquJKt9nr1QzszpBzTbH8Og==", + "license": "MIT", "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" + "@inquirer/core": "^11.1.9", + "@inquirer/type": "^4.0.5" }, "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" + "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" }, "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" + "@types/node": ">=18" }, "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { + "@types/node": { "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==", + "node_modules/@inquirer/core": { + "version": "11.1.9", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-11.1.9.tgz", + "integrity": "sha512-BDE4fG22uYh1bGSifcj7JSx119TVYNViMhMu85usp4Fswrzh6M0DV3yld64jA98uOAa2GSQ4Bg4bZRm2d2cwSg==", + "license": "MIT", "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-primitive": "1.0.3" + "@inquirer/ansi": "^2.0.5", + "@inquirer/figures": "^2.0.5", + "@inquirer/type": "^4.0.5", + "cli-width": "^4.1.0", + "fast-wrap-ansi": "^0.2.0", + "mute-stream": "^3.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" }, "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" + "@types/node": ">=18" }, "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { + "@types/node": { "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/@inquirer/figures": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-2.0.5.tgz", + "integrity": "sha512-NsSs4kzfm12lNetHwAn3GEuH317IzpwrMCbOuMIVytpjnJ90YYHNwdRgYGuKmVxwuIqSgqk3M5qqQt1cDk0tGQ==", + "license": "MIT", + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" } }, - "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==", + "node_modules/@inquirer/type": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-4.0.5.tgz", + "integrity": "sha512-aetVUNeKNc/VriqXlw1NRSW0zhMBB0W4bNbWRJgzRl/3d0QNDQFfk0GO5SDdtjMZVg6o8ZKEiadd7SCCzoOn5Q==", "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" + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.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" + "@types/node": ">=18" }, "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { + "@types/node": { "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/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } }, - "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==", + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" } }, - "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==", + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@modelcontextprotocol/sdk": { + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz", + "integrity": "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==", + "license": "MIT", + "dependencies": { + "@hono/node-server": "^1.19.9", + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", + "content-type": "^1.0.5", + "cors": "^2.8.5", + "cross-spawn": "^7.0.5", + "eventsource": "^3.0.2", + "eventsource-parser": "^3.0.0", + "express": "^5.2.1", + "express-rate-limit": "^8.2.1", + "hono": "^4.11.4", + "jose": "^6.1.3", + "json-schema-typed": "^8.0.2", + "pkce-challenge": "^5.0.0", + "raw-body": "^3.0.0", + "zod": "^3.25 || ^4.0", + "zod-to-json-schema": "^3.25.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@cfworker/json-schema": "^4.1.1", + "zod": "^3.25 || ^4.0" }, "peerDependenciesMeta": { - "@types/react": { + "@cfworker/json-schema": { "optional": true + }, + "zod": { + "optional": false } } }, - "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==", + "node_modules/@modelcontextprotocol/sdk/node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/@mswjs/interceptors": { + "version": "0.41.6", + "resolved": "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.41.6.tgz", + "integrity": "sha512-qmDvJIjcNsZ6tXWy2G9yuCgMPTTn35GMA3dPpSLm7QJVpbQzYdw0ALy1bKoivXnEM3U93/OrK+/M719b+fg84Q==", + "license": "MIT", + "dependencies": { + "@open-draft/deferred-promise": "^2.2.0", + "@open-draft/logger": "^0.3.0", + "@open-draft/until": "^2.0.0", + "is-node-process": "^1.2.0", + "outvariant": "^1.4.3", + "strict-event-emitter": "^0.5.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@mswjs/interceptors/node_modules/@open-draft/deferred-promise": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@open-draft/deferred-promise/-/deferred-promise-2.2.0.tgz", + "integrity": "sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==", + "license": "MIT" + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", + "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, + "node_modules/@noble/ciphers": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.3.0.tgz", + "integrity": "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/curves": { + "version": "1.9.7", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.7.tgz", + "integrity": "sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.8.0" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "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==", + "license": "MIT", + "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==", + "license": "MIT", + "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==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@open-draft/deferred-promise": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@open-draft/deferred-promise/-/deferred-promise-3.0.0.tgz", + "integrity": "sha512-XW375UK8/9SqUVNVa6M0yEy8+iTi4QN5VZ7aZuRFQmy76LRwI9wy5F4YIBU6T+eTe2/DNDo8tqu8RHlwLHM6RA==", + "license": "MIT" + }, + "node_modules/@open-draft/logger": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@open-draft/logger/-/logger-0.3.0.tgz", + "integrity": "sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==", + "license": "MIT", + "dependencies": { + "is-node-process": "^1.2.0", + "outvariant": "^1.4.0" + } + }, + "node_modules/@open-draft/until": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@open-draft/until/-/until-2.1.0.tgz", + "integrity": "sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==", + "license": "MIT" + }, + "node_modules/@oxc-project/types": { + "version": "0.127.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.127.0.tgz", + "integrity": "sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@radix-ui/number": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.1.tgz", + "integrity": "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==", + "license": "MIT" + }, + "node_modules/@radix-ui/primitive": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz", + "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-accessible-icon": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-accessible-icon/-/react-accessible-icon-1.1.7.tgz", + "integrity": "sha512-XM+E4WXl0OqUJFovy6GjmxxFyx9opfCAIUku4dlKRd5YEPqt4kALOkQOp0Of6reHuUkJuiPBEc5k0o4z4lTC8A==", "license": "MIT", "dependencies": { - "@radix-ui/react-compose-refs": "1.1.0", - "@radix-ui/react-use-layout-effect": "1.1.0" + "@radix-ui/react-visually-hidden": "1.2.3" }, "peerDependencies": { "@types/react": "*", @@ -1212,13 +1282,21 @@ } } }, - "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==", + "node_modules/@radix-ui/react-accordion": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@radix-ui/react-accordion/-/react-accordion-1.2.12.tgz", + "integrity": "sha512-T4nygeh9YE9dLRPhAHSeOZi7HBXo+0kYIPJXayZfvWOWA0+n3dESrZbjfDPUABkUNym6Hd+f2IR113To8D2GPA==", "license": "MIT", "dependencies": { - "@radix-ui/react-slot": "1.1.0" + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collapsible": "1.1.12", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", @@ -1235,106 +1313,127 @@ } } }, - "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==", + "node_modules/@radix-ui/react-alert-dialog": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-alert-dialog/-/react-alert-dialog-1.1.15.tgz", + "integrity": "sha512-oTVLkEw5GpdRe29BqJ0LSDFWI3qu0vR1M0mUkOQWDIUnY/QIkLpgDMWuKxP94c2NAC2LGcgVhG1ImF3jkZ5wXw==", "license": "MIT", "dependencies": { - "@radix-ui/react-compose-refs": "1.1.0" + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dialog": "1.1.15", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + "@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 - } - } - }, - "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": { + }, + "@types/react-dom": { "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==", + "node_modules/@radix-ui/react-arrow": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz", + "integrity": "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==", "license": "MIT", "dependencies": { - "@radix-ui/react-use-callback-ref": "1.1.0" + "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + "@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-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==", + "node_modules/@radix-ui/react-aspect-ratio": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-aspect-ratio/-/react-aspect-ratio-1.1.7.tgz", + "integrity": "sha512-Yq6lvO9HQyPwev1onK1daHCHqXVLzPhSVjmsNjCa2Zcxy2f7uJD2itDtxknv6FzAKCwD1qQkeVDmX/cev13n/g==", "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, "peerDependencies": { "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + "@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-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==", + "node_modules/@radix-ui/react-avatar": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-avatar/-/react-avatar-1.1.10.tgz", + "integrity": "sha512-V8piFfWapM5OmNCXTzVQY+E1rDa53zY+MQ4Y7356v4fFz6vqCyUtIz2rUD44ZEdwg78/jKmMJHj07+C/Z/rcog==", "license": "MIT", "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.0" + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-is-hydrated": "0.1.0", + "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + "@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-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==", + "node_modules/@radix-ui/react-checkbox": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.3.3.tgz", + "integrity": "sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw==", + "license": "MIT", "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" + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-use-size": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" + "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": { @@ -1345,86 +1444,85 @@ } } }, - "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==", + "node_modules/@radix-ui/react-collapsible": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.1.12.tgz", + "integrity": "sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA==", + "license": "MIT", "dependencies": { - "@babel/runtime": "^7.13.10" + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0" + "@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-context": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.0.1.tgz", - "integrity": "sha512-ebbrdFoYTcuZ0v4wG5tedGnp9tzcV8awzsxYph7gXUyvnNLuTIcCk1q17JEbnVhXAKG9oX3KtchwiMIAYp9NLg==", + "node_modules/@radix-ui/react-collection": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz", + "integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==", + "license": "MIT", "dependencies": { - "@babel/runtime": "^7.13.10" + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0" + "@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-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" - }, + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", + "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", + "license": "MIT", "peerDependencies": { "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" + "react": "^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-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" - }, + "node_modules/@radix-ui/react-context": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", + "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", + "license": "MIT", "peerDependencies": { "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0" + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "peerDependenciesMeta": { "@types/react": { @@ -1432,23 +1530,24 @@ } } }, - "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==", + "node_modules/@radix-ui/react-context-menu": { + "version": "2.2.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context-menu/-/react-context-menu-2.2.16.tgz", + "integrity": "sha512-O8morBEW+HsVG28gYDZPTrT9UUovQUlJue5YO836tiTJhuIWBm/zQHc7j388sHWtdH/xUZurK9olD2+pcqx5ww==", + "license": "MIT", "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" + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-menu": "2.1.16", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" + "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": { @@ -1459,25 +1558,32 @@ } } }, - "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" + "node_modules/@radix-ui/react-dialog": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.15.tgz", + "integrity": "sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" + "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": { @@ -1488,16 +1594,14 @@ } } }, - "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" - }, + "node_modules/@radix-ui/react-direction": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz", + "integrity": "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==", + "license": "MIT", "peerDependencies": { "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0" + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "peerDependenciesMeta": { "@types/react": { @@ -1505,21 +1609,23 @@ } } }, - "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==", + "node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz", + "integrity": "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==", + "license": "MIT", "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" + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-escape-keydown": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" + "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": { @@ -1530,77 +1636,65 @@ } } }, - "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==", + "node_modules/@radix-ui/react-dropdown-menu": { + "version": "2.1.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.16.tgz", + "integrity": "sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw==", + "license": "MIT", "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-use-layout-effect": "1.0.1" + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-menu": "2.1.16", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0" + "@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-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" - }, + "node_modules/@radix-ui/react-focus-guards": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.3.tgz", + "integrity": "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==", + "license": "MIT", "peerDependencies": { "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" + "react": "^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-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" + "node_modules/@radix-ui/react-focus-scope": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz", + "integrity": "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" + "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": { @@ -1611,33 +1705,24 @@ } } }, - "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" + "node_modules/@radix-ui/react-form": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-form/-/react-form-0.1.8.tgz", + "integrity": "sha512-QM70k4Zwjttifr5a4sZFts9fn8FzHYvQ5PiB19O2HsYibaHSVt9fH9rzB0XZo/YcM+b7t/p7lYCT/F5eOeF5yQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-label": "2.1.7", + "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" + "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": { @@ -1648,28 +1733,27 @@ } } }, - "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==", + "node_modules/@radix-ui/react-hover-card": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-hover-card/-/react-hover-card-1.1.15.tgz", + "integrity": "sha512-qgTkjNT1CfKMoP0rcasmlH2r1DAiYicWsDsufxl940sT2wHNEWWv6FMWIQXWhVdmC1d/HYfbhQx60KYyAtKxjg==", + "license": "MIT", "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" + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" + "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": { @@ -1680,43 +1764,37 @@ } } }, - "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==", + "node_modules/@radix-ui/react-id": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz", + "integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==", + "license": "MIT", "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-primitive": "1.0.3" + "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" + "react": "^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-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==", + "node_modules/@radix-ui/react-label": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.7.tgz", + "integrity": "sha512-YT1GqPSL8kJn20djelMX7/cTRp/Y9w5IZHvfxQTVHrOqa2yMl7i/UfMqKRU5V7mEyKTrUVgJXhNQPVCG8PBLoQ==", + "license": "MIT", "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-compose-refs": "1.0.1", - "@radix-ui/react-use-layout-effect": "1.0.1" + "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" + "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": { @@ -1727,19 +1805,36 @@ } } }, - "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==", + "node_modules/@radix-ui/react-menu": { + "version": "2.1.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.16.tgz", + "integrity": "sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==", + "license": "MIT", "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-slot": "1.0.2" + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" + "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": { @@ -1750,20 +1845,28 @@ } } }, - "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==", + "node_modules/@radix-ui/react-menubar": { + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-menubar/-/react-menubar-1.1.16.tgz", + "integrity": "sha512-EB1FktTz5xRRi2Er974AUQZWg2yVBb1yjip38/lgwtCVRd3a+maUoGHN/xs9Yv8SY8QwbSEb+YrxGadVWbEutA==", + "license": "MIT", "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-context": "1.0.1", - "@radix-ui/react-primitive": "1.0.3" + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-menu": "2.1.16", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" + "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": { @@ -1774,27 +1877,32 @@ } } }, - "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" + "node_modules/@radix-ui/react-navigation-menu": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@radix-ui/react-navigation-menu/-/react-navigation-menu-1.2.14.tgz", + "integrity": "sha512-YB9mTFQvCOAQMHU+C/jVl96WmuWeltyUEpRJJky51huhds5W2FQr1J8D/16sQlf0ozxkPK8uF3niQMdUwZPv5w==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-visually-hidden": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" + "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": { @@ -1805,20 +1913,24 @@ } } }, - "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" + "node_modules/@radix-ui/react-one-time-password-field": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-one-time-password-field/-/react-one-time-password-field-0.1.8.tgz", + "integrity": "sha512-ycS4rbwURavDPVjCb5iS3aG4lURFDILi6sKI/WITUMZ13gMmn/xGjpLoqBAalhJaDk8I3UbCM5GzKHrnzwHbvg==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.1", + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-effect-event": "0.0.2", + "@radix-ui/react-use-is-hydrated": "0.1.0", + "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", @@ -1835,60 +1947,113 @@ } } }, - "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==", + "node_modules/@radix-ui/react-password-toggle-field": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-password-toggle-field/-/react-password-toggle-field-0.1.3.tgz", + "integrity": "sha512-/UuCrDBWravcaMix4TdT+qlNdVwOM1Nck9kWx/vafXsdfj1ChfhOdfi3cy9SGBpWgTXwYCuboT/oYpJy3clqfw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-effect-event": "0.0.2", + "@radix-ui/react-use-is-hydrated": "0.1.0" + }, "peerDependencies": { "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + "@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-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==", + "node_modules/@radix-ui/react-popover": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.15.tgz", + "integrity": "sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, "peerDependencies": { "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + "@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-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==", + "node_modules/@radix-ui/react-popper": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.8.tgz", + "integrity": "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.0.0", + "@radix-ui/react-arrow": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-rect": "1.1.1", + "@radix-ui/react-use-size": "1.1.1", + "@radix-ui/rect": "1.1.1" + }, "peerDependencies": { "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + "@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-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==", + "node_modules/@radix-ui/react-portal": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz", + "integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==", + "license": "MIT", "dependencies": { - "@radix-ui/react-compose-refs": "1.1.0", - "@radix-ui/react-use-layout-effect": "1.1.0" + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", @@ -1905,12 +2070,14 @@ } } }, - "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==", + "node_modules/@radix-ui/react-presence": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz", + "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==", + "license": "MIT", "dependencies": { - "@radix-ui/react-slot": "1.1.0" + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", @@ -1927,77 +2094,100 @@ } } }, - "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==", + "node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", "dependencies": { - "@radix-ui/react-compose-refs": "1.1.0" + "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + "@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-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==", + "node_modules/@radix-ui/react-progress": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.1.7.tgz", + "integrity": "sha512-vPdg/tF6YC/ynuBIJlk1mm7Le0VgW6ub6J2UWnTQ7/D23KXcPI1qy+0vBkgKgd38RCMJavBXpB83HPNFMTb0Fg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3" + }, "peerDependencies": { "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + "@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-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==", + "node_modules/@radix-ui/react-radio-group": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-radio-group/-/react-radio-group-1.3.8.tgz", + "integrity": "sha512-VBKYIYImA5zsxACdisNQ3BjCBfmbGH3kQlnFVqlWU4tXwjy7cGX8ta80BcrO+WJXIn5iBylEH3K6ZTlee//lgQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-use-size": "1.1.1" + }, "peerDependencies": { "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + "@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": { - "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" + "node_modules/@radix-ui/react-roving-focus": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz", + "integrity": "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", @@ -2014,17 +2204,21 @@ } } }, - "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==", + "node_modules/@radix-ui/react-scroll-area": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.10.tgz", + "integrity": "sha512-tAXIa1g3sM5CGpVT0uIbUx/U3Gs5N8T52IICuCtObaos1S8fzsrPXG5WObkQN3S6NVl6wKgPhAIiBGbWnvc97A==", + "license": "MIT", "dependencies": { - "@radix-ui/react-primitive": "2.0.0" + "@radix-ui/number": "1.1.1", + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", @@ -2041,15 +2235,33 @@ } } }, - "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==", + "node_modules/@radix-ui/react-select": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.2.6.tgz", + "integrity": "sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ==", + "license": "MIT", "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" + "@radix-ui/number": "1.1.1", + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-visually-hidden": "1.2.3", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", @@ -2066,38 +2278,70 @@ } } }, - "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==", + "node_modules/@radix-ui/react-separator": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.7.tgz", + "integrity": "sha512-0HEb8R9E8A+jZjvmFCy/J4xhbXy3TV+9XSnGJ3KvTtjlIUy/YQ/p6UYZvi7YbeoeXdyU9+Y3scizK6hkY37baA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, "peerDependencies": { "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + "@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-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==", + "node_modules/@radix-ui/react-slider": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slider/-/react-slider-1.3.6.tgz", + "integrity": "sha512-JPYb1GuM1bxfjMRlNLE+BcmBC8onfCi60Blk7OBqi2MLTFdS+8401U4uFjnwkOr49BLmXxLC6JHkvAsx5OJvHw==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.1", + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-use-size": "1.1.1" + }, "peerDependencies": { "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + "@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-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==", + "node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" @@ -2108,16 +2352,19 @@ } } }, - "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==", + "node_modules/@radix-ui/react-switch": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.2.6.tgz", + "integrity": "sha512-bByzr1+ep1zk4VubeEVViV592vu2lHE2BZY5OnzehZqOOgogN80+mNtCqPkhn2gklJqOpxWgPoYTSnhBCqpOXQ==", + "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" + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-use-size": "1.1.1" }, "peerDependencies": { "@types/react": "*", @@ -2134,28 +2381,54 @@ } } }, - "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==", + "node_modules/@radix-ui/react-tabs": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.13.tgz", + "integrity": "sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, "peerDependencies": { "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + "@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-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==", + "node_modules/@radix-ui/react-toast": { + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.2.15.tgz", + "integrity": "sha512-3OSz3TacUWy4WtOXV38DggwxoqJK4+eDkNMl5Z/MJZaoUPaP4/9lf81xXMe1I2ReTAptverZUpbPY4wWwWyL5g==", + "license": "MIT", "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" + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-visually-hidden": "1.2.3" }, "peerDependencies": { "@types/react": "*", @@ -2172,38 +2445,44 @@ } } }, - "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==", + "node_modules/@radix-ui/react-toggle": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.1.10.tgz", + "integrity": "sha512-lS1odchhFTeZv3xwHH31YPObmJn8gOg7Lq12inrr0+BH/l3Tsq32VfjqH1oh80ARM3mlkfMic15n0kg4sD1poQ==", + "license": "MIT", "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.0" + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + "@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-popper": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.0.tgz", - "integrity": "sha512-ZnRMshKF43aBxVWPWvbj21+7TQCvhuULWJ4gNIKYpRlQt5xGRhLx66tMp8pya2UkGHTSlhpXwmjqltDYHhw7Vg==", + "node_modules/@radix-ui/react-toggle-group": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle-group/-/react-toggle-group-1.1.11.tgz", + "integrity": "sha512-5umnS0T8JQzQT6HbPyO7Hh9dgd82NmS36DQr+X/YJ9ctFNCiiQd6IJAYYZ33LUwm8M+taCz5t2ui29fHZc4Y6Q==", + "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" + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-toggle": "1.1.10", + "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", @@ -2220,13 +2499,19 @@ } } }, - "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==", + "node_modules/@radix-ui/react-toolbar": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toolbar/-/react-toolbar-1.1.11.tgz", + "integrity": "sha512-4ol06/1bLoFu1nwUqzdD4Y5RZ9oDdKeiHIsntug54Hcr1pgaHiPqHFEaXI1IFP/EsOfROQZ8Mig9VTIRza6Tjg==", + "license": "MIT", "dependencies": { - "@radix-ui/react-primitive": "2.0.0", - "@radix-ui/react-use-layout-effect": "1.1.0" + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-separator": "1.1.7", + "@radix-ui/react-toggle-group": "1.1.11" }, "peerDependencies": { "@types/react": "*", @@ -2243,12 +2528,24 @@ } } }, - "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==", + "node_modules/@radix-ui/react-tooltip": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.8.tgz", + "integrity": "sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==", + "license": "MIT", "dependencies": { - "@radix-ui/react-slot": "1.1.0" + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-visually-hidden": "1.2.3" }, "peerDependencies": { "@types/react": "*", @@ -2265,13 +2562,11 @@ } } }, - "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" - }, + "node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz", + "integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==", + "license": "MIT", "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" @@ -2282,10 +2577,15 @@ } } }, - "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==", + "node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz", + "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-effect-event": "0.0.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" @@ -2296,12 +2596,13 @@ } } }, - "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==", + "node_modules/@radix-ui/react-use-effect-event": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz", + "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==", + "license": "MIT", "dependencies": { - "@radix-ui/react-use-callback-ref": "1.1.0" + "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", @@ -2313,12 +2614,13 @@ } } }, - "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==", + "node_modules/@radix-ui/react-use-escape-keydown": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz", + "integrity": "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==", + "license": "MIT", "dependencies": { - "@radix-ui/react-use-callback-ref": "1.1.0" + "@radix-ui/react-use-callback-ref": "1.1.1" }, "peerDependencies": { "@types/react": "*", @@ -2330,26 +2632,13 @@ } } }, - "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==", + "node_modules/@radix-ui/react-use-is-hydrated": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-is-hydrated/-/react-use-is-hydrated-0.1.0.tgz", + "integrity": "sha512-U+UORVEq+cTnRIaostJv9AGdV3G6Y+zbVd+12e18jQ5A3c0xL03IhnHuiU4UV69wolOQp5GfR58NW/EgdQhwOA==", + "license": "MIT", "dependencies": { - "@radix-ui/rect": "1.1.0" + "use-sync-external-store": "^1.5.0" }, "peerDependencies": { "@types/react": "*", @@ -2361,13 +2650,11 @@ } } }, - "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" - }, + "node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", + "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", + "license": "MIT", "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" @@ -2378,50 +2665,14 @@ } } }, - "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" - }, + "node_modules/@radix-ui/react-use-previous": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.1.tgz", + "integrity": "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==", + "license": "MIT", "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" + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "peerDependenciesMeta": { "@types/react": { @@ -2429,40 +2680,35 @@ } } }, - "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==", + "node_modules/@radix-ui/react-use-rect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.1.tgz", + "integrity": "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==", + "license": "MIT", "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-primitive": "1.0.3" + "@radix-ui/rect": "1.1.1" }, "peerDependencies": { "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" + "react": "^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-slot": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.2.tgz", - "integrity": "sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==", + "node_modules/@radix-ui/react-use-size": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz", + "integrity": "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==", + "license": "MIT", "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-compose-refs": "1.0.1" + "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0" + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "peerDependenciesMeta": { "@types/react": { @@ -2470,19 +2716,13 @@ } } }, - "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==", + "node_modules/@radix-ui/react-visually-hidden": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.3.tgz", + "integrity": "sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==", "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" + "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", @@ -2499,1685 +2739,808 @@ } } }, - "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==", + "node_modules/@radix-ui/rect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.1.tgz", + "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==", "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==", + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.17.tgz", + "integrity": "sha512-s70pVGhw4zqGeFnXWvAzJDlvxhlRollagdCCKRgOsgUOH3N1l0LIxf83AtGzmb5SiVM4Hjl5HyarMRfdfj3DaQ==", + "cpu": [ + "arm64" + ], "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "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==", + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.17.tgz", + "integrity": "sha512-4ksWc9n0mhlZpZ9PMZgTGjeOPRu8MB1Z3Tz0Mo02eWfWCHMW1zN82Qz/pL/rC+yQa+8ZnutMF0JjJe7PjwasYw==", + "cpu": [ + "arm64" + ], "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "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==", + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.17.tgz", + "integrity": "sha512-SUSDOI6WwUVNcWxd02QEBjLdY1VPHvlEkw6T/8nYG322iYWCTxRb1vzk4E+mWWYehTp7ERibq54LSJGjmouOsw==", + "cpu": [ + "x64" + ], "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 - } + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "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==", + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.17.tgz", + "integrity": "sha512-hwnz3nw9dbJ05EDO/PvcjaaewqqDy7Y1rn1UO81l8iIK1GjenME75dl16ajbvSSMfv66WXSRCYKIqfgq2KCfxw==", + "cpu": [ + "x64" + ], "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 - } + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "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==", + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.17.tgz", + "integrity": "sha512-IS+W7epTcwANmFSQFrS1SivEXHtl1JtuQA9wlxrZTcNi6mx+FDOYrakGevvvTwgj2JvWiK8B29/qD9BELZPyXQ==", + "cpu": [ + "arm" + ], "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "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==", + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.17.tgz", + "integrity": "sha512-e6usGaHKW5BMNZOymS1UcEYGowQMWcgZ71Z17Sl/h2+ZziNJ1a9n3Zvcz6LdRyIW5572wBCTH/Z+bKuZouGk9Q==", + "cpu": [ + "arm64" + ], "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 - } + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "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==", + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.17.tgz", + "integrity": "sha512-b/CgbwAJpmrRLp02RPfhbudf5tZnN9nsPWK82znefso832etkem8H7FSZwxrOI9djcdTP7U6YfNhbRnh7djErg==", + "cpu": [ + "arm64" + ], "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "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==", + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.17.tgz", + "integrity": "sha512-4EII1iNGRUN5WwGbF/kOh/EIkoDN9HsupgLQoXfY+D1oyJm7/F4t5PYU5n8SWZgG0FEwakyM8pGgwcBYruGTlA==", + "cpu": [ + "ppc64" + ], "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 - } + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "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/@rolldown/binding-linux-s390x-gnu": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.17.tgz", + "integrity": "sha512-AH8oq3XqQo4IibpVXvPeLDI5pzkpYn0WiZAfT05kFzoJ6tQNzwRdDYQ45M8I/gslbodRZwW8uxLhbSBbkv96rA==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "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==", + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.17.tgz", + "integrity": "sha512-cLnjV3xfo7KslbU41Z7z8BH/E1y5mzUYzAqih1d1MDaIGZRCMqTijqLv76/P7fyHuvUcfGsIpqCdddbxLLK9rA==", "cpu": [ - "arm64" + "x64" ], - "dev": true, + "license": "MIT", "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" + "linux" ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ] + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "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==", + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.17.tgz", + "integrity": "sha512-0phclDw1spsL7dUB37sIARuis2tAgomCJXAHZlpt8PXZ4Ba0dRP1e+66lsRqrfhISeN9bEGNjQs+T/Fbd7oYGw==", "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, + "license": "MIT", "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" - ] + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "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==", + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.17.tgz", + "integrity": "sha512-0ag/hEgXOwgw4t8QyQvUCxvEg+V0KBcA6YuOx9g0r02MprutRF5dyljgm3EmR02O292UX7UeS6HzWHAl6KgyhA==", "cpu": [ "arm64" ], - "dev": true, + "license": "MIT", "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" + "openharmony" ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "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==", + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.17.tgz", + "integrity": "sha512-LEXei6vo0E5wTGwpkJ4KoT3OZJRnglwldt5ziLzOlc6qqb55z4tWNq2A+PFqCJuvWWdP53CVhG1Z9NtToDPJrA==", "cpu": [ - "ppc64" + "wasm32" ], - "dev": true, + "license": "MIT", "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "@emnapi/core": "1.10.0", + "@emnapi/runtime": "1.10.0", + "@napi-rs/wasm-runtime": "^1.1.4" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "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==", + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.17.tgz", + "integrity": "sha512-gUmyzBl3SPMa6hrqFUth9sVfcLBlYsbMzBx5PlexMroZStgzGqlZ26pYG89rBb45Mnia+oil6YAIFeEWGWhoZA==", "cpu": [ - "riscv64" + "arm64" ], - "dev": true, + "license": "MIT", "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" + "win32" ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "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==", + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.17.tgz", + "integrity": "sha512-3hkiolcUAvPB9FLb3UZdfjVVNWherN1f/skkGWJP/fgSQhYUZpSIRr0/I8ZK9TkF3F7kxvJAk0+IcKvPHk9qQg==", "cpu": [ "x64" ], - "dev": true, + "license": "MIT", "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" + "win32" ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.7", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.7.tgz", + "integrity": "sha512-qujRfC8sFVInYSPPMLQByRh7zhwkGFS4+tyMQ83srV1qrxL4g8E2tyxVVyxd0+8QeBM1mIk9KbWxkegRr76XzA==", "dev": true, - "optional": true, - "os": [ - "linux" - ] + "license": "MIT" + }, + "node_modules/@sec-ant/readable-stream": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", + "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==", + "license": "MIT" + }, + "node_modules/@sindresorhus/merge-streams": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", + "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@tailwindcss/node": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.4.tgz", + "integrity": "sha512-Ai7+yQPxz3ddrDQzFfBKdHEVBg0w3Zl83jnjuwxnZOsnH9pGn93QHQtpU0p/8rYWxvbFZHneni6p1BSLK4DkGA==", + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "enhanced-resolve": "^5.19.0", + "jiti": "^2.6.1", + "lightningcss": "1.32.0", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.2.4" + } }, - "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==", + "node_modules/@tailwindcss/oxide": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.2.4.tgz", + "integrity": "sha512-9El/iI069DKDSXwTvB9J4BwdO5JhRrOweGaK25taBAvBXyXqJAX+Jqdvs8r8gKpsI/1m0LeJLyQYTf/WLrBT1Q==", + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.2.4", + "@tailwindcss/oxide-darwin-arm64": "4.2.4", + "@tailwindcss/oxide-darwin-x64": "4.2.4", + "@tailwindcss/oxide-freebsd-x64": "4.2.4", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.4", + "@tailwindcss/oxide-linux-arm64-gnu": "4.2.4", + "@tailwindcss/oxide-linux-arm64-musl": "4.2.4", + "@tailwindcss/oxide-linux-x64-gnu": "4.2.4", + "@tailwindcss/oxide-linux-x64-musl": "4.2.4", + "@tailwindcss/oxide-wasm32-wasi": "4.2.4", + "@tailwindcss/oxide-win32-arm64-msvc": "4.2.4", + "@tailwindcss/oxide-win32-x64-msvc": "4.2.4" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.2.4.tgz", + "integrity": "sha512-e7MOr1SAn9U8KlZzPi1ZXGZHeC5anY36qjNwmZv9pOJ8E4Q6jmD1vyEHkQFmNOIN7twGPEMXRHmitN4zCMN03g==", "cpu": [ "arm64" ], - "dev": true, + "license": "MIT", "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" + "android" ], - "dev": true, - "optional": true, - "os": [ - "win32" - ] + "engines": { + "node": ">= 20" + } }, - "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==", + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.2.4.tgz", + "integrity": "sha512-tSC/Kbqpz/5/o/C2sG7QvOxAKqyd10bq+ypZNf+9Fi2TvbVbv1zNpcEptcsU7DPROaSbVgUXmrzKhurFvo5eDg==", "cpu": [ - "x64" + "arm64" ], - "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 - } + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" } }, - "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==", + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.2.4.tgz", + "integrity": "sha512-yPyUXn3yO/ufR6+Kzv0t4fCg2qNr90jxXc5QqBpjlPNd0NqyDXcmQb/6weunH/MEDXW5dhyEi+agTDiqa3WsGg==", "cpu": [ - "arm64" + "x64" ], - "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" ], "engines": { - "node": ">=10" + "node": ">= 20" } }, - "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==", + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.2.4.tgz", + "integrity": "sha512-BoMIB4vMQtZsXdGLVc2z+P9DbETkiopogfWZKbWwM8b/1Vinbs4YcUwo+kM/KeLkX3Ygrf4/PsRndKaYhS8Eiw==", "cpu": [ "x64" ], - "dev": true, + "license": "MIT", "optional": true, "os": [ - "darwin" + "freebsd" ], "engines": { - "node": ">=10" + "node": ">= 20" } }, - "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==", + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.2.4.tgz", + "integrity": "sha512-7pIHBLTHYRAlS7V22JNuTh33yLH4VElwKtB3bwchK/UaKUPpQ0lPQiOWcbm4V3WP2I6fNIJ23vABIvoy2izdwA==", "cpu": [ "arm" ], - "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=10" + "node": ">= 20" } }, - "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==", + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.2.4.tgz", + "integrity": "sha512-+E4wxJ0ZGOzSH325reXTWB48l42i93kQqMvDyz5gqfRzRZ7faNhnmvlV4EPGJU3QJM/3Ab5jhJ5pCRUsKn6OQw==", "cpu": [ "arm64" ], - "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=10" + "node": ">= 20" } }, - "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==", + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.2.4.tgz", + "integrity": "sha512-bBADEGAbo4ASnppIziaQJelekCxdMaxisrk+fB7Thit72IBnALp9K6ffA2G4ruj90G9XRS2VQ6q2bCKbfFV82g==", "cpu": [ "arm64" ], - "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=10" + "node": ">= 20" } }, - "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==", + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.2.4.tgz", + "integrity": "sha512-7Mx25E4WTfnht0TVRTyC00j3i0M+EeFe7wguMDTlX4mRxafznw0CA8WJkFjWYH5BlgELd1kSjuU2JiPnNZbJDA==", "cpu": [ "x64" ], - "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=10" + "node": ">= 20" } }, - "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==", + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.2.4.tgz", + "integrity": "sha512-2wwJRF7nyhOR0hhHoChc04xngV3iS+akccHTGtz965FwF0up4b2lOdo6kI1EbDaEXKgvcrFBYcYQQ/rrnWFVfA==", "cpu": [ "x64" ], - "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=10" + "node": ">= 20" } }, - "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==", + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.2.4.tgz", + "integrity": "sha512-FQsqApeor8Fo6gUEklzmaa9994orJZZDBAlQpK2Mq+DslRKFJeD6AjHpBQ0kZFQohVr8o85PPh8eOy86VlSCmw==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], "cpu": [ - "arm64" + "wasm32" ], - "dev": true, + "license": "MIT", "optional": true, - "os": [ - "win32" - ], + "dependencies": { + "@emnapi/core": "^1.8.1", + "@emnapi/runtime": "^1.8.1", + "@emnapi/wasi-threads": "^1.1.0", + "@napi-rs/wasm-runtime": "^1.1.1", + "@tybys/wasm-util": "^0.10.1", + "tslib": "^2.8.1" + }, "engines": { - "node": ">=10" + "node": ">=14.0.0" } }, - "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==", + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.4.tgz", + "integrity": "sha512-L9BXqxC4ToVgwMFqj3pmZRqyHEztulpUJzCxUtLjobMCzTPsGt1Fa9enKbOpY2iIyVtaHNeNvAK8ERP/64sqGQ==", "cpu": [ - "ia32" + "arm64" ], - "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" ], "engines": { - "node": ">=10" + "node": ">= 20" } }, - "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==", + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.2.4.tgz", + "integrity": "sha512-ESlKG0EpVJQwRjXDDa9rLvhEAh0mhP1sF7sap9dNZT0yyl9SAG6T7gdP09EH0vIv0UNTlo6jPWyujD6559fZvw==", "cpu": [ "x64" ], - "dev": true, + "license": "MIT", "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": ">= 20" } }, - "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==", + "node_modules/@tailwindcss/vite": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.2.4.tgz", + "integrity": "sha512-pCvohwOCspk3ZFn6eJzrrX3g4n2JY73H6MmYC87XfGPyTty4YsCjYTMArRZm/zOI8dIt3+EcrLHAFPe5A4bgtw==", + "license": "MIT", "dependencies": { - "@types/geojson": "*" + "@tailwindcss/node": "4.2.4", + "@tailwindcss/oxide": "4.2.4", + "tailwindcss": "4.2.4" + }, + "peerDependencies": { + "vite": "^5.2.0 || ^6 || ^7 || ^8" } }, - "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/@tanstack/query-core": { + "version": "5.100.10", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.100.10.tgz", + "integrity": "sha512-8UR0yJR+GiQ40m3lPhUr0xbfAupe6GSQiksSBSa9SM2NjezFyxXCIA69/lz8cSoNKZLrw1/PktIyQBJcVeMi3w==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" } }, - "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==", + "node_modules/@tanstack/react-query": { + "version": "5.100.10", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.100.10.tgz", + "integrity": "sha512-FLaZf2RCrA/Zgp4aiu5tG3TyasTRO7aZ99skxQpr3Hg/zXOhu6yq5FZCYQ/tRaJtM9ylnoK8tFK7PolXQadv6Q==", + "license": "MIT", "dependencies": { - "@types/d3-time": "*" + "@tanstack/query-core": "5.100.10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18 || ^19" } }, - "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==", + "node_modules/@ts-morph/common": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.27.0.tgz", + "integrity": "sha512-Wf29UqxWDpc+i61k3oIOzcUfQt79PIT9y/MWfAGlrkjg6lBC1hwDECLXPVJAhWjiGbfBCxZd65F/LIZF3+jeJQ==", + "license": "MIT", "dependencies": { - "@types/d3-path": "*" + "fast-glob": "^3.3.3", + "minimatch": "^10.0.1", + "path-browserify": "^1.0.1" } }, - "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==", + "node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "license": "MIT", + "optional": true, "dependencies": { - "@types/d3-selection": "*" + "tslib": "^2.4.0" } }, - "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/esrecurse": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", + "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", + "dev": true, + "license": "MIT" }, "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==" + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" }, "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": "*" - } + "dev": true, + "license": "MIT" }, "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==", + "version": "24.12.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.2.tgz", + "integrity": "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g==", + "license": "MIT", "dependencies": { - "undici-types": "~5.26.4" + "undici-types": "~7.16.0" } }, - "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==", + "version": "19.2.14", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", + "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", "devOptional": true, + "license": "MIT", "dependencies": { - "@types/prop-types": "*", - "@types/scheduler": "*", - "csstype": "^3.0.2" + "csstype": "^3.2.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==", + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", "devOptional": true, - "dependencies": { - "@types/react": "*" + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" } }, - "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==", + "node_modules/@types/set-cookie-parser": { + "version": "2.4.10", + "resolved": "https://registry.npmjs.org/@types/set-cookie-parser/-/set-cookie-parser-2.4.10.tgz", + "integrity": "sha512-GGmQVGpQWUe5qglJozEjZV/5dyxbOOZ0LHe/lqyWssB88Y4svNfst0uqBVscdDeIKl5Jy5+aPSvy7mI9tYRguw==", + "license": "MIT", "dependencies": { - "@types/node": "*", - "safe-buffer": "~5.1.1" + "@types/node": "*" } }, - "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/statuses": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/statuses/-/statuses-2.0.6.tgz", + "integrity": "sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA==", + "license": "MIT" }, - "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/@types/validate-npm-package-name": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/validate-npm-package-name/-/validate-npm-package-name-4.0.2.tgz", + "integrity": "sha512-lrpDziQipxCEeK5kWxvljWYhUvOiB2A9izZd9B2AFarYAkqZshb4lPbRs7zKEic6eGtH8V/2qJW+dPp9OtF6bw==", + "license": "MIT" }, "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==", + "version": "8.59.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.59.0.tgz", + "integrity": "sha512-HyAZtpdkgZwpq8Sz3FSUvCR4c+ScbuWa9AksK2Jweub7w4M3yTz4O11AqVJzLYjy/B9ZWPyc81I+mOdJU/bDQw==", "dev": true, + "license": "MIT", "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", + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.59.0", + "@typescript-eslint/type-utils": "8.59.0", + "@typescript-eslint/utils": "8.59.0", + "@typescript-eslint/visitor-keys": "8.59.0", + "ignore": "^7.0.5", "natural-compare": "^1.4.0", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" + "ts-api-utils": "^2.5.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.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 - } + "@typescript-eslint/parser": "^8.59.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" } }, "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==", + "version": "8.59.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.59.0.tgz", + "integrity": "sha512-TI1XGwKbDpo9tRW8UDIXCOeLk55qe9ZFGs8MTKU6/M08HWTw52DD/IYhfQtOEhEdPhLMT26Ka/x7p70nd3dzDg==", "dev": true, + "license": "MIT", "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" + "@typescript-eslint/scope-manager": "8.59.0", + "@typescript-eslint/types": "8.59.0", + "@typescript-eslint/typescript-estree": "8.59.0", + "@typescript-eslint/visitor-keys": "8.59.0", + "debug": "^4.4.3" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.59.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.59.0.tgz", + "integrity": "sha512-Lw5ITrR5s5TbC19YSvlr63ZfLaJoU6vtKTHyB0GQOpX0W7d5/Ir6vUahWi/8Sps/nOukZQ0IB3SmlxZnjaKVnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.59.0", + "@typescript-eslint/types": "^8.59.0", + "debug": "^4.4.3" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" } }, "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==", + "version": "8.59.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.59.0.tgz", + "integrity": "sha512-UzR16Ut8IpA3Mc4DbgAShlPPkVm8xXMWafXxB0BocaVRHs8ZGakAxGRskF7FId3sdk9lgGD73GSFaWmWFDE4dg==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "6.18.1", - "@typescript-eslint/visitor-keys": "6.18.1" + "@typescript-eslint/types": "8.59.0", + "@typescript-eslint/visitor-keys": "8.59.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.59.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.59.0.tgz", + "integrity": "sha512-91Sbl3s4Kb3SybliIY6muFBmHVv+pYXfybC4Oolp3dvk8BvIE3wOPc+403CWIT7mJNkfQRGtdqghzs2+Z91Tqg==", + "dev": true, + "license": "MIT", "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" } }, "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==", + "version": "8.59.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.59.0.tgz", + "integrity": "sha512-3TRiZaQSltGqGeNrJzzr1+8YcEobKH9rHnqIp/1psfKFmhRQDNMGP5hBufanYTGznwShzVLs3Mz+gDN7HkWfXg==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "6.18.1", - "@typescript-eslint/utils": "6.18.1", - "debug": "^4.3.4", - "ts-api-utils": "^1.0.1" + "@typescript-eslint/types": "8.59.0", + "@typescript-eslint/typescript-estree": "8.59.0", + "@typescript-eslint/utils": "8.59.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, "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==", + "version": "8.59.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.59.0.tgz", + "integrity": "sha512-nLzdsT1gdOgFxxxwrlNVUBzSNBEEHJ86bblmk4QAS6stfig7rcJzWKqCyxFy3YRRHXDWEkb2NralA1nOYkkm/A==", "dev": true, + "license": "MIT", "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", @@ -4185,109 +3548,133 @@ } }, "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==", + "version": "8.59.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.59.0.tgz", + "integrity": "sha512-O9Re9P1BmBLFJyikRbQpLku/QA3/AueZNO9WePLBwQrvkixTmDe8u76B6CYUAITRl/rHawggEqUGn5QIkVRLMw==", "dev": true, + "license": "MIT", "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" + "@typescript-eslint/project-service": "8.59.0", + "@typescript-eslint/tsconfig-utils": "8.59.0", + "@typescript-eslint/types": "8.59.0", + "@typescript-eslint/visitor-keys": "8.59.0", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" } }, "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==", + "version": "8.59.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.59.0.tgz", + "integrity": "sha512-I1R/K7V07XsMJ12Oaxg/O9GfrysGTmCRhvZJBv0RE0NcULMzjqVpR5kRRQjHsz3J/bElU7HwCO7zkqL+MSUz+g==", "dev": true, + "license": "MIT", "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" + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.59.0", + "@typescript-eslint/types": "8.59.0", + "@typescript-eslint/typescript-estree": "8.59.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.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==", + "version": "8.59.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.59.0.tgz", + "integrity": "sha512-/uejZt4dSere1bx12WLlPfv8GktzcaDtuJ7s42/HEZ5zGj9oxRaD4bj7qwSunXkf+pbAhFt2zjpHYUiT5lHf0Q==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "6.18.1", - "eslint-visitor-keys": "^3.4.1" + "@typescript-eslint/types": "8.59.0", + "eslint-visitor-keys": "^5.0.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.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==", + "node_modules/@vitejs/plugin-react": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.1.tgz", + "integrity": "sha512-l9X/E3cDb+xY3SWzlG1MOGt2usfEHGMNIaegaUGFsLkb3RCn/k8/TOXBcab+OndDI4TBtktT8/9BwwW8Vi9KUQ==", "dev": true, + "license": "MIT", "dependencies": { - "@swc/core": "^1.3.96" + "@rolldown/pluginutils": "1.0.0-rc.7" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" }, "peerDependencies": { - "vite": "^4 || ^5" + "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", + "babel-plugin-react-compiler": "^1.0.0", + "vite": "^8.0.0" + }, + "peerDependenciesMeta": { + "@rolldown/plugin-babel": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + } } }, - "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==", + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", "dependencies": { - "event-target-shim": "^5.0.0" + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" }, "engines": { - "node": ">=6.5" + "node": ">= 0.6" } }, "node_modules/acorn": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", - "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, + "license": "MIT", "bin": { "acorn": "bin/acorn" }, @@ -4300,15 +3687,26 @@ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true, + "license": "MIT", "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", "dev": true, + "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -4320,28 +3718,62 @@ "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/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-formats/node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" }, "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==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, "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==", + "license": "MIT", "dependencies": { "color-convert": "^2.0.1" }, @@ -4352,38 +3784,17 @@ "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 + "license": "Python-2.0" }, "node_modules/aria-hidden": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.3.tgz", - "integrity": "sha512-xcLxITLe2HYa1cnYnwCjkOO1PqUHQpozB8x9AR0OgWN2woOBi5kSDVxKfd0b7sb1hw5qFeJhXm9H1nu3xSfLeQ==", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", + "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==", + "license": "MIT", "dependencies": { "tslib": "^2.0.0" }, @@ -4391,125 +3802,90 @@ "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, + "node_modules/ast-types": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.16.1.tgz", + "integrity": "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.1" + }, "engines": { - "node": ">=8" + "node": ">=4" } }, "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" - } + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" }, "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==", + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.15.2.tgz", + "integrity": "sha512-wLrXxPtcrPTsNlJmKjkPnNPK2Ihe0hn0wGSaTEiHRPxwjvJwT3hKmXF4dpqxmPO9SoNb2FsYXj/xEo0gHN+D5A==", "license": "MIT", "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" + "follow-redirects": "^1.15.11", + "form-data": "^4.0.5", + "proxy-from-env": "^2.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, + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "license": "MIT", "engines": { - "node": "*" + "node": "18 || 20 || >=22" } }, - "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==", + "node_modules/baseline-browser-mapping": { + "version": "2.10.21", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.21.tgz", + "integrity": "sha512-Q+rUQ7Uz8AHM7DEaNdwvfFCTq7a43lNTzuS94eiWqwyxfV/wJv+oUivef51T91mmRY4d4A1u9rcSvkeufCVXlA==", + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, "engines": { - "node": ">=8" + "node": ">=6.0.0" } }, - "node_modules/bl": { - "version": "6.0.10", - "resolved": "https://registry.npmjs.org/bl/-/bl-6.0.10.tgz", - "integrity": "sha512-F14DFhDZfxtVm2FY0k9kG2lWAwzZkO9+jX3Ytuoy/V0E1/5LBuBzzQHXAjqpxXEDIpmTPZZf5GVIGPQcLxFpaA==", + "node_modules/body-parser": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", + "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", + "license": "MIT", "dependencies": { - "buffer": "^6.0.3", - "inherits": "^2.0.4", - "readable-stream": "^4.2.0" + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.3", + "http-errors": "^2.0.0", + "iconv-lite": "^0.7.0", + "on-finished": "^2.4.1", + "qs": "^6.14.1", + "raw-body": "^3.0.1", + "type-is": "^2.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "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==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0" + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" } }, "node_modules/braces": { @@ -4525,10 +3901,9 @@ } }, "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, + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", "funding": [ { "type": "opencollective", @@ -4543,11 +3918,13 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001565", - "electron-to-chromium": "^1.4.601", - "node-releases": "^2.0.14", - "update-browserslist-db": "^1.0.13" + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" @@ -4556,67 +3933,72 @@ "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" - } - ], + "node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "license": "MIT", "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "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/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } }, - "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, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", "dependencies": { - "streamsearch": "^1.1.0" + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" }, "engines": { - "node": ">=10.16.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "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, + "license": "MIT", "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==", + "version": "1.0.30001790", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001790.tgz", + "integrity": "sha512-bOoxfJPyYo+ds6W0YfptaCWbFnJYjh2Y1Eow5lRv+vI2u8ganPZqNm1JwNh0t2ELQCqIWg4B3dWEusgAmsoyOw==", "funding": [ { "type": "opencollective", @@ -4630,121 +4012,160 @@ "type": "github", "url": "https://github.com/sponsors/ai" } - ] + ], + "license": "CC-BY-4.0" }, "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" - }, + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "license": "MIT", "engines": { - "node": ">=10" + "node": "^12.17.0 || ^14.13 || >=16.0.0" }, "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/class-variance-authority": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", + "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", + "license": "Apache-2.0", + "dependencies": { + "clsx": "^2.1.1" + }, + "funding": { + "url": "https://polar.sh/cva" + } }, - "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/" - } - ], + "node_modules/cli-cursor": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", + "license": "MIT", "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" + "restore-cursor": "^5.0.0" }, "engines": { - "node": ">= 8.10.0" + "node": ">=18" }, - "optionalDependencies": { - "fsevents": "~2.3.2" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "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" + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "license": "MIT", + "engines": { + "node": ">=6" }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-width": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "license": "ISC", "engines": { - "node": ">= 6" + "node": ">= 12" } }, - "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==", + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", "dependencies": { - "clsx": "2.0.0" + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" }, - "funding": { - "url": "https://joebell.co.uk" + "engines": { + "node": ">=12" } }, - "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==", + "node_modules/cliui/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==", + "license": "MIT", "engines": { - "node": ">=6" + "node": ">=8" } }, - "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/cliui/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==", + "license": "MIT" + }, + "node_modules/cliui/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==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } }, - "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/cliui/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==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } }, "node_modules/clsx": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.0.tgz", - "integrity": "sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", "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==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cmdk/-/cmdk-1.1.1.tgz", + "integrity": "sha512-Vsv7kFaXm+ptHDMZ7izaRsP70GgrW9NBNGswt9OZaVBLlE0SNpDq8eu/VGXyF9r7M0azK3Wy7OlYXsuyYLFzHg==", + "license": "MIT", "dependencies": { - "@radix-ui/react-dialog": "1.0.5", - "@radix-ui/react-primitive": "1.0.3" + "@radix-ui/react-compose-refs": "^1.1.1", + "@radix-ui/react-dialog": "^1.1.6", + "@radix-ui/react-id": "^1.1.0", + "@radix-ui/react-primitive": "^2.0.2" }, "peerDependencies": { - "react": "^18.0.0", - "react-dom": "^18.0.0" + "react": "^18 || ^19 || ^19.0.0-rc", + "react-dom": "^18 || ^19 || ^19.0.0-rc" } }, + "node_modules/code-block-writer": { + "version": "13.0.3", + "resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-13.0.3.tgz", + "integrity": "sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg==", + "license": "MIT" + }, "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==", + "license": "MIT", "dependencies": { "color-name": "~1.1.4" }, @@ -4755,69 +4176,124 @@ "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==" + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" }, "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==", + "license": "MIT", "dependencies": { "delayed-stream": "~1.0.0" }, "engines": { - "node": ">= 0.8" + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", + "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/content-disposition": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", + "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" } }, - "node_modules/commander": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", - "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "license": "MIT" + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", "engines": { - "node": ">= 6" + "node": ">= 0.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/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } }, - "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" - ], + "node_modules/cors": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "license": "MIT", "dependencies": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.0.2", - "typedarray": "^0.0.6" + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "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==", + "node_modules/cosmiconfig": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.1.tgz", + "integrity": "sha512-hr4ihw+DBqcvrsEDioRO31Z17x71pUYoNe/4h6Z0wB72p7MU7/9gH8Q3s12NFhHPfYBBOV3qyfUxmr/Yn3shnQ==", + "license": "MIT", "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" }, "engines": { - "node": ">= 6" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, "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==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -4827,20 +4303,11 @@ "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==", + "license": "MIT", "bin": { "cssesc": "bin/cssesc" }, @@ -4849,373 +4316,317 @@ } }, "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" - } + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "devOptional": true, + "license": "MIT" }, - "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==", + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "license": "MIT", "engines": { - "node": ">=12" + "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==", + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", "dependencies": { - "d3-dispatch": "1 - 3", - "d3-selection": "3" + "ms": "^2.1.3" }, "engines": { - "node": ">=12" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "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/dedent": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.2.tgz", + "integrity": "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==", + "license": "MIT", + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } } }, - "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==", + "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, + "license": "MIT" + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "license": "MIT", "engines": { - "node": ">=12" + "node": ">=0.10.0" } }, - "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==", + "node_modules/default-browser": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.0.tgz", + "integrity": "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==", + "license": "MIT", "dependencies": { - "d3-color": "1 - 3" + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" }, "engines": { - "node": ">=12" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "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==", + "node_modules/default-browser-id": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz", + "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", + "license": "MIT", "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" + "node": ">=18" }, - "engines": { - "node": ">=12" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/d3-selection": { + "node_modules/define-lazy-prop": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", - "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "license": "MIT", "engines": { "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "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" - }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", "engines": { - "node": ">=12" + "node": ">=0.4.0" } }, - "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" - }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", "engines": { - "node": ">=12" + "node": ">= 0.8" } }, - "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" - }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", "engines": { - "node": ">=12" + "node": ">=8" } }, - "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==", + "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==", + "license": "MIT" + }, + "node_modules/diff": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.4.tgz", + "integrity": "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==", + "license": "BSD-3-Clause", "engines": { - "node": ">=12" + "node": ">=0.3.1" } }, - "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" - }, + "node_modules/dotenv": { + "version": "17.4.2", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz", + "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==", + "license": "BSD-2-Clause", "engines": { "node": ">=12" }, - "peerDependencies": { - "d3-selection": "2 - 3" + "funding": { + "url": "https://dotenvx.com" } }, - "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==", + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", "dependencies": { - "d3-dispatch": "1 - 3", - "d3-drag": "2 - 3", - "d3-interpolate": "1 - 3", - "d3-selection": "2 - 3", - "d3-transition": "2 - 3" + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" }, "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": ">= 0.4" } }, - "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "node_modules/eciesjs": { + "version": "0.4.18", + "resolved": "https://registry.npmjs.org/eciesjs/-/eciesjs-0.4.18.tgz", + "integrity": "sha512-wG99Zcfcys9fZux7Cft8BAX/YrOJLJSZ3jyYPfhZHqN2E+Ffx+QXBDsv3gubEgPtV6dTzJMSQUwk1H98/t/0wQ==", + "license": "MIT", "dependencies": { - "ms": "2.1.2" + "@ecies/ciphers": "^0.2.5", + "@noble/ciphers": "^1.3.0", + "@noble/curves": "^1.9.7", + "@noble/hashes": "^1.8.0" }, "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "bun": ">=1", + "deno": ">=2", + "node": ">=16" } }, - "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/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" }, - "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/electron-to-chromium": { + "version": "1.5.344", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.344.tgz", + "integrity": "sha512-4MxfbmNDm+KPh066EZy+eUnkcDPcZ35wNmOWzFuh/ijvHsve6kbLTLURy88uCNK5FbpN+yk2nQY6BYh1GEt+wg==", + "license": "ISC" }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", "engines": { - "node": ">=0.4.0" + "node": ">= 0.8" } }, - "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, + "node_modules/enhanced-resolve": { + "version": "5.21.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.21.0.tgz", + "integrity": "sha512-otxSQPw4lkOZWkHpB3zaEQs6gWYEsmX4xQF68ElXC/TWvGxGMSGOvoNbaLXm6/cS/fSfHtsEdw90y20PCd+sCA==", + "license": "MIT", "dependencies": { - "path-type": "^4.0.0" + "graceful-fs": "^4.2.4", + "tapable": "^2.3.3" }, "engines": { - "node": ">=8" + "node": ">=10.13.0" } }, - "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" - }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "license": "MIT", "engines": { - "node": ">=6.0.0" + "node": ">=6" } }, - "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==", + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "license": "MIT", "dependencies": { - "@babel/runtime": "^7.8.7", - "csstype": "^3.0.2" + "is-arrayish": "^0.2.1" } }, - "node_modules/dotenv": { - "version": "16.3.1", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", - "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==", + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/motdotla/dotenv?sponsor=1" + "node": ">= 0.4" } }, - "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/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } }, - "node_modules/envify": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/envify/-/envify-1.2.1.tgz", - "integrity": "sha512-iShXdC8O/5jo8hPIh65Y1M28YkxW4cqPw3/mg4g5q4RQbMUljOeWp2Ctno4S1ZyTUyoGrioGAZu2t9kP79wp0Q==", + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", "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" + "es-errors": "^1.3.0" }, - "bin": { - "envify": "bin/envify" + "engines": { + "node": ">= 0.4" } }, - "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" + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" }, "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": ">= 0.4" } }, "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true, + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", "engines": { "node": ">=6" } }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, "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, + "license": "MIT", "engines": { "node": ">=10" }, @@ -5224,166 +4635,160 @@ } }, "node_modules/eslint": { - "version": "8.56.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.56.0.tgz", - "integrity": "sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==", + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.2.1.tgz", + "integrity": "sha512-wiyGaKsDgqXvF40P8mDwiUp/KQjE1FdrIEJsM8PZ3XCiniTMXS3OHWWUe5FI5agoCnr8x4xPrTDZuxsBlNHl+Q==", "dev": true, + "license": "MIT", "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", + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.2", + "@eslint/config-array": "^0.23.5", + "@eslint/config-helpers": "^0.5.5", + "@eslint/core": "^1.2.1", + "@eslint/plugin-kit": "^0.7.1", + "@humanfs/node": "^0.16.6", "@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", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "cross-spawn": "^7.0.6", "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", + "eslint-scope": "^9.1.2", + "eslint-visitor-keys": "^5.0.1", + "espree": "^11.2.0", + "esquery": "^1.7.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", + "file-entry-cache": "^8.0.0", "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", + "minimatch": "^10.2.4", "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" + "optionator": "^0.9.3" }, "bin": { "eslint": "bin/eslint.js" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } } }, "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==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.1.1.tgz", + "integrity": "sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==", "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.24.4", + "@babel/parser": "^7.24.4", + "hermes-parser": "^0.25.1", + "zod": "^3.25.0 || ^4.0.0", + "zod-validation-error": "^3.5.0 || ^4.0.0" + }, "engines": { - "node": ">=10" + "node": ">=18" }, "peerDependencies": { - "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.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==", + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.5.2.tgz", + "integrity": "sha512-hmgTH57GfzoTFjVN0yBwTggnsVUF2tcqi7RJZHqi9lIezSs4eFyAMktA68YD4r5kNw1mxyY4dmkyoFDb3FIqrA==", "dev": true, + "license": "MIT", "peerDependencies": { - "eslint": ">=7" + "eslint": "^9 || ^10" } }, "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==", + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", + "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { + "@types/esrecurse": "^4.3.1", + "@types/estree": "^1.0.8", "esrecurse": "^4.3.0", "estraverse": "^5.2.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "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==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", "dev": true, + "license": "Apache-2.0", "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "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==", + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", + "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "acorn": "^8.9.0", + "acorn": "^8.16.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" + "eslint-visitor-keys": "^5.0.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "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, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "license": "BSD-2-Clause", "bin": { "esparse": "bin/esparse.js", "esvalidate": "bin/esvalidate.js" }, "engines": { - "node": ">=0.4.0" + "node": ">=4" } }, "node_modules/esquery": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", - "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "estraverse": "^5.1.0" }, @@ -5396,6 +4801,7 @@ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "estraverse": "^5.2.0" }, @@ -5408,6 +4814,7 @@ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, + "license": "BSD-2-Clause", "engines": { "node": ">=4.0" } @@ -5417,55 +4824,145 @@ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true, + "license": "BSD-2-Clause", "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==", + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", "engines": { - "node": ">=6" + "node": ">= 0.6" + } + }, + "node_modules/eventsource": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", + "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", + "license": "MIT", + "dependencies": { + "eventsource-parser": "^3.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/eventsource-parser": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.8.tgz", + "integrity": "sha512-70QWGkr4snxr0OXLRWsFLeRBIRPuQOvt4s8QYjmUlmlkyTZkRqS7EDVRZtzU3TiyDbXSzaOeF0XUKy8PchzukQ==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/execa": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-9.6.1.tgz", + "integrity": "sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==", + "license": "MIT", + "dependencies": { + "@sindresorhus/merge-streams": "^4.0.0", + "cross-spawn": "^7.0.6", + "figures": "^6.1.0", + "get-stream": "^9.0.0", + "human-signals": "^8.0.1", + "is-plain-obj": "^4.1.0", + "is-stream": "^4.0.1", + "npm-run-path": "^6.0.0", + "pretty-ms": "^9.2.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^4.0.0", + "yoctocolors": "^2.1.1" + }, + "engines": { + "node": "^18.19.0 || >=20.5.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/eventemitter3": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" + "node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } }, - "node_modules/events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "node_modules/express-rate-limit": { + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.4.1.tgz", + "integrity": "sha512-NGVYwQSAyEQgzxX1iCM978PP9AdO/hW93gMcF6ZwQCm+rFvLsBH6w4xcXWTcliS8La5EPRN3p9wzItqBwJrfNw==", + "license": "MIT", + "dependencies": { + "ip-address": "10.1.0" + }, "engines": { - "node": ">=0.8.x" + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": ">= 4.11" } }, "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" - } + "license": "MIT" }, "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==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "license": "MIT", "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" + "micromatch": "^4.0.8" }, "engines": { "node": ">=8.6.0" @@ -5475,6 +4972,7 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", "dependencies": { "is-glob": "^4.0.1" }, @@ -5486,49 +4984,131 @@ "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 + "dev": true, + "license": "MIT" }, "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 + "dev": true, + "license": "MIT" + }, + "node_modules/fast-string-truncated-width": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fast-string-truncated-width/-/fast-string-truncated-width-3.0.3.tgz", + "integrity": "sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==", + "license": "MIT" }, - "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==", + "node_modules/fast-string-width": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/fast-string-width/-/fast-string-width-3.0.2.tgz", + "integrity": "sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==", + "license": "MIT", "dependencies": { - "@babel/runtime": "^7.23.8", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.1.0" + "fast-string-truncated-width": "^3.0.2" + } + }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fast-wrap-ansi": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/fast-wrap-ansi/-/fast-wrap-ansi-0.2.0.tgz", + "integrity": "sha512-rLV8JHxTyhVmFYhBJuMujcrHqOT2cnO5Zxj37qROj23CP39GXubJRBUFF0z8KFK77Uc0SukZUf7JZhsVEQ6n8w==", + "license": "MIT", + "dependencies": { + "fast-string-width": "^3.0.2" } }, "node_modules/fastq": { - "version": "1.16.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.16.0.tgz", - "integrity": "sha512-ifCoaXsDrsdkWTtiNJX5uzHDsrck5TzfKKDcuFFTIrrc/BS076qgEIfoIy1VeZqViznfKiysPYTh/QeHtnIsYA==", + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "license": "ISC", "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/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "node_modules/figures": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz", + "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==", + "license": "MIT", + "dependencies": { + "is-unicode-supported": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "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==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", "dev": true, + "license": "MIT", "dependencies": { - "flat-cache": "^3.0.4" + "flat-cache": "^4.0.0" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=16.0.0" } }, "node_modules/fill-range": { @@ -5543,11 +5123,33 @@ "node": ">=8" } }, + "node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "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, + "license": "MIT", "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" @@ -5560,35 +5162,37 @@ } }, "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==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", "dev": true, + "license": "MIT", "dependencies": { "flatted": "^3.2.9", - "keyv": "^4.5.3", - "rimraf": "^3.0.2" + "keyv": "^4.5.4" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=16" } }, "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 + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true, + "license": "ISC" }, "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==", + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", "funding": [ { "type": "individual", "url": "https://github.com/sponsors/RubenVerborgh" } ], + "license": "MIT", "engines": { "node": ">=4.0" }, @@ -5598,219 +5202,332 @@ } } }, - "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==", + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", "dependencies": { - "cross-spawn": "^7.0.0", - "signal-exit": "^4.0.1" + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" }, "engines": { - "node": ">=14" + "node": ">= 6" + } + }, + "node_modules/form-data/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==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/form-data/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==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "engines": { + "node": ">= 0.6" } }, - "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==", + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "license": "MIT", "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" + "fetch-blob": "^3.1.2" }, "engines": { - "node": ">= 6" + "node": ">=12.20.0" } }, - "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, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", "engines": { - "node": "*" + "node": ">= 0.6" + } + }, + "node_modules/framer-motion": { + "version": "12.38.0", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.38.0.tgz", + "integrity": "sha512-rFYkY/pigbcswl1XQSb7q424kSTQ8q6eAC+YUsSKooHQYuLdzdHjrt6uxUC+PRAO++q5IS7+TamgIw1AphxR+g==", + "license": "MIT", + "dependencies": { + "motion-dom": "^12.38.0", + "motion-utils": "^12.36.0", + "tslib": "^2.4.0" }, - "funding": { - "type": "patreon", - "url": "https://github.com/sponsors/rawify" + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } } }, - "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/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/fs-extra": { + "version": "11.3.4", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.4.tgz", + "integrity": "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } }, "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, + "license": "MIT", "optional": true, "os": [ "darwin" ], "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + "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==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/fuzzysort": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fuzzysort/-/fuzzysort-3.1.0.tgz", + "integrity": "sha512-sR9BNCjBg6LNgwvxlBd0sBABvQitkLzoVY9MYYROQVX/FvfJ4Mai9LsGhDgd8qYdds0bY77VzYd5iuB+v5rwQQ==", + "license": "MIT" + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" } }, - "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==", + "node_modules/get-east-asian-width": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.5.0.tgz", + "integrity": "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, - "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", + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, "engines": { - "node": ">=10" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "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==", + "license": "MIT", "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" - }, + "node_modules/get-own-enumerable-keys": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-own-enumerable-keys/-/get-own-enumerable-keys-1.0.0.tgz", + "integrity": "sha512-PKsK2FSrQCyxcGHsGrLDcK0lx+0Ke+6e8KFFozA9/fIQLhQzPaRvJFdcz7+Axg3jUH/Mq+NI4xa5u/UT2tQskA==", + "license": "MIT", "engines": { - "node": "*" + "node": ">=14.16" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/sponsors/sindresorhus" } }, - "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==", + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", "dependencies": { - "is-glob": "^4.0.3" + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" }, "engines": { - "node": ">=10.13.0" + "node": ">= 0.4" } }, - "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, + "node_modules/get-stream": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", + "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", + "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "@sec-ant/readable-stream": "^0.4.1", + "is-stream": "^4.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "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==", + "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==", "dev": true, + "license": "ISC", "dependencies": { - "brace-expansion": "^1.1.7" + "is-glob": "^4.0.3" }, "engines": { - "node": "*" + "node": ">=10.13.0" } }, "node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "version": "17.5.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.5.0.tgz", + "integrity": "sha512-qoV+HK2yFl/366t2/Cb3+xxPUo5BuMynomoDmiaZBIdbs+0pYbjfZU+twLhGKp4uCZ/+NbtpVepH5bGCxRyy2g==", "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=18" }, "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" - }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", "engines": { - "node": ">=10" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, "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 + "license": "ISC" }, - "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/graphql": { + "version": "16.13.2", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.13.2.tgz", + "integrity": "sha512-5bJ+nf/UCpAjHM8i06fl7eLyVC9iuNAjm9qzkiu2ZGhM0VscSvS6WDPfAwkdkBuoXGM9FJSbKl6wylMwP9Ktig==", + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" + } }, - "node_modules/grid-index": { + "node_modules/has-symbols": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/grid-index/-/grid-index-1.1.0.tgz", - "integrity": "sha512-HZRwumpOGUrHyxO5bqKZL0B0GlUpwtCAzZ42sgxUPniu33R1LSFH5yrIcBCHjkctCAh3mtWKcKd9J4vDDdeVHA==" + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "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, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, "engines": { - "node": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "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==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", + "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", + "license": "MIT", "dependencies": { "function-bind": "^1.1.2" }, @@ -5818,58 +5535,114 @@ "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/headers-polyfill": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/headers-polyfill/-/headers-polyfill-5.0.1.tgz", + "integrity": "sha512-1TJ6Fih/b8h5TIcv+1+Hw0PDQWJTKDKzFZzcKOiW1wJza3XoAQlkCuXLbymPYB8+ZQyw8mHvdw560e8zVFIWyA==", + "license": "MIT", + "dependencies": { + "@types/set-cookie-parser": "^2.4.10", + "set-cookie-parser": "^3.0.1" } }, - "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/hermes-estree": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", + "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", + "dev": true, + "license": "MIT" }, - "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==", + "node_modules/hermes-parser": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", + "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", "dev": true, + "license": "MIT", + "dependencies": { + "hermes-estree": "0.25.1" + } + }, + "node_modules/hono": { + "version": "4.12.15", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.15.tgz", + "integrity": "sha512-qM0jDhFEaCBb4TxoW7f53Qrpv9RBiayUHo0S52JudprkhvpjIrGoU1mnnr29Fvd1U335ZFPZQY1wlkqgfGXyLg==", + "license": "MIT", + "engines": { + "node": ">=16.9.0" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/human-signals": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz", + "integrity": "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "license": "MIT", "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, + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "license": "MIT", "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -5886,157 +5659,259 @@ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true, + "license": "MIT", "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==" + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" }, - "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==", + "node_modules/ip-address": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", + "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", + "license": "MIT", "engines": { - "node": ">=12" + "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/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" } }, - "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" + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "license": "MIT" + }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "license": "MIT", + "bin": { + "is-docker": "cli.js" }, "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" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "license": "MIT", + "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==", + "license": "MIT", + "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==", + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-in-ssh": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-in-ssh/-/is-in-ssh-1.0.0.tgz", + "integrity": "sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw==", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-interactive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", + "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-node-process": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-node-process/-/is-node-process-1.2.0.tgz", + "integrity": "sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==", + "license": "MIT" + }, + "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.10.0" + "node": ">=0.12.0" } }, - "node_modules/is-fullwidth-code-point": { + "node_modules/is-obj": { "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==", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-3.0.0.tgz", + "integrity": "sha512-IlsXEHOjtKhpN8r/tRFj2nDyTmHvcfNeu/nrRIcXE17ROeatXchkojffa1SpdqW4cr/Fj6QkEf/Gn4zf6KKvEQ==", + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "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" - }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "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==", + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, + "node_modules/is-regexp": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-3.1.0.tgz", + "integrity": "sha512-rbku49cWloU5bSMI+zaRaXdQHXnthP6DZ/vLnfdSKyL4zUzuWnomtOEiZZOd+ioQ+avFo/qau3KPTc7Fjy1uPA==", "license": "MIT", "engines": { - "node": ">=0.12.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "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, + "node_modules/is-stream": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", + "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + "node_modules/is-unicode-supported": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "node_modules/jackspeak": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", - "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", + "node_modules/is-wsl": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", + "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", + "license": "MIT", "dependencies": { - "@isaacs/cliui": "^8.0.2" + "is-inside-container": "^1.0.0" }, "engines": { - "node": ">=14" + "node": ">=16" }, "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" + "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, "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==", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "license": "MIT", "bin": { - "jiti": "bin/jiti.js" + "jiti": "lib/jiti-cli.mjs" } }, - "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==", + "node_modules/jose": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.2.tgz", + "integrity": "sha512-d7kPDd34KO/YnzaDOlikGpOurfF0ByC2sEV4cANCtdqLlTfBlw2p14O/5d/zv40gJPbIQxfES3nSx1/oYNyuZQ==", + "license": "MIT", "funding": { - "type": "opencollective", - "url": "https://opencollective.com/js-sdsl" + "url": "https://github.com/sponsors/panva" } }, "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==" + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" }, "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, + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "license": "MIT", "dependencies": { "argparse": "^2.0.1" }, @@ -6044,83 +5919,369 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, "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 + "dev": true, + "license": "MIT" + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "license": "MIT" }, "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 + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-typed": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", + "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", + "license": "BSD-2-Clause" }, "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 + "dev": true, + "license": "MIT" }, - "node_modules/jstransform": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/jstransform/-/jstransform-3.0.0.tgz", - "integrity": "sha512-sMwqW0EdQk2A5NjddlcSSLp6t7pIknOrJtxPU3kMN82RJXPGbdC3fcM5VhIsApNKL1hpeH38iSQsJRbgprPQZg==", - "peer": true, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "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, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "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, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "license": "MPL-2.0", "dependencies": { - "base62": "0.1.1", - "esprima-fb": "~3001.1.0-dev-harmony-fb", - "source-map": "0.1.31" + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=0.8.8" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "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/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "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" - }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">= 0.8.0" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/lilconfig": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", - "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=10" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, "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==" + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "license": "MIT" }, "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, + "license": "MIT", "dependencies": { "p-locate": "^5.0.0" }, @@ -6131,91 +6292,102 @@ "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==", + "node_modules/log-symbols": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-6.0.0.tgz", + "integrity": "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==", + "license": "MIT", "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" + "chalk": "^5.3.0", + "is-unicode-supported": "^1.3.0" }, - "bin": { - "loose-envify": "cli.js" + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols/node_modules/is-unicode-supported": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", + "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "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, + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "license": "ISC", "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" + "yallist": "^3.0.2" } }, "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==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.11.0.tgz", + "integrity": "sha512-UOhjdztXCgdBReRcIhsvz2siIBogfv/lhJEIViCpLt924dO+GDms9T7DNoucI23s6kEPpe988m5N0D2ajnzb2g==", + "license": "ISC", "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" + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "license": "MIT" + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "license": "MIT", "engines": { "node": ">= 8" } @@ -6233,34 +6405,74 @@ "node": ">=8.6" } }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "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==", + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", "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==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", "dependencies": { - "mime-db": "1.52.0" + "mime-db": "^1.54.0" }, "engines": { - "node": ">= 0.6" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^5.0.5" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -6270,106 +6482,135 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", "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/motion": { + "version": "12.38.0", + "resolved": "https://registry.npmjs.org/motion/-/motion-12.38.0.tgz", + "integrity": "sha512-uYfXzeHlgThchzwz5Te47dlv5JOUC7OB4rjJ/7XTUgtBZD8CchMN8qEJ4ZVsUmTyYA44zjV0fBwsiktRuFnn+w==", + "license": "MIT", + "dependencies": { + "framer-motion": "^12.38.0", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } } }, - "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/motion-dom": { + "version": "12.38.0", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.38.0.tgz", + "integrity": "sha512-pdkHLD8QYRp8VfiNLb8xIBJis1byQ9gPT3Jnh2jqfFtAsWUA3dEepDlsWe/xMpO8McV+VdpKVcp+E+TGJEtOoA==", + "license": "MIT", + "dependencies": { + "motion-utils": "^12.36.0" } }, - "node_modules/mqtt": { - "version": "5.3.5", - "resolved": "https://registry.npmjs.org/mqtt/-/mqtt-5.3.5.tgz", - "integrity": "sha512-xd7qt/LEM721U6yHQcqjlaAKXL1Fsqf/MXq6C2WPi/6OXK2jdSzL1eZ7ZUgjea7IY2yFLRWD5LNdT1mL0arPoA==", + "node_modules/motion-utils": { + "version": "12.36.0", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.36.0.tgz", + "integrity": "sha512-eHWisygbiwVvf6PZ1vhaHCLamvkSbPIeAYxWUuL3a2PD/TROgE7FvfHWTIH4vMl798QLfMw15nRqIaRDXTlYRg==", + "license": "MIT" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/msw": { + "version": "2.13.6", + "resolved": "https://registry.npmjs.org/msw/-/msw-2.13.6.tgz", + "integrity": "sha512-GAJbQy8Ra/Ydjt0Hb2MGT2qhzd83J3+QZMHdH85uW7r/XkKc846+Ma2PLif5hGvTm5Yqa+wkcstpim0WeLZU9g==", + "hasInstallScript": true, + "license": "MIT", "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" + "@inquirer/confirm": "^6.0.11", + "@mswjs/interceptors": "^0.41.3", + "@open-draft/deferred-promise": "^3.0.0", + "@types/statuses": "^2.0.6", + "cookie": "^1.1.1", + "graphql": "^16.13.2", + "headers-polyfill": "^5.0.1", + "is-node-process": "^1.2.0", + "outvariant": "^1.4.3", + "path-to-regexp": "^6.3.0", + "picocolors": "^1.1.1", + "rettime": "^0.11.7", + "statuses": "^2.0.2", + "strict-event-emitter": "^0.5.1", + "tough-cookie": "^6.0.1", + "type-fest": "^5.5.0", + "until-async": "^3.0.2", + "yargs": "^17.7.2" }, "bin": { - "mqtt": "build/bin/mqtt.js", - "mqtt_pub": "build/bin/pub.js", - "mqtt_sub": "build/bin/sub.js" + "msw": "cli/index.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": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/mswjs" + }, + "peerDependencies": { + "typescript": ">= 4.8.x" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "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==", + "node_modules/msw/node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "license": "MIT", "engines": { - "node": "14 || >=16.14" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "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/mute-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-3.0.0.tgz", + "integrity": "sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw==", + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", "funding": [ { "type": "github", "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -6381,191 +6622,239 @@ "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" - }, + "dev": true, + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", "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": ">= 0.6" } }, "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==", + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.4.6.tgz", + "integrity": "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==", + "license": "MIT", "peerDependencies": { - "next": "*", - "react": "*", - "react-dom": "*" + "react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc" } }, - "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==", + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", "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/jimmywarting" }, { "type": "github", - "url": "https://github.com/sponsors/ai" + "url": "https://paypal.me/jimmywarting" } ], - "peer": true, - "dependencies": { - "nanoid": "^3.3.6", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" - }, + "license": "MIT", "engines": { - "node": "^10 || ^12 || >=14" + "node": ">=10.5.0" } }, - "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==", + "node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "license": "MIT", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, "engines": { - "node": ">=0.10.0" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" } }, - "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/node-releases": { + "version": "2.0.38", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.38.tgz", + "integrity": "sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw==", + "license": "MIT" }, - "node_modules/number-allocator": { - "version": "1.0.14", - "resolved": "https://registry.npmjs.org/number-allocator/-/number-allocator-1.0.14.tgz", - "integrity": "sha512-OrL44UTVAvkKdOdRQZIJpLkAdjXGTRda052sN4sO77bKEzYYqWKMBjQvrJFzqygI99gL6Z4u2xctPW1tB8ErvA==", + "node_modules/npm-run-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", + "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==", + "license": "MIT", "dependencies": { - "debug": "^4.3.1", - "js-sdsl": "4.3.0" + "path-key": "^4.0.0", + "unicorn-magic": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/numeral": { - "version": "1.5.6", - "resolved": "https://registry.npmjs.org/numeral/-/numeral-1.5.6.tgz", - "integrity": "sha512-ajp+xurmcvkOLZURhHP2O7AyyF+v2xQDeCODlzALrNeAQnriYaWu0c8I/mu985WaVl2O2lgdOt0QgQHlCAQ3UA==", + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", "license": "MIT", "engines": { - "node": "*" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "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==", + "license": "MIT", "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==", + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", "engines": { - "node": ">= 6" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "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==", + "node_modules/object-treeify": { + "version": "1.1.33", + "resolved": "https://registry.npmjs.org/object-treeify/-/object-treeify-1.1.33.tgz", + "integrity": "sha512-EFVjAYfzWqWsBMRHPMAXLCDIJnpMhdWAqR7xG6M6a2cs6PMFpl/+Z20w9zDW4vkxOFfddegBKq9Rehd0bxWE7A==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", "license": "MIT", - "peer": true + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } }, "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, + "license": "ISC", "dependencies": { "wrappy": "1" } }, + "node_modules/onetime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "license": "MIT", + "dependencies": { + "mimic-function": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/open/-/open-11.0.0.tgz", + "integrity": "sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw==", + "license": "MIT", + "dependencies": { + "default-browser": "^5.4.0", + "define-lazy-prop": "^3.0.0", + "is-in-ssh": "^1.0.0", + "is-inside-container": "^1.0.0", + "powershell-utils": "^0.1.0", + "wsl-utils": "^0.3.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/optionator": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", - "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", "dev": true, + "license": "MIT", "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" + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" }, "engines": { "node": ">= 0.8.0" } }, + "node_modules/ora": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/ora/-/ora-8.2.0.tgz", + "integrity": "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==", + "license": "MIT", + "dependencies": { + "chalk": "^5.3.0", + "cli-cursor": "^5.0.0", + "cli-spinners": "^2.9.2", + "is-interactive": "^2.0.0", + "is-unicode-supported": "^2.0.0", + "log-symbols": "^6.0.0", + "stdin-discarder": "^0.2.2", + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/outvariant": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/outvariant/-/outvariant-1.4.3.tgz", + "integrity": "sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==", + "license": "MIT" + }, "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, + "license": "MIT", "dependencies": { "yocto-queue": "^0.1.0" }, @@ -6581,6 +6870,7 @@ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, + "license": "MIT", "dependencies": { "p-limit": "^3.0.2" }, @@ -6595,7 +6885,7 @@ "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, + "license": "MIT", "dependencies": { "callsites": "^3.0.0" }, @@ -6603,117 +6893,107 @@ "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==", + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "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" - }, + "node_modules/parse-ms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-4.0.0.tgz", + "integrity": "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==", + "license": "MIT", "engines": { - "node": ">=16 || 14 >=14.17" + "node": ">=18" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/sponsors/sindresorhus" } }, - "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==", + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", "engines": { - "node": "14 || >=16.14" + "node": ">= 0.8" } }, - "node_modules/path-type": { + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "license": "MIT" + }, + "node_modules/path-exists": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true, + "license": "MIT", "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/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" } }, + "node_modules/path-to-regexp": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", + "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", + "license": "MIT" + }, "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" }, "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "license": "MIT", "engines": { - "node": ">=8.6" + "node": ">=12" }, "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==", + "node_modules/pkce-challenge": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", + "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", + "license": "MIT", "engines": { - "node": ">= 6" + "node": ">=16.20.0" } }, "node_modules/postcss": { - "version": "8.4.38", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", - "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", + "version": "8.5.10", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.10.tgz", + "integrity": "sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==", "funding": [ { "type": "opencollective", @@ -6728,113 +7008,21 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { - "nanoid": "^3.3.7", - "picocolors": "^1.0.0", - "source-map-js": "^1.2.0" + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" }, "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==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", + "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", + "license": "MIT", "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -6843,151 +7031,112 @@ "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/powershell-utils": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/powershell-utils/-/powershell-utils-0.1.0.tgz", + "integrity": "sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "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, + "license": "MIT", "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/pretty-ms": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.3.0.tgz", + "integrity": "sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==", + "license": "MIT", + "dependencies": { + "parse-ms": "^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "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==", + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "license": "MIT", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, "engines": { - "node": ">= 0.6.0" + "node": ">= 6" } }, - "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/prompts/node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "license": "MIT", + "engines": { + "node": ">=6" + } }, - "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==", + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", "dependencies": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.13.1" + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" } }, - "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==" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } }, "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, + "license": "MIT", "engines": { "node": ">=6" } }, + "node_modules/qs": { + "version": "6.15.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.1.tgz", + "integrity": "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -7005,142 +7154,158 @@ "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==", + "node_modules/radix-ui": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/radix-ui/-/radix-ui-1.4.3.tgz", + "integrity": "sha512-aWizCQiyeAenIdUbqEpXgRA1ya65P13NKn/W8rWkcN0OPkRDxdBVLWnIEDsS2RpwCK2nobI7oMUSmexzTDyAmA==", "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" + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-accessible-icon": "1.1.7", + "@radix-ui/react-accordion": "1.2.12", + "@radix-ui/react-alert-dialog": "1.1.15", + "@radix-ui/react-arrow": "1.1.7", + "@radix-ui/react-aspect-ratio": "1.1.7", + "@radix-ui/react-avatar": "1.1.10", + "@radix-ui/react-checkbox": "1.3.3", + "@radix-ui/react-collapsible": "1.1.12", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-context-menu": "2.2.16", + "@radix-ui/react-dialog": "1.1.15", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-dropdown-menu": "2.1.16", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-form": "0.1.8", + "@radix-ui/react-hover-card": "1.1.15", + "@radix-ui/react-label": "2.1.7", + "@radix-ui/react-menu": "2.1.16", + "@radix-ui/react-menubar": "1.1.16", + "@radix-ui/react-navigation-menu": "1.2.14", + "@radix-ui/react-one-time-password-field": "0.1.8", + "@radix-ui/react-password-toggle-field": "0.1.3", + "@radix-ui/react-popover": "1.1.15", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-progress": "1.1.7", + "@radix-ui/react-radio-group": "1.3.8", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-scroll-area": "1.2.10", + "@radix-ui/react-select": "2.2.6", + "@radix-ui/react-separator": "1.1.7", + "@radix-ui/react-slider": "1.3.6", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-switch": "1.2.6", + "@radix-ui/react-tabs": "1.1.13", + "@radix-ui/react-toast": "1.2.15", + "@radix-ui/react-toggle": "1.1.10", + "@radix-ui/react-toggle-group": "1.1.11", + "@radix-ui/react-toolbar": "1.1.11", + "@radix-ui/react-tooltip": "1.2.8", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-effect-event": "0.0.2", + "@radix-ui/react-use-escape-keydown": "1.1.1", + "@radix-ui/react-use-is-hydrated": "0.1.0", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-size": "1.1.1", + "@radix-ui/react-visually-hidden": "1.2.3" }, "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" + "@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" }, - "peerDependencies": { - "date-fns": "^2.28.0 || ^3.0.0", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } } }, - "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/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" } }, - "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==", + "node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "license": "MIT", "dependencies": { - "clsx": "^1.1.1", - "prop-types": "^15.8.1" + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" }, - "peerDependencies": { - "react": ">= 16.3.0", - "react-dom": ">= 16.3.0" + "engines": { + "node": ">= 0.10" } }, - "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==", + "node_modules/react": { + "version": "19.2.5", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.5.tgz", + "integrity": "sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==", + "license": "MIT", "engines": { - "node": ">=6" + "node": ">=0.10.0" } }, - "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==", + "node_modules/react-dom": { + "version": "19.2.5", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.5.tgz", + "integrity": "sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag==", + "license": "MIT", "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" + "scheduler": "^0.27.0" }, "peerDependencies": { - "react": ">= 16.3.0", - "react-dom": ">= 16.3.0" + "react": "^19.2.5" } }, - "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-international-phone": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/react-international-phone/-/react-international-phone-4.8.0.tgz", + "integrity": "sha512-PoyXx8t0OZNZXLupZN5UtmLb8nO6PQ6f6jQvYCAtg7VzxonuBcDs/4YA4+flqZZj5QOVqN4DLY1p39mEtJAwzw==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } }, "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==", + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz", + "integrity": "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==", + "license": "MIT", "dependencies": { - "react-remove-scroll-bar": "^2.3.3", - "react-style-singleton": "^2.2.1", + "react-remove-scroll-bar": "^2.3.7", + "react-style-singleton": "^2.2.3", "tslib": "^2.1.0", - "use-callback-ref": "^1.3.0", - "use-sidecar": "^1.1.2" + "use-callback-ref": "^1.3.3", + "use-sidecar": "^1.1.3" }, "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" + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "peerDependenciesMeta": { "@types/react": { @@ -7149,19 +7314,20 @@ } }, "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==", + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz", + "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==", + "license": "MIT", "dependencies": { - "react-style-singleton": "^2.2.1", + "react-style-singleton": "^2.2.2", "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" + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" }, "peerDependenciesMeta": { "@types/react": { @@ -7169,77 +7335,78 @@ } } }, - "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==", + "version": "7.14.2", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.14.2.tgz", + "integrity": "sha512-yCqNne6I8IB6rVCH7XUvlBK7/QKyqypBFGv+8dj4QBFJiiRX+FG7/nkdAvGElyvVZ/HQP5N19wzteuTARXi5Gw==", + "license": "MIT", "dependencies": { - "@remix-run/router": "1.14.2" + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0" }, "engines": { - "node": ">=14.0.0" + "node": ">=20.0.0" }, "peerDependencies": { - "react": ">=16.8" + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } } }, "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==", + "version": "7.14.2", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.14.2.tgz", + "integrity": "sha512-YZcM5ES8jJSM+KrJ9BdvHHqlnGTg5tH3sC5ChFRj4inosKctdyzBDhOyyHdGk597q2OT6NTrCA1OvB/YDwfekQ==", + "license": "MIT", "dependencies": { - "@remix-run/router": "1.14.2", - "react-router": "6.21.2" + "react-router": "7.14.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=20.0.0" }, "peerDependencies": { - "react": ">=16.8", - "react-dom": ">=16.8" + "react": ">=18", + "react-dom": ">=18" } }, - "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" + "node_modules/react-router/node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "license": "MIT", + "engines": { + "node": ">=18" }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, + "node_modules/react-router/node_modules/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "license": "MIT" + }, "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==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", + "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==", + "license": "MIT", "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" + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "peerDependenciesMeta": { "@types/react": { @@ -7247,230 +7414,156 @@ } } }, - "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==", + "node_modules/recast": { + "version": "0.23.11", + "resolved": "https://registry.npmjs.org/recast/-/recast-0.23.11.tgz", + "integrity": "sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA==", "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" + "ast-types": "^0.16.1", + "esprima": "~4.0.0", + "source-map": "~0.6.1", + "tiny-invariant": "^1.3.3", + "tslib": "^2.0.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">= 4" } }, - "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" - }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", "engines": { - "node": ">=8.10.0" + "node": ">=0.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" - }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", "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": ">=0.10.0" } }, "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, + "license": "MIT", "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==", + "node_modules/restore-cursor": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", + "license": "MIT", "dependencies": { - "protocol-buffers-schema": "^3.3.1" + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/rettime": { + "version": "0.11.8", + "resolved": "https://registry.npmjs.org/rettime/-/rettime-0.11.8.tgz", + "integrity": "sha512-0fERGXktJTyJ+h8fBEiPxHPEFOu0h15JY7JtwrOVqR5K+vb99ho6IyOo7ekLS3h4sJCzIDy4VWKIbZUfe9njmg==", + "license": "MIT" + }, "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "license": "MIT", "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, + "node_modules/rolldown": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.17.tgz", + "integrity": "sha512-ZrT53oAKrtA4+YtBWPQbtPOxIbVDbxT0orcYERKd63VJTF13zPcgXTvD4843L8pcsI7M6MErt8QtON6lrB9tyA==", + "license": "MIT", "dependencies": { - "glob": "^7.1.3" + "@oxc-project/types": "=0.127.0", + "@rolldown/pluginutils": "1.0.0-rc.17" }, "bin": { - "rimraf": "bin.js" + "rolldown": "bin/cli.mjs" }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.0.0-rc.17", + "@rolldown/binding-darwin-arm64": "1.0.0-rc.17", + "@rolldown/binding-darwin-x64": "1.0.0-rc.17", + "@rolldown/binding-freebsd-x64": "1.0.0-rc.17", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.17", + "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.17", + "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.17", + "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.17", + "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.17", + "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.17", + "@rolldown/binding-linux-x64-musl": "1.0.0-rc.17", + "@rolldown/binding-openharmony-arm64": "1.0.0-rc.17", + "@rolldown/binding-wasm32-wasi": "1.0.0-rc.17", + "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.17", + "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.17" + } + }, + "node_modules/rolldown/node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.17.tgz", + "integrity": "sha512-n8iosDOt6Ig1UhJ2AYqoIhHWh/isz0xpicHTzpKBeotdVsTEcxsSA/i3EVM7gQAj0rU27OLAxCjzlj15IWY7bg==", + "license": "MIT" }, - "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, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", "dependencies": { - "@types/estree": "1.0.5" - }, - "bin": { - "rollup": "dist/bin/rollup" + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" }, "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" + "node": ">= 18" + } + }, + "node_modules/router/node_modules/path-to-regexp": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/run-applescript": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", + "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", + "license": "MIT", + "engines": { + "node": ">=18" }, - "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" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/run-parallel": { @@ -7491,55 +7584,148 @@ "url": "https://feross.org/support" } ], + "license": "MIT", "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/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" }, "node_modules/scheduler": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", - "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "license": "MIT", "dependencies": { - "loose-envify": "^1.1.0" + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "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, + "node_modules/serve-static": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "license": "MIT", "dependencies": { - "lru-cache": "^6.0.0" + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" }, - "bin": { - "semver": "bin/semver.js" + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/set-cookie-parser": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-3.1.0.tgz", + "integrity": "sha512-kjnC1DXBHcxaOaOXBHBeRtltsDG2nUiUni+jP92M9gYdW12rsmx92UsfpH7o5tDRs7I1ZZPSQJQGv3UaRfCiuw==", + "license": "MIT" + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/shadcn": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/shadcn/-/shadcn-4.5.0.tgz", + "integrity": "sha512-ZpNOz7IMI5aezbMEWNxBvl2aJ1ek6NuAMqpL/FUnk5IuRxERl8ohYEnqqAmhPOcur8RbGuCoqTZLQ3Oi4Xkf8A==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/parser": "^7.28.0", + "@babel/plugin-transform-typescript": "^7.28.0", + "@babel/preset-typescript": "^7.27.1", + "@dotenvx/dotenvx": "^1.48.4", + "@modelcontextprotocol/sdk": "^1.26.0", + "@types/validate-npm-package-name": "^4.0.2", + "browserslist": "^4.26.2", + "commander": "^14.0.0", + "cosmiconfig": "^9.0.0", + "dedent": "^1.6.0", + "deepmerge": "^4.3.1", + "diff": "^8.0.2", + "execa": "^9.6.0", + "fast-glob": "^3.3.3", + "fs-extra": "^11.3.1", + "fuzzysort": "^3.1.0", + "https-proxy-agent": "^7.0.6", + "kleur": "^4.1.5", + "msw": "^2.10.4", + "node-fetch": "^3.3.2", + "open": "^11.0.0", + "ora": "^8.2.0", + "postcss": "^8.5.6", + "postcss-selector-parser": "^7.1.0", + "prompts": "^2.4.2", + "recast": "^0.23.11", + "stringify-object": "^5.0.0", + "tailwind-merge": "^3.0.1", + "ts-morph": "^26.0.0", + "tsconfig-paths": "^4.2.0", + "validate-npm-package-name": "^7.0.1", + "zod": "^3.24.1", + "zod-to-json-schema": "^3.24.6" }, - "engines": { - "node": ">=10" + "bin": { + "shadcn": "dist/index.js" } }, - "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/shadcn/node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" } }, "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==", + "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" }, @@ -7551,14 +7737,88 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", "engines": { "node": ">=8" } }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "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==", + "license": "ISC", "engines": { "node": ">=14" }, @@ -7566,140 +7826,108 @@ "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/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "license": "MIT" }, "node_modules/sonner": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/sonner/-/sonner-1.4.0.tgz", - "integrity": "sha512-nvkTsIuOmi9e5Wz5If8ldasJjZNVfwiXYijBi2dbijvTQnQppvMcXTFNxL/NUFWlI2yJ1JX7TREDsg+gYm9WyA==", + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/sonner/-/sonner-2.0.7.tgz", + "integrity": "sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==", + "license": "MIT", "peerDependencies": { - "react": "^18.0.0", - "react-dom": "^18.0.0" + "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", + "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "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" - }, + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", "engines": { - "node": ">=0.8.0" + "node": ">=0.10.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==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", "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==", + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", "engines": { - "node": ">= 10.x" + "node": ">= 0.8" } }, - "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, + "node_modules/stdin-discarder": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.2.2.tgz", + "integrity": "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==", + "license": "MIT", "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": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "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/strict-event-emitter": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.5.1.tgz", + "integrity": "sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==", + "license": "MIT" }, "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==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "license": "MIT", "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" }, "engines": { - "node": ">=12" + "node": ">=18" }, "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==", + "node_modules/stringify-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-5.0.0.tgz", + "integrity": "sha512-zaJYxz2FtcMb4f+g60KsRNFOpVMUyuJgA51Zi5Z1DOTC3S59+OQiVOzE9GZt0x72uBGWKsQIuBKeF9iusmKFsg==", + "license": "BSD-2-Clause", "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" + "get-own-enumerable-keys": "^1.0.0", + "is-obj": "^3.0.0", + "is-regexp": "^3.1.0" }, "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" + "node": ">=14.16" }, "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" + "url": "https://github.com/yeoman/stringify-object?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==", + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "license": "MIT", "dependencies": { - "ansi-regex": "^6.0.1" + "ansi-regex": "^6.2.2" }, "engines": { "node": ">=12" @@ -7708,283 +7936,191 @@ "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" - }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=4" } }, - "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, + "node_modules/strip-final-newline": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz", + "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==", + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=18" }, "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" - }, + "node_modules/tagged-tag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/tagged-tag/-/tagged-tag-1.0.0.tgz", + "integrity": "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==", + "license": "MIT", "engines": { - "node": ">= 12.0.0" + "node": ">=20" }, - "peerDependencies": { - "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0" - }, - "peerDependenciesMeta": { - "@babel/core": { - "optional": true - }, - "babel-plugin-macros": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "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/tailwind-merge": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.5.0.tgz", + "integrity": "sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" } }, - "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" - }, + "node_modules/tailwindcss": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.4.tgz", + "integrity": "sha512-HhKppgO81FQof5m6TEnuBWCZGgfRAWbaeOaGT00KOy/Pf/j6oUihdvBpA7ltCeAvZpFhW3j0PTclkxsd4IXYDA==", + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", + "license": "MIT", "engines": { - "node": ">=16 || 14 >=14.17" + "node": ">=6" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, - "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/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==", + "license": "MIT" }, - "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, + "node_modules/tinyglobby": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "fdir": "^6.5.0", + "picomatch": "^4.0.4" }, "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" + "node": ">=12.0.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/SuperchupuDev" } }, - "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==", + "node_modules/tldts": { + "version": "7.0.28", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.28.tgz", + "integrity": "sha512-+Zg3vWhRUv8B1maGSTFdev9mjoo8Etn2Ayfs4cnjlD3CsGkxXX4QyW3j2WJ0wdjYcYmy7Lx2RDsZMhgCWafKIw==", + "license": "MIT", "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" + "tldts-core": "^7.0.28" }, "bin": { - "tailwind": "lib/cli.js", - "tailwindcss": "lib/cli.js" - }, - "engines": { - "node": ">=14.0.0" + "tldts": "bin/cli.js" } }, - "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/tldts-core": { + "version": "7.0.28", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.28.tgz", + "integrity": "sha512-7W5Efjhsc3chVdFhqtaU0KtK32J37Zcr9RKtID54nG+tIpcY79CQK/veYPODxtD/LJ4Lue66jvrQzIX2Z2/pUQ==", + "license": "MIT" }, - "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==", + "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": { - "thenify": ">= 3.1.0 < 4" + "is-number": "^7.0.0" }, "engines": { - "node": ">=0.8" + "node": ">=8.0" } }, - "node_modules/through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", "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": ">=0.6" } }, - "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", + "node_modules/tough-cookie": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.1.tgz", + "integrity": "sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==", + "license": "BSD-3-Clause", "dependencies": { - "is-number": "^7.0.0" + "tldts": "^7.0.5" }, "engines": { - "node": ">=8.0" + "node": ">=16" } }, "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==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", "dev": true, + "license": "MIT", "engines": { - "node": ">=16.13.0" + "node": ">=18.12" }, "peerDependencies": { - "typescript": ">=4.2.0" + "typescript": ">=4.8.4" + } + }, + "node_modules/ts-morph": { + "version": "26.0.0", + "resolved": "https://registry.npmjs.org/ts-morph/-/ts-morph-26.0.0.tgz", + "integrity": "sha512-ztMO++owQnz8c/gIENcM9XfCEzgoGphTv+nKpYNM1bgsdOVC/jRZuEBf6N+mLLDNg68Kl+GgUZfOySaRiG1/Ug==", + "license": "MIT", + "dependencies": { + "@ts-morph/common": "~0.27.0", + "code-block-writer": "^13.0.3" } }, - "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/tsconfig-paths": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", + "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", + "license": "MIT", + "dependencies": { + "json5": "^2.2.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=6" + } }, "node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" }, - "node_modules/tweakpane": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/tweakpane/-/tweakpane-4.0.3.tgz", - "integrity": "sha512-BlcWOAe8oe4c+k9pmLBARGdWB6MVZMszayekkixQXTgkxTaYoTUpHpwVEp+3HkoamZkomodpbBf0CkguIHTgLg==", + "node_modules/tw-animate-css": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/tw-animate-css/-/tw-animate-css-1.4.0.tgz", + "integrity": "sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==", + "license": "MIT", "funding": { - "url": "https://github.com/sponsors/cocopon" + "url": "https://github.com/sponsors/Wombosvideo" } }, "node_modules/type-check": { @@ -7992,6 +8128,7 @@ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, + "license": "MIT", "dependencies": { "prelude-ls": "^1.2.1" }, @@ -8000,27 +8137,40 @@ } }, "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, + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.6.0.tgz", + "integrity": "sha512-8ZiHFm91orbSAe2PSAiSVBVko18pbhbiB3U9GglSzF/zCGkR+rxpHx6sEMCUm4kxY4LjDIUGgCfUMtwfZfjfUA==", + "license": "(MIT OR CC0-1.0)", + "dependencies": { + "tagged-tag": "^1.0.0" + }, "engines": { - "node": ">=10" + "node": ">=20" }, "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/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } }, "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, + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "devOptional": true, + "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -8029,16 +8179,79 @@ "node": ">=14.17" } }, + "node_modules/typescript-eslint": { + "version": "8.59.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.59.0.tgz", + "integrity": "sha512-BU3ONW9X+v90EcCH9ZS6LMackcVtxRLlI3XrYyqZIwVSHIk7Qf7bFw1z0M9Q0IUxhTMZCf8piY9hTYaNEIASrw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.59.0", + "@typescript-eslint/parser": "8.59.0", + "@typescript-eslint/typescript-estree": "8.59.0", + "@typescript-eslint/utils": "8.59.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, "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==" + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "license": "MIT" + }, + "node_modules/unicorn-magic": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", + "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/until-async": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/until-async/-/until-async-3.0.2.tgz", + "integrity": "sha512-IiSk4HlzAMqTUseHHe3VhIGyuFmN90zMTpD3Z3y8jeQbzLIq500MVM7Jq2vUAnTKAFPJrqwkzr6PoTcPhGcOiw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/kettanaito" + } }, "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, + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", "funding": [ { "type": "opencollective", @@ -8053,9 +8266,10 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" + "escalade": "^3.2.0", + "picocolors": "^1.1.1" }, "bin": { "update-browserslist-db": "cli.js" @@ -8069,14 +8283,16 @@ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, + "license": "BSD-2-Clause", "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==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", + "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==", + "license": "MIT", "dependencies": { "tslib": "^2.0.0" }, @@ -8084,8 +8300,8 @@ "node": ">=10" }, "peerDependencies": { - "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "peerDependenciesMeta": { "@types/react": { @@ -8094,9 +8310,10 @@ } }, "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==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz", + "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==", + "license": "MIT", "dependencies": { "detect-node-es": "^1.1.0", "tslib": "^2.0.0" @@ -8105,8 +8322,8 @@ "node": ">=10" }, "peerDependencies": { - "@types/react": "^16.9.0 || ^17.0.0 || ^18.0.0", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "peerDependenciesMeta": { "@types/react": { @@ -8115,54 +8332,55 @@ } }, "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==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.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" + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/validate-npm-package-name": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-7.0.2.tgz", + "integrity": "sha512-hVDIBwsRruT73PbK7uP5ebUt+ezEtCmzZz3F59BSr2F6OVFnJ/6h8liuvdLrQ88Xmnk6/+xGGuq+pG9WwTuy3A==", + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" } }, "node_modules/vite": { - "version": "5.2.12", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.12.tgz", - "integrity": "sha512-/gC8GxzxMK5ntBwb48pR32GGhENnjtY30G4A0jemunsBkiEZFw60s8InGpN8gkhHEkjnRK1aSAxeQgwvFhUHAA==", - "dev": true, + "version": "8.0.10", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.10.tgz", + "integrity": "sha512-rZuUu9j6J5uotLDs+cAA4O5H4K1SfPliUlQwqa6YEwSrWDZzP4rhm00oJR5snMewjxF5V/K3D4kctsUTsIU9Mw==", + "license": "MIT", "dependencies": { - "esbuild": "^0.20.1", - "postcss": "^8.4.38", - "rollup": "^4.13.0" + "lightningcss": "^1.32.0", + "picomatch": "^4.0.4", + "postcss": "^8.5.10", + "rolldown": "1.0.0-rc.17", + "tinyglobby": "^0.2.16" }, "bin": { "vite": "bin/vite.js" }, "engines": { - "node": "^18.0.0 || >=20.0.0" + "node": "^20.19.0 || >=22.12.0" }, "funding": { "url": "https://github.com/vitejs/vite?sponsor=1" @@ -8171,27 +8389,41 @@ "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" + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.1.0", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" }, "peerDependenciesMeta": { "@types/node": { "optional": true }, - "less": { + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { "optional": true }, - "lightningcss": { + "jiti": { + "optional": true + }, + "less": { "optional": true }, "sass": { "optional": true }, + "sass-embedded": { + "optional": true + }, "stylus": { "optional": true }, @@ -8200,23 +8432,29 @@ }, "terser": { "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "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/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "license": "MIT", + "engines": { + "node": ">= 8" } }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", "dependencies": { "isexe": "^2.0.0" }, @@ -8227,58 +8465,21 @@ "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" - }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "node": ">=0.10.0" } }, - "node_modules/wrap-ansi-cjs": { - "name": "wrap-ansi", + "node_modules/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==", + "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -8291,15 +8492,26 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "node_modules/wrap-ansi/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==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/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==" + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" }, - "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "node_modules/wrap-ansi/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==", + "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -8309,93 +8521,121 @@ "node": ">=8" } }, - "node_modules/wrap-ansi/node_modules/ansi-regex": { + "node_modules/wrap-ansi/node_modules/strip-ansi": { "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", - "engines": { - "node": ">=12" + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" + "engines": { + "node": ">=8" } }, - "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==", + "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==", + "license": "ISC" + }, + "node_modules/wsl-utils": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.3.1.tgz", + "integrity": "sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg==", + "license": "MIT", + "dependencies": { + "is-wsl": "^3.1.0", + "powershell-utils": "^0.1.0" + }, "engines": { - "node": ">=12" + "node": ">=20" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "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==", + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "license": "ISC" + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "license": "MIT", "dependencies": { - "ansi-regex": "^6.0.1" + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.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/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", + "engines": { + "node": ">=12" + } }, - "node_modules/ws": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", - "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "node_modules/yargs/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==", "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": ">=8" } }, - "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, + "node_modules/yargs/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==", + "license": "MIT" + }, + "node_modules/yargs/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==", + "license": "MIT", "dependencies": { - "object-keys": "~0.4.0" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" }, "engines": { - "node": ">=0.4" + "node": ">=8" } }, - "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==", + "node_modules/yargs/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==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, "engines": { - "node": ">= 14" + "node": ">=8" } }, "node_modules/yocto-queue": { @@ -8403,6 +8643,7 @@ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" }, @@ -8410,31 +8651,62 @@ "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==", + "node_modules/yocto-spinner": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/yocto-spinner/-/yocto-spinner-1.1.0.tgz", + "integrity": "sha512-/BY0AUXnS7IKO354uLLA2eRcWiqDifEbd6unXCsOxkFDAkhgUL3PH9X2bFoaU0YchnDXsF+iKleeTLJGckbXfA==", + "license": "MIT", "dependencies": { - "use-sync-external-store": "1.2.0" + "yoctocolors": "^2.1.1" }, "engines": { - "node": ">=12.7.0" + "node": ">=18.19" }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yoctocolors": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.2.tgz", + "integrity": "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", + "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.25.2", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", + "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", + "license": "ISC", "peerDependencies": { - "@types/react": ">=16.8", - "immer": ">=9.0.6", - "react": ">=16.8" + "zod": "^3.25.28 || ^4" + } + }, + "node_modules/zod-validation-error": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", + "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "immer": { - "optional": true - }, - "react": { - "optional": true - } + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" } } } diff --git a/web/package.json b/web/package.json index 6070e80..de7e6eb 100644 --- a/web/package.json +++ b/web/package.json @@ -1,84 +1,49 @@ { - "name": "sentinel", + "name": "web", "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 .", + "build": "tsc -b && vite build", + "lint": "eslint .", "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" + "@fontsource-variable/geist": "^5.2.8", + "@fontsource-variable/inter": "^5.2.8", + "@tailwindcss/vite": "^4.2.4", + "@tanstack/react-query": "^5.100.10", + "axios": "^1.15.2", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "cmdk": "^1.1.1", + "lucide-react": "^1.11.0", + "motion": "^12.38.0", + "next-themes": "^0.4.6", + "radix-ui": "^1.4.3", + "react": "^19.2.5", + "react-dom": "^19.2.5", + "react-international-phone": "^4.8.0", + "react-router-dom": "^7.14.2", + "shadcn": "^4.5.0", + "sonner": "^2.0.7", + "tailwind-merge": "^3.5.0", + "tailwindcss": "^4.2.4", + "tw-animate-css": "^1.4.0" }, "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" + "@eslint/js": "^10.0.1", + "@types/node": "^24.12.2", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "eslint": "^10.2.1", + "eslint-plugin-react-hooks": "^7.1.1", + "eslint-plugin-react-refresh": "^0.5.2", + "globals": "^17.5.0", + "typescript": "~6.0.2", + "typescript-eslint": "^8.58.2", + "vite": "^8.0.10" } } 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/favicon.svg b/web/public/favicon.svg new file mode 100644 index 0000000..6893eb1 --- /dev/null +++ b/web/public/favicon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/public/icons.svg b/web/public/icons.svg new file mode 100644 index 0000000..e952219 --- /dev/null +++ b/web/public/icons.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + 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-car-logo.png b/web/public/logo/gr-car-logo.png new file mode 100644 index 0000000..cdf45d5 Binary files /dev/null and b/web/public/logo/gr-car-logo.png differ diff --git a/web/public/logo/gr-logo-blank.png b/web/public/logo/gr-logo-blank.png index f2f0869..81e2c21 100644 Binary files a/web/public/logo/gr-logo-blank.png and b/web/public/logo/gr-logo-blank.png differ diff --git a/web/public/logo/gr-logo.png b/web/public/logo/gr-logo.png index 102b73a..ae65902 100644 Binary files a/web/public/logo/gr-logo.png and b/web/public/logo/gr-logo.png differ diff --git a/web/public/logo/gr-mechanic-black.png b/web/public/logo/gr-mechanic-black.png new file mode 100644 index 0000000..fce9aad Binary files /dev/null and b/web/public/logo/gr-mechanic-black.png differ diff --git a/web/public/logo/gr-mechanic-gray.png b/web/public/logo/gr-mechanic-gray.png new file mode 100644 index 0000000..498b59a Binary files /dev/null and b/web/public/logo/gr-mechanic-gray.png differ diff --git a/web/public/logo/gr-mechanic-white.png b/web/public/logo/gr-mechanic-white.png new file mode 100644 index 0000000..5e3042d Binary files /dev/null and b/web/public/logo/gr-mechanic-white.png differ diff --git a/web/public/logo/gr-wordmark-black.png b/web/public/logo/gr-wordmark-black.png new file mode 100644 index 0000000..22415e6 Binary files /dev/null and b/web/public/logo/gr-wordmark-black.png differ diff --git a/web/public/logo/gr-wordmark.png b/web/public/logo/gr-wordmark.png new file mode 100644 index 0000000..5982f6a Binary files /dev/null and b/web/public/logo/gr-wordmark.png 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.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/AppCard.tsx b/web/src/components/AppCard.tsx new file mode 100644 index 0000000..6584a5d --- /dev/null +++ b/web/src/components/AppCard.tsx @@ -0,0 +1,56 @@ +import { ChevronRight } from "lucide-react" +import { Link } from "react-router-dom" + +import type { Application } from "@/lib/applications" + +function initial(name: string) { + return name.slice(0, 1).toUpperCase() +} + +function relativeTime(iso?: string) { + if (!iso) return null + const ms = Date.now() - new Date(iso).getTime() + const minutes = Math.floor(ms / 60_000) + if (minutes < 1) return "just now" + if (minutes < 60) return `${minutes}m ago` + const hours = Math.floor(minutes / 60) + if (hours < 24) return `${hours}h ago` + const days = Math.floor(hours / 24) + if (days < 30) return `${days}d ago` + const months = Math.floor(days / 30) + return `${months}mo ago` +} + +export function AppCard({ + app, + lastAccessedAt, +}: { + app: Application + lastAccessedAt?: string +}) { + const accessed = relativeTime(lastAccessedAt) + return ( + +
+
+ {app.icon_url ? ( + {app.name} + ) : ( + initial(app.name) + )} +
+ +
+
+

{app.name}

+

{app.description}

+
+ {accessed && ( +

Last accessed {accessed}

+ )} + + ) +} diff --git a/web/src/components/AppFooter.tsx b/web/src/components/AppFooter.tsx new file mode 100644 index 0000000..e2fca7a --- /dev/null +++ b/web/src/components/AppFooter.tsx @@ -0,0 +1,73 @@ +import { useEffect, useState } from "react" + +import { GithubIcon, InstagramIcon, LinkedinIcon, TwitterIcon } from "@/components/icons/socials" +import { api } from "@/lib/api" +import { SOCIAL_LINKS } from "@/lib/links" + +function useCoreVersion() { + const [version, setVersion] = useState(null) + + useEffect(() => { + let cancelled = false + api + .get<{ message?: string }>("/core/ping") + .then((res) => { + if (cancelled) return + const match = res.data?.message?.match(/v([\d.]+)/) + if (match) setVersion(`v${match[1]}`) + }) + .catch(() => {}) + return () => { + cancelled = true + } + }, []) + + return version +} + +const SOCIALS = [ + { href: SOCIAL_LINKS.github, label: "GitHub", Icon: GithubIcon }, + { href: SOCIAL_LINKS.instagram, label: "Instagram", Icon: InstagramIcon }, + { href: SOCIAL_LINKS.twitter, label: "Twitter", Icon: TwitterIcon }, + { href: SOCIAL_LINKS.linkedin, label: "LinkedIn", Icon: LinkedinIcon }, +] + +export function AppFooter() { + const version = useCoreVersion() + + return ( +
+
+
+ Gaucho Racing + Gaucho Racing +
+ + sentinel{version ? ` · ${version}` : ""} + +
+ +
+ +
+

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

+
+ {SOCIALS.map(({ href, label, Icon }) => ( + + + + ))} +
+
+
+ ) +} 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/AppHeader.tsx b/web/src/components/AppHeader.tsx new file mode 100644 index 0000000..aa5c981 --- /dev/null +++ b/web/src/components/AppHeader.tsx @@ -0,0 +1,77 @@ +import { useNavigate } from "react-router-dom" + +import { HeaderNotifications } from "@/components/HeaderNotifications" +import { HeaderSearch } from "@/components/HeaderSearch" +import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar" +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu" +import { Skeleton } from "@/components/ui/skeleton" +import { useAuth } from "@/lib/auth" + +function initials(name: string) { + return name + .split(" ") + .map((part) => part[0]) + .filter(Boolean) + .slice(0, 2) + .join("") + .toUpperCase() +} + +function HeaderUserMenu() { + const navigate = useNavigate() + const { user, isLoading, logout } = useAuth() + + if (isLoading || !user?.user) { + return + } + + const name = `${user.user.first_name} ${user.user.last_name}`.trim() || user.user.username + const email = user.email_auth?.email ?? user.user.email + const avatarUrl = user.user.avatar_url + + return ( + + + + + + + {name} + {email} + + + navigate("/settings")}>Settings + + + Sign out + + + + ) +} + +export function AppHeader() { + return ( +
+
+ + + +
+ ) +} diff --git a/web/src/components/AppShell.tsx b/web/src/components/AppShell.tsx new file mode 100644 index 0000000..1355aaf --- /dev/null +++ b/web/src/components/AppShell.tsx @@ -0,0 +1,26 @@ +import { Outlet } from "react-router-dom" + +import { AppFooter } from "@/components/AppFooter" +import { AppHeader } from "@/components/AppHeader" +import { AppSidebar } from "@/components/AppSidebar" +import { SidebarInset, SidebarProvider } from "@/components/ui/sidebar" +import { TooltipProvider } from "@/components/ui/tooltip" + +export function AppShell() { + return ( + + + + +
+ +
+ +
+ +
+
+
+
+ ) +} diff --git a/web/src/components/AppSidebar.tsx b/web/src/components/AppSidebar.tsx new file mode 100644 index 0000000..a7623bf --- /dev/null +++ b/web/src/components/AppSidebar.tsx @@ -0,0 +1,69 @@ +import { BarChart3, Boxes, Bug, LayoutDashboard, Settings, Users } from "lucide-react" +import { Link, useLocation } from "react-router-dom" + +import { + Sidebar, + SidebarContent, + SidebarFooter, + SidebarGroup, + SidebarGroupContent, + SidebarHeader, + SidebarMenu, + SidebarMenuButton, + SidebarMenuItem, +} from "@/components/ui/sidebar" + +const NAV_ITEMS = [ + { to: "/", label: "Dashboard", icon: LayoutDashboard }, + { to: "/applications", label: "Applications", icon: Boxes }, + { to: "/groups", label: "Groups", icon: Users }, + { to: "/analytics", label: "Analytics", icon: BarChart3 }, + { to: "/settings", label: "Settings", icon: Settings }, + { to: "/debug", label: "Debug", icon: Bug }, +] + +function isActive(currentPath: string, target: string) { + if (target === "/") return currentPath === "/" + return currentPath === target || currentPath.startsWith(`${target}/`) +} + +export function AppSidebar() { + const { pathname } = useLocation() + + return ( + + + +
+ Sentinel + + + + + + + + {NAV_ITEMS.map((item) => ( + + + + + {item.label} + + + + ))} + + + + + + +
+ Gaucho Racing · + v0.1.0 +
+
+ + ) +} 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/HeaderNotifications.tsx b/web/src/components/HeaderNotifications.tsx new file mode 100644 index 0000000..39e43f6 --- /dev/null +++ b/web/src/components/HeaderNotifications.tsx @@ -0,0 +1,111 @@ +import { Bell, Check, ShieldAlert, UserPlus } from "lucide-react" +import { useState } from "react" +import { useNavigate } from "react-router-dom" + +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu" +import { mockNotifications, type Notification } from "@/lib/mock" + +const ICONS: Record = { + group_request: UserPlus, + token_issued: Check, + system: ShieldAlert, +} + +function relativeTime(iso: string) { + const ms = Date.now() - new Date(iso).getTime() + const minutes = Math.floor(ms / 60_000) + if (minutes < 1) return "just now" + if (minutes < 60) return `${minutes}m ago` + const hours = Math.floor(minutes / 60) + if (hours < 24) return `${hours}h ago` + const days = Math.floor(hours / 24) + return `${days}d ago` +} + +export function HeaderNotifications() { + const navigate = useNavigate() + const [items, setItems] = useState(mockNotifications) + const unreadCount = items.filter((n) => !n.read).length + + function open(notification: Notification) { + setItems((current) => + current.map((n) => (n.id === notification.id ? { ...n, read: true } : n)), + ) + if (notification.href) navigate(notification.href) + } + + function markAllRead() { + setItems((current) => current.map((n) => ({ ...n, read: true }))) + } + + return ( + + + + + + +
+ Notifications + {unreadCount > 0 && ( + + )} +
+ + + {items.length === 0 ? ( +
+ You're all caught up. +
+ ) : ( +
    + {items.map((notification) => { + const Icon = ICONS[notification.type] + return ( +
  • + +
  • + ) + })} +
+ )} +
+
+ ) +} diff --git a/web/src/components/HeaderSearch.tsx b/web/src/components/HeaderSearch.tsx new file mode 100644 index 0000000..7b0c686 --- /dev/null +++ b/web/src/components/HeaderSearch.tsx @@ -0,0 +1,107 @@ +import { Boxes, Search, User, Users } from "lucide-react" +import { useEffect, useState } from "react" +import { useNavigate } from "react-router-dom" + +import { + CommandDialog, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, + CommandSeparator, + CommandShortcut, +} from "@/components/ui/command" +import { mockApplications, mockGroups, mockMembers } from "@/lib/mock" + +export function HeaderSearch() { + const [open, setOpen] = useState(false) + const navigate = useNavigate() + + useEffect(() => { + function onKeyDown(event: KeyboardEvent) { + if (event.key === "k" && (event.metaKey || event.ctrlKey)) { + event.preventDefault() + setOpen((current) => !current) + } + } + window.addEventListener("keydown", onKeyDown) + return () => window.removeEventListener("keydown", onKeyDown) + }, []) + + function go(href: string) { + setOpen(false) + navigate(href) + } + + return ( + <> + + + + + + No results. + + + {mockApplications.map((app) => ( + go(`/applications`)} + > + + {app.name} + {app.clientId} + + ))} + + + + + + {mockGroups.map((group) => ( + go(`/groups`)} + > + + {group.name} + {group.memberCount} members + + ))} + + + + + + {mockMembers.map((member) => ( + go(`/settings`)} + > + + {member.name} + {member.email} + + ))} + + + + + ) +} diff --git a/web/src/components/LaunchAppCard.tsx b/web/src/components/LaunchAppCard.tsx new file mode 100644 index 0000000..3483681 --- /dev/null +++ b/web/src/components/LaunchAppCard.tsx @@ -0,0 +1,60 @@ +import { ExternalLink } from "lucide-react" + +import type { Application } from "@/lib/applications" + +function initial(name: string) { + return name.slice(0, 1).toUpperCase() +} + +function relativeTime(iso?: string) { + if (!iso) return null + const ms = Date.now() - new Date(iso).getTime() + const minutes = Math.floor(ms / 60_000) + if (minutes < 1) return "just now" + if (minutes < 60) return `${minutes}m ago` + const hours = Math.floor(minutes / 60) + if (hours < 24) return `${hours}h ago` + const days = Math.floor(hours / 24) + if (days < 30) return `${days}d ago` + const months = Math.floor(days / 30) + return `${months}mo ago` +} + +// LaunchAppCard opens the app's launch_url in a new tab on click. Used on +// the dashboard's "Recently Accessed" section where the user wants to jump +// straight back into the app. +export function LaunchAppCard({ + app, + lastAccessedAt, +}: { + app: Application + lastAccessedAt?: string +}) { + const accessed = relativeTime(lastAccessedAt) + return ( + +
+
+ {app.icon_url ? ( + {app.name} + ) : ( + initial(app.name) + )} +
+ +
+
+

{app.name}

+

{app.description}

+
+ {accessed && ( +

Last accessed {accessed}

+ )} +
+ ) +} diff --git a/web/src/components/OutlineButton.tsx b/web/src/components/OutlineButton.tsx new file mode 100644 index 0000000..e1e69c1 --- /dev/null +++ b/web/src/components/OutlineButton.tsx @@ -0,0 +1,42 @@ +import { Loader2 } from "lucide-react" +import type { ButtonHTMLAttributes } from "react" + +import { cn } from "@/lib/utils" + +type OutlineButtonProps = ButtonHTMLAttributes & { + loading?: boolean + innerClassName?: string +} + +/** + * Outline-style button with a gradient border (gr-pink -> gr-purple). + * On hover the inner fill fades to reveal the full gradient. + */ +export function OutlineButton({ + className, + innerClassName, + children, + loading = false, + disabled, + ...props +}: OutlineButtonProps) { + return ( + + ) +} diff --git a/web/src/components/PageContainer.tsx b/web/src/components/PageContainer.tsx new file mode 100644 index 0000000..1d91dc9 --- /dev/null +++ b/web/src/components/PageContainer.tsx @@ -0,0 +1,32 @@ +import type { ReactNode } from "react" + +import { cn } from "@/lib/utils" + +export function PageContainer({ + children, + className, +}: { + children: ReactNode + className?: string +}) { + return ( +
+ {children} +
+ ) +} + +export function PageHeader({ + title, + description, +}: { + title: string + description?: string +}) { + return ( +
+

{title}

+ {description &&

{description}

} +
+ ) +} diff --git a/web/src/components/RequireAuth.tsx b/web/src/components/RequireAuth.tsx new file mode 100644 index 0000000..d0143e9 --- /dev/null +++ b/web/src/components/RequireAuth.tsx @@ -0,0 +1,12 @@ +import { Navigate, Outlet, useLocation } from "react-router-dom" + +import { loadSession } from "@/lib/auth" + +export function RequireAuth() { + const location = useLocation() + const session = loadSession() + if (!session) { + return + } + return +} diff --git a/web/src/components/SuccessCheck.tsx b/web/src/components/SuccessCheck.tsx new file mode 100644 index 0000000..b9353f8 --- /dev/null +++ b/web/src/components/SuccessCheck.tsx @@ -0,0 +1,46 @@ +import { useId } from "react" + +/** + * Animated success checkmark — circle draws first, then the check stroke. + * Stroke uses the gr-pink -> gr-purple brand gradient. + * Sized via the className passed in. + */ +export function SuccessCheck({ className }: { className?: string }) { + const gradientId = `success-check-${useId().replace(/:/g, "")}` + return ( + + + + + + + + + + + ) +} diff --git a/web/src/components/icons/socials.tsx b/web/src/components/icons/socials.tsx new file mode 100644 index 0000000..d3d5702 --- /dev/null +++ b/web/src/components/icons/socials.tsx @@ -0,0 +1,64 @@ +import type { SVGProps } from "react" + +export function GithubIcon(props: SVGProps) { + return ( + + + + ) +} + +export function InstagramIcon(props: SVGProps) { + return ( + + + + ) +} + +export function TwitterIcon(props: SVGProps) { + return ( + + + + ) +} + +export function LinkedinIcon(props: SVGProps) { + return ( + + + + ) +} + +export function DiscordIcon(props: SVGProps) { + return ( + + + + ) +} + +export function GoogleIcon(props: SVGProps) { + return ( + + + + + + + ) +} 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 index d86e4b7..99f3ed2 100644 --- a/web/src/components/ui/avatar.tsx +++ b/web/src/components/ui/avatar.tsx @@ -1,48 +1,110 @@ -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 }; +import * as React from "react" +import { Avatar as AvatarPrimitive } from "radix-ui" + +import { cn } from "@/lib/utils" + +function Avatar({ + className, + size = "default", + ...props +}: React.ComponentProps & { + size?: "default" | "sm" | "lg" +}) { + return ( + + ) +} + +function AvatarImage({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AvatarFallback({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AvatarBadge({ className, ...props }: React.ComponentProps<"span">) { + return ( + svg]:hidden", + "group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>svg]:size-2", + "group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2", + className + )} + {...props} + /> + ) +} + +function AvatarGroup({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function AvatarGroupCount({ + className, + ...props +}: React.ComponentProps<"div">) { + return ( +
svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3", + className + )} + {...props} + /> + ) +} + +export { + Avatar, + AvatarImage, + AvatarFallback, + AvatarGroup, + AvatarGroupCount, + AvatarBadge, +} diff --git a/web/src/components/ui/badge.tsx b/web/src/components/ui/badge.tsx index d3d5d60..cacff11 100644 --- a/web/src/components/ui/badge.tsx +++ b/web/src/components/ui/badge.tsx @@ -1,36 +1,49 @@ -import * as React from "react"; -import { cva, type VariantProps } from "class-variance-authority"; +import * as React from "react" +import { cva, type VariantProps } from "class-variance-authority" +import { Slot } from "radix-ui" -import { cn } from "@/lib/utils"; +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", + "group/badge inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-4xl border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3!", { variants: { variant: { - default: - "border-transparent bg-primary text-primary-foreground hover:bg-primary/80", + default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80", secondary: - "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", + "bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80", destructive: - "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80", - outline: "text-foreground", + "bg-destructive/10 text-destructive focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 [a]:hover:bg-destructive/20", + outline: + "border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground", + ghost: + "hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50", + link: "text-primary underline-offset-4 hover:underline", }, }, defaultVariants: { variant: "default", }, - }, -); + } +) -export interface BadgeProps - extends React.HTMLAttributes, - VariantProps {} +function Badge({ + className, + variant = "default", + asChild = false, + ...props +}: React.ComponentProps<"span"> & + VariantProps & { asChild?: boolean }) { + const Comp = asChild ? Slot.Root : "span" -function Badge({ className, variant, ...props }: BadgeProps) { return ( -
- ); + + ) } -export { Badge, badgeVariants }; +export { Badge, badgeVariants } diff --git a/web/src/components/ui/button.tsx b/web/src/components/ui/button.tsx index ee3907a..6138844 100644 --- a/web/src/components/ui/button.tsx +++ b/web/src/components/ui/button.tsx @@ -1,56 +1,67 @@ -import * as React from "react"; -import { Slot } from "@radix-ui/react-slot"; -import { cva, type VariantProps } from "class-variance-authority"; +import * as React from "react" +import { cva, type VariantProps } from "class-variance-authority" +import { Slot } from "radix-ui" -import { cn } from "@/lib/utils"; +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", + "group/button inline-flex shrink-0 items-center justify-center rounded-lg border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", { variants: { variant: { - default: "bg-primary text-primary-foreground hover:bg-primary/90", - destructive: - "bg-destructive text-destructive-foreground hover:bg-destructive/90", + default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80", outline: - "border border-input bg-background hover:bg-accent hover:text-accent-foreground", + "border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50", 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", + "bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground", + ghost: + "hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50", + destructive: + "bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40", + link: "text-primary 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", + default: + "h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2", + xs: "h-6 gap-1 rounded-[min(var(--radius-md),10px)] px-2 text-xs in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3", + sm: "h-7 gap-1 rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5", + lg: "h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2", + icon: "size-8", + "icon-xs": + "size-6 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-lg [&_svg:not([class*='size-'])]:size-3", + "icon-sm": + "size-7 rounded-[min(var(--radius-md),12px)] in-data-[slot=button-group]:rounded-lg", + "icon-lg": "size-9", }, }, defaultVariants: { variant: "default", size: "default", }, - }, -); + } +) -export interface ButtonProps - extends React.ButtonHTMLAttributes, - VariantProps { - asChild?: boolean; -} +function Button({ + className, + variant = "default", + size = "default", + asChild = false, + ...props +}: React.ComponentProps<"button"> & + VariantProps & { + asChild?: boolean + }) { + const Comp = asChild ? Slot.Root : "button" -const Button = React.forwardRef( - ({ className, variant, size, asChild = false, ...props }, ref) => { - const Comp = asChild ? Slot : "button"; - return ( - - ); - }, -); -Button.displayName = "Button"; + return ( + + ) +} -export { Button, buttonVariants }; +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 index 704e43a..40cac5f 100644 --- a/web/src/components/ui/card.tsx +++ b/web/src/components/ui/card.tsx @@ -1,86 +1,103 @@ -import * as React from "react"; +import * as React from "react" -import { cn } from "@/lib/utils"; +import { cn } from "@/lib/utils" -const Card = React.forwardRef< - HTMLDivElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( -
-)); -Card.displayName = "Card"; +function Card({ + className, + size = "default", + ...props +}: React.ComponentProps<"div"> & { size?: "default" | "sm" }) { + return ( +
img:first-child]:pt-0 data-[size=sm]:gap-3 data-[size=sm]:py-3 data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl", + className + )} + {...props} + /> + ) +} -const CardHeader = React.forwardRef< - HTMLDivElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( -
-)); -CardHeader.displayName = "CardHeader"; +function CardHeader({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} -const CardTitle = React.forwardRef< - HTMLParagraphElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( -

-)); -CardTitle.displayName = "CardTitle"; +function CardTitle({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} -const CardDescription = React.forwardRef< - HTMLParagraphElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( -

-)); -CardDescription.displayName = "CardDescription"; +function CardDescription({ className, ...props }: React.ComponentProps<"div">) { + return ( +

+ ) +} -const CardContent = React.forwardRef< - HTMLDivElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( -
-)); -CardContent.displayName = "CardContent"; +function CardAction({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} -const CardFooter = React.forwardRef< - HTMLDivElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( -
-)); -CardFooter.displayName = "CardFooter"; +function CardContent({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardFooter({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} export { Card, CardHeader, CardFooter, CardTitle, + CardAction, 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 index 0e82ac9..720b82e 100644 --- a/web/src/components/ui/command.tsx +++ b/web/src/components/ui/command.tsx @@ -1,144 +1,184 @@ -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 {} +import * as React from "react" +import { Command as CommandPrimitive } from "cmdk" + +import { cn } from "@/lib/utils" +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog" +import { + InputGroup, + InputGroupAddon, +} from "@/components/ui/input-group" +import { SearchIcon, CheckIcon } from "lucide-react" + +function Command({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} -const CommandDialog = ({ children, ...props }: CommandDialogProps) => { +function CommandDialog({ + title = "Command Palette", + description = "Search for a command to run...", + children, + className, + showCloseButton = false, + ...props +}: React.ComponentProps & { + title?: string + description?: string + className?: string + showCloseButton?: boolean +}) { return ( - - - {children} - + + {title} + {description} + + + {children} - ); -}; + ) +} -const CommandInput = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( -
- - ) { + return ( +
+ + + + + + +
+ ) +} + +function CommandList({ + className, + ...props +}: React.ComponentProps) { + return ( + -
-)); - -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; +function CommandEmpty({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} -const CommandItem = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); +function CommandGroup({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} -CommandItem.displayName = CommandPrimitive.Item.displayName; +function CommandSeparator({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} -const CommandShortcut = ({ +function CommandItem({ + className, + children, + ...props +}: React.ComponentProps) { + return ( + + {children} + + + ) +} + +function CommandShortcut({ className, ...props -}: React.HTMLAttributes) => { +}: React.ComponentProps<"span">) { return ( - ); -}; -CommandShortcut.displayName = "CommandShortcut"; + ) +} export { Command, @@ -150,4 +190,4 @@ export { CommandItem, CommandShortcut, CommandSeparator, -}; +} diff --git a/web/src/components/ui/dialog.tsx b/web/src/components/ui/dialog.tsx index 7191ede..53797cf 100644 --- a/web/src/components/ui/dialog.tsx +++ b/web/src/components/ui/dialog.tsx @@ -1,120 +1,166 @@ -import * as React from "react"; -import * as DialogPrimitive from "@radix-ui/react-dialog"; -import { X } from "lucide-react"; +import * as React from "react" +import { Dialog as DialogPrimitive } from "radix-ui" -import { cn } from "@/lib/utils"; +import { cn } from "@/lib/utils" +import { Button } from "@/components/ui/button" +import { XIcon } from "lucide-react" -const Dialog = DialogPrimitive.Root; +function Dialog({ + ...props +}: React.ComponentProps) { + return +} -const DialogTrigger = DialogPrimitive.Trigger; +function DialogTrigger({ + ...props +}: React.ComponentProps) { + return +} -const DialogPortal = DialogPrimitive.Portal; +function DialogPortal({ + ...props +}: React.ComponentProps) { + return +} + +function DialogClose({ + ...props +}: React.ComponentProps) { + return +} + +function DialogOverlay({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} -const DialogClose = DialogPrimitive.Close; +function DialogContent({ + className, + children, + showCloseButton = true, + ...props +}: React.ComponentProps & { + showCloseButton?: boolean +}) { + return ( + + + + {children} + {showCloseButton && ( + + + + )} + + + ) +} -const DialogOverlay = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); -DialogOverlay.displayName = DialogPrimitive.Overlay.displayName; +function DialogHeader({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} -const DialogContent = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, children, ...props }, ref) => ( - - - & { + showCloseButton?: boolean +}) { + return ( +
{children} - - - Close - - - -)); -DialogContent.displayName = DialogPrimitive.Content.displayName; + {showCloseButton && ( + + + + )} +
+ ) +} -const DialogHeader = ({ +function DialogTitle({ className, ...props -}: React.HTMLAttributes) => ( -
-); -DialogHeader.displayName = "DialogHeader"; +}: React.ComponentProps) { + return ( + + ) +} -const DialogFooter = ({ +function DialogDescription({ 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; +}: React.ComponentProps) { + return ( + + ) +} export { Dialog, - DialogPortal, - DialogOverlay, DialogClose, - DialogTrigger, DialogContent, - DialogHeader, + DialogDescription, DialogFooter, + DialogHeader, + DialogOverlay, + DialogPortal, DialogTitle, - DialogDescription, -}; + DialogTrigger, +} diff --git a/web/src/components/ui/dropdown-menu.tsx b/web/src/components/ui/dropdown-menu.tsx index 3a0c7fe..c263ad5 100644 --- a/web/src/components/ui/dropdown-menu.tsx +++ b/web/src/components/ui/dropdown-menu.tsx @@ -1,200 +1,269 @@ -"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) => ( - - ) { + return +} + +function DropdownMenuPortal({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function DropdownMenuTrigger({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function DropdownMenuContent({ + className, + align = "start", + sideOffset = 4, + ...props +}: React.ComponentProps) { + return ( + + + + ) +} + +function DropdownMenuGroup({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function DropdownMenuItem({ + className, + inset, + variant = "default", + ...props +}: React.ComponentProps & { + inset?: boolean + variant?: "default" | "destructive" +}) { + return ( + + ) +} + +function DropdownMenuCheckboxItem({ + className, + children, + checked, + inset, + ...props +}: React.ComponentProps & { + inset?: boolean +}) { + return ( + + + + + + + {children} + + ) +} + +function DropdownMenuRadioGroup({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function DropdownMenuRadioItem({ + className, + children, + inset, + ...props +}: React.ComponentProps & { + inset?: boolean +}) { + return ( + + + + + + + {children} + + ) +} + +function DropdownMenuLabel({ + className, + inset, + ...props +}: React.ComponentProps & { + inset?: boolean +}) { + return ( + - -)); -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 = ({ + ) +} + +function DropdownMenuSeparator({ className, ...props -}: React.HTMLAttributes) => { +}: React.ComponentProps) { + return ( + + ) +} + +function DropdownMenuShortcut({ + className, + ...props +}: React.ComponentProps<"span">) { return ( + ) +} + +function DropdownMenuSub({ + ...props +}: React.ComponentProps) { + return +} + +function DropdownMenuSubTrigger({ + className, + inset, + children, + ...props +}: React.ComponentProps & { + inset?: boolean +}) { + return ( + + {children} + + + ) +} + +function DropdownMenuSubContent({ + className, + ...props +}: React.ComponentProps) { + return ( + - ); -}; -DropdownMenuShortcut.displayName = "DropdownMenuShortcut"; + ) +} export { DropdownMenu, + DropdownMenuPortal, DropdownMenuTrigger, DropdownMenuContent, + DropdownMenuGroup, + DropdownMenuLabel, DropdownMenuItem, DropdownMenuCheckboxItem, + DropdownMenuRadioGroup, DropdownMenuRadioItem, - DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuShortcut, - DropdownMenuGroup, - DropdownMenuPortal, DropdownMenuSub, - DropdownMenuSubContent, DropdownMenuSubTrigger, - DropdownMenuRadioGroup, -}; + DropdownMenuSubContent, +} diff --git a/web/src/components/ui/input-group.tsx b/web/src/components/ui/input-group.tsx new file mode 100644 index 0000000..0475d27 --- /dev/null +++ b/web/src/components/ui/input-group.tsx @@ -0,0 +1,154 @@ +import * as React from "react" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" +import { Button } from "@/components/ui/button" +import { Input } from "@/components/ui/input" +import { Textarea } from "@/components/ui/textarea" + +function InputGroup({ className, ...props }: React.ComponentProps<"div">) { + return ( +
[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>textarea]:h-auto dark:bg-input/30 dark:has-disabled:bg-input/80 dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40 has-[>[data-align=block-end]]:[&>input]:pt-3 has-[>[data-align=block-start]]:[&>input]:pb-3 has-[>[data-align=inline-end]]:[&>input]:pr-1.5 has-[>[data-align=inline-start]]:[&>input]:pl-1.5", + className + )} + {...props} + /> + ) +} + +const inputGroupAddonVariants = cva( + "flex h-auto cursor-text items-center justify-center gap-2 py-1.5 text-sm font-medium text-muted-foreground select-none group-data-[disabled=true]/input-group:opacity-50 [&>kbd]:rounded-[calc(var(--radius)-5px)] [&>svg:not([class*='size-'])]:size-4", + { + variants: { + align: { + "inline-start": + "order-first pl-2 has-[>button]:ml-[-0.3rem] has-[>kbd]:ml-[-0.15rem]", + "inline-end": + "order-last pr-2 has-[>button]:mr-[-0.3rem] has-[>kbd]:mr-[-0.15rem]", + "block-start": + "order-first w-full justify-start px-2.5 pt-2 group-has-[>input]/input-group:pt-2 [.border-b]:pb-2", + "block-end": + "order-last w-full justify-start px-2.5 pb-2 group-has-[>input]/input-group:pb-2 [.border-t]:pt-2", + }, + }, + defaultVariants: { + align: "inline-start", + }, + } +) + +function InputGroupAddon({ + className, + align = "inline-start", + ...props +}: React.ComponentProps<"div"> & VariantProps) { + return ( +
{ + if ((e.target as HTMLElement).closest("button")) { + return + } + e.currentTarget.parentElement?.querySelector("input")?.focus() + }} + {...props} + /> + ) +} + +const inputGroupButtonVariants = cva( + "flex items-center gap-2 text-sm shadow-none", + { + variants: { + size: { + xs: "h-6 gap-1 rounded-[calc(var(--radius)-3px)] px-1.5 [&>svg:not([class*='size-'])]:size-3.5", + sm: "", + "icon-xs": + "size-6 rounded-[calc(var(--radius)-3px)] p-0 has-[>svg]:p-0", + "icon-sm": "size-8 p-0 has-[>svg]:p-0", + }, + }, + defaultVariants: { + size: "xs", + }, + } +) + +function InputGroupButton({ + className, + type = "button", + variant = "ghost", + size = "xs", + ...props +}: Omit, "size"> & + VariantProps) { + return ( +