From 61f2b628fcaf9b3de4dadd64a174bc5421439804 Mon Sep 17 00:00:00 2001 From: Jeremy Massel <1123407+jkmassel@users.noreply.github.com> Date: Fri, 23 May 2025 11:46:48 -0600 Subject: [PATCH 1/4] Add support tickets endpoints --- .../swift/Sources/wordpress-api/Exports.swift | 4 + .../wordpress-api/WPComApiClient.swift | 4 + wp_api/src/wp_com/client.rs | 10 +- wp_api/src/wp_com/endpoint.rs | 1 + .../endpoint/support_tickets_endpoint.rs | 29 ++++ wp_api/src/wp_com/mod.rs | 1 + wp_api/src/wp_com/support_tickets.rs | 145 ++++++++++++++++++ .../support_tickets/conversation-list.json | 90 +++++++++++ .../support_tickets/single-conversation.json | 131 ++++++++++++++++ 9 files changed, 413 insertions(+), 2 deletions(-) create mode 100644 wp_api/src/wp_com/endpoint/support_tickets_endpoint.rs create mode 100644 wp_api/src/wp_com/support_tickets.rs create mode 100644 wp_api/tests/wpcom/support_tickets/conversation-list.json create mode 100644 wp_api/tests/wpcom/support_tickets/single-conversation.json diff --git a/native/swift/Sources/wordpress-api/Exports.swift b/native/swift/Sources/wordpress-api/Exports.swift index eda7b11b..3f1afc0b 100644 --- a/native/swift/Sources/wordpress-api/Exports.swift +++ b/native/swift/Sources/wordpress-api/Exports.swift @@ -149,3 +149,7 @@ public typealias CreateBotConversationParams = WordPressAPIInternal.CreateBotCon public typealias AddMessageToBotConversationParams = WordPressAPIInternal.AddMessageToBotConversationParams public typealias GetBotConversationParams = WordPressAPIInternal.GetBotConversationParams public typealias CreateBotConversationFeedbackParams = WordPressAPIInternal.CreateBotConversationFeedbackParams + +// MARK: Support Tickets +public typealias CreateSupportTicketParams = WordPressAPIInternal.CreateSupportTicketParams +public typealias AddMessageToSupportConversationParams = WordPressAPIInternal.AddMessageToSupportConversationParams diff --git a/native/swift/Sources/wordpress-api/WPComApiClient.swift b/native/swift/Sources/wordpress-api/WPComApiClient.swift index 2bdd408e..20bc5ff7 100644 --- a/native/swift/Sources/wordpress-api/WPComApiClient.swift +++ b/native/swift/Sources/wordpress-api/WPComApiClient.swift @@ -25,4 +25,8 @@ public class WPComApiClient { public var supportBots: SupportBotsRequestExecutor { internalClient.supportBots() } + + public var supportTickets: SupportTicketsRequestExecutor { + internalClient.supportTickets() + } } diff --git a/wp_api/src/wp_com/client.rs b/wp_api/src/wp_com/client.rs index fd145eef..2a0203c2 100644 --- a/wp_api/src/wp_com/client.rs +++ b/wp_api/src/wp_com/client.rs @@ -5,6 +5,7 @@ use super::endpoint::{ oauth2::{Oauth2RequestBuilder, Oauth2RequestExecutor}, subscribers::{SubscribersRequestBuilder, SubscribersRequestExecutor}, support_bots_endpoint::{SupportBotsRequestBuilder, SupportBotsRequestExecutor}, + support_tickets_endpoint::{SupportTicketsRequestBuilder, SupportTicketsRequestExecutor}, }; use crate::{ WpApiClientDelegate, api_client_generate_api_client, api_client_generate_endpoint_impl, @@ -33,6 +34,7 @@ pub struct WpComApiRequestBuilder { oauth2: Arc, subscribers: Arc, support_bots: Arc, + support_tickets: Arc, } impl WpComApiRequestBuilder { @@ -45,7 +47,8 @@ impl WpComApiRequestBuilder { jetpack_connection, oauth2, subscribers, - support_bots + support_bots, + support_tickets ) } } @@ -70,6 +73,7 @@ pub struct WpComApiClient { oauth2: Arc, subscribers: Arc, support_bots: Arc, + support_tickets: Arc, } impl WpComApiClient { @@ -83,7 +87,8 @@ impl WpComApiClient { jetpack_connection, oauth2, subscribers, - support_bots + support_bots, + support_tickets ) } } @@ -91,3 +96,4 @@ api_client_generate_endpoint_impl!(WpComApi, jetpack_connection); api_client_generate_endpoint_impl!(WpComApi, oauth2); api_client_generate_endpoint_impl!(WpComApi, subscribers); api_client_generate_endpoint_impl!(WpComApi, support_bots); +api_client_generate_endpoint_impl!(WpComApi, support_tickets); diff --git a/wp_api/src/wp_com/endpoint.rs b/wp_api/src/wp_com/endpoint.rs index 0ed0f034..28a7cdbe 100644 --- a/wp_api/src/wp_com/endpoint.rs +++ b/wp_api/src/wp_com/endpoint.rs @@ -10,6 +10,7 @@ pub mod jetpack_connection_endpoint; pub mod oauth2; pub mod subscribers; pub mod support_bots_endpoint; +pub mod support_tickets_endpoint; #[derive(uniffi::Object)] pub struct WpComDotOrgApiUrlResolver { diff --git a/wp_api/src/wp_com/endpoint/support_tickets_endpoint.rs b/wp_api/src/wp_com/endpoint/support_tickets_endpoint.rs new file mode 100644 index 00000000..bdc19b3a --- /dev/null +++ b/wp_api/src/wp_com/endpoint/support_tickets_endpoint.rs @@ -0,0 +1,29 @@ +use crate::{ + request::endpoint::{AsNamespace, DerivedRequest}, + wp_com::{ + WpComNamespace, + support_tickets::{ + AddMessageToSupportConversationParams, ConversationId, CreateSupportTicketParams, + SupportConversation, SupportConversationSummary, + }, + }, +}; +use wp_derive_request_builder::WpDerivedRequest; + +#[derive(WpDerivedRequest)] +enum SupportTicketsRequest { + #[post(url = "/mobile-support/conversations", params = &CreateSupportTicketParams, output = SupportConversation)] + CreateSupportTicket, + #[get(url = "/mobile-support/conversations", output = Vec)] + GetSupportConversationList, + #[get(url = "/mobile-support/conversations/", output = SupportConversation)] + GetSupportConversation, + #[post(url = "/mobile-support/conversations/", params = &AddMessageToSupportConversationParams, output = SupportConversation)] + AddMessageToSupportConversation, +} + +impl DerivedRequest for SupportTicketsRequest { + fn namespace() -> impl AsNamespace { + WpComNamespace::V2 + } +} diff --git a/wp_api/src/wp_com/mod.rs b/wp_api/src/wp_com/mod.rs index 252cbc34..7f255ac7 100644 --- a/wp_api/src/wp_com/mod.rs +++ b/wp_api/src/wp_com/mod.rs @@ -8,6 +8,7 @@ pub mod jetpack_connection; pub mod oauth2; pub mod subscribers; pub mod support_bots; +pub mod support_tickets; impl_as_query_value_for_new_type!(WpComSiteId); uniffi::custom_newtype!(WpComSiteId, u64); diff --git a/wp_api/src/wp_com/support_tickets.rs b/wp_api/src/wp_com/support_tickets.rs new file mode 100644 index 00000000..324ce334 --- /dev/null +++ b/wp_api/src/wp_com/support_tickets.rs @@ -0,0 +1,145 @@ +use std::collections::HashMap; + +use serde::{Deserialize, Serialize}; + +use crate::{date::WpGmtDateTime, impl_as_query_value_for_new_type}; + +#[derive(Debug, PartialEq, Eq, Serialize, uniffi::Record)] +pub struct CreateSupportTicketParams { + pub subject: String, + pub message: String, + pub application: String, + #[uniffi(default = None)] + #[serde(skip_serializing_if = "Option::is_none")] + pub wpcom_site_id: Option, + #[uniffi(default = [])] + pub tags: Vec, + #[uniffi(default = [])] + pub attachments: Vec, +} + +#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, uniffi::Record)] +pub struct SupportConversationSummary { + pub id: ConversationId, + pub title: String, + pub description: String, + pub status: String, + pub created_at: WpGmtDateTime, + pub updated_at: WpGmtDateTime, +} + +#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, uniffi::Record)] +pub struct SupportConversation { + pub id: ConversationId, + pub title: String, + pub description: String, + pub status: String, + pub created_at: WpGmtDateTime, + pub updated_at: WpGmtDateTime, + pub messages: Vec, +} + +#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, uniffi::Record)] +pub struct SupportMessage { + pub id: u64, + pub content: String, + pub author: SupportMessageAuthor, + pub role: String, + pub author_is_current_user: bool, + pub created_at: WpGmtDateTime, + pub attachments: Vec, +} + +#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, uniffi::Record)] +pub struct SupportUserIdentity { + pub id: u64, + pub email: String, + pub display_name: String, + pub avatar_url: String, +} + +#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, uniffi::Record)] +pub struct SupportAttachment { + pub id: u64, + pub filename: String, + pub content_type: String, + pub size: u64, + pub url: String, + pub metadata: HashMap, +} + +#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, uniffi::Enum)] +#[serde(untagged)] +pub enum SupportMessageAuthor { + User(SupportUserIdentity), + SupportAgent(SupportAgentIdentity), +} + +#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, uniffi::Enum, strum_macros::Display)] +#[strum(serialize_all = "snake_case")] +pub enum AttachmentMetadataKey { + Width, + Height, + Other(String), +} + +#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, uniffi::Enum)] +#[serde(untagged)] +pub enum AttachmentMetadataValue { + String(String), + Number(u64), + Boolean(bool), +} + +#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, uniffi::Record)] +pub struct SupportAgentIdentity { + pub id: u64, + pub name: String, +} + +#[derive(Debug, PartialEq, Eq, Serialize, uniffi::Record)] +pub struct AddMessageToSupportConversationParams { + pub message: String, + #[uniffi(default = [])] + pub attachments: Vec, +} + +impl_as_query_value_for_new_type!(ConversationId); +uniffi::custom_newtype!(ConversationId, u64); +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +pub struct ConversationId(pub u64); + +impl std::str::FromStr for ConversationId { + type Err = std::num::ParseIntError; + + fn from_str(s: &str) -> Result { + s.parse().map(Self) + } +} + +impl std::fmt::Display for ConversationId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_support_conversation_deserialization() { + let json = include_str!("../../tests/wpcom/support_tickets/single-conversation.json"); + let conversation: SupportConversation = + serde_json::from_str(json).expect("Failed to deserialize support conversation"); + assert_eq!(conversation.messages.len(), 7); + } + + #[test] + fn test_support_conversation_list_deserialization() { + let json = include_str!("../../tests/wpcom/support_tickets/conversation-list.json"); + let conversation_list: Vec = + serde_json::from_str(json).expect("Failed to deserialize support conversation list"); + assert_eq!(conversation_list.len(), 11); + } +} diff --git a/wp_api/tests/wpcom/support_tickets/conversation-list.json b/wp_api/tests/wpcom/support_tickets/conversation-list.json new file mode 100644 index 00000000..f07097a8 --- /dev/null +++ b/wp_api/tests/wpcom/support_tickets/conversation-list.json @@ -0,0 +1,90 @@ +[ + { + "id": 9033503, + "title": "๐ŸŽจ Theme Customization Help", + "description": "Hello! ๐Ÿ‘‹\n\nI see you're having trouble with your theme customization. Let me help you with that!\n\nThe color picker issue you're experiencing is a known bug that we've fixed in the latest update. Please try updating your theme to version 2.3.1.\n\nIf you need any further assistance, I'm here to help!\n\nBest regards,\n\nTheme Support Team ๐ŸŒŸ", + "status": "closed", + "created_at": "2024-03-15T14:30:22+00:00", + "updated_at": "2024-03-18T09:45:33+00:00" + }, + { + "id": 1698812, + "title": "๐Ÿ”’ Security Update Required", + "description": "ู…ุฑุญุจุงู‹!\n\nู„ู‚ุฏ ู„ุงุญุธู†ุง ุฃู† ู…ูˆู‚ุนูƒ ูŠุญุชุงุฌ ุฅู„ู‰ ุชุญุฏูŠุซ ุฃู…ู†ูŠ. ูŠุฑุฌู‰ ุชุญุฏูŠุซ ุฅุตุฏุงุฑ WordPress ุงู„ุฎุงุต ุจูƒ ุฅู„ู‰ ุฃุญุฏุซ ุฅุตุฏุงุฑ ู…ุชุงุญ.\n\nูŠู…ูƒู†ูƒ ุงู„ู‚ูŠุงู… ุจุฐู„ูƒ ู…ู† ู„ูˆุญุฉ ุงู„ุชุญูƒู… ุงู„ุฎุงุตุฉ ุจูƒ.\n\nู…ุน ุชุญูŠุงุชู†ุงุŒ\n\nูุฑูŠู‚ ุงู„ุฏุนู… ุงู„ุฃู…ู†ูŠ", + "status": "closed", + "created_at": "2024-03-10T09:15:45+00:00", + "updated_at": "2024-03-11T16:20:12+00:00" + }, + { + "id": 1629241, + "title": "๐Ÿš€ Performance Optimization", + "description": "Bonjour!\n\nJe vois que vous souhaitez optimiser les performances de votre site. Voici quelques suggestions:\n\n1. Activez la mise en cache\n2. Optimisez vos images\n3. Utilisez un CDN\n\nVoulez-vous que je vous guide ร  travers ces รฉtapes?\n\nCordialement,\n\nL'รฉquipe de support", + "status": "closed", + "created_at": "2024-03-05T11:22:18+00:00", + "updated_at": "2024-03-07T14:33:29+00:00" + }, + { + "id": 1500710, + "title": "๐Ÿ“ฑ Mobile App Feedback", + "description": "Hi there! ๐Ÿ‘‹\n\nThank you for your feedback about our mobile app. We're constantly working to improve the user experience.\n\nYour suggestion about the navigation menu has been forwarded to our development team.\n\nKeep the feedback coming! ๐Ÿ’ช\n\nBest regards,\n\nMobile Support Team", + "status": "closed", + "created_at": "2024-02-28T15:40:55+00:00", + "updated_at": "2024-03-01T10:15:22+00:00" + }, + { + "id": 1499869, + "title": "๐ŸŽฏ SEO Optimization Help", + "description": "Hello!\n\nI'd be happy to help you with your SEO optimization. Here are some key areas we can focus on:\n\n1. Meta descriptions\n2. Image alt tags\n3. URL structure\n\nWould you like me to provide more specific guidance on any of these topics?\n\nBest regards,\n\nSEO Support Team", + "status": "closed", + "created_at": "2024-02-25T13:20:33+00:00", + "updated_at": "2024-02-27T09:45:11+00:00" + }, + { + "id": 1500203, + "title": "๐Ÿ’ฐ Payment Gateway Setup", + "description": "Hi!\n\nI see you're setting up a new payment gateway. Let's make sure everything is configured correctly.\n\nPlease verify these settings:\n1. API keys\n2. Webhook endpoints\n3. Test mode status\n\nNeed any clarification? ๐Ÿค”\n\nBest regards,\n\nPayment Support Team", + "status": "closed", + "created_at": "2024-02-20T10:15:44+00:00", + "updated_at": "2024-02-22T16:30:27+00:00" + }, + { + "id": 1698812, + "title": "๐ŸŽจ Design Consultation", + "description": "Hello! ๐Ÿ‘‹\n\nThank you for reaching out about your site's design. I'd be happy to help you achieve the look you're going for.\n\nWould you like to schedule a design consultation call? We can discuss:\n- Color schemes\n- Layout options\n- Typography\n\nLet me know what works best for you! โœจ\n\nBest regards,\n\nDesign Support Team", + "status": "closed", + "created_at": "2024-02-15T09:30:22+00:00", + "updated_at": "2024-02-17T14:45:33+00:00" + }, + { + "id": 1629241, + "title": "๐Ÿ“Š Analytics Integration", + "description": "Hi there!\n\nI see you're having trouble with your analytics integration. Let's get that sorted out.\n\nFirst, please verify that you've added the tracking code correctly to your site's header.\n\nNeed help finding where to add it? ๐Ÿง\n\nBest regards,\n\nAnalytics Support Team", + "status": "closed", + "created_at": "2024-02-10T11:20:18+00:00", + "updated_at": "2024-02-12T15:33:29+00:00" + }, + { + "id": 1500710, + "title": "๐ŸŒ Multilingual Setup", + "description": "Bonjour!\n\nJe vois que vous souhaitez configurer votre site en plusieurs langues. Voici les รฉtapes ร  suivre:\n\n1. Installer le plugin de traduction\n2. Configurer les langues\n3. Traduire le contenu\n\nBesoin d'aide avec l'une de ces รฉtapes? ๐Ÿค\n\nCordialement,\n\nL'รฉquipe de support multilingue", + "status": "closed", + "created_at": "2024-02-05T13:40:55+00:00", + "updated_at": "2024-02-07T10:15:22+00:00" + }, + { + "id": 1499869, + "title": "๐Ÿ”ง Technical Support", + "description": "Hello! ๐Ÿ‘‹\n\nI understand you're experiencing some technical issues. Let's troubleshoot this step by step.\n\nCould you please:\n1. Clear your browser cache\n2. Try a different browser\n3. Check your internet connection\n\nLet me know if you need help with any of these steps! ๐Ÿ› ๏ธ\n\nBest regards,\n\nTechnical Support Team", + "status": "closed", + "created_at": "2024-02-01T10:20:33+00:00", + "updated_at": "2024-02-03T09:45:11+00:00" + }, + { + "id": 1500203, + "title": "๐Ÿ“ Content Migration", + "description": "Hi there!\n\nI see you're planning to migrate your content. Let me help you with that process.\n\nWe can use our automated migration tool, or I can guide you through a manual migration.\n\nWhich option would you prefer? ๐Ÿค”\n\nBest regards,\n\nMigration Support Team", + "status": "closed", + "created_at": "2024-01-28T09:15:44+00:00", + "updated_at": "2024-01-30T16:30:27+00:00" + } + ] diff --git a/wp_api/tests/wpcom/support_tickets/single-conversation.json b/wp_api/tests/wpcom/support_tickets/single-conversation.json new file mode 100644 index 00000000..40ada997 --- /dev/null +++ b/wp_api/tests/wpcom/support_tickets/single-conversation.json @@ -0,0 +1,131 @@ +{ + "id": 9796958, + "title": "Issue with Jetpack Connection", + "description": "Having trouble connecting Jetpack to my site", + "status": "open", + "messages": [ + { + "id": 37691022090900, + "content": "I'm having trouble connecting Jetpack to my WordPress site. The connection keeps failing and I'm not sure what to do.", + "role": "user", + "author": { + "id": 375621849651, + "name": "Test User", + "email": "test.user@example.com" + }, + "author_is_current_user": true, + "created_at": "2025-05-21T01:03:11+00:00", + "attachments": [] + }, + { + "id": 37691118978324, + "content": "Hi Test User,\n\nI'm sorry to hear you're having trouble with your Jetpack connection. Could you please try disconnecting and reconnecting Jetpack from your site's dashboard? Also, please check if your site's XML-RPC is enabled.\n\nSupport Agent\nSupport Agent\n**| Automattic | WordPress.com**", + "role": "support", + "author": { + "id": 370234131652, + "name": "Support Agent" + }, + "author_is_current_user": false, + "created_at": "2025-05-21T01:05:27+00:00", + "attachments": [] + }, + { + "id": 37691524437652, + "content": "I've tried disconnecting and reconnecting, but I'm still seeing the same error message. Here's what I see on my screen.\n\nSupport Agent\nSupport Agent\n**| Automattic | WordPress.com**", + "role": "support", + "author": { + "id": 370234131652, + "name": "Support Agent" + }, + "author_is_current_user": false, + "created_at": "2025-05-21T01:35:23+00:00", + "attachments": [] + }, + { + "id": 37691573997588, + "content": "Could you please check if your site's firewall or security plugins might be blocking the connection? Sometimes these can interfere with Jetpack's connection process.\n\nSupport Agent\nSupport Agent\n**| Automattic | WordPress.com**", + "role": "support", + "author": { + "id": 370234131652, + "name": "Support Agent" + }, + "author_is_current_user": false, + "created_at": "2025-05-21T01:37:51+00:00", + "attachments": [] + }, + { + "id": 37725928573972, + "content": "I've attached a screenshot of the error message I'm seeing. It says 'Connection failed' with error code 403.", + "role": "user", + "author": { + "id": 375621849651, + "name": "Test User", + "email": "test.user@example.com" + }, + "author_is_current_user": true, + "created_at": "2025-05-22T00:04:48+00:00", + "attachments": [ + { + "id": 37725924561684, + "filename": "test_screenshot_1.jpeg", + "content_type": "image\/jpeg", + "size": 246171, + "url": "https:\/\/a8c.zendesk.com\/attachments\/token\/nZ1I5S7NkM8J8lpf9wAA9V6BL\/?name=test_screenshot_1.jpeg", + "metadata": { + "width": 1179, + "height": 2556 + } + } + ] + }, + { + "id": 37726042281364, + "content": "Here's another screenshot showing my site's security settings. I've temporarily disabled the firewall to test the connection.", + "role": "user", + "author": { + "id": 375621849651, + "name": "Test User", + "email": "test.user@example.com" + }, + "author_is_current_user": true, + "created_at": "2025-05-22T00:11:50+00:00", + "attachments": [ + { + "id": 37726025831316, + "filename": "test_screenshot_2.jpeg", + "content_type": "image\/jpeg", + "size": 246171, + "url": "https:\/\/a8c.zendesk.com\/attachments\/token\/nGyuwTgotHH3HAnm6WOCmfXuP\/?name=test_screenshot_2.jpeg", + "metadata": { + "width": 1179, + "height": 2556 + } + } + ] + }, + { + "id": 37726222988180, + "content": "I've recorded a video showing the connection process and the error that occurs. You can see exactly what happens when I try to connect.", + "role": "user", + "author": { + "id": 375621849651, + "name": "Test User", + "email": "test.user@example.com" + }, + "author_is_current_user": true, + "created_at": "2025-05-22T00:22:09+00:00", + "attachments": [ + { + "id": 37726236284948, + "filename": "test_recording_1.mp4", + "content_type": "video\/mp4", + "size": 25897659, + "url": "https:\/\/a8c.zendesk.com\/attachments\/token\/pxM2QJE7errXpreLxEgFA4w5L\/?name=test_recording_1.mp4", + "metadata": {} + } + ] + } + ], + "created_at": "2025-05-21T01:03:10+00:00", + "updated_at": "2025-05-22T00:22:09+00:00" +} From 483545545ded5e1cfb72ec8b0af12fde410c719a Mon Sep 17 00:00:00 2001 From: Jeremy Massel <1123407+jkmassel@users.noreply.github.com> Date: Fri, 23 May 2025 11:33:35 -0600 Subject: [PATCH 2/4] Add support eligibility endpoint support Add Swift Support Eligibility support --- .../Sources/wordpress-api/WPComApiClient.swift | 4 ++++ wp_api/src/wp_com/client.rs | 8 ++++++++ wp_api/src/wp_com/endpoint.rs | 1 + .../endpoint/support_eligibility_endpoint.rs | 18 ++++++++++++++++++ wp_api/src/wp_com/mod.rs | 1 + wp_api/src/wp_com/support_eligibility.rs | 6 ++++++ 6 files changed, 38 insertions(+) create mode 100644 wp_api/src/wp_com/endpoint/support_eligibility_endpoint.rs create mode 100644 wp_api/src/wp_com/support_eligibility.rs diff --git a/native/swift/Sources/wordpress-api/WPComApiClient.swift b/native/swift/Sources/wordpress-api/WPComApiClient.swift index 20bc5ff7..88c2fb80 100644 --- a/native/swift/Sources/wordpress-api/WPComApiClient.swift +++ b/native/swift/Sources/wordpress-api/WPComApiClient.swift @@ -26,6 +26,10 @@ public class WPComApiClient { internalClient.supportBots() } + public var supportEligibility: SupportEligibilityRequestExecutor { + internalClient.supportEligibility() + } + public var supportTickets: SupportTicketsRequestExecutor { internalClient.supportTickets() } diff --git a/wp_api/src/wp_com/client.rs b/wp_api/src/wp_com/client.rs index 2a0203c2..024e2b9b 100644 --- a/wp_api/src/wp_com/client.rs +++ b/wp_api/src/wp_com/client.rs @@ -5,6 +5,9 @@ use super::endpoint::{ oauth2::{Oauth2RequestBuilder, Oauth2RequestExecutor}, subscribers::{SubscribersRequestBuilder, SubscribersRequestExecutor}, support_bots_endpoint::{SupportBotsRequestBuilder, SupportBotsRequestExecutor}, + support_eligibility_endpoint::{ + SupportEligibilityRequestBuilder, SupportEligibilityRequestExecutor, + }, support_tickets_endpoint::{SupportTicketsRequestBuilder, SupportTicketsRequestExecutor}, }; use crate::{ @@ -34,6 +37,7 @@ pub struct WpComApiRequestBuilder { oauth2: Arc, subscribers: Arc, support_bots: Arc, + support_eligibility: Arc, support_tickets: Arc, } @@ -48,6 +52,7 @@ impl WpComApiRequestBuilder { oauth2, subscribers, support_bots, + support_eligibility, support_tickets ) } @@ -73,6 +78,7 @@ pub struct WpComApiClient { oauth2: Arc, subscribers: Arc, support_bots: Arc, + support_eligibility: Arc, support_tickets: Arc, } @@ -88,6 +94,7 @@ impl WpComApiClient { oauth2, subscribers, support_bots, + support_eligibility, support_tickets ) } @@ -96,4 +103,5 @@ api_client_generate_endpoint_impl!(WpComApi, jetpack_connection); api_client_generate_endpoint_impl!(WpComApi, oauth2); api_client_generate_endpoint_impl!(WpComApi, subscribers); api_client_generate_endpoint_impl!(WpComApi, support_bots); +api_client_generate_endpoint_impl!(WpComApi, support_eligibility); api_client_generate_endpoint_impl!(WpComApi, support_tickets); diff --git a/wp_api/src/wp_com/endpoint.rs b/wp_api/src/wp_com/endpoint.rs index 28a7cdbe..df826fd3 100644 --- a/wp_api/src/wp_com/endpoint.rs +++ b/wp_api/src/wp_com/endpoint.rs @@ -10,6 +10,7 @@ pub mod jetpack_connection_endpoint; pub mod oauth2; pub mod subscribers; pub mod support_bots_endpoint; +pub mod support_eligibility_endpoint; pub mod support_tickets_endpoint; #[derive(uniffi::Object)] diff --git a/wp_api/src/wp_com/endpoint/support_eligibility_endpoint.rs b/wp_api/src/wp_com/endpoint/support_eligibility_endpoint.rs new file mode 100644 index 00000000..07f09590 --- /dev/null +++ b/wp_api/src/wp_com/endpoint/support_eligibility_endpoint.rs @@ -0,0 +1,18 @@ +use wp_derive_request_builder::WpDerivedRequest; + +use crate::{ + request::endpoint::{AsNamespace, DerivedRequest}, + wp_com::{WpComNamespace, support_eligibility::SupportEligibility}, +}; + +#[derive(WpDerivedRequest)] +enum SupportEligibilityRequest { + #[get(url = "/mobile-support/eligibility", output = SupportEligibility)] + GetSupportEligibility, +} + +impl DerivedRequest for SupportEligibilityRequest { + fn namespace() -> impl AsNamespace { + WpComNamespace::V2 + } +} diff --git a/wp_api/src/wp_com/mod.rs b/wp_api/src/wp_com/mod.rs index 7f255ac7..87f07537 100644 --- a/wp_api/src/wp_com/mod.rs +++ b/wp_api/src/wp_com/mod.rs @@ -8,6 +8,7 @@ pub mod jetpack_connection; pub mod oauth2; pub mod subscribers; pub mod support_bots; +pub mod support_eligibility; pub mod support_tickets; impl_as_query_value_for_new_type!(WpComSiteId); diff --git a/wp_api/src/wp_com/support_eligibility.rs b/wp_api/src/wp_com/support_eligibility.rs new file mode 100644 index 00000000..3b25a1c8 --- /dev/null +++ b/wp_api/src/wp_com/support_eligibility.rs @@ -0,0 +1,6 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, uniffi::Record)] +pub struct SupportEligibility { + pub is_user_eligible: bool, +} From 4a98907214a78c13c79a492eac75219fd433f547 Mon Sep 17 00:00:00 2001 From: Jeremy Massel <1123407+jkmassel@users.noreply.github.com> Date: Fri, 23 May 2025 11:33:54 -0600 Subject: [PATCH 3/4] add wp_com_e2e package --- Cargo.lock | 17 +++++ Cargo.toml | 1 + wp_com_e2e/Cargo.toml | 20 ++++++ wp_com_e2e/src/main.rs | 82 ++++++++++++++++++++++ wp_com_e2e/src/oauth2_tests.rs | 27 +++++++ wp_com_e2e/src/support_eligibility_test.rs | 23 ++++++ wp_com_e2e/src/support_tickets_test.rs | 59 ++++++++++++++++ 7 files changed, 229 insertions(+) create mode 100644 wp_com_e2e/Cargo.toml create mode 100644 wp_com_e2e/src/main.rs create mode 100644 wp_com_e2e/src/oauth2_tests.rs create mode 100644 wp_com_e2e/src/support_eligibility_test.rs create mode 100644 wp_com_e2e/src/support_tickets_test.rs diff --git a/Cargo.lock b/Cargo.lock index bea3f0c8..2551254f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4904,6 +4904,23 @@ dependencies = [ "wp_serde_helper", ] +[[package]] +name = "wp_com_e2e" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-trait", + "clap", + "colored", + "csv", + "futures", + "serde", + "serde_json", + "tokio", + "tokio-stream", + "wp_api", +] + [[package]] name = "wp_contextual" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 58eb1791..5292fc20 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ members = [ "wp_api_integration_tests", "wp_api_integration_tests_backend", "wp_cli", + "wp_com_e2e", "wp_contextual", "wp_derive", "wp_derive_request_builder", diff --git a/wp_com_e2e/Cargo.toml b/wp_com_e2e/Cargo.toml new file mode 100644 index 00000000..bf8ee15d --- /dev/null +++ b/wp_com_e2e/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "wp_com_e2e" +version = "0.1.0" +edition = "2024" + +[dependencies] +anyhow = { workspace = true } +async-trait = { workspace = true } +clap = { workspace = true, features = ["derive", "env"] } +colored = { workspace = true } +csv = { workspace = true } +futures = { workspace = true } +serde = { workspace = true, features = ["derive"] } +serde_json = { workspace = true } +tokio = { workspace = true, features = ["full"] } +tokio-stream = { workspace = true } +wp_api = { path = "../wp_api", features = [ "reqwest-request-executor" ] } + +[[bin]] +name = "wp_com_e2e" diff --git a/wp_com_e2e/src/main.rs b/wp_com_e2e/src/main.rs new file mode 100644 index 00000000..f747f1d8 --- /dev/null +++ b/wp_com_e2e/src/main.rs @@ -0,0 +1,82 @@ +use anyhow::Result; +use async_trait::async_trait; +use clap::{Parser, Subcommand}; +use oauth2_tests::Oauth2Test; +use std::sync::Arc; +use support_eligibility_test::SupportEligibilityTest; +use support_tickets_test::SupportTicketsTest; +use wp_api::{ + WpApiClientDelegate, WpAppNotifier, + auth::{WpAuthentication, WpAuthenticationProvider}, + middleware::WpApiMiddlewarePipeline, + reqwest_request_executor::ReqwestRequestExecutor, + wp_com::client::WpComApiClient, +}; + +mod oauth2_tests; +mod support_eligibility_test; +mod support_tickets_test; + +#[derive(Parser)] +#[command(author, version, about, long_about = None)] +struct Cli { + #[command(subcommand)] + command: Commands, +} + +#[derive(Subcommand)] +enum Commands { + Test { + #[arg(short = 't', long = "token", env = "WP_COM_API_KEY")] + token: String, + }, +} + +#[async_trait] +trait Testable { + async fn test(&self) -> Result<(), anyhow::Error>; +} + +#[derive(Debug)] +pub struct EmptyAppNotifier; + +#[async_trait] +impl WpAppNotifier for EmptyAppNotifier { + async fn requested_with_invalid_authentication(&self) { + // no-op + } +} + +#[tokio::main] +async fn main() -> Result<(), anyhow::Error> { + let cli = Cli::parse(); + + match cli.command { + Commands::Test { token } => { + let delegate = WpApiClientDelegate { + auth_provider: WpAuthenticationProvider::static_with_auth( + WpAuthentication::Bearer { + token: token.clone(), + }, + ) + .into(), + request_executor: Arc::new(ReqwestRequestExecutor::default()), + middleware_pipeline: Arc::new(WpApiMiddlewarePipeline::default()), + app_notifier: Arc::new(EmptyAppNotifier), + }; + + let client = WpComApiClient::new(delegate); + + Oauth2Test { + client: &client, + token: &token, + } + .test() + .await?; + SupportTicketsTest { client: &client }.test().await?; + SupportEligibilityTest { client: &client }.test().await?; + } + } + + Ok(()) +} diff --git a/wp_com_e2e/src/oauth2_tests.rs b/wp_com_e2e/src/oauth2_tests.rs new file mode 100644 index 00000000..85b4f872 --- /dev/null +++ b/wp_com_e2e/src/oauth2_tests.rs @@ -0,0 +1,27 @@ +use anyhow::{Ok, Result}; +use async_trait::async_trait; +use wp_api::wp_com::{client::WpComApiClient, oauth2::TokenValidationParameters}; + +use crate::Testable; + +pub struct Oauth2Test<'a> { + pub token: &'a String, + pub client: &'a WpComApiClient, +} + +#[async_trait] +impl Testable for Oauth2Test<'_> { + async fn test(&self) -> Result<(), anyhow::Error> { + println!("== OAuth 2 Token Test =="); + self.client + .oauth2() + .fetch_info(&TokenValidationParameters { + client_id: "11".to_string(), + token: self.token.clone(), + }) + .await?; + println!("โœ… Get OAuth 2 Token Info"); + + Ok(()) + } +} diff --git a/wp_com_e2e/src/support_eligibility_test.rs b/wp_com_e2e/src/support_eligibility_test.rs new file mode 100644 index 00000000..246fb06c --- /dev/null +++ b/wp_com_e2e/src/support_eligibility_test.rs @@ -0,0 +1,23 @@ +use anyhow::{Ok, Result}; +use async_trait::async_trait; +use wp_api::wp_com::client::WpComApiClient; + +use crate::Testable; + +pub struct SupportEligibilityTest<'a> { + pub client: &'a WpComApiClient, +} + +#[async_trait] +impl Testable for SupportEligibilityTest<'_> { + async fn test(&self) -> Result<(), anyhow::Error> { + println!("== Support Eligibility Test =="); + self.client + .support_eligibility() + .get_support_eligibility() + .await?; + println!("โœ… Get Support Eligibility"); + + Ok(()) + } +} diff --git a/wp_com_e2e/src/support_tickets_test.rs b/wp_com_e2e/src/support_tickets_test.rs new file mode 100644 index 00000000..33bcbaac --- /dev/null +++ b/wp_com_e2e/src/support_tickets_test.rs @@ -0,0 +1,59 @@ +use anyhow::{Ok, Result}; +use async_trait::async_trait; +use wp_api::wp_com::{ + client::WpComApiClient, + support_tickets::{AddMessageToSupportConversationParams, CreateSupportTicketParams}, +}; + +use crate::Testable; + +pub struct SupportTicketsTest<'a> { + pub client: &'a WpComApiClient, +} + +#[async_trait] +impl Testable for SupportTicketsTest<'_> { + async fn test(&self) -> Result<(), anyhow::Error> { + println!("== Support Tickets Test =="); + let new_conversation = self + .client + .support_tickets() + .create_support_ticket(&CreateSupportTicketParams { + subject: "Mobile Support Test Message".to_string(), + message: "This is a test โ€“ it can be deleted without replying.".to_string(), + application: "jetpack".to_string(), + wpcom_site_id: None, + tags: vec!["jetpack_mobile".to_string(), "test".to_string()], + attachments: vec![], + }) + .await? + .data; + println!("โœ… Create Conversation"); + + self.client + .support_tickets() + .add_message_to_support_conversation( + &new_conversation.id, + &AddMessageToSupportConversationParams { + message: "Test Message".to_string(), + attachments: vec![], + }, + ) + .await?; + println!("โœ… Add Message to Conversation"); + + self.client + .support_tickets() + .get_support_conversation(&new_conversation.id) + .await?; + println!("โœ… Fetch Conversation"); + + self.client + .support_tickets() + .get_support_conversation_list() + .await?; + println!("โœ… Fetch Conversation List"); + + Ok(()) + } +} From d08e2061a2c7b5cc74dd7e2e2130cd716fa9e6bf Mon Sep 17 00:00:00 2001 From: Oguz Kocer Date: Mon, 9 Jun 2025 18:16:38 -0400 Subject: [PATCH 4/4] Remove `Testable` trait and simplify wp_com_e2e tests --- wp_com_e2e/src/main.rs | 19 +---- wp_com_e2e/src/oauth2_tests.rs | 36 ++++------ wp_com_e2e/src/support_eligibility_test.rs | 27 +++---- wp_com_e2e/src/support_tickets_test.rs | 84 ++++++++++------------ 4 files changed, 59 insertions(+), 107 deletions(-) diff --git a/wp_com_e2e/src/main.rs b/wp_com_e2e/src/main.rs index f747f1d8..355b64eb 100644 --- a/wp_com_e2e/src/main.rs +++ b/wp_com_e2e/src/main.rs @@ -1,10 +1,7 @@ use anyhow::Result; use async_trait::async_trait; use clap::{Parser, Subcommand}; -use oauth2_tests::Oauth2Test; use std::sync::Arc; -use support_eligibility_test::SupportEligibilityTest; -use support_tickets_test::SupportTicketsTest; use wp_api::{ WpApiClientDelegate, WpAppNotifier, auth::{WpAuthentication, WpAuthenticationProvider}, @@ -32,11 +29,6 @@ enum Commands { }, } -#[async_trait] -trait Testable { - async fn test(&self) -> Result<(), anyhow::Error>; -} - #[derive(Debug)] pub struct EmptyAppNotifier; @@ -67,14 +59,9 @@ async fn main() -> Result<(), anyhow::Error> { let client = WpComApiClient::new(delegate); - Oauth2Test { - client: &client, - token: &token, - } - .test() - .await?; - SupportTicketsTest { client: &client }.test().await?; - SupportEligibilityTest { client: &client }.test().await?; + oauth2_tests::oauth2_test(&client, token.clone()).await?; + support_tickets_test::support_tickets_test(&client).await?; + support_eligibility_test::support_eligibility_test(&client).await?; } } diff --git a/wp_com_e2e/src/oauth2_tests.rs b/wp_com_e2e/src/oauth2_tests.rs index 85b4f872..d96e8657 100644 --- a/wp_com_e2e/src/oauth2_tests.rs +++ b/wp_com_e2e/src/oauth2_tests.rs @@ -1,27 +1,15 @@ -use anyhow::{Ok, Result}; -use async_trait::async_trait; use wp_api::wp_com::{client::WpComApiClient, oauth2::TokenValidationParameters}; -use crate::Testable; - -pub struct Oauth2Test<'a> { - pub token: &'a String, - pub client: &'a WpComApiClient, -} - -#[async_trait] -impl Testable for Oauth2Test<'_> { - async fn test(&self) -> Result<(), anyhow::Error> { - println!("== OAuth 2 Token Test =="); - self.client - .oauth2() - .fetch_info(&TokenValidationParameters { - client_id: "11".to_string(), - token: self.token.clone(), - }) - .await?; - println!("โœ… Get OAuth 2 Token Info"); - - Ok(()) - } +pub async fn oauth2_test(client: &WpComApiClient, token: String) -> anyhow::Result<()> { + println!("== OAuth 2 Token Test =="); + client + .oauth2() + .fetch_info(&TokenValidationParameters { + client_id: "11".to_string(), + token, + }) + .await?; + println!("โœ… Get OAuth 2 Token Info"); + + Ok(()) } diff --git a/wp_com_e2e/src/support_eligibility_test.rs b/wp_com_e2e/src/support_eligibility_test.rs index 246fb06c..0ba1c1e6 100644 --- a/wp_com_e2e/src/support_eligibility_test.rs +++ b/wp_com_e2e/src/support_eligibility_test.rs @@ -1,23 +1,12 @@ -use anyhow::{Ok, Result}; -use async_trait::async_trait; use wp_api::wp_com::client::WpComApiClient; -use crate::Testable; +pub async fn support_eligibility_test(client: &WpComApiClient) -> anyhow::Result<()> { + println!("== Support Eligibility Test =="); + client + .support_eligibility() + .get_support_eligibility() + .await?; + println!("โœ… Get Support Eligibility"); -pub struct SupportEligibilityTest<'a> { - pub client: &'a WpComApiClient, -} - -#[async_trait] -impl Testable for SupportEligibilityTest<'_> { - async fn test(&self) -> Result<(), anyhow::Error> { - println!("== Support Eligibility Test =="); - self.client - .support_eligibility() - .get_support_eligibility() - .await?; - println!("โœ… Get Support Eligibility"); - - Ok(()) - } + Ok(()) } diff --git a/wp_com_e2e/src/support_tickets_test.rs b/wp_com_e2e/src/support_tickets_test.rs index 33bcbaac..e2fa5096 100644 --- a/wp_com_e2e/src/support_tickets_test.rs +++ b/wp_com_e2e/src/support_tickets_test.rs @@ -1,59 +1,47 @@ -use anyhow::{Ok, Result}; -use async_trait::async_trait; use wp_api::wp_com::{ client::WpComApiClient, support_tickets::{AddMessageToSupportConversationParams, CreateSupportTicketParams}, }; -use crate::Testable; +pub async fn support_tickets_test(client: &WpComApiClient) -> anyhow::Result<()> { + println!("== Support Tickets Test =="); + let new_conversation = client + .support_tickets() + .create_support_ticket(&CreateSupportTicketParams { + subject: "Mobile Support Test Message".to_string(), + message: "This is a test โ€“ it can be deleted without replying.".to_string(), + application: "jetpack".to_string(), + wpcom_site_id: None, + tags: vec!["jetpack_mobile".to_string(), "test".to_string()], + attachments: vec![], + }) + .await? + .data; + println!("โœ… Create Conversation"); -pub struct SupportTicketsTest<'a> { - pub client: &'a WpComApiClient, -} - -#[async_trait] -impl Testable for SupportTicketsTest<'_> { - async fn test(&self) -> Result<(), anyhow::Error> { - println!("== Support Tickets Test =="); - let new_conversation = self - .client - .support_tickets() - .create_support_ticket(&CreateSupportTicketParams { - subject: "Mobile Support Test Message".to_string(), - message: "This is a test โ€“ it can be deleted without replying.".to_string(), - application: "jetpack".to_string(), - wpcom_site_id: None, - tags: vec!["jetpack_mobile".to_string(), "test".to_string()], + client + .support_tickets() + .add_message_to_support_conversation( + &new_conversation.id, + &AddMessageToSupportConversationParams { + message: "Test Message".to_string(), attachments: vec![], - }) - .await? - .data; - println!("โœ… Create Conversation"); - - self.client - .support_tickets() - .add_message_to_support_conversation( - &new_conversation.id, - &AddMessageToSupportConversationParams { - message: "Test Message".to_string(), - attachments: vec![], - }, - ) - .await?; - println!("โœ… Add Message to Conversation"); + }, + ) + .await?; + println!("โœ… Add Message to Conversation"); - self.client - .support_tickets() - .get_support_conversation(&new_conversation.id) - .await?; - println!("โœ… Fetch Conversation"); + client + .support_tickets() + .get_support_conversation(&new_conversation.id) + .await?; + println!("โœ… Fetch Conversation"); - self.client - .support_tickets() - .get_support_conversation_list() - .await?; - println!("โœ… Fetch Conversation List"); + client + .support_tickets() + .get_support_conversation_list() + .await?; + println!("โœ… Fetch Conversation List"); - Ok(()) - } + Ok(()) }