rename project from SimpleshopTheme to Berrypod
All modules, configs, paths, and references updated. 836 tests pass, zero warnings. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -14,7 +14,7 @@ Build a Products context that syncs products from external POD providers (Printi
|
||||
|
||||
## Current Domain Analysis
|
||||
|
||||
SimpleShop has **6 well-defined domains** with clear boundaries:
|
||||
Berrypod has **6 well-defined domains** with clear boundaries:
|
||||
|
||||
| Domain | Purpose | Schemas | Public Functions |
|
||||
|--------|---------|---------|------------------|
|
||||
@@ -36,12 +36,12 @@ The new **Products** context will be a new top-level domain that:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ SimpleshopTheme │
|
||||
│ Berrypod │
|
||||
├─────────────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌──────────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ WEB LAYER │ │
|
||||
│ │ SimpleshopThemeWeb │ │
|
||||
│ │ BerrypodWeb │ │
|
||||
│ │ ┌────────────────┐ ┌────────────────┐ ┌─────────────────────────┐ │ │
|
||||
│ │ │ Shop LiveViews │ │ Admin LiveViews│ │ Theme Editor LiveView │ │ │
|
||||
│ │ │ - ProductShow │ │ - UserLogin │ │ - ThemeLive.Index │ │ │
|
||||
@@ -299,22 +299,22 @@ Extract reusable helpers into new `Printify.Catalog` module:
|
||||
|
||||
**Module structure:**
|
||||
```
|
||||
lib/simpleshop_theme/clients/
|
||||
lib/berrypod/clients/
|
||||
├── printify.ex # Printify HTTP client (moved from printify/client.ex)
|
||||
├── gelato.ex # Gelato HTTP client
|
||||
└── prodigi.ex # Prodigi HTTP client
|
||||
|
||||
lib/simpleshop_theme/providers/
|
||||
lib/berrypod/providers/
|
||||
├── provider.ex # Behaviour definition
|
||||
├── printify.ex # Printify implementation (uses Clients.Printify)
|
||||
├── gelato.ex # Gelato implementation (uses Clients.Gelato)
|
||||
└── prodigi.ex # Prodigi implementation (uses Clients.Prodigi)
|
||||
|
||||
lib/simpleshop_theme/mockups/
|
||||
lib/berrypod/mockups/
|
||||
└── generator.ex # Mockup generation (currently uses Clients.Printify)
|
||||
# Provider-agnostic location for future flexibility
|
||||
|
||||
lib/simpleshop_theme/printify/
|
||||
lib/berrypod/printify/
|
||||
└── catalog.ex # Blueprint/variant discovery helpers (Printify-specific)
|
||||
```
|
||||
|
||||
@@ -343,7 +343,7 @@ Each provider module uses its corresponding client. The mockup generator is in a
|
||||
- Unique constraint: `[:provider_connection_id, :provider_product_id]`
|
||||
|
||||
2. **Credentials encrypted in database**
|
||||
- Use `SimpleshopTheme.Vault` for at-rest encryption
|
||||
- Use `Berrypod.Vault` for at-rest encryption
|
||||
- `api_key_encrypted`, `oauth_access_token_encrypted` fields
|
||||
|
||||
3. **Cost tracking for profit calculation**
|
||||
@@ -832,46 +832,46 @@ Add to `mix.exs`:
|
||||
- `*_create_admin_notifications.exs`
|
||||
|
||||
### Schemas
|
||||
- `lib/simpleshop_theme/products/provider_connection.ex`
|
||||
- `lib/simpleshop_theme/products/product.ex`
|
||||
- `lib/simpleshop_theme/products/product_image.ex`
|
||||
- `lib/simpleshop_theme/products/product_variant.ex`
|
||||
- `lib/simpleshop_theme/orders/order.ex`
|
||||
- `lib/simpleshop_theme/orders/order_fulfillment.ex`
|
||||
- `lib/simpleshop_theme/orders/order_line_item.ex`
|
||||
- `lib/simpleshop_theme/orders/order_event.ex`
|
||||
- `lib/simpleshop_theme/admin_notifications/notification.ex`
|
||||
- `lib/berrypod/products/provider_connection.ex`
|
||||
- `lib/berrypod/products/product.ex`
|
||||
- `lib/berrypod/products/product_image.ex`
|
||||
- `lib/berrypod/products/product_variant.ex`
|
||||
- `lib/berrypod/orders/order.ex`
|
||||
- `lib/berrypod/orders/order_fulfillment.ex`
|
||||
- `lib/berrypod/orders/order_line_item.ex`
|
||||
- `lib/berrypod/orders/order_event.ex`
|
||||
- `lib/berrypod/admin_notifications/notification.ex`
|
||||
|
||||
### Contexts
|
||||
- `lib/simpleshop_theme/products.ex` - Product queries, sync logic
|
||||
- `lib/simpleshop_theme/orders.ex` - Order creation, submission
|
||||
- `lib/simpleshop_theme/admin_notifications.ex` - Admin notification management
|
||||
- `lib/berrypod/products.ex` - Product queries, sync logic
|
||||
- `lib/berrypod/orders.ex` - Order creation, submission
|
||||
- `lib/berrypod/admin_notifications.ex` - Admin notification management
|
||||
|
||||
### Providers
|
||||
- `lib/simpleshop_theme/providers/provider.ex` - Behaviour definition
|
||||
- `lib/simpleshop_theme/providers/printify.ex` - Printify implementation
|
||||
- `lib/berrypod/providers/provider.ex` - Behaviour definition
|
||||
- `lib/berrypod/providers/printify.ex` - Printify implementation
|
||||
|
||||
### Workers
|
||||
- `lib/simpleshop_theme/sync/product_sync_worker.ex` - Oban worker
|
||||
- `lib/berrypod/sync/product_sync_worker.ex` - Oban worker
|
||||
|
||||
### Webhooks
|
||||
- `lib/simpleshop_theme_web/controllers/webhook_controller.ex`
|
||||
- `lib/simpleshop_theme/webhooks/printify_handler.ex`
|
||||
- `lib/berrypod_web/controllers/webhook_controller.ex`
|
||||
- `lib/berrypod/webhooks/printify_handler.ex`
|
||||
|
||||
### Notifiers
|
||||
- `lib/simpleshop_theme_web/notifiers/customer_notifier.ex` - Customer emails
|
||||
- `lib/berrypod_web/notifiers/customer_notifier.ex` - Customer emails
|
||||
|
||||
### Support
|
||||
- `lib/simpleshop_theme/vault.ex` - Credential encryption
|
||||
- `lib/berrypod/vault.ex` - Credential encryption
|
||||
|
||||
---
|
||||
|
||||
## Files to Modify
|
||||
|
||||
- `lib/simpleshop_theme/printify/client.ex` → Move to `lib/simpleshop_theme/clients/printify.ex`
|
||||
- `lib/simpleshop_theme/printify/mockup_generator.ex` → Move to `lib/simpleshop_theme/mockups/generator.ex`
|
||||
- `lib/simpleshop_theme/theme/preview_data.ex` - Query real products when available
|
||||
- `lib/simpleshop_theme_web/live/shop_live/*.ex` - Use Products context instead of PreviewData
|
||||
- `lib/berrypod/printify/client.ex` → Move to `lib/berrypod/clients/printify.ex`
|
||||
- `lib/berrypod/printify/mockup_generator.ex` → Move to `lib/berrypod/mockups/generator.ex`
|
||||
- `lib/berrypod/theme/preview_data.ex` - Query real products when available
|
||||
- `lib/berrypod_web/live/shop_live/*.ex` - Use Products context instead of PreviewData
|
||||
|
||||
---
|
||||
|
||||
@@ -943,10 +943,10 @@ end
|
||||
```
|
||||
|
||||
### Files to Create
|
||||
- `lib/simpleshop_theme/admin_notifications.ex` - Admin notification context
|
||||
- `lib/simpleshop_theme/admin_notifications/notification.ex` - Schema
|
||||
- `lib/simpleshop_theme/orders/order_event.ex` - Customer-facing event schema
|
||||
- `lib/simpleshop_theme_web/notifiers/customer_notifier.ex` - Emails
|
||||
- `lib/berrypod/admin_notifications.ex` - Admin notification context
|
||||
- `lib/berrypod/admin_notifications/notification.ex` - Schema
|
||||
- `lib/berrypod/orders/order_event.ex` - Customer-facing event schema
|
||||
- `lib/berrypod_web/notifiers/customer_notifier.ex` - Emails
|
||||
|
||||
---
|
||||
|
||||
@@ -1012,15 +1012,15 @@ end
|
||||
**Mocking External APIs (Mox pattern):**
|
||||
```elixir
|
||||
# test/support/mocks.ex
|
||||
Mox.defmock(SimpleshopTheme.Clients.MockPrintify, for: SimpleshopTheme.Clients.PrintifyBehaviour)
|
||||
Mox.defmock(Berrypod.Clients.MockPrintify, for: Berrypod.Clients.PrintifyBehaviour)
|
||||
|
||||
# config/test.exs
|
||||
config :simpleshop_theme, :printify_client, SimpleshopTheme.Clients.MockPrintify
|
||||
config :berrypod, :printify_client, Berrypod.Clients.MockPrintify
|
||||
```
|
||||
|
||||
**Oban testing:**
|
||||
```elixir
|
||||
use Oban.Testing, repo: SimpleshopTheme.Repo
|
||||
use Oban.Testing, repo: Berrypod.Repo
|
||||
# Jobs run synchronously in tests via perform_job/2
|
||||
```
|
||||
|
||||
@@ -1049,7 +1049,7 @@ use Oban.Testing, repo: SimpleshopTheme.Repo
|
||||
### Example Test Cases
|
||||
|
||||
```elixir
|
||||
# test/simpleshop_theme/products_test.exs
|
||||
# test/berrypod/products_test.exs
|
||||
describe "sync_products/1" do
|
||||
test "syncs products from provider" do
|
||||
conn = provider_connection_fixture()
|
||||
@@ -1082,7 +1082,7 @@ describe "sync_products/1" do
|
||||
end
|
||||
end
|
||||
|
||||
# test/simpleshop_theme/orders_test.exs
|
||||
# test/berrypod/orders_test.exs
|
||||
describe "create_order_from_cart/1" do
|
||||
test "splits cart into fulfillments by provider" do
|
||||
printify_variant = variant_fixture(provider: :printify)
|
||||
@@ -1098,7 +1098,7 @@ describe "create_order_from_cart/1" do
|
||||
end
|
||||
end
|
||||
|
||||
# test/simpleshop_theme/sync/product_sync_worker_test.exs
|
||||
# test/berrypod/sync/product_sync_worker_test.exs
|
||||
describe "perform/1" do
|
||||
test "retries on API failure" do
|
||||
expect(MockPrintify, :list_products, fn _ -> {:error, :timeout} end)
|
||||
@@ -1197,12 +1197,12 @@ live "/admin/providers/:id/edit", ProviderLive.Index, :edit
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `lib/simpleshop_theme_web/live/provider_live/index.ex` | LiveView for provider list + modal forms |
|
||||
| `lib/simpleshop_theme_web/live/provider_live/index.html.heex` | Template |
|
||||
| `lib/simpleshop_theme_web/live/provider_live/form_component.ex` | Form component for new/edit |
|
||||
| `lib/simpleshop_theme/providers/printify.ex` | Add `register_webhooks/1`, `unregister_webhooks/1` |
|
||||
| `lib/simpleshop_theme/workers/product_sync_worker.ex` | Stub for "Sync Now" (full impl in next task) |
|
||||
| `test/simpleshop_theme_web/live/provider_live_test.exs` | LiveView tests |
|
||||
| `lib/berrypod_web/live/provider_live/index.ex` | LiveView for provider list + modal forms |
|
||||
| `lib/berrypod_web/live/provider_live/index.html.heex` | Template |
|
||||
| `lib/berrypod_web/live/provider_live/form_component.ex` | Form component for new/edit |
|
||||
| `lib/berrypod/providers/printify.ex` | Add `register_webhooks/1`, `unregister_webhooks/1` |
|
||||
| `lib/berrypod/workers/product_sync_worker.ex` | Stub for "Sync Now" (full impl in next task) |
|
||||
| `test/berrypod_web/live/provider_live_test.exs` | LiveView tests |
|
||||
|
||||
### UI Design
|
||||
|
||||
@@ -1264,11 +1264,11 @@ Single-page admin with modal for add/edit (follows Phoenix generator pattern):
|
||||
**Index LiveView (`provider_live/index.ex`):**
|
||||
|
||||
```elixir
|
||||
defmodule SimpleshopThemeWeb.ProviderLive.Index do
|
||||
use SimpleshopThemeWeb, :live_view
|
||||
defmodule BerrypodWeb.ProviderLive.Index do
|
||||
use BerrypodWeb, :live_view
|
||||
|
||||
alias SimpleshopTheme.Products
|
||||
alias SimpleshopTheme.Products.ProviderConnection
|
||||
alias Berrypod.Products
|
||||
alias Berrypod.Products.ProviderConnection
|
||||
|
||||
@impl true
|
||||
def mount(_params, _session, socket) do
|
||||
@@ -1300,7 +1300,7 @@ defmodule SimpleshopThemeWeb.ProviderLive.Index do
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_info({SimpleshopThemeWeb.ProviderLive.FormComponent, {:saved, connection}}, socket) do
|
||||
def handle_info({BerrypodWeb.ProviderLive.FormComponent, {:saved, connection}}, socket) do
|
||||
{:noreply, stream_insert(socket, :connections, connection)}
|
||||
end
|
||||
|
||||
@@ -1351,7 +1351,7 @@ end
|
||||
defp test_provider_connection("printify", api_key) do
|
||||
# Build temporary connection struct for testing
|
||||
conn = %ProviderConnection{provider_type: "printify", api_key: api_key}
|
||||
SimpleshopTheme.Providers.Printify.test_connection(conn)
|
||||
Berrypod.Providers.Printify.test_connection(conn)
|
||||
end
|
||||
```
|
||||
|
||||
@@ -1404,7 +1404,7 @@ def handle_event("save", %{"provider" => params}, socket) do
|
||||
end
|
||||
|
||||
defp register_webhooks(%{provider_type: "printify"} = conn) do
|
||||
SimpleshopTheme.Providers.Printify.register_webhooks(conn)
|
||||
Berrypod.Providers.Printify.register_webhooks(conn)
|
||||
end
|
||||
```
|
||||
|
||||
@@ -1422,7 +1422,7 @@ def handle_event("delete", %{"id" => id}, socket) do
|
||||
end
|
||||
|
||||
defp unregister_webhooks(%{provider_type: "printify"} = conn) do
|
||||
SimpleshopTheme.Providers.Printify.unregister_webhooks(conn)
|
||||
Berrypod.Providers.Printify.unregister_webhooks(conn)
|
||||
end
|
||||
```
|
||||
|
||||
@@ -1432,7 +1432,7 @@ end
|
||||
@webhook_topics ~w(product:publish:started product:deleted shop:disconnected)
|
||||
|
||||
def register_webhooks(conn) do
|
||||
webhook_url = SimpleshopThemeWeb.Endpoint.url() <> "/webhooks/printify"
|
||||
webhook_url = BerrypodWeb.Endpoint.url() <> "/webhooks/printify"
|
||||
shop_id = get_shop_id(conn)
|
||||
|
||||
results = Enum.map(@webhook_topics, fn topic ->
|
||||
@@ -1455,7 +1455,7 @@ def unregister_webhooks(conn) do
|
||||
# List existing webhooks and delete ours
|
||||
case Client.get(conn, "/shops/#{shop_id}/webhooks.json") do
|
||||
{:ok, %{"webhooks" => webhooks}} ->
|
||||
our_url = SimpleshopThemeWeb.Endpoint.url() <> "/webhooks/printify"
|
||||
our_url = BerrypodWeb.Endpoint.url() <> "/webhooks/printify"
|
||||
|
||||
webhooks
|
||||
|> Enum.filter(&(&1["url"] == our_url))
|
||||
@@ -1504,7 +1504,7 @@ Template snippet:
|
||||
|
||||
### Context Additions
|
||||
|
||||
Add to `lib/simpleshop_theme/products.ex`:
|
||||
Add to `lib/berrypod/products.ex`:
|
||||
|
||||
```elixir
|
||||
@doc """
|
||||
@@ -1513,7 +1513,7 @@ Returns {:ok, job} or {:error, changeset}.
|
||||
"""
|
||||
def enqueue_sync(%ProviderConnection{} = conn) do
|
||||
%{connection_id: conn.id}
|
||||
|> SimpleshopTheme.Workers.ProductSyncWorker.new()
|
||||
|> Berrypod.Workers.ProductSyncWorker.new()
|
||||
|> Oban.insert()
|
||||
end
|
||||
|
||||
@@ -1543,7 +1543,7 @@ end
|
||||
### Testing
|
||||
|
||||
```elixir
|
||||
# test/simpleshop_theme_web/live/provider_live_test.exs
|
||||
# test/berrypod_web/live/provider_live_test.exs
|
||||
describe "Index" do
|
||||
setup :register_and_log_in_user
|
||||
|
||||
@@ -1619,25 +1619,25 @@ Implement a robust product sync strategy with three mechanisms:
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `lib/simpleshop_theme/workers/product_sync_worker.ex` | Oban worker for full/single product sync |
|
||||
| `lib/simpleshop_theme_web/controllers/webhook_controller.ex` | Receives webhooks from providers |
|
||||
| `lib/simpleshop_theme/webhooks/printify_handler.ex` | Printify-specific webhook processing |
|
||||
| `test/simpleshop_theme/workers/product_sync_worker_test.exs` | Worker tests |
|
||||
| `test/simpleshop_theme_web/controllers/webhook_controller_test.exs` | Webhook endpoint tests |
|
||||
| `lib/berrypod/workers/product_sync_worker.ex` | Oban worker for full/single product sync |
|
||||
| `lib/berrypod_web/controllers/webhook_controller.ex` | Receives webhooks from providers |
|
||||
| `lib/berrypod/webhooks/printify_handler.ex` | Printify-specific webhook processing |
|
||||
| `test/berrypod/workers/product_sync_worker_test.exs` | Worker tests |
|
||||
| `test/berrypod_web/controllers/webhook_controller_test.exs` | Webhook endpoint tests |
|
||||
|
||||
### Part 1: ProductSyncWorker (~1hr)
|
||||
|
||||
Oban worker that syncs products from a provider connection.
|
||||
|
||||
```elixir
|
||||
defmodule SimpleshopTheme.Workers.ProductSyncWorker do
|
||||
defmodule Berrypod.Workers.ProductSyncWorker do
|
||||
use Oban.Worker,
|
||||
queue: :sync,
|
||||
max_attempts: 3,
|
||||
unique: [period: 60, fields: [:args, :queue]]
|
||||
|
||||
alias SimpleshopTheme.Products
|
||||
alias SimpleshopTheme.Providers
|
||||
alias Berrypod.Products
|
||||
alias Berrypod.Providers
|
||||
|
||||
@impl Oban.Worker
|
||||
def perform(%Oban.Job{args: %{"connection_id" => conn_id} = args}) do
|
||||
@@ -1714,10 +1714,10 @@ post "/webhooks/printify", WebhookController, :printify
|
||||
|
||||
**Controller:**
|
||||
```elixir
|
||||
defmodule SimpleshopThemeWeb.WebhookController do
|
||||
use SimpleshopThemeWeb, :controller
|
||||
defmodule BerrypodWeb.WebhookController do
|
||||
use BerrypodWeb, :controller
|
||||
|
||||
alias SimpleshopTheme.Webhooks.PrintifyHandler
|
||||
alias Berrypod.Webhooks.PrintifyHandler
|
||||
|
||||
def printify(conn, params) do
|
||||
with :ok <- verify_printify_signature(conn),
|
||||
@@ -1737,7 +1737,7 @@ defmodule SimpleshopThemeWeb.WebhookController do
|
||||
# Header: X-Printify-Signature
|
||||
signature = get_req_header(conn, "x-printify-signature") |> List.first()
|
||||
body = conn.assigns[:raw_body]
|
||||
secret = Application.get_env(:simpleshop_theme, :printify_webhook_secret)
|
||||
secret = Application.get_env(:berrypod, :printify_webhook_secret)
|
||||
|
||||
expected = :crypto.mac(:hmac, :sha256, secret, body) |> Base.encode16(case: :lower)
|
||||
|
||||
@@ -1752,9 +1752,9 @@ end
|
||||
|
||||
**Handler:**
|
||||
```elixir
|
||||
defmodule SimpleshopTheme.Webhooks.PrintifyHandler do
|
||||
alias SimpleshopTheme.Products
|
||||
alias SimpleshopTheme.Workers.ProductSyncWorker
|
||||
defmodule Berrypod.Webhooks.PrintifyHandler do
|
||||
alias Berrypod.Products
|
||||
alias Berrypod.Workers.ProductSyncWorker
|
||||
|
||||
def handle(%{"type" => "product:publish:started", "resource" => resource}) do
|
||||
%{"shop_id" => shop_id, "id" => product_id} = resource
|
||||
@@ -1800,16 +1800,16 @@ end
|
||||
Add to Oban config for daily fallback:
|
||||
```elixir
|
||||
# In config/config.exs
|
||||
config :simpleshop_theme, Oban,
|
||||
config :berrypod, Oban,
|
||||
plugins: [
|
||||
{Oban.Plugins.Cron, crontab: [
|
||||
{"0 3 * * *", SimpleshopTheme.Workers.ScheduledSyncWorker} # 3 AM daily
|
||||
{"0 3 * * *", Berrypod.Workers.ScheduledSyncWorker} # 3 AM daily
|
||||
]}
|
||||
]
|
||||
```
|
||||
|
||||
```elixir
|
||||
defmodule SimpleshopTheme.Workers.ScheduledSyncWorker do
|
||||
defmodule Berrypod.Workers.ScheduledSyncWorker do
|
||||
use Oban.Worker, queue: :sync
|
||||
|
||||
def perform(_job) do
|
||||
@@ -1827,7 +1827,7 @@ end
|
||||
|
||||
### Context Additions
|
||||
|
||||
Add to `lib/simpleshop_theme/products.ex`:
|
||||
Add to `lib/berrypod/products.ex`:
|
||||
|
||||
```elixir
|
||||
def archive_product_by_provider(connection_id, provider_product_id) do
|
||||
|
||||
Reference in New Issue
Block a user