an updated and hopefully faster version of the ST Toolbox
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

239 lines
7.2 KiB

package api
import (
"bytes"
"encoding/json"
"fmt"
"io"
"log"
"mime/multipart"
"net/http"
"strconv"
"strings"
)
// ServiceTrade attachment purpose constants with file type restrictions
const (
// AttachmentPurposeJobPaperwork - PDF documents accepted
AttachmentPurposeJobPaperwork = 1
// AttachmentPurposeJobVendorBill - PDF documents accepted
AttachmentPurposeJobVendorBill = 2
// AttachmentPurposeJobPicture - Only image files: *.gif, *.jpeg, *.jpg, *.png, *.tif, *.tiff
AttachmentPurposeJobPicture = 3
// AttachmentPurposeDeficiencyRepairProposal - PDF documents accepted
AttachmentPurposeDeficiencyRepairProposal = 5
// AttachmentPurposeGenericAttachment - PDF documents accepted
AttachmentPurposeGenericAttachment = 7
// AttachmentPurposeAvatarImage - Only image files: *.gif, *.jpeg, *.jpg, *.png, *.tif, *.tiff
AttachmentPurposeAvatarImage = 8
// AttachmentPurposeImport - Only text files: *.csv, *.txt
AttachmentPurposeImport = 9
// AttachmentPurposeBlankPaperwork - PDF documents accepted
AttachmentPurposeBlankPaperwork = 10
// AttachmentPurposeWorkAcknowledgement - Only JSON files: *.json
AttachmentPurposeWorkAcknowledgement = 11
// AttachmentPurposeLogo - Only image files: *.gif, *.jpeg, *.jpg, *.png, *.tif, *.tiff
AttachmentPurposeLogo = 12
// AttachmentPurposeJobInvoice - PDF documents accepted
AttachmentPurposeJobInvoice = 14
)
// UploadAttachment uploads a file as an attachment to a job
func (s *Session) UploadAttachment(jobID, filename, purpose string, fileContent []byte) (map[string]interface{}, error) {
url := fmt.Sprintf("%s/attachment", BaseURL)
// Create a buffer to hold the form data
var b bytes.Buffer
w := multipart.NewWriter(&b)
// Log received values
log.Printf("Uploading attachment to job ID: %s", jobID)
log.Printf("Filename: %s", filename)
log.Printf("Purpose value: '%s'", purpose)
log.Printf("File content length: %d bytes", len(fileContent))
// The ServiceTrade API expects the purpose ID as an integer
purposeStr := strings.TrimSpace(purpose)
// Try to parse the purpose as an integer, removing any leading zeros first
purposeStr = strings.TrimLeft(purposeStr, "0")
if purposeStr == "" {
purposeStr = "0" // If only zeros were provided
}
purposeInt, err := strconv.Atoi(purposeStr)
if err != nil {
return nil, fmt.Errorf("invalid purpose value '%s': must be a valid integer: %v", purpose, err)
}
log.Printf("Using purpose value: %d for job: %s", purposeInt, jobID)
// Add the purposeId (attachment type) as an integer
// NOTE: The API expects "purposeId", not "purpose"
if err := w.WriteField("purposeId", fmt.Sprintf("%d", purposeInt)); err != nil {
return nil, fmt.Errorf("error writing purposeId field: %v", err)
}
// Add the entityType (3 for Job) and entityId (jobID)
if err := w.WriteField("entityType", "3"); err != nil { // 3 = Job
return nil, fmt.Errorf("error writing entityType field: %v", err)
}
if err := w.WriteField("entityId", jobID); err != nil {
return nil, fmt.Errorf("error writing entityId field: %v", err)
}
// Ensure we have a file with content to upload
if len(fileContent) == 0 {
return nil, fmt.Errorf("no file content provided for upload")
}
// Add a description field with the filename for better identification
if err := w.WriteField("description", filename); err != nil {
return nil, fmt.Errorf("error writing description field: %v", err)
}
// Add the file - make sure we use the real filename with extension for content-type detection
// The API requires the file extension to determine the content type
if !strings.Contains(filename, ".") {
return nil, fmt.Errorf("filename must include an extension (e.g. .pdf, .docx) for API content type detection")
}
fw, err := w.CreateFormFile("uploadedFile", filename)
if err != nil {
return nil, fmt.Errorf("error creating form file: %v", err)
}
bytesWritten, err := io.Copy(fw, bytes.NewReader(fileContent))
if err != nil {
return nil, fmt.Errorf("error copying file content: %v", err)
}
log.Printf("Wrote %d bytes of file content to the form", bytesWritten)
// Close the writer
if err := w.Close(); err != nil {
return nil, fmt.Errorf("error closing multipart writer: %v", err)
}
// Create the request
req, err := http.NewRequest("POST", url, &b)
if err != nil {
return nil, fmt.Errorf("error creating request: %v", err)
}
// Set headers
req.Header.Set("Content-Type", w.FormDataContentType())
req.Header.Set("Cookie", s.Cookie)
// Debug information
log.Printf("Sending request to: %s", url)
log.Printf("Content-Type: %s", w.FormDataContentType())
log.Printf("Request body fields: purposeId=%d, entityType=3, entityId=%s, filename=%s", purposeInt, jobID, filename)
// Send the request
resp, err := s.Client.Do(req)
if err != nil {
return nil, fmt.Errorf("error sending request: %v", err)
}
defer resp.Body.Close()
// Read the response
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("error reading response body: %v", err)
}
// Check for errors
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated {
log.Printf("API error response: %s - %s", resp.Status, string(body))
return nil, fmt.Errorf("API returned error: %s - %s", resp.Status, string(body))
}
// Parse the response
var result map[string]interface{}
if err := json.Unmarshal(body, &result); err != nil {
return nil, fmt.Errorf("error parsing response: %v", err)
}
log.Printf("Successfully uploaded attachment %s to job %s", filename, jobID)
return result, nil
}
// GetAttachmentInfo gets information about a specific attachment
func (s *Session) GetAttachmentInfo(attachmentID string) (map[string]interface{}, error) {
url := fmt.Sprintf("%s/attachment/%s", BaseURL, attachmentID)
// Create the request
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, fmt.Errorf("error creating request: %v", err)
}
// Set headers
req.Header.Set("Cookie", s.Cookie)
// Send the request
resp, err := s.Client.Do(req)
if err != nil {
return nil, fmt.Errorf("error sending request: %v", err)
}
defer resp.Body.Close()
// Read the response
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("error reading response body: %v", err)
}
// Check for errors
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("API returned error: %s - %s", resp.Status, string(body))
}
// Parse the response
var result map[string]interface{}
if err := json.Unmarshal(body, &result); err != nil {
return nil, fmt.Errorf("error parsing response: %v", err)
}
return result, nil
}
// DeleteAttachment deletes an attachment
func (s *Session) DeleteAttachment(attachmentID string) error {
url := fmt.Sprintf("%s/attachment/%s", BaseURL, attachmentID)
// Create the request
req, err := http.NewRequest("DELETE", url, nil)
if err != nil {
return fmt.Errorf("error creating request: %v", err)
}
// Set headers
req.Header.Set("Cookie", s.Cookie)
// Send the request
resp, err := s.Client.Do(req)
if err != nil {
return fmt.Errorf("error sending request: %v", err)
}
defer resp.Body.Close()
// Check for errors
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusNoContent {
body, _ := io.ReadAll(resp.Body)
return fmt.Errorf("API returned error: %s - %s", resp.Status, string(body))
}
return nil
}