Current section
Files
Jump to
Current section
Files
src/admin/products.gleam
import admin/types.{
type Edge, type Pagination, type SortKey, Edge, pagination_decoder,
pagination_to_json, sort_key_to_json,
}
import gleam/dynamic/decode.{type Decoder}
import gleam/json.{type Json}
import gleam/option.{type Option, None, Some}
import midas/task as t
import pify.{
type AdminApiClientConfig, type AdminClientResponse, type AdminResponseErrors,
AdminClientResponse, admin_handler,
}
import snag
pub const get_products_query = "
query getProducts($numProducts: Int!, $cursor: String, $sortKey: ProductSortKeys, $reverse: Boolean, $query: String) {
products(
first: $numProducts
after: $cursor
sortKey: $sortKey
reverse: $reverse
query: $query
) {
edges {
node {
id
handle
title
totalInventory
description
descriptionHtml
vendor
options {
id
name
values
}
priceRangeV2 {
maxVariantPrice {
amount
currencyCode
}
minVariantPrice {
amount
currencyCode
}
}
compareAtPriceRange {
maxVariantCompareAtPrice {
amount
currencyCode
}
minVariantCompareAtPrice {
amount
currencyCode
}
}
variants(first: 20) {
edges {
node {
id
title
availableForSale
selectedOptions {
name
value
}
price
}
}
}
featuredMedia {
id
mediaContentType
alt
preview {
image {
url
id
altText
width
height
thumbhash
}
status
}
}
media(first: 20) {
edges {
node {
id
mediaContentType
alt
preview {
image {
url
id
altText
width
height
thumbhash
}
status
}
}
}
}
seo {
description
title
}
tags
updatedAt
createdAt
publishedAt
variantsCount {
count
precision
}
}
}
pageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
}
}
}
"
pub fn get_products(
config: AdminApiClientConfig,
query: Option(String),
reverse: Option(Bool),
sort_key: Option(SortKey),
num_products: Int,
cursor: Option(String),
) -> t.Effect(GetProductsQuery, AdminResponseErrors) {
let validade_int = case num_products <= 0 {
True -> 1
_ -> num_products
}
let handler = admin_handler(config)
let variables =
json.object([
#("query", json.nullable(query, of: json.string)),
#("reverse", json.nullable(reverse, of: json.bool)),
#("sortKey", json.nullable(sort_key, of: sort_key_to_json)),
#("numProducts", json.int(validade_int)),
#("cursor", json.nullable(cursor, of: json.string)),
])
use decoded_response <- t.do(handler.fetch(
get_products_query,
Some(variables),
get_products_admin_client_response_decoder(),
))
let products = decoded_response.data
case products {
None -> t.Abort(snag.new("No product to be fetched"))
Some(value) -> {
t.Done(value)
}
}
}
pub fn get_products_admin_client_response_decoder() -> decode.Decoder(
AdminClientResponse(GetProductsQuery),
) {
use data <- decode.field(
"data",
decode.optional(get_products_query_decoder()),
)
decode.success(AdminClientResponse(data:))
}
pub fn get_products_admin_client_response_to_json(
admin_client_response: AdminClientResponse(GetProductsQuery),
) -> Json {
let AdminClientResponse(data:) = admin_client_response
json.object([
#("data", case data {
None -> json.null()
Some(value) -> get_products_query_to_json(value)
}),
])
}
pub type GetProductsQuery {
GetProductsQuery(products: ProductConnection)
}
pub fn get_products_query_to_json(get_products_query: GetProductsQuery) -> Json {
let GetProductsQuery(products:) = get_products_query
json.object([
#("products", product_connection_to_json(products)),
])
}
fn get_products_query_decoder() -> decode.Decoder(GetProductsQuery) {
use products <- decode.field("products", product_connection_decoder())
decode.success(GetProductsQuery(products:))
}
pub type ProductConnection {
ProductConnection(edges: List(Edge(Product)), pagination: Pagination)
}
fn product_connection_to_json(product_connection: ProductConnection) -> Json {
let ProductConnection(edges:, pagination:) = product_connection
json.object([
#("edges", json.array(edges, product_edge_to_json)),
#("pageInfo", pagination_to_json(pagination)),
])
}
fn product_connection_decoder() -> Decoder(ProductConnection) {
use edges <- decode.field("edges", decode.list(product_edge_decoder()))
use pagination <- decode.field("pageInfo", pagination_decoder())
decode.success(ProductConnection(edges:, pagination:))
}
pub fn product_edge_decoder() -> Decoder(Edge(Product)) {
use node <- decode.field("node", product_decoder())
decode.success(Edge(node:))
}
pub fn product_edge_to_json(edge: Edge(Product)) -> Json {
let Edge(node:) = edge
json.object([
#("node", product_to_json(node)),
])
}
pub type Product {
Product(
id: String,
handle: String,
title: String,
total_inventory: Option(Int),
description: Option(String),
description_html: Option(String),
vendor: String,
options: List(ProductOption),
price_range_v2: ProductPriceRange,
compare_at_price_range: Option(ProductCompareAtPriceRange),
variants: ProductVariantConnection,
featured_media: Option(Media),
media: MediaConnection,
seo: Option(Seo),
tags: List(String),
updated_at: String,
created_at: String,
published_at: Option(String),
variants_count: VariantsCount,
)
}
pub fn product_to_json(product: Product) -> json.Json {
let Product(
id:,
handle:,
title:,
total_inventory:,
description:,
description_html:,
vendor:,
options:,
price_range_v2:,
compare_at_price_range:,
variants:,
featured_media:,
media:,
seo:,
tags:,
updated_at:,
created_at:,
published_at:,
variants_count:,
) = product
json.object([
#("id", json.string(id)),
#("handle", json.string(handle)),
#("title", json.string(title)),
#("totalInventory", case total_inventory {
None -> json.null()
Some(value) -> json.int(value)
}),
#("description", case description {
None -> json.null()
Some(value) -> json.string(value)
}),
#("descriptionHtml", case description_html {
None -> json.null()
Some(value) -> json.string(value)
}),
#("vendor", json.string(vendor)),
#("options", json.array(options, product_option_to_json)),
#("priceRangeV2", product_price_range_to_json(price_range_v2)),
#("compareAtPriceRange", case compare_at_price_range {
None -> json.null()
Some(value) -> product_compare_at_price_range_to_json(value)
}),
#("variants", product_variant_connection_to_json(variants)),
#("featuredMedia", case featured_media {
None -> json.null()
Some(value) -> media_to_json(value)
}),
#("media", media_connection_to_json(media)),
#("seo", case seo {
None -> json.null()
Some(value) -> seo_to_json(value)
}),
#("tags", json.array(tags, json.string)),
#("updatedAt", json.string(updated_at)),
#("createdAt", json.string(created_at)),
#("publishedAt", case published_at {
None -> json.null()
Some(value) -> json.string(value)
}),
#("variantsCount", variants_count_to_json(variants_count)),
])
}
pub fn product_decoder() -> decode.Decoder(Product) {
use id <- decode.field("id", decode.string)
use handle <- decode.field("handle", decode.string)
use title <- decode.field("title", decode.string)
use total_inventory <- decode.field(
"totalInventory",
decode.optional(decode.int),
)
use description <- decode.field("description", decode.optional(decode.string))
use description_html <- decode.field(
"descriptionHtml",
decode.optional(decode.string),
)
use vendor <- decode.field("vendor", decode.string)
use options <- decode.field("options", decode.list(product_option_decoder()))
use price_range_v2 <- decode.field(
"priceRangeV2",
product_price_range_decoder(),
)
use compare_at_price_range <- decode.field(
"compareAtPriceRange",
decode.optional(product_compare_at_price_range_decoder()),
)
use variants <- decode.field("variants", product_variant_connection_decoder())
use featured_media <- decode.field(
"featuredMedia",
decode.optional(media_decoder()),
)
use media <- decode.field("media", media_connection_decoder())
use seo <- decode.field("seo", decode.optional(seo_decoder()))
use tags <- decode.field("tags", decode.list(decode.string))
use updated_at <- decode.field("updatedAt", decode.string)
use created_at <- decode.field("createdAt", decode.string)
use published_at <- decode.field(
"publishedAt",
decode.optional(decode.string),
)
use variants_count <- decode.field("variantsCount", variants_count_decoder())
decode.success(Product(
id:,
handle:,
title:,
total_inventory:,
description:,
description_html:,
vendor:,
options:,
price_range_v2:,
compare_at_price_range:,
variants:,
featured_media:,
media:,
seo:,
tags:,
updated_at:,
created_at:,
published_at:,
variants_count:,
))
}
pub type ProductOption {
ProductOption(id: String, name: String, values: List(String))
}
fn product_option_to_json(product_option: ProductOption) -> json.Json {
let ProductOption(id:, name:, values:) = product_option
json.object([
#("id", json.string(id)),
#("name", json.string(name)),
#("values", json.array(values, json.string)),
])
}
fn product_option_decoder() -> decode.Decoder(ProductOption) {
use id <- decode.field("id", decode.string)
use name <- decode.field("name", decode.string)
use values <- decode.field("values", decode.list(decode.string))
decode.success(ProductOption(id:, name:, values:))
}
pub type Money {
Money(amount: String, currency_code: String)
}
fn money_to_json(money: Money) -> json.Json {
let Money(amount:, currency_code:) = money
json.object([
#("amount", json.string(amount)),
#("currencyCode", json.string(currency_code)),
])
}
fn money_decoder() -> decode.Decoder(Money) {
use amount <- decode.field("amount", decode.string)
use currency_code <- decode.field("currencyCode", decode.string)
decode.success(Money(amount:, currency_code:))
}
pub type ProductPriceRange {
ProductPriceRange(max_variant_price: Money, min_variant_price: Money)
}
fn product_price_range_to_json(
product_price_range: ProductPriceRange,
) -> json.Json {
let ProductPriceRange(max_variant_price:, min_variant_price:) =
product_price_range
json.object([
#("maxVariantPrice", money_to_json(max_variant_price)),
#("minVariantPrice", money_to_json(min_variant_price)),
])
}
fn product_price_range_decoder() -> decode.Decoder(ProductPriceRange) {
use max_variant_price <- decode.field("maxVariantPrice", money_decoder())
use min_variant_price <- decode.field("minVariantPrice", money_decoder())
decode.success(ProductPriceRange(max_variant_price:, min_variant_price:))
}
pub type ProductCompareAtPriceRange {
ProductCompareAtPriceRange(
max_variant_compare_at_price: Option(Money),
min_variant_compare_at_price: Option(Money),
)
}
fn product_compare_at_price_range_to_json(
product_compare_at_price_range: ProductCompareAtPriceRange,
) -> json.Json {
let ProductCompareAtPriceRange(
max_variant_compare_at_price:,
min_variant_compare_at_price:,
) = product_compare_at_price_range
json.object([
#("maxVariantCompareAtPrice", case max_variant_compare_at_price {
None -> json.null()
Some(value) -> money_to_json(value)
}),
#("minVariantCompareAtPrice", case min_variant_compare_at_price {
None -> json.null()
Some(value) -> money_to_json(value)
}),
])
}
fn product_compare_at_price_range_decoder() -> decode.Decoder(
ProductCompareAtPriceRange,
) {
use max_variant_compare_at_price <- decode.field(
"maxVariantCompareAtPrice",
decode.optional(money_decoder()),
)
use min_variant_compare_at_price <- decode.field(
"minVariantCompareAtPrice",
decode.optional(money_decoder()),
)
decode.success(ProductCompareAtPriceRange(
max_variant_compare_at_price:,
min_variant_compare_at_price:,
))
}
pub type ProductVariantConnection {
ProductVariantConnection(edges: List(ProductVariantEdge))
}
fn product_variant_connection_to_json(
product_variant_connection: ProductVariantConnection,
) -> json.Json {
let ProductVariantConnection(edges:) = product_variant_connection
json.object([
#("edges", json.array(edges, product_variant_edge_to_json)),
])
}
fn product_variant_connection_decoder() -> decode.Decoder(
ProductVariantConnection,
) {
use edges <- decode.field(
"edges",
decode.list(product_variant_edge_decoder()),
)
decode.success(ProductVariantConnection(edges:))
}
pub type ProductVariantEdge {
ProductVariantEdge(node: ProductVariant)
}
fn product_variant_edge_to_json(
product_variant_edge: ProductVariantEdge,
) -> json.Json {
let ProductVariantEdge(node:) = product_variant_edge
json.object([
#("node", product_variant_to_json(node)),
])
}
fn product_variant_edge_decoder() -> decode.Decoder(ProductVariantEdge) {
use node <- decode.field("node", product_variant_decoder())
decode.success(ProductVariantEdge(node:))
}
pub type ProductVariant {
ProductVariant(
id: String,
title: String,
available_for_sale: Bool,
selected_options: List(SelectedOption),
price: String,
)
}
fn product_variant_to_json(product_variant: ProductVariant) -> json.Json {
let ProductVariant(
id:,
title:,
available_for_sale:,
selected_options:,
price:,
) = product_variant
json.object([
#("id", json.string(id)),
#("title", json.string(title)),
#("availableForSale", json.bool(available_for_sale)),
#("selectedOptions", json.array(selected_options, selected_option_to_json)),
#("price", json.string(price)),
])
}
fn product_variant_decoder() -> decode.Decoder(ProductVariant) {
use id <- decode.field("id", decode.string)
use title <- decode.field("title", decode.string)
use available_for_sale <- decode.field("availableForSale", decode.bool)
use selected_options <- decode.field(
"selectedOptions",
decode.list(selected_option_decoder()),
)
use price <- decode.field("price", decode.string)
decode.success(ProductVariant(
id:,
title:,
available_for_sale:,
selected_options:,
price:,
))
}
pub type SelectedOption {
SelectedOption(name: String, value: String)
}
fn selected_option_to_json(selected_option: SelectedOption) -> json.Json {
let SelectedOption(name:, value:) = selected_option
json.object([
#("name", json.string(name)),
#("value", json.string(value)),
])
}
fn selected_option_decoder() -> decode.Decoder(SelectedOption) {
use name <- decode.field("name", decode.string)
use value <- decode.field("value", decode.string)
decode.success(SelectedOption(name:, value:))
}
pub type MediaConnection {
MediaConnection(edges: List(MediaEdge))
}
fn media_connection_to_json(media_connection: MediaConnection) -> json.Json {
let MediaConnection(edges:) = media_connection
json.object([
#("edges", json.array(edges, media_edge_to_json)),
])
}
fn media_connection_decoder() -> decode.Decoder(MediaConnection) {
use edges <- decode.field("edges", decode.list(media_edge_decoder()))
decode.success(MediaConnection(edges:))
}
pub type MediaEdge {
MediaEdge(node: Media)
}
fn media_edge_to_json(media_edge: MediaEdge) -> json.Json {
let MediaEdge(node:) = media_edge
json.object([
#("node", media_to_json(node)),
])
}
fn media_edge_decoder() -> decode.Decoder(MediaEdge) {
use node <- decode.field("node", media_decoder())
decode.success(MediaEdge(node:))
}
pub type Media {
Media(
id: String,
media_content_type: String,
alt: Option(String),
preview: Option(Preview),
)
}
fn media_to_json(media: Media) -> json.Json {
let Media(id:, media_content_type:, alt:, preview:) = media
json.object([
#("id", json.string(id)),
#("mediaContentType", json.string(media_content_type)),
#("alt", case alt {
None -> json.null()
Some(value) -> json.string(value)
}),
#("preview", case preview {
None -> json.null()
Some(value) -> preview_to_json(value)
}),
])
}
fn media_decoder() -> decode.Decoder(Media) {
use id <- decode.field("id", decode.string)
use media_content_type <- decode.field("mediaContentType", decode.string)
use alt <- decode.field("alt", decode.optional(decode.string))
use preview <- decode.field("preview", decode.optional(preview_decoder()))
decode.success(Media(id:, media_content_type:, alt:, preview:))
}
pub type Preview {
Preview(image: Option(Image), status: String)
}
fn preview_to_json(preview: Preview) -> json.Json {
let Preview(image:, status:) = preview
json.object([
#("image", case image {
None -> json.null()
Some(value) -> image_to_json(value)
}),
#("status", json.string(status)),
])
}
fn preview_decoder() -> decode.Decoder(Preview) {
use image <- decode.field("image", decode.optional(image_decoder()))
use status <- decode.field("status", decode.string)
decode.success(Preview(image:, status:))
}
pub type Image {
Image(
url: String,
id: String,
alt_text: Option(String),
width: Option(Int),
height: Option(Int),
thumbhash: Option(String),
)
}
fn image_to_json(image: Image) -> json.Json {
let Image(url:, id:, alt_text:, width:, height:, thumbhash:) = image
json.object([
#("url", json.string(url)),
#("id", json.string(id)),
#("altText", case alt_text {
None -> json.null()
Some(value) -> json.string(value)
}),
#("width", case width {
None -> json.null()
Some(value) -> json.int(value)
}),
#("height", case height {
None -> json.null()
Some(value) -> json.int(value)
}),
#("thumbhash", case thumbhash {
None -> json.null()
Some(value) -> json.string(value)
}),
])
}
fn image_decoder() -> decode.Decoder(Image) {
use url <- decode.field("url", decode.string)
use id <- decode.field("id", decode.string)
use alt_text <- decode.field("altText", decode.optional(decode.string))
use width <- decode.field("width", decode.optional(decode.int))
use height <- decode.field("height", decode.optional(decode.int))
use thumbhash <- decode.field("thumbhash", decode.optional(decode.string))
decode.success(Image(url:, id:, alt_text:, width:, height:, thumbhash:))
}
pub type Seo {
Seo(description: Option(String), title: Option(String))
}
fn seo_to_json(seo: Seo) -> json.Json {
let Seo(description:, title:) = seo
json.object([
#("description", case description {
None -> json.null()
Some(value) -> json.string(value)
}),
#("title", case title {
None -> json.null()
Some(value) -> json.string(value)
}),
])
}
fn seo_decoder() -> decode.Decoder(Seo) {
use description <- decode.field("description", decode.optional(decode.string))
use title <- decode.field("title", decode.optional(decode.string))
decode.success(Seo(description:, title:))
}
pub type VariantsCount {
VariantsCount(count: Int, precision: String)
}
fn variants_count_to_json(variants_count: VariantsCount) -> json.Json {
let VariantsCount(count:, precision:) = variants_count
json.object([
#("count", json.int(count)),
#("precision", json.string(precision)),
])
}
fn variants_count_decoder() -> decode.Decoder(VariantsCount) {
use count <- decode.field("count", decode.int)
use precision <- decode.field("precision", decode.string)
decode.success(VariantsCount(count:, precision:))
}
/// Queries
///
pub const get_product_recommendations_query = "
query getProductRecommendations($productId: ID!) {
productRecommendations(productId: $productId) {
...product
}
}
"
<> product_fragment
pub const get_product_query = "
query getProduct($handle: ProductIdentifierInput!){
productByIdentifier(identifier:$handle){
...product
}
}
"
<> product_fragment
pub const product_fragment = "
fragment product on Product {
id
handle
title
totalInventory
description
descriptionHtml
vendor
options {
id
name
values
}
priceRangeV2 {
maxVariantPrice {
amount
currencyCode
}
minVariantPrice {
amount
currencyCode
}
}
compareAtPriceRange {
maxVariantCompareAtPrice {
amount
currencyCode
}
minVariantCompareAtPrice {
amount
currencyCode
}
}
variants(first: 20) {
edges {
node {
id
title
availableForSale
selectedOptions {
name
value
}
price
}
}
}
featuredMedia {
id
mediaContentType
alt
preview {
image {
url
id
altText
width
height
thumbhash
}
status
}
}
media(first: 20) {
edges {
node {
id
mediaContentType
alt
preview {
image {
url
id
altText
width
height
thumbhash
}
status
}
}
}
}
seo {
description
title
}
tags
updatedAt
createdAt
publishedAt
variantsCount {
count
precision
}
}
"