shopify_client/admin/bulk_operation/
remote.rs1use crate::common::ServiceContext;
2use crate::{
3 common::{http::execute_graphql, types::APIError},
4 types::bulk_operation::{
5 CancelResp, GetBulkOperationResp, ListBulkOperationsParams, ListBulkOperationsResp,
6 RunMutationResp, RunQueryResp, StagedUploadInput, StagedUploadsCreateResp,
7 },
8};
9
10use serde_json::json;
11
12pub async fn run_query(
15 ctx: &ServiceContext,
16 query_string: &str,
17 group_objects: Option<bool>,
18) -> Result<RunQueryResp, APIError> {
19 let query = r#"
20 mutation bulkOperationRunQuery($query: String!) {
21 bulkOperationRunQuery(query: $query) {
22 bulkOperation {
23 id
24 status
25 errorCode
26 createdAt
27 objectCount
28 rootObjectCount
29 url
30 query
31 type
32 }
33 userErrors {
34 code
35 field
36 message
37 }
38 }
39 }
40 "#;
41
42 let mut query_with_group = query_string.to_string();
43 if let Some(true) = group_objects {
44 if !query_with_group.contains("groupObjects") {
46 query_with_group = query_string.to_string();
47 }
48 }
49
50 let variables = json!({
51 "query": query_with_group
52 });
53
54 execute_graphql(ctx, query, variables).await
55}
56
57pub async fn run_mutation(
58 ctx: &ServiceContext,
59 mutation: &str,
60 staged_upload_path: &str,
61 client_identifier: Option<&str>,
62) -> Result<RunMutationResp, APIError> {
63 let query = r#"
64 mutation bulkOperationRunMutation($mutation: String!, $stagedUploadPath: String!, $clientIdentifier: String) {
65 bulkOperationRunMutation(mutation: $mutation, stagedUploadPath: $stagedUploadPath, clientIdentifier: $clientIdentifier) {
66 bulkOperation {
67 id
68 status
69 errorCode
70 createdAt
71 objectCount
72 rootObjectCount
73 url
74 type
75 }
76 userErrors {
77 code
78 field
79 message
80 }
81 }
82 }
83 "#;
84
85 let variables = json!({
86 "mutation": mutation,
87 "stagedUploadPath": staged_upload_path,
88 "clientIdentifier": client_identifier
89 });
90
91 execute_graphql(ctx, query, variables).await
92}
93
94pub async fn cancel(ctx: &ServiceContext, id: &str) -> Result<CancelResp, APIError> {
95 let query = r#"
96 mutation bulkOperationCancel($id: ID!) {
97 bulkOperationCancel(id: $id) {
98 bulkOperation {
99 id
100 status
101 errorCode
102 }
103 userErrors {
104 field
105 message
106 }
107 }
108 }
109 "#;
110
111 let variables = json!({ "id": id });
112
113 execute_graphql(ctx, query, variables).await
114}
115
116pub async fn get(ctx: &ServiceContext, id: &str) -> Result<GetBulkOperationResp, APIError> {
117 let query = format!(
118 r#"
119 query {{
120 node(id: "{}") {{
121 ... on BulkOperation {{
122 id
123 status
124 errorCode
125 createdAt
126 completedAt
127 objectCount
128 rootObjectCount
129 fileSize
130 url
131 partialDataUrl
132 query
133 type
134 }}
135 }}
136 }}
137 "#,
138 id
139 );
140
141 let variables = json!({});
142
143 execute_graphql(ctx, &query, variables).await
144}
145
146pub async fn list(
147 ctx: &ServiceContext,
148 params: &ListBulkOperationsParams,
149) -> Result<ListBulkOperationsResp, APIError> {
150 let mut args = Vec::new();
151
152 if let Some(first) = params.first {
153 args.push(format!("first: {}", first));
154 }
155 if let Some(after) = ¶ms.after {
156 args.push(format!("after: \"{}\"", after));
157 }
158 if let Some(last) = params.last {
159 args.push(format!("last: {}", last));
160 }
161 if let Some(before) = ¶ms.before {
162 args.push(format!("before: \"{}\"", before));
163 }
164 if let Some(reverse) = params.reverse {
165 args.push(format!("reverse: {}", reverse));
166 }
167 if let Some(sort_key) = ¶ms.sort_key {
168 let key_str = serde_json::to_string(sort_key).unwrap_or_default();
169 args.push(format!("sortKey: {}", key_str.trim_matches('"')));
170 }
171
172 let mut query_filters = Vec::new();
173 if let Some(status) = ¶ms.status {
174 let status_str = serde_json::to_string(status).unwrap_or_default();
175 query_filters.push(format!("status:{}", status_str.trim_matches('"')));
176 }
177 if let Some(op_type) = ¶ms.operation_type {
178 let type_str = serde_json::to_string(op_type).unwrap_or_default();
179 query_filters.push(format!("type:{}", type_str.trim_matches('"')));
180 }
181 if !query_filters.is_empty() {
182 args.push(format!("query: \"{}\"", query_filters.join(" ")));
183 }
184
185 let args_str = if args.is_empty() {
186 String::new()
187 } else {
188 format!("({})", args.join(", "))
189 };
190
191 let query = format!(
192 r#"
193 query {{
194 bulkOperations{} {{
195 nodes {{
196 id
197 status
198 errorCode
199 createdAt
200 completedAt
201 objectCount
202 rootObjectCount
203 fileSize
204 url
205 query
206 type
207 }}
208 pageInfo {{
209 hasNextPage
210 hasPreviousPage
211 startCursor
212 endCursor
213 }}
214 }}
215 }}
216 "#,
217 args_str
218 );
219
220 let variables = json!({});
221
222 execute_graphql(ctx, &query, variables).await
223}
224
225pub async fn create_staged_upload(
226 ctx: &ServiceContext,
227 input: &[StagedUploadInput],
228) -> Result<StagedUploadsCreateResp, APIError> {
229 let query = r#"
230 mutation stagedUploadsCreate($input: [StagedUploadInput!]!) {
231 stagedUploadsCreate(input: $input) {
232 stagedTargets {
233 url
234 resourceUrl
235 parameters {
236 name
237 value
238 }
239 }
240 userErrors {
241 field
242 message
243 }
244 }
245 }
246 "#;
247
248 let variables = json!({ "input": input });
249
250 execute_graphql(ctx, query, variables).await
251}
252
253fn build_export_query(resource: &str, query_body: &str, filter: Option<&str>) -> String {
258 let mut args = Vec::new();
259
260 if resource == "inventoryItems" {
261 args.push("first: 1".to_string());
262 }
263 if let Some(f) = filter {
264 args.push(format!("query: \"{}\"", f));
265 }
266
267 let args_str = if args.is_empty() {
268 String::new()
269 } else {
270 format!("({})", args.join(", "))
271 };
272
273 format!(
274 r#"{{
275 {}{} {{
276 edges {{
277 node {{
278 {}
279 }}
280 }}
281 }}
282}}"#,
283 resource, args_str, query_body
284 )
285}
286
287pub fn products_query(filter: Option<&str>) -> String {
288 let body = r#"id
289 title
290 handle
291 descriptionHtml
292 status
293 vendor
294 productType
295 tags
296 isGiftCard
297 hasOnlyDefaultVariant
298 templateSuffix
299 onlineStoreUrl
300 createdAt
301 updatedAt
302 publishedAt
303 seo {
304 title
305 description
306 }
307 priceRangeV2 {
308 minVariantPrice {
309 amount
310 currencyCode
311 }
312 maxVariantPrice {
313 amount
314 currencyCode
315 }
316 }
317 options {
318 id
319 name
320 values
321 }
322 category {
323 id
324 name
325 fullName
326 isLeaf
327 isRoot
328 isArchived
329 level
330 parentId
331 ancestorIds
332 childrenIds
333 }
334 featuredMedia {
335 preview {
336 image {
337 url
338 altText
339 width
340 height
341 }
342 }
343 }
344 variants {
345 edges {
346 node {
347 id
348 title
349 sku
350 price
351 compareAtPrice
352 barcode
353 position
354 inventoryQuantity
355 taxable
356 taxCode
357 availableForSale
358 selectedOptions {
359 name
360 value
361 }
362 inventoryItem {
363 id
364 tracked
365 requiresShipping
366 unitCost {
367 amount
368 currencyCode
369 }
370 }
371 image {
372 url
373 altText
374 width
375 height
376 }
377 }
378 }
379 }
380 media {
381 edges {
382 node {
383 mediaContentType
384 status
385 alt
386 preview {
387 image {
388 url
389 altText
390 width
391 height
392 }
393 }
394 ... on MediaImage {
395 id
396 mimeType
397 image {
398 url
399 altText
400 width
401 height
402 }
403 }
404 ... on Video {
405 id
406 filename
407 sources {
408 url
409 format
410 width
411 height
412 mimeType
413 }
414 }
415 ... on ExternalVideo {
416 id
417 embedUrl
418 host
419 }
420 ... on Model3d {
421 id
422 filename
423 originalSource {
424 url
425 format
426 filesize
427 }
428 }
429 }
430 }
431 }"#;
432
433 build_export_query("products", body, filter)
434}
435
436pub fn orders_query(filter: Option<&str>) -> String {
437 let body = r#"id
438 name
439 email
440 phone
441 note
442 tags
443 displayFinancialStatus
444 displayFulfillmentStatus
445 cancelledAt
446 cancelReason
447 closedAt
448 createdAt
449 updatedAt
450 processedAt
451 test
452 confirmed
453 taxesIncluded
454 currencyCode
455 presentmentCurrencyCode
456 subtotalPriceSet {
457 shopMoney { amount currencyCode }
458 presentmentMoney { amount currencyCode }
459 }
460 totalPriceSet {
461 shopMoney { amount currencyCode }
462 presentmentMoney { amount currencyCode }
463 }
464 totalDiscountsSet {
465 shopMoney { amount currencyCode }
466 presentmentMoney { amount currencyCode }
467 }
468 totalTaxSet {
469 shopMoney { amount currencyCode }
470 presentmentMoney { amount currencyCode }
471 }
472 totalShippingPriceSet {
473 shopMoney { amount currencyCode }
474 presentmentMoney { amount currencyCode }
475 }
476 totalRefundedSet {
477 shopMoney { amount currencyCode }
478 presentmentMoney { amount currencyCode }
479 }
480 totalWeight
481 customer {
482 id
483 email
484 firstName
485 lastName
486 }
487 shippingAddress {
488 address1 address2 city province provinceCode
489 country countryCodeV2 zip phone
490 firstName lastName company name
491 latitude longitude
492 }
493 billingAddress {
494 address1 address2 city province provinceCode
495 country countryCodeV2 zip phone
496 firstName lastName company name
497 latitude longitude
498 }
499 sourceName
500 fulfillable
501 requiresShipping
502 riskLevel
503 discountCodes
504 lineItems {
505 edges {
506 node {
507 id
508 title
509 name
510 sku
511 quantity
512 variantTitle
513 vendor
514 product { id }
515 variant { id }
516 originalUnitPriceSet {
517 shopMoney { amount currencyCode }
518 presentmentMoney { amount currencyCode }
519 }
520 discountedUnitPriceSet {
521 shopMoney { amount currencyCode }
522 presentmentMoney { amount currencyCode }
523 }
524 discountedTotalSet {
525 shopMoney { amount currencyCode }
526 presentmentMoney { amount currencyCode }
527 }
528 totalDiscountSet {
529 shopMoney { amount currencyCode }
530 presentmentMoney { amount currencyCode }
531 }
532 taxLines {
533 title
534 rate
535 ratePercentage
536 priceSet {
537 shopMoney { amount currencyCode }
538 presentmentMoney { amount currencyCode }
539 }
540 }
541 requiresShipping
542 taxable
543 fulfillableQuantity
544 fulfillmentStatus
545 customAttributes { key value }
546 duties {
547 id
548 harmonizedSystemCode
549 price {
550 shopMoney { amount currencyCode }
551 presentmentMoney { amount currencyCode }
552 }
553 taxLines {
554 title rate ratePercentage
555 priceSet {
556 shopMoney { amount currencyCode }
557 presentmentMoney { amount currencyCode }
558 }
559 }
560 }
561 }
562 }
563 }"#;
564
565 build_export_query("orders", body, filter)
566}
567
568pub fn collections_query(filter: Option<&str>) -> String {
569 let body = r#"id
570 title
571 handle
572 descriptionHtml
573 sortOrder
574 templateSuffix
575 productsCount {
576 count
577 precision
578 }
579 updatedAt
580 seo {
581 title
582 description
583 }
584 image {
585 url
586 altText
587 width
588 height
589 }
590 products {
591 edges {
592 node {
593 id
594 title
595 handle
596 status
597 vendor
598 productType
599 }
600 }
601 }"#;
602
603 build_export_query("collections", body, filter)
604}
605
606pub fn customers_query(filter: Option<&str>) -> String {
607 let body = r#"id
608 defaultEmailAddress {
609 emailAddress
610 }
611 firstName
612 lastName
613 displayName
614 defaultPhoneNumber {
615 phoneNumber
616 }
617 note
618 tags
619 state
620 taxExempt
621 verifiedEmail
622 locale
623 numberOfOrders
624 amountSpent {
625 amount
626 currencyCode
627 }
628 createdAt
629 updatedAt
630 defaultAddress {
631 address1 address2 city province provinceCode
632 country countryCodeV2 zip phone
633 firstName lastName company name
634 latitude longitude
635 }
636 image {
637 url
638 altText
639 width
640 height
641 }
642 addresses {
643 id
644 address1 address2 city province provinceCode
645 country countryCodeV2 zip phone
646 firstName lastName company name
647 }"#;
648
649 build_export_query("customers", body, filter)
650}
651
652pub fn inventory_items_query(filter: Option<&str>) -> String {
653 let body = r#"id
654 sku
655 tracked
656 requiresShipping
657 countryCodeOfOrigin
658 provinceCodeOfOrigin
659 harmonizedSystemCode
660 createdAt
661 updatedAt
662 unitCost {
663 amount
664 currencyCode
665 }
666 inventoryLevels {
667 edges {
668 node {
669 id
670 quantities(names: ["available"]) {
671 name
672 quantity
673 }
674 location {
675 id
676 name
677 }
678 updatedAt
679 }
680 }
681 }"#;
682
683 build_export_query("inventoryItems", body, filter)
684}
685
686pub fn draft_orders_query(filter: Option<&str>) -> String {
687 let body = r#"id
688 name
689 email
690 phone
691 note2
692 tags
693 status
694 currencyCode
695 taxExempt
696 taxesIncluded
697 createdAt
698 updatedAt
699 completedAt
700 invoiceSentAt
701 subtotalPriceSet {
702 shopMoney { amount currencyCode }
703 presentmentMoney { amount currencyCode }
704 }
705 totalPriceSet {
706 shopMoney { amount currencyCode }
707 presentmentMoney { amount currencyCode }
708 }
709 totalTaxSet {
710 shopMoney { amount currencyCode }
711 presentmentMoney { amount currencyCode }
712 }
713 totalDiscountsSet {
714 shopMoney { amount currencyCode }
715 presentmentMoney { amount currencyCode }
716 }
717 totalShippingPriceSet {
718 shopMoney { amount currencyCode }
719 presentmentMoney { amount currencyCode }
720 }
721 customer {
722 id
723 email
724 firstName
725 lastName
726 }
727 shippingAddress {
728 address1 address2 city province provinceCode
729 country countryCodeV2 zip phone
730 firstName lastName company name
731 latitude longitude
732 }
733 billingAddress {
734 address1 address2 city province provinceCode
735 country countryCodeV2 zip phone
736 firstName lastName company name
737 latitude longitude
738 }
739 order { id }
740 lineItems {
741 edges {
742 node {
743 id
744 title
745 name
746 sku
747 quantity
748 variantTitle
749 vendor
750 product { id }
751 variant { id }
752 originalUnitPriceSet {
753 shopMoney { amount currencyCode }
754 presentmentMoney { amount currencyCode }
755 }
756 discountedUnitPriceSet {
757 shopMoney { amount currencyCode }
758 presentmentMoney { amount currencyCode }
759 }
760 discountedTotalSet {
761 shopMoney { amount currencyCode }
762 presentmentMoney { amount currencyCode }
763 }
764 totalDiscountSet {
765 shopMoney { amount currencyCode }
766 presentmentMoney { amount currencyCode }
767 }
768 requiresShipping
769 taxable
770 customAttributes { key value }
771 }
772 }
773 }"#;
774
775 build_export_query("draftOrders", body, filter)
776}
777
778