package lulu import ( "encoding/json" "fmt" "time" ) const ( MinProductionDelay = 60 * time.Minute MaxProductionDelay = 48 * time.Hour ) type printReq[P Printable | Reprintable] struct { Contact EmailAddress `json:"contact_email"` ExternalId string `json:"external_id"` LineItems []P `json:"line_items"` ProductionDelayMins uint `json:"production_delay"` ShipAddr ShippingAddress `json:"shipping_address"` ShipOpt ShippingLevel `json:"shipping_level"` } // PrintableId uniquely identifies a book to be printed: a "printable". A // printable consists of a cover and an interior file as well as a // PodPkgId which specifies the manufacturing options. type PrintableId string // Printable is a book to be printed. type Printable struct { // Arbitrary string to identify and connect a print job to your // systems. Set it to an order number, a purchase order or // whatever else works for your particular use case. ExternalId string `json:"external_id"` // Interior and cover files have to be specified with a URL from // which Lulu can download the files. Using encoded basic // authentication in the URL is ok. All files processed by Lulu // will be validated and normalized before sending them to // production. If problems with the file occur, the PrintJob will // be rejected or cancelled and an error message will be // displayed. CoverUrl string `json:"cover"` InteriorUrl string `json:"interior"` // Manufacturing options. Mfg PkgId `json:"pod_package_id"` // The number of copies to print. Quantity uint `json:"quantity"` // The title of the book. Must not exceed 255 bytes. Title string `json:"title"` } // Reprintable is a printable whose PrintableId is known from an prior // print job. type Reprintable struct { // Arbitrary string to identify and connect a print job to your // systems. Set it to an order number, a purchase order or // whatever else works for your particular use case. ExternalId string `json:"external_id"` // Uniquely identifies the printable. PrintableId PrintableId `json:"printable_id"` // The number of copies to print. Quantity uint `json:"quantity"` // The title of the book. Must not exceed 255 bytes. Title string `json:"title"` } type getPrintJobsResp struct { Count uint `json:"count"` Next string `json:"next"` Prev string `json:"previous"` Results []PrintJob `json:"results"` } type PrintJob struct { // An email address for questions regarding the print // job—normally, you want to use the email address of a // developer or shop owner, not the end customer. Contact EmailAddress `json:"contact_email"` Cost PrintJobCost `json:"costs"` Created time.Time `json:"date_created"` Modified time.Time `json:"date_modified"` EstimatedShippingDates EstimatedShippingDates `json:"estimated_shipping_dates"` // Arbitrary string to identify and connect a print job to your // systems. Set it to an order number, a purchase order or // whatever else works for your particular use case. ExternalId string `json:"external_id"` Id uint64 `json:"id"` LineItems []LineItem `json:"line_items"` OrderId string `json:"order_id"` // Delay before a newly created Print-Job is sent to production. // Minimum is 60 minutes, maximum is 2880 minutes (=48 hours). As // most cancellation requests occur right after an order has been // placed, it makes sense to wait for some time before sending an // order to production. Once production has started, orders // cannot be canceled anymore. ProductionDelay time.Duration // Target timestamp of when this job will move into production. ProductionDue time.Time `json:"production_due_time"` AddressValidation ShippingAddressValidation `json:"shipping_address"` // The shipping level that this Print-Job is shipped with. ShipOpt ShippingLevel `json:"shipping_level"` // ISO 3166-1 alpha-2 country code of the tax country determined for this job TaxCountry string `json:"tax_country"` Status PrintJobStatus `json:"status"` } func (pj *PrintJob) UnmarshalJSON(data []byte) error { type alias PrintJob // prevent infinite recursion if err := json.Unmarshal(data, (*alias)(pj)); err != nil { return err } var extra struct { ProductionDelay uint `json:"production_delay"` } if err := json.Unmarshal(data, &extra); err != nil { return fmt.Errorf("error unmarshaling %T.ProductionDelay: %w", *pj, err) } pj.ProductionDelay = time.Minute * time.Duration(extra.ProductionDelay) return nil } // LineItem represents a book that should be printed: a "printable" for short. type LineItem struct { // Arbitrary string to identify and connect a print job to your // systems. Set it to an order number, a purchase order or // whatever else works for your particular use case. ExternalId string `json:"external_id"` Id uint64 `json:"id"` NPages uint `json:"page_count"` // Manufacturing options. Mfg PkgId `json:"pod_package_id"` // Id of the printable. It can be used instead of PrintableNormalization. PrintableId PrintableId `json:"printable_id"` // Normalization process of the cover and interior source files. PrintableNormalization PrintableNormalization `json:"printable_normalization"` // Quantity of printed books. Quantity uint `json:"quantity"` Status LineItemStatus `json:"status"` // Title of the line item. Should be on the cover. Title string `json:"title"` // Tracking id for this line item's shipment. TrackingId string `json:"tracking_id"` // A list of tracking urls for this line item's shipment. TrackingUrls []string `json:"tracking_urls"` // Name of the carrier handling the shipment. Carrier string `json:"carrier_name"` } type EstimatedShippingDates struct { ArrivalMax Date `json:"arrival_max"` ArrivalMin Date `json:"arrival_min"` DispatchMax Date `json:"dispatch_max"` DispatchMin Date `json:"dispatch_min"` } // PrintableNormalization represents the normalization processes of the // interior and cover source files. type PrintableNormalization struct { Cover NormalizationJob `json:"cover"` Interior NormalizationJob `json:"interior"` } // NormalizationJob represents the normalization process of an interior // or cover source file. type NormalizationJob struct { JobId uint `json:"job_id"` NormalizedFile NormalizedFile `json:"normalized_file"` SrcMd5Sum string `json:"source_md5_sum"` // md5 hash of the source file. SrcUrl string `json:"source_url"` // URL of the source file. } // NormalizedFile represents a file on the server that was created by // normalizing a cover or interior source file. type NormalizedFile struct { Id uint64 `json:"file_id"` Name string `json:"filename"` } type LineItemStatus struct { Messages LineItemStatusMessages `json:"messages"` Status ItemStatus `json:"name"` } type LineItemStatusMessages struct { Delay time.Duration // Expected delay due to the error, if present. Error string `json:"error"` Info string `json:"info"` PrintableNormalization PrintableNormalization `json:"printable_normalization"` Timestamp time.Time `json:"timestamp"` // Timestamp of the last status change. TrackingUrls []string TrackingId string `json:"tracking_id"` // Tracking ID for this line item's shipment. Carrier string `json:"carrier_name"` // Name of the carrier handling the shipment. } func (msgs *LineItemStatusMessages) UnmarshalJSON(data []byte) error { type alias LineItemStatusMessages // prevent infinite recursion if err := json.Unmarshal(data, (*alias)(msgs)); err != nil { return err } var extra struct { Delay string `json:"delay"` TrackingUrls json.RawMessage `json:"tracking_urls"` } if err := json.Unmarshal(data, &extra); err != nil { return err } var err error if len(extra.Delay) > 0 { msgs.Delay, err = time.ParseDuration(fmt.Sprintf("%sh", extra.Delay)) if err != nil { return fmt.Errorf("error unmarshaling %T.Delay: %w", *msgs, err) } } if len(extra.TrackingUrls) > 0 { msgs.TrackingUrls, err = unmarshalSliceOrVal[string](extra.TrackingUrls) if err != nil { return fmt.Errorf("error unmarshaling %T.TrackingUrls: %w", *msgs, err) } } return nil } type PrintJobStatus struct { Changed time.Time `json:"changed"` // time of the last status change Msg string `json:"message"` Status OrderStatus `json:"name"` }