Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ on:
- main

env:
VERSION_NUMBER: 'v1.10.0'
VERSION_NUMBER: 'v1.10.1'
DOCKERHUB_REGISTRY_NAME: 'digitalghostdev/poke-cli'
AWS_REGION: 'us-west-2'

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/python_testing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ jobs:
uses: astral-sh/setup-uv@v7

- name: Install dependencies
run: uv sync --dev
run: uv sync --all-groups

- name: Run tests
run: uv run pytest app_test.py -v
22 changes: 14 additions & 8 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
*.so
*.dylib
*.DS_Store
**/.DS_Store
*.idea
.dccache
dist/
Expand Down Expand Up @@ -38,6 +39,8 @@ web/.coverage
card_data/.venv
__pycache__/
.ruff_cache/
.coverage
htmlcov/

# Terraform
.terraformrc
Expand All @@ -56,28 +59,31 @@ logs/

target/

# Card Data
card_data/infrastructure/supabase/access-token
/card_data/infrastructure/supabase/access-token
**/.terraform/

card_data/.tmp*/**

card_data/pipelines/poke_cli_dbt/.user.yml
/card_data/supabase/
/card_data/sample_scripts/

card_data/~/
card_data/storage/
/.claude/
CLAUDE.md
REFACTORING.md
/card_data/.codspeed/
/.ai/

# Version management
VERSION
version-bump.sh

# Testing libraries
.codspeed/
.pytest_cache/
.pytest_cache/

# AI
/.claude/
CLAUDE.md
REFACTORING.md
AGENTS.md
.agents/
.codex/
/.ai/
2 changes: 1 addition & 1 deletion .goreleaser.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ builds:
- windows
- darwin
ldflags:
- -s -w -X main.version=v1.10.0
- -s -w -X main.version=v1.10.1

archives:
- formats: [ 'zip' ]
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ RUN go mod download

COPY . .

RUN go build -ldflags "-X main.version=v1.10.0" -o poke-cli .
RUN go build -ldflags "-X main.version=v1.10.1" -o poke-cli .

# build 2
FROM --platform=$BUILDPLATFORM alpine:3.23
Expand Down
19 changes: 10 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<img width="425" src="poke-cli.png" alt="pokemon-logo"/>
<h4></h4>
<img src="https://img.shields.io/github/v/release/digitalghost-dev/poke-cli?style=flat-square&logo=git&logoColor=FFCC00&label=Release%20Version&labelColor=EEE&color=FFCC00" alt="version-label">
<img src="https://img.shields.io/docker/image-size/digitalghostdev/poke-cli/v1.10.0?arch=arm64&style=flat-square&logo=docker&logoColor=FFCC00&labelColor=EEE&color=FFCC00" alt="docker-image-size">
<img src="https://img.shields.io/docker/image-size/digitalghostdev/poke-cli/v1.10.1?arch=arm64&style=flat-square&logo=docker&logoColor=FFCC00&labelColor=EEE&color=FFCC00" alt="docker-image-size">
<img src="https://img.shields.io/github/actions/workflow/status/digitalghost-dev/poke-cli/ci.yml?branch=main&style=flat-square&logo=github&logoColor=FFCC00&label=CI&labelColor=EEE&color=FFCC00" alt="ci-status-badge">
</div>
<div align="center">
Expand Down Expand Up @@ -99,11 +99,11 @@ Cloudsmith is a fully cloud-based service that lets you easily create, store, an
3. Choose how to interact with the container:
* Run a single command and exit:
```bash
docker run --rm -it digitalghostdev/poke-cli:v1.10.0 <command> [subcommand] [flag]
docker run --rm -it digitalghostdev/poke-cli:v1.10.1 <command> [subcommand] [flag]
```
* Enter the container and use its shell:
```bash
docker run --rm -it --name poke-cli --entrypoint /bin/sh digitalghostdev/poke-cli:v1.10.0 -c "cd /app && exec sh"
docker run --rm -it --name poke-cli --entrypoint /bin/sh digitalghostdev/poke-cli:v1.10.1 -c "cd /app && exec sh"
# placed into the /app directory, run the program with './poke-cli'
# example: ./poke-cli ability swift-swim
```
Expand All @@ -112,13 +112,13 @@ Cloudsmith is a fully cloud-based service that lets you easily create, store, an
> The `card` command renders TCG card images using your terminal's graphics protocol. When running inside Docker, pass your terminal's environment variables so image rendering works correctly:
> ```bash
> # Kitty
> docker run --rm -it -e TERM -e KITTY_WINDOW_ID digitalghostdev/poke-cli:v1.10.0 card
> docker run --rm -it -e TERM -e KITTY_WINDOW_ID digitalghostdev/poke-cli:v1.10.1 card
>
> # WezTerm, iTerm2, Ghostty, Konsole, Rio, Tabby
> docker run --rm -it -e TERM -e TERM_PROGRAM digitalghostdev/poke-cli:v1.10.0 card
> docker run --rm -it -e TERM -e TERM_PROGRAM digitalghostdev/poke-cli:v1.10.1 card
>
> # Windows Terminal (Sixel)
> docker run --rm -it -e WT_SESSION digitalghostdev/poke-cli:v1.10.0 card
> docker run --rm -it -e WT_SESSION digitalghostdev/poke-cli:v1.10.1 card
> ```
> If your terminal is not listed above, image rendering is not supported inside Docker.

Expand Down Expand Up @@ -215,15 +215,16 @@ Below is a list of the planned/completed commands and flags:
- [x] add sun & moon data
- [ ] add x & y data
- [x] `item`: get data about an item.
- [x] `move`: get data about a move.
- [ ] `move`: get data about a move.
- [ ] `-p | --pokemon`: display Pokémon that learn this move.
- [x] `natures`: get data about natures.
- [x] `pokemon`: get data about a Pokémon.
- [ ] `pokemon`: get data about a Pokémon.
- [x] `-a | --abilities`: display the Pokémon's abilities.
- [ ] `-c | --cry`: play the Pokémon's cry.
- [x] `-d | --defense`: display the Pokémon's type defences.
- [x] `-i | --image`: display a pixel image of the Pokémon.
- [x] `-s | --stats`: display the Pokémon's base stats.
- [x] `-m | --moves`: display learnable moves.
- [x] `-s | --stats`: display the Pokémon's base stats.
- [ ] `search`: search for a resource
- [x] `ability`
- [ ] `berry`
Expand Down
17 changes: 15 additions & 2 deletions card_data/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,32 @@ and decided to process all the data myself, load it into Supabase, and read from

## Data Architecture
Runs at 2:00PM PST daily.
![data_diagram](data_infrastructure_v2.png)
![data_diagram](data_infrastructure_diagram.png)


1. TCGPlayer pricing data and TCGDex card data are called and processed through a data pipeline orchestrated by Dagster and hosted on AWS.
- Dagster runs on an EC2 instance.
- Dagster metadata is stored separately in RDS.
- The pricing pipeline is scheduled with cron: `0 14 * * *`.
- Tournament standings data is also pulled from Limitless.

2. When the pipeline starts, Pydantic validates the incoming API data against a pre-defined schema, ensuring the data types match the expected structure.
- Invalid or unexpected payloads fail early before data is loaded downstream.

3. Polars is used to create DataFrames.
- DataFrames are used to clean, normalize, and prepare records for database loading.

4. The data is loaded into a Supabase staging schema.
- The staging schema acts as the raw/validated landing area before production tables are built.

5. Soda data quality checks are performed.
- Checks validate expectations such as row counts, required columns, missing values, duplicate keys, and URL formats.

6. `dbt` runs and builds the final tables in a Supabase production schema.
6. dbt runs tests and builds the final tables in a Supabase production schema.
- dbt transforms staged data into the final public-facing models.
- The production schema powers TCG/card/tournament queries.

7. Users are then able to query the `pokeapi.co` or supabase APIs for either video game or trading card data, respectively.
- The CLI uses PokéAPI for video game data.
- The CLI and Streamlit web app use Supabase for TCG data.
- Dagster run status is sent through an n8n webhook for Discord notifications.
Binary file added card_data/data_infrastructure_diagram.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed card_data/data_infrastructure_v2.png
Binary file not shown.
2 changes: 1 addition & 1 deletion card_data/pipelines/poke_cli_dbt/dbt_project.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: 'poke_cli_dbt'
version: 'v1.10.0'
version: '1.10.1'

profile: 'poke_cli_dbt'

Expand Down
2 changes: 1 addition & 1 deletion card_data/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "card-data"
version = "v1.10.0"
version = "v1.10.1"
description = "File directory to store all data related processes for the Pokémon TCG."
readme = "README.md"
requires-python = ">=3.12"
Expand Down
3 changes: 1 addition & 2 deletions cmd/berry/berry.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package berry
import (
"flag"
"fmt"
"log"
"os"
"strings"

Expand Down Expand Up @@ -141,7 +140,7 @@ func tableGeneration() error {
ORDER BY
name`)
if err != nil {
log.Fatalf("Failed to get berry names: %v", err)
return fmt.Errorf("failed to get berry names: %w", err)
}

rows := make([]table.Row, len(namesList))
Expand Down
3 changes: 2 additions & 1 deletion cmd/pokemon/pokemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,11 @@ func PokemonCommand() (string, error) {
ShowHyphenHint: true,
Flags: []utils.FlagHelp{
{Short: "-a", Long: "--abilities", Description: "Prints the Pokémon's abilities."},
{Short: "-d", Long: "--defense", Description: "Prints the Pokémon's type defenses."},
{Short: "-i=xx", Long: "--image=xx", Description: "Prints out the Pokémon's default sprite.\n\t " + styling.StyleItalic.Render("options: [sm, md, lg]")},
{Short: "-m", Long: "--moves", Description: "Prints the Pokémon's learnable moves."},
{Short: "-s", Long: "--stats", Description: "Prints the Pokémon's base stats."},
{Short: "-t", Long: "--types", Description: styling.ErrorColor.Render("Deprecated. Types are included with each Pokémon.")},
{Short: "-t", Long: "--types", Description: styling.ErrorColor.Render("Deprecated. Typing is included by default.")},
},
},
),
Expand Down
3 changes: 1 addition & 2 deletions cmd/speed/speed.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"errors"
"flag"
"fmt"
"log"
"math"
"os"
"strconv"
Expand Down Expand Up @@ -260,7 +259,7 @@ func formula() (string, error) {

speedStageInt, err := strconv.Atoi(pokemon.SpeedStage)
if err != nil {
log.Fatalf("Invalid SpeedStage: %v", err)
return "", fmt.Errorf("invalid SpeedStage: %w", err)
}
stageMultiplier := stageMultipliers[speedStageInt]

Expand Down
14 changes: 14 additions & 0 deletions cmd/speed/speed_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,20 @@ func TestFormula(t *testing.T) {
expectedSpeed: "1035",
wantError: false,
},
{
name: "Invalid SpeedStage returns error",
pokemonDetails: PokemonDetails{
Name: "pikachu",
SpeedStage: "abc",
Nature: "0%",
Level: "50",
Modifier: []string{},
Ability: "None",
SpeedEV: "0",
SpeedIV: "0",
},
wantError: true,
},
}

for _, tt := range tests {
Expand Down
23 changes: 23 additions & 0 deletions cmd/utils/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,26 @@ func FormatError(message string) string {
"\n"+message,
)
}

func FormatNotFoundError(resourceType string) string {
return FormatError(resourceType + " not found.\n• Perhaps a typo?\n• Missing a hyphen instead of a space?")
}

func FormatNetworkError(resourceType string) string {
return FormatError("Could not reach " + resourceType + " data.\nCheck your connection and try again.")
}

func FormatServerError(resourceType string) string {
return FormatError(resourceType + " data source returned a server error.\nPlease try again later.")
}

func FormatUnexpectedDataError(resourceType string) string {
return FormatError(resourceType + " data source returned data in an unexpected format.")
}

func FormatFetchError(resourceType string, err error) string {
if err == nil {
return FormatError("Could not fetch " + resourceType + " data.")
}
return FormatError("Could not fetch " + resourceType + " data.\n" + err.Error())
}
76 changes: 76 additions & 0 deletions cmd/utils/errors_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package utils

import (
"errors"
"testing"

"github.com/digitalghost-dev/poke-cli/styling"
"github.com/stretchr/testify/assert"
)

func TestFormatResourceErrors(t *testing.T) {
tests := []struct {
name string
format func() string
contains []string
}{
{
name: "not found",
format: func() string { return FormatNotFoundError("Pokémon") },
contains: []string{
"Pokémon not found.",
"Perhaps a typo?",
"Missing a hyphen instead of a space?",
},
},
{
name: "network",
format: func() string { return FormatNetworkError("Pokémon") },
contains: []string{
"Could not reach Pokémon data.",
"Check your connection and try again.",
},
},
{
name: "server",
format: func() string { return FormatServerError("Pokémon") },
contains: []string{
"Pokémon data source returned a server error.",
"Please try again later.",
},
},
{
name: "unexpected data",
format: func() string { return FormatUnexpectedDataError("Pokémon") },
contains: []string{
"Pokémon data source returned data in an unexpected format.",
},
},
{
name: "fetch with error",
format: func() string { return FormatFetchError("Pokémon", errors.New("request failed")) },
contains: []string{
"Could not fetch Pokémon data.",
"request failed",
},
},
{
name: "fetch with nil error",
format: func() string { return FormatFetchError("Pokémon", nil) },
contains: []string{
"Could not fetch Pokémon data.",
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
output := styling.StripANSI(tt.format())

assert.Contains(t, output, "✖ Error!")
for _, expected := range tt.contains {
assert.Contains(t, output, expected)
}
})
}
}
Loading
Loading