Skip to main content

resourcespace_client/api/
resource.rs

1use serde::Serialize;
2use serde_with::json::JsonString;
3use serde_with::{serde_as, skip_serializing_none};
4use std::collections::HashMap;
5
6use crate::client::Client;
7use crate::error::RsError;
8
9use super::{List, SortOrder};
10
11#[derive(Debug)]
12pub struct ResourceApi<'a> {
13    client: &'a Client,
14}
15
16/// Sub-API for resource endpoints.
17impl<'a> ResourceApi<'a> {
18    pub(crate) fn new(client: &'a Client) -> Self {
19        Self { client }
20    }
21
22    /// Add a new alternative file to a resource.
23    ///
24    /// ## Arguments
25    /// * `request` - Parameters built via [`AddAlternativeFileRequest`]
26    ///
27    /// ## Returns
28    ///
29    /// The ID of the new alternative file, or false on failure.
30    ///
31    /// ## TODO: Errors
32    ///
33    /// ## TODO: Examples
34    pub async fn add_alternative_file(
35        &self,
36        request: AddAlternativeFileRequest,
37    ) -> Result<serde_json::Value, RsError> {
38        self.client
39            .send_request("add_alternative_file", reqwest::Method::POST, request)
40            .await
41    }
42
43    /// Copy a resource. Note that attached files are not copied — this is a metadata
44    /// and property copy only.
45    ///
46    /// ## Arguments
47    /// * `request` - Parameters built via [`CopyResourceRequest`]
48    ///
49    /// ## Returns
50    ///
51    /// The ID of the newly created resource, or false if the operation failed.
52    ///
53    /// ## TODO: Errors
54    ///
55    /// ## TODO: Examples
56    pub async fn copy_resource(
57        &self,
58        request: CopyResourceRequest,
59    ) -> Result<serde_json::Value, RsError> {
60        self.client
61            .send_request("copy_resource", reqwest::Method::POST, request)
62            .await
63    }
64
65    /// Create a new resource.
66    ///
67    /// ## Arguments
68    /// * `request` - Parameters built via [`CreateResourceRequest`]
69    ///
70    /// ## Returns
71    ///
72    /// The ID of the newly created resource.
73    ///
74    /// ## TODO: Errors
75    ///
76    /// ## TODO: Examples
77    pub async fn create_resource(
78        &self,
79        request: CreateResourceRequest,
80    ) -> Result<serde_json::Value, RsError> {
81        self.client
82            .send_request("create_resource", reqwest::Method::POST, request)
83            .await
84    }
85
86    /// Deletes an alternative file.
87    ///
88    /// ## Arguments
89    /// * `request` - Parameters built via [`DeleteAlternativeFile`]
90    ///
91    /// ## Returns
92    ///
93    /// The success of the operation (true/false).
94    ///
95    /// ## TODO: Errors
96    ///
97    /// ## TODO: Examples
98    pub async fn delete_alternative_file(
99        &self,
100        request: DeleteAlternativeFile,
101    ) -> Result<serde_json::Value, RsError> {
102        self.client
103            .send_request("delete_alternative_file", reqwest::Method::POST, request)
104            .await
105    }
106
107    pub async fn delete_comment() -> Result<serde_json::Value, RsError> {
108        todo!("available from RS v11.0")
109    }
110
111    /// Delete a resource.
112    ///
113    /// ## Arguments
114    /// * `request` - Parameters built via [`DeleteResourceRequest`]
115    ///
116    /// ## Returns
117    ///
118    /// True or false depending on operation success.
119    ///
120    /// ## TODO: Errors
121    ///
122    /// ## TODO: Examples
123    pub async fn delete_resource(
124        &self,
125        request: DeleteResourceRequest,
126    ) -> Result<serde_json::Value, RsError> {
127        self.client
128            .send_request("delete_resource", reqwest::Method::POST, request)
129            .await
130    }
131
132    /// Returns a list of alternative files for a resource.
133    ///
134    /// ## Arguments
135    /// * `request` - Parameters built via [`GetAlternativeFilesRequest`]
136    ///
137    /// ## Returns
138    ///
139    /// A list of alternative files.
140    ///
141    /// ## TODO: Errors
142    ///
143    /// ## TODO: Examples
144    pub async fn get_alternative_files(
145        &self,
146        request: GetAlternativeFilesRequest,
147    ) -> Result<serde_json::Value, RsError> {
148        self.client
149            .send_request("get_alternative_files", reqwest::Method::GET, request)
150            .await
151    }
152
153    /// Check if the current user has edit access to a resource.
154    ///
155    /// ## Arguments
156    /// * `request` - Parameters built via [`GetEditAccessRequest`]
157    ///
158    /// ## Returns
159    ///
160    /// True if the user has edit access, false otherwise.
161    ///
162    /// ## TODO: Errors
163    ///
164    /// ## TODO: Examples
165    pub async fn get_edit_access(
166        &self,
167        request: GetEditAccessRequest,
168    ) -> Result<serde_json::Value, RsError> {
169        self.client
170            .send_request("get_edit_access", reqwest::Method::GET, request)
171            .await
172    }
173
174    /// Returns a list of resources related to a resource.
175    ///
176    /// ## Arguments
177    /// * `request` - Parameters built via [`GetRelatedResourcesRequest`]
178    ///
179    /// ## TODO: Errors
180    ///
181    /// ## TODO: Examples
182    pub async fn get_related_resources(
183        &self,
184        request: GetRelatedResourcesRequest,
185    ) -> Result<serde_json::Value, RsError> {
186        self.client
187            .send_request("get_related_resources", reqwest::Method::GET, request)
188            .await
189    }
190
191    /// Retrieves the access level for the current user for a specified resource.
192    ///
193    /// Returns 0 (full), 1 (restricted), 2 (confidential), 99 (not found), or false (invalid ID).
194    ///
195    /// ## Arguments
196    /// * `request` - Parameters built via [`GetResourceAccessRequest`]
197    ///
198    /// ## TODO: Errors
199    ///
200    /// ## TODO: Examples
201    pub async fn get_resource_access(
202        &self,
203        request: GetResourceAccessRequest,
204    ) -> Result<serde_json::Value, RsError> {
205        self.client
206            .send_request("get_resource_access", reqwest::Method::GET, request)
207            .await
208    }
209
210    /// Get all preview sizes available for a specific resource.
211    ///
212    /// Multi-page resources will include each page size in the response.
213    ///
214    /// ## Arguments
215    /// * `request` - Parameters built via [`GetResourceAllImageSizesRequest`]
216    ///
217    /// ## Returns
218    ///
219    /// JSON containing the resource's available sizes.
220    ///
221    /// ## TODO: Errors
222    ///
223    /// ## TODO: Examples
224    pub async fn get_resource_all_image_sizes(
225        &self,
226        request: GetResourceAllImageSizesRequest,
227    ) -> Result<serde_json::Value, RsError> {
228        self.client
229            .send_request(
230                "get_resource_all_image_sizes",
231                reqwest::Method::GET,
232                request,
233            )
234            .await
235    }
236
237    /// Retrieve comments for a resource.
238    ///
239    /// Available from **RS 11.0+**.
240    ///
241    /// ## Arguments
242    /// * `request` - Parameters built via [`GetResourceCommentsRequest`]
243    ///
244    /// ## Returns
245    ///
246    /// Array of comments in tree view by default, or flat list if requested. Returns an empty
247    /// array if the user lacks permission or commenting is disabled.
248    ///
249    /// ## TODO: Errors
250    ///
251    /// ## TODO: Examples
252    pub async fn get_resource_comments(
253        &self,
254        _request: GetResourceCommentsRequest,
255    ) -> Result<serde_json::Value, RsError> {
256        todo!("available from RS v11.0");
257        // self.client
258        //     .send_request("get_resource_comments", reqwest::Method::GET, request)
259        //     .await
260    }
261
262    /// Returns the top level property data for a resource, including truncated summary metadata.
263    ///
264    /// For full non-truncated metadata use [`get_resource_field_data`](Self::get_resource_field_data).
265    ///
266    /// ## Arguments
267    /// * `request` - Parameters built via [`GetResourceDataRequest`]
268    ///
269    /// ## Returns
270    ///
271    /// The resource properties. Actual values depend on system configuration.
272    ///
273    /// ## TODO: Errors
274    ///
275    /// ## TODO: Examples
276    pub async fn get_resource_data(
277        &self,
278        request: GetResourceDataRequest,
279    ) -> Result<serde_json::Value, RsError> {
280        self.client
281            .send_request("get_resource_data", reqwest::Method::GET, request)
282            .await
283    }
284
285    /// Return all field data for a given resource.
286    ///
287    /// ## Arguments
288    /// * `request` - Parameters built via [`GetResourceFieldDataRequest`]
289    ///
290    /// ## Returns
291    ///
292    /// JSON containing the resource metadata.
293    ///
294    /// ## TODO: Errors
295    ///
296    /// ## TODO: Examples
297    pub async fn get_resource_field_data(
298        &self,
299        request: GetResourceFieldDataRequest,
300    ) -> Result<serde_json::Value, RsError> {
301        self.client
302            .send_request("get_resource_field_data", reqwest::Method::GET, request)
303            .await
304    }
305
306    /// Returns the full log for a resource.
307    ///
308    /// ## Arguments
309    /// * `request` - Parameters built via [`GetResourceLogRequest`]
310    ///
311    /// ## Returns
312    ///
313    /// The resource log entries.
314    ///
315    /// ## TODO: Errors
316    ///
317    /// ## TODO: Examples
318    pub async fn get_resource_log(
319        &self,
320        request: GetResourceLogRequest,
321    ) -> Result<serde_json::Value, RsError> {
322        self.client
323            .send_request("get_resource_log", reqwest::Method::GET, request)
324            .await
325    }
326
327    /// Returns a temporary URL for downloading a resource file.
328    ///
329    /// The URL is valid for 24 hours by default (configurable via `$api_resource_path_expiry_hours`).
330    ///
331    /// ## Arguments
332    /// * `request` - Parameters built via [`GetResourcePathRequest`]
333    ///
334    /// ## Returns
335    ///
336    /// A temporary URL for the requested resource file, or false on failure.
337    ///
338    /// ## TODO: Errors
339    ///
340    /// ## TODO: Examples
341    pub async fn get_resource_path(
342        &self,
343        request: GetResourcePathRequest,
344    ) -> Result<serde_json::Value, RsError> {
345        self.client
346            .send_request("get_resource_path", reqwest::Method::GET, request)
347            .await
348    }
349
350    /// Returns all configured resource types available to the current user.
351    ///
352    /// From RS v10.2, the associated resource type field IDs are also returned.
353    ///
354    /// ## TODO: Errors
355    ///
356    /// ## TODO: Examples
357    pub async fn get_resource_types(&self) -> Result<serde_json::Value, RsError> {
358        self.client
359            .send_request("get_resource_types", reqwest::Method::GET, ())
360            .await
361    }
362
363    /// Update resource properties (not metadata fields).
364    ///
365    /// Valid columns: `resource_type`, `creation_date`, `rating`, `user_rating`, `archive`,
366    /// `access`, `created_by`, `mapzoom`, `modified`, `geo_lat`, `geo_long`.
367    ///
368    /// ## Arguments
369    /// * `request` - Parameters built via [`PutResourceDataRequest`]
370    ///
371    /// ## TODO: Errors
372    ///
373    /// ## TODO: Examples
374    pub async fn put_resource_data(
375        &self,
376        request: PutResourceDataRequest,
377    ) -> Result<serde_json::Value, RsError> {
378        self.client
379            .send_request("put_resource_data", reqwest::Method::POST, request)
380            .await
381    }
382
383    /// Relate all the provided resources with each other.
384    ///
385    /// ## Arguments
386    /// * `request` - Parameters built via [`RelateAllResourcesRequest`]
387    ///
388    /// ## Returns
389    ///
390    /// True or false depending on operation success.
391    ///
392    /// ## TODO: Errors
393    ///
394    /// ## TODO: Examples
395    pub async fn relate_all_resources(
396        &self,
397        request: RelateAllResourcesRequest,
398    ) -> Result<serde_json::Value, RsError> {
399        self.client
400            .send_request("relate_all_resources", reqwest::Method::POST, request)
401            .await
402    }
403
404    /// Replaces the primary resource file for a given resource.
405    ///
406    /// The file location must be accessible without authentication from the RS server —
407    /// either a local path or a publicly accessible URL.
408    ///
409    /// ## Arguments
410    /// * `request` - Parameters built via [`ReplaceResourceFileRequest`]
411    ///
412    /// ## Returns
413    ///
414    /// A JSON encoded array with `Status` (SUCCESS/FAILED) and `Message`.
415    ///
416    /// ## TODO: Errors
417    ///
418    /// ## TODO: Examples
419    pub async fn replace_resource_file(
420        &self,
421        request: ReplaceResourceFileRequest,
422    ) -> Result<serde_json::Value, RsError> {
423        self.client
424            .send_request("replace_resource_file", reqwest::Method::POST, request)
425            .await
426    }
427
428    /// Check if a resource file is read-only due to filestore template threshold configuration.
429    ///
430    /// Available from **RS 10.5+**.
431    ///
432    /// ## Arguments
433    /// * `request` - Parameters built via [`ResourceFileReadonlyRequest`]
434    ///
435    /// ## Returns
436    ///
437    /// A 200 HTTP status will be returned with a payload detailing if successful or a 400 status otherwise.
438    ///
439    /// ## TODO: Errors
440    ///
441    /// ## TODO: Examples
442    pub async fn resource_file_readonly(
443        &self,
444        request: ResourceFileReadonlyRequest,
445    ) -> Result<serde_json::Value, RsError> {
446        self.client
447            .send_request("resource_file_readonly", reqwest::Method::GET, request)
448            .await
449    }
450
451    /// Retrieve recent entries from the resource log
452    ///
453    /// ## Arguments
454    /// * `request` - Parameters built via [`ResourceLogLastRowsRequest`]
455    ///
456    /// ## Returns
457    ///
458    /// Log entries in JSON format, including date, ref, resource, type (type of log entry), resource_type_field, user ID, notes, diff and usageoption value
459    ///
460    /// ## TODO: Errors
461    ///
462    /// ## TODO: Examples
463    pub async fn resource_log_last_rows(
464        &self,
465        request: ResourceLogLastRowsRequest,
466    ) -> Result<serde_json::Value, RsError> {
467        self.client
468            .send_request("resource_log_last_rows", reqwest::Method::GET, request)
469            .await
470    }
471
472    /// Uploads a file from a local server path to an existing resource.
473    ///
474    /// The path must be local to the RS server. See `$valid_upload_paths` in RS config
475    /// if using a custom upload path.
476    ///
477    /// ## Arguments
478    /// * `request` - Parameters built via [`UploadFileRequest`]
479    ///
480    /// ## Returns
481    ///
482    /// True or false depending on operation success.
483    ///
484    /// ## TODO: Errors
485    ///
486    /// ## TODO: Examples
487    pub async fn upload_file(
488        &self,
489        request: UploadFileRequest,
490    ) -> Result<serde_json::Value, RsError> {
491        self.client
492            .send_request("upload_file", reqwest::Method::POST, request)
493            .await
494    }
495
496    /// Uploads a remote file to an existing resource by URL.
497    ///
498    /// RS fetches the file server-side. The URL hostname must be allowed via
499    /// `$api_upload_urls` in RS config.
500    ///
501    /// ## Arguments
502    /// * `request` - Parameters built via [`UploadFileByUrlRequest`]
503    ///
504    /// ## Returns
505    ///
506    /// True or false depending on operation success.
507    ///
508    /// ## TODO: Errors
509    ///
510    /// ## TODO: Examples
511    pub async fn upload_file_by_url(
512        &self,
513        request: UploadFileByUrlRequest,
514    ) -> Result<serde_json::Value, RsError> {
515        self.client
516            .send_request("upload_file_by_url", reqwest::Method::POST, request)
517            .await
518    }
519
520    /// Uploads files using HTTP multipart to an existing resource, replacing any file that is already attached.
521    ///
522    /// ## Arguments
523    /// * `request` - Parameters built via [`UploadMultipartRequest`]
524    ///
525    /// ## Returns
526    ///
527    /// 204 if succesful other status codes (413, 400, 500) if not
528    ///
529    /// ## TODO: Errors
530    ///
531    /// ## TODO: Examples
532    pub async fn upload_multipart(
533        &self,
534        request: UploadMultipartRequest,
535        file: impl AsRef<std::path::Path>,
536    ) -> Result<serde_json::Value, RsError> {
537        self.client
538            .send_multipart_request("upload_multipart", request, file.as_ref())
539            .await
540    }
541
542    /// Add or remove resource relationships.
543    ///
544    /// ## Arguments
545    /// * `request` - Parameters built via [`UpdateRelatedResourceRequest`]
546    ///
547    /// ## Returns
548    ///
549    /// True or false depending on operation success.
550    ///
551    /// ## TODO: Errors
552    ///
553    /// ## TODO: Examples
554    pub async fn update_related_resource(
555        &self,
556        request: UpdateRelatedResourceRequest,
557    ) -> Result<serde_json::Value, RsError> {
558        self.client
559            .send_request("update_related_resource", reqwest::Method::POST, request)
560            .await
561    }
562
563    /// Change the resource type of a resource.
564    ///
565    /// ## Arguments
566    /// * `request` - Parameters built via [`UpdateResourceTypeRequest`]
567    ///
568    /// ## Returns
569    ///
570    /// True or false depending on operation success.
571    ///
572    /// ## TODO: Errors
573    ///
574    /// ## TODO: Examples
575    pub async fn update_resource_type(
576        &self,
577        request: UpdateResourceTypeRequest,
578    ) -> Result<serde_json::Value, RsError> {
579        self.client
580            .send_request("update_resource_type", reqwest::Method::POST, request)
581            .await
582    }
583
584    /// Retrieves a list of collections that a resource is used in for the specified resource reference.
585    ///
586    /// ## Arguments
587    /// * `request` - Parameters built via [`GetResourceCollectionsRequest`]
588    ///
589    /// ## Returns
590    ///
591    /// Array of collections with the collection ID, name and description. False on failure.
592    ///
593    /// ## TODO: Errors
594    ///
595    /// ## TODO: Examples
596    pub async fn get_resource_collections(
597        &self,
598        request: GetResourceCollectionsRequest,
599    ) -> Result<serde_json::Value, RsError> {
600        self.client
601            .send_request("get_resource_collections", reqwest::Method::GET, request)
602            .await
603    }
604
605    /// Validate a URL supplied in the create_resource or upload_file_by_url API calls.
606    ///
607    /// Requires the URL hostname to be added in the config option $api_upload_urls, for example:
608    /// `$api_upload_urls = array('resourcespace.com', 'localhost');`
609    ///
610    /// ## Arguments
611    /// * `request` - Parameters built via [`ValidateUploadUrlRequest`]
612    ///
613    /// ## Returns
614    ///
615    /// Returns true if a valid URL is found, false otherwise.
616    ///
617    /// ## TODO: Errors
618    ///
619    /// ## TODO: Examples
620    pub async fn validate_upload_url(
621        &self,
622        request: ValidateUploadUrlRequest,
623    ) -> Result<serde_json::Value, RsError> {
624        self.client
625            .send_request("validate_upload_url", reqwest::Method::GET, request)
626            .await
627    }
628}
629
630#[skip_serializing_none]
631#[derive(Clone, Debug, PartialEq, Serialize)]
632pub struct AddAlternativeFileRequest {
633    /// The ID of the resource to attach the alternative file to.
634    pub resource: u32,
635    /// Display name for the alternative file.
636    pub name: String,
637    /// Optional description of the alternative file.
638    pub description: Option<String>,
639    /// Original file name of the alternative file.
640    pub file_name: Option<String>,
641    /// File extension of the alternative file (e.g. `"pdf"`).
642    pub file_extension: Option<String>,
643    /// Size of the file in bytes.
644    pub file_size: Option<u64>,
645    /// Alternative file type identifier used to categorise the file.
646    pub alt_type: Option<String>,
647    /// Local server path or publicly accessible URL of the file to attach.
648    pub file: Option<String>,
649}
650
651impl AddAlternativeFileRequest {
652    pub fn new(resource: u32, name: impl Into<String>) -> Self {
653        Self {
654            resource,
655            name: name.into(),
656            description: None,
657            file_name: None,
658            file_extension: None,
659            file_size: None,
660            alt_type: None,
661            file: None,
662        }
663    }
664
665    pub fn description(mut self, description: impl Into<String>) -> Self {
666        self.description = Some(description.into());
667        self
668    }
669
670    pub fn file_name(mut self, file_name: impl Into<String>) -> Self {
671        self.file_name = Some(file_name.into());
672        self
673    }
674
675    pub fn file_extension(mut self, file_extension: impl Into<String>) -> Self {
676        self.file_extension = Some(file_extension.into());
677        self
678    }
679
680    pub fn file_size(mut self, file_size: u64) -> Self {
681        self.file_size = Some(file_size);
682        self
683    }
684
685    pub fn alt_type(mut self, alt_type: impl Into<String>) -> Self {
686        self.alt_type = Some(alt_type.into());
687        self
688    }
689
690    pub fn file(mut self, file: impl Into<String>) -> Self {
691        self.file = Some(file.into());
692        self
693    }
694}
695
696#[skip_serializing_none]
697#[derive(Clone, Debug, PartialEq, Serialize)]
698pub struct CopyResourceRequest {
699    /// The ID of the resource to copy.
700    pub from: u32,
701    /// Resource type ID to assign to the copy; defaults to the source resource type if omitted.
702    pub resource_type: Option<u32>,
703}
704
705impl CopyResourceRequest {
706    pub fn new(from: u32) -> Self {
707        Self {
708            from,
709            resource_type: None,
710        }
711    }
712
713    pub fn resource_type(mut self, resource_type: u32) -> Self {
714        self.resource_type = Some(resource_type);
715        self
716    }
717}
718
719#[serde_as]
720#[skip_serializing_none]
721#[derive(Clone, Debug, PartialEq, Serialize)]
722pub struct CreateResourceRequest {
723    /// The resource type ID for the new resource.
724    pub resource_type: u32,
725    /// Initial archive state: 0 = live, 1 = archived, 2 = deleted.
726    pub archive: Option<i16>,
727    /// URL of a remote file to attach to the resource at creation time.
728    pub url: Option<String>,
729    /// If 1, skips reading EXIF data from the attached file.
730    pub no_exif: Option<u8>,
731    /// If 1, automatically rotates the image based on EXIF orientation.
732    pub autorotate: Option<u8>,
733    /// JSON-encoded metadata fields to set on the resource at creation time.
734    #[serde_as(as = "JsonString")]
735    pub metadata: Option<HashMap<u32, String>>,
736}
737
738impl CreateResourceRequest {
739    pub fn new(resource_type: u32) -> Self {
740        Self {
741            resource_type,
742            archive: None,
743            url: None,
744            no_exif: None,
745            autorotate: None,
746            metadata: None,
747        }
748    }
749
750    pub fn archive(mut self, archive: i16) -> Self {
751        self.archive = Some(archive);
752        self
753    }
754
755    pub fn url(mut self, url: impl Into<String>) -> Self {
756        self.url = Some(url.into());
757        self
758    }
759
760    pub fn no_exif(mut self, no_exif: bool) -> Self {
761        self.no_exif = Some(no_exif as u8);
762        self
763    }
764
765    pub fn autorotate(mut self, autorotate: bool) -> Self {
766        self.autorotate = Some(autorotate as u8);
767        self
768    }
769
770    pub fn metadata(mut self, metadata: HashMap<u32, String>) -> Self {
771        self.metadata = Some(metadata);
772        self
773    }
774}
775
776#[derive(Clone, Debug, PartialEq, Serialize)]
777pub struct DeleteAlternativeFile {
778    /// The ID of the resource the alternative file belongs to.
779    pub resource: u32,
780    /// The ID of the alternative file to delete.
781    #[serde(rename = "ref")]
782    pub r#ref: u32,
783}
784
785impl DeleteAlternativeFile {
786    pub fn new(resource: u32, r#ref: u32) -> Self {
787        Self { resource, r#ref }
788    }
789}
790
791#[derive(Clone, Debug, PartialEq, Serialize)]
792pub struct DeleteResourceRequest {
793    /// The ID of the resource to delete.
794    pub resource: u32,
795}
796
797impl DeleteResourceRequest {
798    pub fn new(resource: u32) -> Self {
799        Self { resource }
800    }
801}
802
803#[skip_serializing_none]
804#[derive(Clone, Debug, PartialEq, Serialize)]
805pub struct GetAlternativeFilesRequest {
806    /// The ID of the resource whose alternative files should be returned.
807    pub resource: u32,
808    /// Field name to order the alternative files by.
809    pub orderby: Option<String>,
810    /// Sort direction for the results.
811    pub sort: Option<SortOrder>,
812    /// Filter results to only alternative files of this type.
813    pub r#type: Option<String>,
814}
815
816impl GetAlternativeFilesRequest {
817    pub fn new(resource: u32) -> Self {
818        Self {
819            resource,
820            orderby: None,
821            sort: None,
822            r#type: None,
823        }
824    }
825
826    pub fn orderby(mut self, orderby: impl Into<String>) -> Self {
827        self.orderby = Some(orderby.into());
828        self
829    }
830
831    pub fn sort(mut self, sort: SortOrder) -> Self {
832        self.sort = Some(sort);
833        self
834    }
835
836    pub fn r#type(mut self, r#type: impl Into<String>) -> Self {
837        self.r#type = Some(r#type.into());
838        self
839    }
840}
841
842#[derive(Clone, Debug, PartialEq, Serialize)]
843pub struct GetEditAccessRequest {
844    /// The ID of the resource to check edit access for.
845    pub resource: u32,
846}
847
848impl GetEditAccessRequest {
849    pub fn new(resource: u32) -> Self {
850        Self { resource }
851    }
852}
853
854#[derive(Clone, Debug, PartialEq, Serialize)]
855pub struct GetRelatedResourcesRequest {
856    /// The ID of the resource whose related resources should be returned.
857    #[serde(rename = "ref")]
858    pub r#ref: u32,
859}
860
861impl GetRelatedResourcesRequest {
862    pub fn new(r#ref: u32) -> Self {
863        Self { r#ref }
864    }
865}
866
867#[derive(Clone, Debug, PartialEq, Serialize)]
868pub struct GetResourceAccessRequest {
869    /// The ID of the resource to retrieve the access level for.
870    pub resource: u32,
871}
872
873impl GetResourceAccessRequest {
874    pub fn new(resource: u32) -> Self {
875        Self { resource }
876    }
877}
878
879#[derive(Clone, Debug, PartialEq, Serialize)]
880pub struct GetResourceAllImageSizesRequest {
881    /// The ID of the resource to retrieve available preview sizes for.
882    pub resource: u32,
883}
884
885impl GetResourceAllImageSizesRequest {
886    pub fn new(resource: u32) -> Self {
887        Self { resource }
888    }
889}
890
891#[skip_serializing_none]
892#[derive(Clone, Debug, PartialEq, Serialize)]
893pub struct GetResourceCommentsRequest {
894    /// The ID of the resource to retrieve comments for.
895    pub resource_ref: u32,
896    /// If set, returns comments as a flat list rather than a threaded tree.
897    pub flat_view: Option<bool>,
898}
899
900impl GetResourceCommentsRequest {
901    pub fn new(resource_ref: u32) -> Self {
902        Self {
903            resource_ref,
904            flat_view: None,
905        }
906    }
907
908    pub fn flat_view(mut self, flat_view: bool) -> Self {
909        self.flat_view = Some(flat_view);
910        self
911    }
912}
913
914#[derive(Clone, Debug, PartialEq, Serialize)]
915pub struct GetResourceDataRequest {
916    /// The ID of the resource to retrieve top-level property data for.
917    pub resource: u32,
918}
919
920impl GetResourceDataRequest {
921    pub fn new(resource: u32) -> Self {
922        Self { resource }
923    }
924}
925
926#[derive(Clone, Debug, PartialEq, Serialize)]
927pub struct GetResourceFieldDataRequest {
928    /// The ID of the resource to retrieve full metadata field data for.
929    pub resource: u32,
930}
931
932impl GetResourceFieldDataRequest {
933    pub fn new(resource: u32) -> Self {
934        Self { resource }
935    }
936}
937
938#[skip_serializing_none]
939#[derive(Clone, Debug, PartialEq, Serialize)]
940pub struct GetResourceLogRequest {
941    /// The ID of the resource whose log entries should be returned.
942    pub resource: u32,
943    /// Maximum number of log rows to return.
944    pub fetchrows: Option<u32>,
945}
946
947impl GetResourceLogRequest {
948    pub fn new(resource: u32) -> Self {
949        Self {
950            resource,
951            fetchrows: None,
952        }
953    }
954
955    pub fn fetchrows(mut self, fetchrows: u32) -> Self {
956        self.fetchrows = Some(fetchrows);
957        self
958    }
959}
960
961#[skip_serializing_none]
962#[derive(Clone, Debug, PartialEq, Serialize)]
963pub struct GetResourcePathRequest {
964    /// The ID of the resource to generate a download URL for.
965    #[serde(rename = "ref")]
966    pub r#ref: u32,
967    /// Preview size to retrieve (e.g. `"thm"`, `"scr"`, `"pre"`). Omit for the original file.
968    pub size: Option<String>,
969    /// If 1, generates the preview if it does not yet exist.
970    pub generate: Option<u8>,
971    /// Override the file extension of the returned URL.
972    pub extension: Option<String>,
973    /// Page number for multi-page resources (e.g. PDF).
974    pub page: Option<u32>,
975    /// If 1, returns a URL to the watermarked version of the file.
976    pub watermarked: Option<u8>,
977    /// ID of the alternative file to return a URL for, or -1 for the original.
978    pub alternative: Option<i32>,
979    /// If set, writes embedded metadata into the file before returning the URL.
980    pub write_metadata: Option<bool>,
981}
982
983impl GetResourcePathRequest {
984    pub fn new(r#ref: u32) -> Self {
985        Self {
986            r#ref,
987            size: None,
988            generate: None,
989            extension: None,
990            page: None,
991            watermarked: None,
992            alternative: None,
993            write_metadata: None,
994        }
995    }
996
997    pub fn size(mut self, size: impl Into<String>) -> Self {
998        self.size = Some(size.into());
999        self
1000    }
1001
1002    pub fn generate(mut self, generate: bool) -> Self {
1003        self.generate = Some(generate as u8);
1004        self
1005    }
1006
1007    pub fn extension(mut self, extension: impl Into<String>) -> Self {
1008        self.extension = Some(extension.into());
1009        self
1010    }
1011
1012    pub fn page(mut self, page: u32) -> Self {
1013        self.page = Some(page);
1014        self
1015    }
1016
1017    pub fn watermarked(mut self, watermarked: bool) -> Self {
1018        self.watermarked = Some(watermarked as u8);
1019        self
1020    }
1021
1022    pub fn alternative(mut self, alternative: i32) -> Self {
1023        self.alternative = Some(alternative);
1024        self
1025    }
1026
1027    pub fn write_metadata(mut self, write_metadata: bool) -> Self {
1028        self.write_metadata = Some(write_metadata);
1029        self
1030    }
1031}
1032
1033#[serde_as]
1034#[derive(Clone, Debug, PartialEq, Serialize)]
1035pub struct PutResourceDataRequest {
1036    /// The ID of the resource to update.
1037    pub resource: u32,
1038    /// JSON-encoded object mapping column names to new values. For valid columns/values view API docs.
1039    #[serde_as(as = "JsonString")]
1040    pub data: HashMap<String, String>,
1041}
1042
1043impl PutResourceDataRequest {
1044    pub fn new(resource: u32, data: HashMap<String, String>) -> Self {
1045        Self { resource, data }
1046    }
1047}
1048
1049#[derive(Clone, Debug, PartialEq, Serialize)]
1050pub struct RelateAllResourcesRequest {
1051    /// Comma-separated list of resource IDs to relate with each other.
1052    pub related: List<u32>,
1053}
1054
1055impl RelateAllResourcesRequest {
1056    pub fn new(related: impl Into<List<u32>>) -> Self {
1057        Self {
1058            related: related.into(),
1059        }
1060    }
1061}
1062
1063#[skip_serializing_none]
1064#[derive(Clone, Debug, PartialEq, Serialize)]
1065pub struct ReplaceResourceFileRequest {
1066    /// The ID of the resource whose file should be replaced.
1067    pub resource: u32,
1068    /// Local server path or publicly accessible URL of the replacement file.
1069    pub file_location: String,
1070    /// If 1, skips reading EXIF data from the replacement file.
1071    pub no_exif: Option<u8>,
1072    /// If 1, automatically rotates the image based on EXIF orientation.
1073    pub autorotate: Option<u8>,
1074    /// If 1, retains the previous file as an alternative file rather than deleting it.
1075    pub keep_original: Option<u8>,
1076}
1077
1078impl ReplaceResourceFileRequest {
1079    pub fn new(resource: u32, file_location: impl Into<String>) -> Self {
1080        Self {
1081            resource,
1082            file_location: file_location.into(),
1083            no_exif: None,
1084            autorotate: None,
1085            keep_original: None,
1086        }
1087    }
1088
1089    pub fn no_exif(mut self, no_exif: bool) -> Self {
1090        self.no_exif = Some(no_exif as u8);
1091        self
1092    }
1093
1094    pub fn autorotate(mut self, autorotate: bool) -> Self {
1095        self.autorotate = Some(autorotate as u8);
1096        self
1097    }
1098
1099    pub fn keep_original(mut self, keep_original: bool) -> Self {
1100        self.keep_original = Some(keep_original as u8);
1101        self
1102    }
1103}
1104
1105#[derive(Clone, Debug, PartialEq, Serialize)]
1106pub struct ResourceFileReadonlyRequest {
1107    /// The ID of the resource to check for read-only file status.
1108    #[serde(rename = "ref")]
1109    pub r#ref: u32,
1110}
1111
1112impl ResourceFileReadonlyRequest {
1113    pub fn new(r#ref: u32) -> Self {
1114        Self { r#ref }
1115    }
1116}
1117
1118#[skip_serializing_none]
1119#[derive(Clone, Debug, Default, PartialEq, Serialize)]
1120pub struct ResourceLogLastRowsRequest {
1121    /// Only return log entries with a ref greater than this value.
1122    pub minref: Option<u32>,
1123    /// Only return log entries from the last N days.
1124    pub days: Option<u32>,
1125    /// Maximum number of log entries to return.
1126    pub maxrecords: Option<u32>,
1127    /// Comma-separated list of field IDs to limit results to.
1128    pub field: Option<List<u32>>,
1129    /// Comma-separated list of log codes to limit results to (e.g. `"FD"` for field data changes).
1130    pub log_code: Option<List<String>>,
1131}
1132
1133impl ResourceLogLastRowsRequest {
1134    pub fn new() -> Self {
1135        Self::default()
1136    }
1137
1138    pub fn minref(mut self, minref: u32) -> Self {
1139        self.minref = Some(minref);
1140        self
1141    }
1142
1143    pub fn days(mut self, days: u32) -> Self {
1144        self.days = Some(days);
1145        self
1146    }
1147
1148    pub fn maxrecords(mut self, maxrecords: u32) -> Self {
1149        self.maxrecords = Some(maxrecords);
1150        self
1151    }
1152
1153    pub fn field(mut self, field: impl Into<List<u32>>) -> Self {
1154        self.field = Some(field.into());
1155        self
1156    }
1157
1158    pub fn log_code(mut self, log_code: impl Into<List<String>>) -> Self {
1159        self.log_code = Some(log_code.into());
1160        self
1161    }
1162}
1163
1164#[skip_serializing_none]
1165#[derive(Clone, Debug, PartialEq, Serialize)]
1166pub struct UploadFileRequest {
1167    /// The ID of the resource to upload the file to.
1168    #[serde(rename = "ref")]
1169    pub r#ref: u32,
1170    /// If 1, skips reading EXIF data from the uploaded file.
1171    pub no_exif: Option<u8>,
1172    /// If 1, reverts to the original file instead of uploading a new one.
1173    pub revert: Option<u8>,
1174    /// If 1, automatically rotates the image based on EXIF orientation.
1175    pub autorotate: Option<u8>,
1176    /// Local server path of the file to upload (must be within `$valid_upload_paths`).
1177    pub file_path: Option<String>,
1178}
1179
1180impl UploadFileRequest {
1181    pub fn new(r#ref: u32) -> Self {
1182        Self {
1183            r#ref,
1184            no_exif: None,
1185            revert: None,
1186            autorotate: None,
1187            file_path: None,
1188        }
1189    }
1190
1191    pub fn no_exif(mut self, no_exif: bool) -> Self {
1192        self.no_exif = Some(no_exif as u8);
1193        self
1194    }
1195
1196    pub fn revert(mut self, revert: bool) -> Self {
1197        self.revert = Some(revert as u8);
1198        self
1199    }
1200
1201    pub fn autorotate(mut self, autorotate: bool) -> Self {
1202        self.autorotate = Some(autorotate as u8);
1203        self
1204    }
1205
1206    pub fn file_path(mut self, file_path: impl Into<String>) -> Self {
1207        self.file_path = Some(file_path.into());
1208        self
1209    }
1210}
1211
1212#[skip_serializing_none]
1213#[derive(Clone, Debug, PartialEq, Serialize)]
1214pub struct UploadFileByUrlRequest {
1215    /// The ID of the resource to upload the file to.
1216    #[serde(rename = "ref")]
1217    pub r#ref: u32,
1218    /// If 1, skips reading EXIF data from the downloaded file.
1219    pub no_exif: Option<u8>,
1220    /// If 1, reverts to the original file instead of uploading a new one.
1221    pub revert: Option<u8>,
1222    /// If 1, automatically rotates the image based on EXIF orientation.
1223    pub autorotate: Option<u8>,
1224    /// Publicly accessible URL for the RS server to fetch and attach (hostname must be in `$api_upload_urls`).
1225    pub url: Option<String>,
1226}
1227
1228impl UploadFileByUrlRequest {
1229    pub fn new(r#ref: u32) -> Self {
1230        Self {
1231            r#ref,
1232            no_exif: None,
1233            revert: None,
1234            autorotate: None,
1235            url: None,
1236        }
1237    }
1238
1239    pub fn no_exif(mut self, no_exif: bool) -> Self {
1240        self.no_exif = Some(no_exif as u8);
1241        self
1242    }
1243
1244    pub fn revert(mut self, revert: bool) -> Self {
1245        self.revert = Some(revert as u8);
1246        self
1247    }
1248
1249    pub fn autorotate(mut self, autorotate: bool) -> Self {
1250        self.autorotate = Some(autorotate as u8);
1251        self
1252    }
1253
1254    pub fn url(mut self, url: impl Into<String>) -> Self {
1255        self.url = Some(url.into());
1256        self
1257    }
1258}
1259
1260#[skip_serializing_none]
1261#[derive(Clone, Debug, PartialEq, Serialize)]
1262pub struct UploadMultipartRequest {
1263    /// The ID of the resource to upload the file to.
1264    #[serde(rename = "ref")]
1265    pub r#ref: u32,
1266    /// If 1, skips reading EXIF data from the uploaded file.
1267    pub no_exif: u8,
1268    /// If 1, reverts to the original file instead of uploading a new one.
1269    pub revert: u8,
1270    /// If set, only generates a preview without replacing the stored file.
1271    pub previewonly: Option<bool>,
1272    /// ID of an alternative file slot to upload into instead of the primary file.
1273    pub alternative: Option<u32>,
1274}
1275
1276impl UploadMultipartRequest {
1277    pub fn new(r#ref: u32, no_exif: bool, revert: bool) -> Self {
1278        Self {
1279            r#ref,
1280            no_exif: no_exif as u8,
1281            revert: revert as u8,
1282            previewonly: None,
1283            alternative: None,
1284        }
1285    }
1286    pub fn previewonly(mut self, previewonly: bool) -> Self {
1287        self.previewonly = Some(previewonly);
1288        self
1289    }
1290
1291    pub fn alternative(mut self, alternative: u32) -> Self {
1292        self.alternative = Some(alternative);
1293        self
1294    }
1295}
1296
1297#[skip_serializing_none]
1298#[derive(Clone, Debug, PartialEq, Serialize)]
1299pub struct UpdateRelatedResourceRequest {
1300    /// The ID of the resource to update relationships for.
1301    #[serde(rename = "ref")]
1302    pub r#ref: u32,
1303    /// Comma-separated list of resource IDs to add or remove as related resources.
1304    pub related: List<u32>,
1305    /// If 1, adds the related resources; if 0, removes them.
1306    pub add: Option<u8>,
1307}
1308
1309impl UpdateRelatedResourceRequest {
1310    pub fn new(r#ref: u32, related: impl Into<List<u32>>) -> Self {
1311        Self {
1312            r#ref,
1313            related: related.into(),
1314            add: None,
1315        }
1316    }
1317
1318    #[allow(clippy::should_implement_trait)]
1319    pub fn add(mut self, add: bool) -> Self {
1320        self.add = Some(add as u8);
1321        self
1322    }
1323}
1324
1325#[derive(Clone, Debug, PartialEq, Serialize)]
1326pub struct UpdateResourceTypeRequest {
1327    /// The ID of the resource to update.
1328    pub resource: u32,
1329    /// The new resource type ID to assign to the resource.
1330    pub resourcetype: u32,
1331}
1332
1333impl UpdateResourceTypeRequest {
1334    pub fn new(resource: u32, resourcetype: u32) -> Self {
1335        Self {
1336            resource,
1337            resourcetype,
1338        }
1339    }
1340}
1341
1342#[derive(Clone, Debug, PartialEq, Serialize)]
1343pub struct GetResourceCollectionsRequest {
1344    /// The ID of the resource to retrieve associated collections for.
1345    #[serde(rename = "ref")]
1346    pub r#ref: u32,
1347}
1348
1349impl GetResourceCollectionsRequest {
1350    pub fn new(r#ref: u32) -> Self {
1351        Self { r#ref }
1352    }
1353}
1354
1355#[derive(Clone, Debug, PartialEq, Serialize)]
1356pub struct ValidateUploadUrlRequest {
1357    /// The URL to validate against the server's allowed `$api_upload_urls` list.
1358    pub url: String,
1359}
1360
1361impl ValidateUploadUrlRequest {
1362    pub fn new(url: impl Into<String>) -> Self {
1363        Self { url: url.into() }
1364    }
1365}