Demonstration bridge between ATproto and GraphQL. Generate schema types and interface with the ATmosphere via GraphQL queries. Includes a TypeScript server with IDE.
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add serde support to GraphQL types

Tim Ryan 01c5e907 fbc8b558

+64 -105
+5 -7
graphql/useInventoryQuery.json
··· 12 12 "location": "Portland, OR", 13 13 "condition": "Used - Good", 14 14 "category": "Furniture", 15 - "postedAt": "2024-01-15" 15 + "posted_at": "2024-01-15" 16 16 }, 17 17 { 18 18 "id": "orbi-router", 19 19 "title": "Orbi Router", 20 20 "cost": "0", 21 - "images": [ 22 - "https://www.netgear.com/support/cloudimage/1/11/123573" 23 - ], 21 + "images": ["https://www.netgear.com/support/cloudimage/1/11/123573"], 24 22 "description": "This is an orbi wireless router. Normally it comes with satellites but\nthis one does not.", 25 23 "location": "Portland, OR", 26 24 "condition": "Used - Good", 27 25 "category": "Electronics", 28 - "postedAt": "2024-01-18" 26 + "posted_at": "2024-01-18" 29 27 }, 30 28 { 31 29 "id": "arris-router", ··· 38 36 "location": "Portland, OR", 39 37 "condition": "Used - Good", 40 38 "category": "Electronics", 41 - "postedAt": "2024-01-20" 39 + "posted_at": "2024-01-20" 42 40 }, 43 41 { 44 42 "id": "dell-soundbar", ··· 52 50 "location": "Portland, OR", 53 51 "condition": "Used - Good", 54 52 "category": "Electronics", 55 - "postedAt": "2024-01-22" 53 + "posted_at": "2024-01-22" 56 54 } 57 55 ] 58 56 },
+59 -98
src/main.rs
··· 3 3 routing::{get, on, MethodFilter}, 4 4 Extension, Router, 5 5 }; 6 - use chrono::Utc; 7 6 use dotenv::dotenv; 8 7 use juniper::{graphql_object, EmptyMutation, EmptySubscription, RootNode}; 9 8 use juniper_axum::{graphiql, graphql}; 10 - use serde_json::json; 9 + use serde::Deserialize; 11 10 use std::env; 12 11 use std::net::SocketAddr; 13 12 use std::sync::Arc; 14 13 use uuid::Uuid; 15 14 16 15 // Define our GraphQL schema types 17 - #[derive(juniper::GraphQLObject)] 16 + #[derive(juniper::GraphQLObject, Deserialize, Debug)] 18 17 struct Me { 19 18 id: String, 20 19 } 21 20 22 - #[derive(juniper::GraphQLObject)] 21 + #[derive(juniper::GraphQLObject, Deserialize, Debug)] 23 22 struct ProfileViewDetailed { 24 23 id: String, 25 24 did: String, ··· 35 34 posts_count: Option<i32>, 36 35 } 37 36 38 - #[derive(juniper::GraphQLObject)] 37 + #[derive(juniper::GraphQLObject, Deserialize, Debug)] 39 38 struct InventoryItem { 40 39 id: String, 41 40 title: String, ··· 48 47 posted_at: String, 49 48 } 50 49 51 - #[derive(juniper::GraphQLEnum)] 50 + #[derive(juniper::GraphQLEnum, Deserialize, Debug)] 52 51 enum Sender { 52 + #[graphql(name = "them")] 53 + #[serde(alias = "them")] 53 54 Them, 55 + 56 + #[graphql(name = "me")] 57 + #[serde(alias = "me")] 54 58 Me, 55 59 } 56 60 57 - #[derive(juniper::GraphQLObject)] 61 + #[derive(juniper::GraphQLObject, Deserialize, Debug)] 58 62 struct Message { 59 63 id: String, 60 64 sender: Sender, ··· 62 66 time: String, 63 67 } 64 68 65 - #[derive(juniper::GraphQLObject)] 69 + #[derive(juniper::GraphQLObject, Deserialize, Debug)] 66 70 struct Chat { 67 71 id: String, 68 72 unread: bool, ··· 71 75 messages: Vec<Message>, 72 76 } 73 77 78 + /** 79 + * Inventory Placeholder Query 80 + */ 81 + 82 + const USE_INVENTORY_QUERY: &'static str = include_str!("../graphql/useInventoryQuery.json"); 83 + 84 + #[derive(Deserialize, Debug)] 85 + pub struct InventoryQueryRoot { 86 + inventory: Vec<InventoryItem>, 87 + } 88 + 89 + #[derive(Deserialize, Debug)] 90 + pub struct InventoryQuery { 91 + data: InventoryQueryRoot, 92 + } 93 + 94 + /** 95 + * Chat Placeholder Query 96 + */ 97 + 98 + const USE_CHAT_QUERY: &'static str = include_str!("../graphql/useMessagesQuery.json"); 99 + 100 + #[derive(Deserialize, Debug)] 101 + pub struct ChatQueryRoot { 102 + chats: Vec<Chat>, 103 + } 104 + 105 + #[derive(Deserialize, Debug)] 106 + pub struct ChatQuery { 107 + data: ChatQueryRoot, 108 + } 109 + 110 + /** 111 + * Schema root 112 + */ 113 + 74 114 // Define our GraphQL schema queries 75 115 #[derive(Clone, Copy, Debug)] 76 116 pub struct Query; 77 - 78 - const placeholder_json = json!(include_str!("graphql/useInventoryQuery.json")); 79 117 80 118 #[graphql_object] 81 119 impl Query { ··· 103 141 } 104 142 105 143 async fn inventory(&self) -> Vec<InventoryItem> { 106 - vec![ 107 - InventoryItem { 108 - id: Uuid::new_v4().to_string(), 109 - title: "Vintage Record Player".to_string(), 110 - cost: "$150.00".to_string(), 111 - images: vec![ 112 - "https://example.com/record-player-1.jpg".to_string(), 113 - "https://example.com/record-player-2.jpg".to_string(), 114 - ], 115 - description: "A fully functional vintage record player from the 1970s. Works perfectly and looks great!".to_string(), 116 - location: Some("Portland, OR".to_string()), 117 - condition: Some("Good".to_string()), 118 - category: Some("Electronics".to_string()), 119 - posted_at: Utc::now().format("%Y-%m-%d %H:%M:%S").to_string(), 120 - }, 121 - InventoryItem { 122 - id: Uuid::new_v4().to_string(), 123 - title: "Leather Jacket".to_string(), 124 - cost: "$85.00".to_string(), 125 - images: vec![ 126 - "https://example.com/jacket-1.jpg".to_string(), 127 - ], 128 - description: "Genuine leather jacket in excellent condition. Slightly worn but very stylish.".to_string(), 129 - location: Some("Seattle, WA".to_string()), 130 - condition: Some("Very Good".to_string()), 131 - category: Some("Clothing".to_string()), 132 - posted_at: Utc::now().format("%Y-%m-%d %H:%M:%S").to_string(), 133 - }, 134 - InventoryItem { 135 - id: Uuid::new_v4().to_string(), 136 - title: "Antique Desk Lamp".to_string(), 137 - cost: "$40.00".to_string(), 138 - images: vec![ 139 - "https://example.com/lamp-1.jpg".to_string(), 140 - "https://example.com/lamp-2.jpg".to_string(), 141 - "https://example.com/lamp-3.jpg".to_string(), 142 - ], 143 - description: "Beautiful antique desk lamp with brass detailing. Perfect for any study or office.".to_string(), 144 - location: Some("Portland, OR".to_string()), 145 - condition: Some("Good".to_string()), 146 - category: Some("Home".to_string()), 147 - posted_at: Utc::now().format("%Y-%m-%d %H:%M:%S").to_string(), 148 - }, 149 - ] 144 + let a = serde_json::from_str(USE_INVENTORY_QUERY) 145 + .map(|q: InventoryQuery| q.data.inventory) 146 + .unwrap_or(vec![]); 147 + dbg!(&serde_json::from_str::<InventoryQuery>(USE_INVENTORY_QUERY).err()); 148 + return a; 150 149 } 151 150 152 151 async fn chats(&self) -> Vec<Chat> { 153 - vec![ 154 - Chat { 155 - id: Uuid::new_v4().to_string(), 156 - unread: true, 157 - sender: "john_doe".to_string(), 158 - avatar: Some("https://example.com/avatar1.jpg".to_string()), 159 - messages: vec![ 160 - Message { 161 - id: Uuid::new_v4().to_string(), 162 - sender: Sender::Them, 163 - text: "Hey, are you still selling that record player?".to_string(), 164 - time: Utc::now().format("%Y-%m-%d %H:%M:%S").to_string(), 165 - }, 166 - Message { 167 - id: Uuid::new_v4().to_string(), 168 - sender: Sender::Them, 169 - text: "It would be perfect for my collection!".to_string(), 170 - time: Utc::now().format("%Y-%m-%d %H:%M:%S").to_string(), 171 - }, 172 - ], 173 - }, 174 - Chat { 175 - id: Uuid::new_v4().to_string(), 176 - unread: false, 177 - sender: "jane_smith".to_string(), 178 - avatar: Some("https://example.com/avatar2.jpg".to_string()), 179 - messages: vec![ 180 - Message { 181 - id: Uuid::new_v4().to_string(), 182 - sender: Sender::Them, 183 - text: "Thanks for the jacket! It arrived safely.".to_string(), 184 - time: Utc::now().format("%Y-%m-%d %H:%M:%S").to_string(), 185 - }, 186 - Message { 187 - id: Uuid::new_v4().to_string(), 188 - sender: Sender::Me, 189 - text: "Glad to hear it! Let me know if you need anything else.".to_string(), 190 - time: Utc::now().format("%Y-%m-%d %H:%M:%S").to_string(), 191 - }, 192 - ], 193 - }, 194 - ] 152 + let a = serde_json::from_str(USE_CHAT_QUERY) 153 + .map(|q: ChatQuery| q.data.chats) 154 + .unwrap_or(vec![]); 155 + dbg!(&serde_json::from_str::<ChatQuery>(USE_CHAT_QUERY).err()); 156 + return a; 195 157 } 196 158 } 197 159 ··· 200 162 async fn homepage() -> Html<String> { 201 163 Html( 202 164 "<html><h1>Mothball GraphQL API</h1>\ 203 - <div>Visit <a href=\"/graphiql\">GraphiQL</a> for an interactive interface</div>\ 204 - <div>Visit <a href=\"/playground\">GraphQL Playground</a> for the web-based IDE</div>\ 165 + <div>Visit <a href=\"/graphiql\">GraphiQL</a> for the web-based IDE</div>\ 205 166 <div>Access the API directly at <a href=\"/graphql\">/graphql</a></div>\ 206 167 </html>" 207 168 .to_string(),