Skip to content

Commit 928528d

Browse files
committed
feat: custom deck tags data model and API
Add per-deck custom tags (name, color, target count, ordering) with a many-to-many relationship to deck cards, alongside the existing getting/consider_cutting tag axis which is left untouched. - deck_tags and deck_card_tags tables (denormalized deck_id for same-deck guard) - DeckTag/DeckCardTag schemas; Decks.Tags context (CRUD, reorder, assign/unassign) - GraphQL deck.tags, deckCard.tagIds, and 6 tag mutations - Frontend GraphQL documents + generated types - Card counts sum deck card quantity; cross-deck assignment rejected
1 parent b9b1dae commit 928528d

21 files changed

Lines changed: 976 additions & 7 deletions

File tree

‎assets/react/src/gql/gql.ts‎

Lines changed: 39 additions & 3 deletions
Large diffs are not rendered by default.

‎assets/react/src/gql/graphql.ts‎

Lines changed: 61 additions & 2 deletions
Large diffs are not rendered by default.

‎assets/react/src/pages/decks/deck-types.ts‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,27 @@ export const DECK_CARD_TAGS = [
169169
iconClassName: string
170170
icon: LucideIcon
171171
}>
172+
173+
export const DECK_TAG_COLORS = [
174+
"#7C5CFF",
175+
"#22C55E",
176+
"#3B82F6",
177+
"#F59E0B",
178+
"#EF4444",
179+
"#EC4899",
180+
"#14B8A6",
181+
"#A855F7",
182+
] as const
183+
// TODO: derive DeckCustomTag from `DeckQuery["deck"]["tags"][number]` once codegen has
184+
// run and the `tags` selection on DeckDocument is reflected in the generated types.
185+
export type DeckCustomTag = {
186+
id: string
187+
name: string
188+
color: string
189+
targetCount?: number | null
190+
position: number
191+
cardCount: number
192+
}
172193
export const DECK_FORMATS = [
173194
"commander",
174195
"standard",

‎assets/react/src/pages/decks/queries.ts‎

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,14 @@ export const DeckDocument = graphql(`
163163
cardName
164164
}
165165
}
166+
tags {
167+
id
168+
name
169+
color
170+
targetCount
171+
position
172+
cardCount
173+
}
166174
deckCards(first: 500, after: $deckCardsAfter) {
167175
pageInfo {
168176
endCursor
@@ -175,6 +183,7 @@ export const DeckDocument = graphql(`
175183
zone
176184
finish
177185
tag
186+
tagIds
178187
priceCents
179188
card {
180189
id
@@ -339,6 +348,89 @@ export const UpdateDeckCardsTagDocument = graphql(`
339348
}
340349
`)
341350

351+
export const CreateDeckTagDocument = graphql(`
352+
mutation CreateDeckTag($deckId: ID!, $input: DeckTagInput!) {
353+
createDeckTag(deckId: $deckId, input: $input) {
354+
deckTag {
355+
id
356+
name
357+
color
358+
targetCount
359+
position
360+
cardCount
361+
}
362+
}
363+
}
364+
`)
365+
366+
export const UpdateDeckTagDocument = graphql(`
367+
mutation UpdateDeckTag($id: ID!, $input: DeckTagInput!) {
368+
updateDeckTag(id: $id, input: $input) {
369+
deckTag {
370+
id
371+
name
372+
color
373+
targetCount
374+
position
375+
cardCount
376+
}
377+
}
378+
}
379+
`)
380+
381+
export const DeleteDeckTagDocument = graphql(`
382+
mutation DeleteDeckTag($id: ID!) {
383+
deleteDeckTag(id: $id) {
384+
deckTagId
385+
}
386+
}
387+
`)
388+
389+
export const ReorderDeckTagsDocument = graphql(`
390+
mutation ReorderDeckTags($deckId: ID!, $tagIds: [ID!]!) {
391+
reorderDeckTags(deckId: $deckId, tagIds: $tagIds) {
392+
tags {
393+
id
394+
name
395+
color
396+
targetCount
397+
position
398+
cardCount
399+
}
400+
}
401+
}
402+
`)
403+
404+
export const AssignDeckCardTagDocument = graphql(`
405+
mutation AssignDeckCardTag($deckCardId: ID!, $tagId: ID!) {
406+
assignDeckCardTag(deckCardId: $deckCardId, tagId: $tagId) {
407+
deckCard {
408+
id
409+
tagIds
410+
}
411+
deckTags {
412+
id
413+
cardCount
414+
}
415+
}
416+
}
417+
`)
418+
419+
export const UnassignDeckCardTagDocument = graphql(`
420+
mutation UnassignDeckCardTag($deckCardId: ID!, $tagId: ID!) {
421+
unassignDeckCardTag(deckCardId: $deckCardId, tagId: $tagId) {
422+
deckCard {
423+
id
424+
tagIds
425+
}
426+
deckTags {
427+
id
428+
cardCount
429+
}
430+
}
431+
}
432+
`)
433+
342434
export const OptimizeDeckCardPrintingsDocument = graphql(`
343435
mutation OptimizeDeckCardPrintings($deckCardIds: [ID!]!) {
344436
optimizeDeckCardPrintings(deckCardIds: $deckCardIds) {

‎lib/manavault/catalog.ex‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,15 @@ defmodule Manavault.Catalog do
103103
defdelegate put_deck_card_allocation_statuses(deck_cards), to: Decks
104104
defdelegate put_deck_card_fallback_printings(deck_cards), to: Decks
105105

106+
defdelegate list_deck_tags(deck), to: Cached
107+
defdelegate create_deck_tag(deck, attrs), to: Cached
108+
defdelegate update_deck_tag(deck_tag, attrs), to: Cached
109+
defdelegate delete_deck_tag(deck_tag), to: Cached
110+
defdelegate reorder_deck_tags(deck, ordered_tag_ids), to: Cached
111+
defdelegate assign_deck_card_tag(deck_card_id, deck_tag_id), to: Cached
112+
defdelegate unassign_deck_card_tag(deck_card_id, deck_tag_id), to: Cached
113+
defdelegate put_deck_card_tag_ids(deck_cards), to: Cached
114+
106115
defdelegate allocate_collection_item_to_deck_card(
107116
deck_card_id,
108117
collection_item_id,

‎lib/manavault/catalog/cached.ex‎

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -472,6 +472,45 @@ defmodule Manavault.Catalog.Cached do
472472
defdelegate put_deck_card_allocation_statuses(deck_cards), to: Decks
473473
defdelegate put_deck_card_fallback_printings(deck_cards), to: Decks
474474

475+
defdelegate list_deck_tags(deck), to: Decks
476+
defdelegate put_deck_card_tag_ids(deck_cards), to: Decks
477+
478+
def create_deck_tag(deck, attrs) do
479+
deck
480+
|> Decks.create_deck_tag(attrs)
481+
|> invalidate_on_ok(&Cache.invalidate_decks/0)
482+
end
483+
484+
def update_deck_tag(deck_tag, attrs) do
485+
deck_tag
486+
|> Decks.update_deck_tag(attrs)
487+
|> invalidate_on_ok(&Cache.invalidate_decks/0)
488+
end
489+
490+
def delete_deck_tag(deck_tag) do
491+
deck_tag
492+
|> Decks.delete_deck_tag()
493+
|> invalidate_on_ok(&Cache.invalidate_decks/0)
494+
end
495+
496+
def reorder_deck_tags(deck, ordered_tag_ids) do
497+
deck
498+
|> Decks.reorder_deck_tags(ordered_tag_ids)
499+
|> invalidate_on_ok(&Cache.invalidate_decks/0)
500+
end
501+
502+
def assign_deck_card_tag(deck_card_id, deck_tag_id) do
503+
deck_card_id
504+
|> Decks.assign_deck_card_tag(deck_tag_id)
505+
|> invalidate_on_ok(&Cache.invalidate_decks/0)
506+
end
507+
508+
def unassign_deck_card_tag(deck_card_id, deck_tag_id) do
509+
deck_card_id
510+
|> Decks.unassign_deck_card_tag(deck_tag_id)
511+
|> invalidate_on_ok(&Cache.invalidate_decks/0)
512+
end
513+
475514
def allocate_collection_item_to_deck_card(deck_card_id, collection_item_id, quantity \\ 1) do
476515
deck_card_id
477516
|> Decks.allocate_collection_item_to_deck_card(collection_item_id, quantity)

‎lib/manavault/catalog/deck.ex‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ defmodule Manavault.Catalog.Deck do
1818

1919
has_many :deck_cards, Manavault.Catalog.DeckCard, on_replace: :delete
2020
has_many :deck_allocations, through: [:deck_cards, :deck_allocations]
21+
has_many :deck_tags, Manavault.Catalog.DeckTag, on_replace: :delete
2122

2223
timestamps(type: :utc_datetime)
2324
end

‎lib/manavault/catalog/deck_card.ex‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ defmodule Manavault.Catalog.DeckCard do
1515
field :tag, :string
1616
field :allocation_status, :map, virtual: true
1717
field :fallback_printing, :map, virtual: true
18+
field :tag_ids, {:array, :id}, virtual: true, default: []
1819

1920
belongs_to :deck, Manavault.Catalog.Deck
2021

@@ -30,6 +31,8 @@ defmodule Manavault.Catalog.DeckCard do
3031

3132
has_many :deck_allocations, Manavault.Catalog.DeckAllocation, on_replace: :delete
3233
has_many :collection_items, through: [:deck_allocations, :collection_item]
34+
has_many :deck_card_tags, Manavault.Catalog.DeckCardTag
35+
has_many :deck_tags, through: [:deck_card_tags, :deck_tag]
3336

3437
timestamps(type: :utc_datetime)
3538
end
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
defmodule Manavault.Catalog.DeckCardTag do
2+
use Ecto.Schema
3+
4+
import Ecto.Changeset
5+
6+
schema "deck_card_tags" do
7+
field :deck_id, :id
8+
9+
belongs_to :deck_card, Manavault.Catalog.DeckCard
10+
belongs_to :deck_tag, Manavault.Catalog.DeckTag
11+
12+
timestamps(type: :utc_datetime)
13+
end
14+
15+
def changeset(deck_card_tag, attrs) do
16+
deck_card_tag
17+
|> cast(attrs, [:deck_card_id, :deck_tag_id, :deck_id])
18+
|> validate_required([:deck_card_id, :deck_tag_id, :deck_id])
19+
|> unique_constraint([:deck_card_id, :deck_tag_id])
20+
end
21+
end

‎lib/manavault/catalog/deck_tag.ex‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
defmodule Manavault.Catalog.DeckTag do
2+
use Ecto.Schema
3+
4+
import Ecto.Changeset
5+
6+
schema "deck_tags" do
7+
field :name, :string
8+
field :color, :string
9+
field :target_count, :integer
10+
field :position, :integer, default: 0
11+
field :card_count, :integer, virtual: true
12+
13+
belongs_to :deck, Manavault.Catalog.Deck
14+
has_many :deck_card_tags, Manavault.Catalog.DeckCardTag
15+
16+
timestamps(type: :utc_datetime)
17+
end
18+
19+
def changeset(deck_tag, attrs) do
20+
deck_tag
21+
|> cast(attrs, [:name, :color, :target_count, :position, :deck_id])
22+
|> validate_required([:name, :color, :deck_id])
23+
|> validate_length(:name, min: 1, max: 60)
24+
|> validate_format(:color, ~r/^#[0-9a-fA-F]{6}$/)
25+
|> validate_number(:target_count, greater_than: 0)
26+
|> unique_constraint([:deck_id, :name])
27+
end
28+
end

0 commit comments

Comments
 (0)