chore: removed comments for now
70257d55
6 file(s) · +454 −660
| 1 | - | import { useState, useEffect } from "react"; |
|
| 2 | - | ||
| 3 | - | const API_URL = import.meta.env.PUBLIC_API_URL || "https://api.stevedylan.dev"; |
|
| 4 | - | ||
| 5 | - | interface GuestAuthState { |
|
| 6 | - | authenticated: boolean; |
|
| 7 | - | did?: string; |
|
| 8 | - | handle?: string; |
|
| 9 | - | isGuest?: boolean; |
|
| 10 | - | loading: boolean; |
|
| 11 | - | } |
|
| 1 | + | // COMMENTS FUNCTIONALITY DISABLED - Only email reply remains |
|
| 12 | 2 | ||
| 13 | 3 | interface GuestReplyProps { |
|
| 14 | 4 | atUri: string; |
|
| 21 | 11 | postTitle, |
|
| 22 | 12 | onReplyPosted, |
|
| 23 | 13 | }: GuestReplyProps) { |
|
| 24 | - | const [authState, setAuthState] = useState<GuestAuthState>({ |
|
| 25 | - | authenticated: false, |
|
| 26 | - | loading: true, |
|
| 27 | - | }); |
|
| 28 | - | const [replyContent, setReplyContent] = useState(""); |
|
| 29 | - | const [handleInput, setHandleInput] = useState(""); |
|
| 30 | - | const [showHandleForm, setShowHandleForm] = useState(false); |
|
| 31 | - | const [isSubmitting, setIsSubmitting] = useState(false); |
|
| 32 | - | const [error, setError] = useState<string | null>(null); |
|
| 33 | - | const [success, setSuccess] = useState(false); |
|
| 34 | - | ||
| 35 | - | useEffect(() => { |
|
| 36 | - | checkAuthStatus(); |
|
| 37 | - | }, []); |
|
| 38 | - | ||
| 39 | - | const checkAuthStatus = async () => { |
|
| 40 | - | try { |
|
| 41 | - | const response = await fetch(`${API_URL}/guest-auth/status`, { |
|
| 42 | - | credentials: "include", |
|
| 43 | - | }); |
|
| 44 | - | const data = await response.json(); |
|
| 45 | - | setAuthState({ |
|
| 46 | - | authenticated: data.authenticated, |
|
| 47 | - | did: data.did, |
|
| 48 | - | handle: data.handle, |
|
| 49 | - | isGuest: data.isGuest, |
|
| 50 | - | loading: false, |
|
| 51 | - | }); |
|
| 52 | - | } catch (error) { |
|
| 53 | - | console.error("Failed to check auth status:", error); |
|
| 54 | - | setAuthState({ authenticated: false, loading: false }); |
|
| 55 | - | } |
|
| 56 | - | }; |
|
| 57 | - | ||
| 58 | - | const handleLogin = (e: React.FormEvent) => { |
|
| 59 | - | e.preventDefault(); |
|
| 60 | - | if (!handleInput.trim()) { |
|
| 61 | - | setError("Handle is required"); |
|
| 62 | - | return; |
|
| 63 | - | } |
|
| 64 | - | // Pass current URL as returnTo parameter and handle |
|
| 65 | - | const currentPath = window.location.pathname; |
|
| 66 | - | const handle = handleInput.trim().replace(/^@/, ""); // Remove leading @ if present |
|
| 67 | - | window.location.href = `${API_URL}/guest-auth/login?handle=${encodeURIComponent(handle)}&returnTo=${encodeURIComponent(currentPath)}`; |
|
| 68 | - | }; |
|
| 69 | - | ||
| 70 | - | const handleLogout = async () => { |
|
| 71 | - | try { |
|
| 72 | - | await fetch(`${API_URL}/guest-auth/logout`, { |
|
| 73 | - | method: "POST", |
|
| 74 | - | credentials: "include", |
|
| 75 | - | }); |
|
| 76 | - | setAuthState({ authenticated: false, loading: false }); |
|
| 77 | - | setReplyContent(""); |
|
| 78 | - | } catch (error) { |
|
| 79 | - | console.error("Logout failed:", error); |
|
| 80 | - | } |
|
| 81 | - | }; |
|
| 82 | - | ||
| 83 | - | const handleReply = async (e: React.FormEvent) => { |
|
| 84 | - | e.preventDefault(); |
|
| 85 | - | ||
| 86 | - | if (!replyContent.trim()) { |
|
| 87 | - | setError("Reply content is required"); |
|
| 88 | - | return; |
|
| 89 | - | } |
|
| 90 | - | ||
| 91 | - | setIsSubmitting(true); |
|
| 92 | - | setError(null); |
|
| 93 | - | setSuccess(false); |
|
| 94 | - | ||
| 95 | - | try { |
|
| 96 | - | const response = await fetch(`${API_URL}/now/reply`, { |
|
| 97 | - | method: "POST", |
|
| 98 | - | credentials: "include", |
|
| 99 | - | headers: { |
|
| 100 | - | "Content-Type": "application/json", |
|
| 101 | - | }, |
|
| 102 | - | body: JSON.stringify({ |
|
| 103 | - | parentUri: atUri, |
|
| 104 | - | content: replyContent.trim(), |
|
| 105 | - | }), |
|
| 106 | - | }); |
|
| 107 | - | ||
| 108 | - | const data = await response.json(); |
|
| 109 | - | ||
| 110 | - | if (!response.ok) { |
|
| 111 | - | throw new Error(data.error || "Failed to post reply"); |
|
| 112 | - | } |
|
| 113 | - | ||
| 114 | - | setReplyContent(""); |
|
| 115 | - | setSuccess(true); |
|
| 116 | - | setTimeout(() => setSuccess(false), 5000); |
|
| 117 | - | ||
| 118 | - | // Notify parent to refresh replies list |
|
| 119 | - | setTimeout(() => { |
|
| 120 | - | onReplyPosted?.(); |
|
| 121 | - | }, 2000); |
|
| 122 | - | } catch (err) { |
|
| 123 | - | setError(err instanceof Error ? err.message : "Failed to post reply"); |
|
| 124 | - | } finally { |
|
| 125 | - | setIsSubmitting(false); |
|
| 126 | - | } |
|
| 127 | - | }; |
|
| 128 | - | ||
| 129 | 14 | const emailSubject = encodeURIComponent(`Re: ${postTitle}`); |
|
| 130 | 15 | const mailtoLink = `mailto:contact@stevedylan.dev?subject=${emailSubject}`; |
|
| 131 | 16 | ||
| 132 | - | if (authState.loading) { |
|
| 133 | - | return ( |
|
| 134 | - | <div className="mt-8 p-6 border border-gray-700 rounded-lg"> |
|
| 135 | - | <p className="text-sm text-gray-400">Loading...</p> |
|
| 136 | - | </div> |
|
| 137 | - | ); |
|
| 138 | - | } |
|
| 139 | - | ||
| 140 | 17 | return ( |
|
| 141 | 18 | <div className="mt-8 space-y-4"> |
|
| 142 | - | <h2 className="text-xl font-bold">Reply</h2> |
|
| 143 | - | ||
| 144 | - | {!authState.authenticated ? ( |
|
| 145 | - | <div className="space-y-4"> |
|
| 146 | - | {!showHandleForm ? ( |
|
| 147 | - | <> |
|
| 148 | - | {/*<p className="text-sm text-gray-400"> |
|
| 149 | - | Sign in with your ATProto account to reply, or send an email. |
|
| 150 | - | </p>*/} |
|
| 151 | - | <div className="flex gap-3 flex-wrap"> |
|
| 152 | - | <button |
|
| 153 | - | onClick={() => setShowHandleForm(true)} |
|
| 154 | - | className="px-2 py-0.5 border border-white hover:border-gray-400 hover:text-gray-400 transition-colors text-xs" |
|
| 155 | - | > |
|
| 156 | - | Sign in with ATProto |
|
| 157 | - | </button> |
|
| 158 | - | <a |
|
| 159 | - | href={mailtoLink} |
|
| 160 | - | className="px-2 py-0.5 border border-white hover:border-gray-400 hover:text-gray-400 transition-colors text-xs" |
|
| 161 | - | > |
|
| 162 | - | Reply via Email |
|
| 163 | - | </a> |
|
| 164 | - | </div> |
|
| 165 | - | </> |
|
| 166 | - | ) : ( |
|
| 167 | - | <form onSubmit={handleLogin} className="space-y-3"> |
|
| 168 | - | <p className="text-sm text-gray-400"> |
|
| 169 | - | Enter your handle to sign in: |
|
| 170 | - | </p> |
|
| 171 | - | <div className="flex gap-2"> |
|
| 172 | - | <input |
|
| 173 | - | type="text" |
|
| 174 | - | value={handleInput} |
|
| 175 | - | onChange={(e) => setHandleInput(e.target.value)} |
|
| 176 | - | placeholder="user.bsky.social or mydomain.com" |
|
| 177 | - | className="flex-1 bg-transparent px-3 py-1 border border-white text-white text-sm" |
|
| 178 | - | /> |
|
| 179 | - | <button |
|
| 180 | - | type="submit" |
|
| 181 | - | className="px-3 py-1 border border-white hover:border-gray-400 hover:text-gray-400 transition-colors text-xs" |
|
| 182 | - | > |
|
| 183 | - | Sign in |
|
| 184 | - | </button> |
|
| 185 | - | <button |
|
| 186 | - | type="button" |
|
| 187 | - | onClick={() => { |
|
| 188 | - | setShowHandleForm(false); |
|
| 189 | - | setHandleInput(""); |
|
| 190 | - | setError(null); |
|
| 191 | - | }} |
|
| 192 | - | className="px-3 py-1 text-gray-500 hover:text-gray-300 transition-colors text-xs" |
|
| 193 | - | > |
|
| 194 | - | Cancel |
|
| 195 | - | </button> |
|
| 196 | - | </div> |
|
| 197 | - | {error && <p className="text-sm text-red-500">{error}</p>} |
|
| 198 | - | </form> |
|
| 199 | - | )} |
|
| 200 | - | </div> |
|
| 201 | - | ) : ( |
|
| 202 | - | <div className="space-y-4"> |
|
| 203 | - | <div className="flex items-center justify-between"> |
|
| 204 | - | <p className="text-sm text-gray-400"> |
|
| 205 | - | Signed in as {authState.handle || authState.did} |
|
| 206 | - | </p> |
|
| 207 | - | <button |
|
| 208 | - | onClick={handleLogout} |
|
| 209 | - | className="text-xs text-gray-500 hover:text-gray-300 transition-colors" |
|
| 210 | - | > |
|
| 211 | - | Sign out |
|
| 212 | - | </button> |
|
| 213 | - | </div> |
|
| 214 | - | ||
| 215 | - | <form onSubmit={handleReply} className="space-y-3"> |
|
| 216 | - | <textarea |
|
| 217 | - | value={replyContent} |
|
| 218 | - | onChange={(e) => setReplyContent(e.target.value)} |
|
| 219 | - | placeholder="Write your reply..." |
|
| 220 | - | rows={4} |
|
| 221 | - | className="w-full bg-transparent p-3 border border-white text-white resize-none" |
|
| 222 | - | disabled={isSubmitting} |
|
| 223 | - | /> |
|
| 224 | - | ||
| 225 | - | <div className="flex items-center justify-between"> |
|
| 226 | - | <div className="flex items-center gap-3"> |
|
| 227 | - | {error && <span className="text-sm text-red-500">{error}</span>} |
|
| 228 | - | {success && ( |
|
| 229 | - | <span className="text-sm text-white"> |
|
| 230 | - | Reply posted successfully! |
|
| 231 | - | </span> |
|
| 232 | - | )} |
|
| 233 | - | </div> |
|
| 234 | - | ||
| 235 | - | <div className="flex gap-3"> |
|
| 236 | - | <button |
|
| 237 | - | type="submit" |
|
| 238 | - | disabled={isSubmitting || !replyContent.trim()} |
|
| 239 | - | className="px-4 py-0.5 border border-white hover:border-gray-400 hover:text-gray-400 disabled:border-opacity-50 disabled:cursor-not-allowed transition-colors text-xs" |
|
| 240 | - | > |
|
| 241 | - | {isSubmitting ? "Posting..." : "Post Reply"} |
|
| 242 | - | </button> |
|
| 243 | - | </div> |
|
| 244 | - | </div> |
|
| 245 | - | </form> |
|
| 246 | - | </div> |
|
| 247 | - | )} |
|
| 19 | + | <div className="flex gap-3 flex-wrap"> |
|
| 20 | + | <a |
|
| 21 | + | href={mailtoLink} |
|
| 22 | + | className="px-2 py-0.5 border border-white hover:border-gray-400 hover:text-gray-400 transition-colors text-xs" |
|
| 23 | + | > |
|
| 24 | + | Reply via Email |
|
| 25 | + | </a> |
|
| 26 | + | </div> |
|
| 248 | 27 | </div> |
|
| 249 | 28 | ); |
|
| 250 | 29 | } |
|
| 1 | - | import { useState } from "react"; |
|
| 2 | - | import { ReplyList } from "./ReplyList"; |
|
| 1 | + | // COMMENTS FUNCTIONALITY DISABLED - Only email reply remains |
|
| 2 | + | // import { useState } from "react"; |
|
| 3 | + | // import { ReplyList } from "./ReplyList"; |
|
| 3 | 4 | import { GuestReply } from "./GuestReply"; |
|
| 4 | 5 | ||
| 5 | 6 | interface ReplyContainerProps { |
|
| 8 | 9 | } |
|
| 9 | 10 | ||
| 10 | 11 | export function ReplyContainer({ atUri, postTitle }: ReplyContainerProps) { |
|
| 11 | - | const [refreshKey, setRefreshKey] = useState(0); |
|
| 12 | + | // const [refreshKey, setRefreshKey] = useState(0); |
|
| 12 | 13 | ||
| 13 | - | const handleReplyPosted = () => { |
|
| 14 | - | // Increment key to force ReplyList to re-fetch |
|
| 15 | - | setRefreshKey((prev) => prev + 1); |
|
| 16 | - | }; |
|
| 14 | + | // const handleReplyPosted = () => { |
|
| 15 | + | // setRefreshKey((prev) => prev + 1); |
|
| 16 | + | // }; |
|
| 17 | 17 | ||
| 18 | 18 | return ( |
|
| 19 | 19 | <> |
|
| 20 | - | <ReplyList key={refreshKey} atUri={atUri} /> |
|
| 20 | + | {/* <ReplyList key={refreshKey} atUri={atUri} /> */} |
|
| 21 | 21 | <GuestReply |
|
| 22 | 22 | atUri={atUri} |
|
| 23 | 23 | postTitle={postTitle} |
|
| 24 | - | onReplyPosted={handleReplyPosted} |
|
| 24 | + | // onReplyPosted={handleReplyPosted} |
|
| 25 | 25 | /> |
|
| 26 | 26 | </> |
|
| 27 | 27 | ); |
|
| 1 | - | import { useState, useEffect } from "react"; |
|
| 1 | + | // COMMENTS FUNCTIONALITY DISABLED |
|
| 2 | + | // import { useState, useEffect } from "react"; |
|
| 2 | 3 | ||
| 3 | - | const API_URL = import.meta.env.PUBLIC_API_URL || "https://api.stevedylan.dev"; |
|
| 4 | + | // const API_URL = import.meta.env.PUBLIC_API_URL || "https://api.stevedylan.dev"; |
|
| 4 | 5 | ||
| 5 | - | interface Author { |
|
| 6 | - | did: string; |
|
| 7 | - | handle: string; |
|
| 8 | - | displayName?: string; |
|
| 9 | - | avatar?: string; |
|
| 10 | - | } |
|
| 6 | + | // interface Author { |
|
| 7 | + | // did: string; |
|
| 8 | + | // handle: string; |
|
| 9 | + | // displayName?: string; |
|
| 10 | + | // avatar?: string; |
|
| 11 | + | // } |
|
| 11 | 12 | ||
| 12 | - | interface CommentReference { |
|
| 13 | - | createdAt: string; |
|
| 14 | - | did: string; |
|
| 15 | - | uri: string; |
|
| 16 | - | } |
|
| 13 | + | // interface CommentReference { |
|
| 14 | + | // createdAt: string; |
|
| 15 | + | // did: string; |
|
| 16 | + | // uri: string; |
|
| 17 | + | // } |
|
| 17 | 18 | ||
| 18 | - | interface Reply { |
|
| 19 | - | uri: string; |
|
| 20 | - | cid: string; |
|
| 21 | - | author: Author; |
|
| 22 | - | root: { |
|
| 23 | - | cid: string; |
|
| 24 | - | uri: string; |
|
| 25 | - | }; |
|
| 26 | - | parent: { |
|
| 27 | - | cid: string; |
|
| 28 | - | uri: string; |
|
| 29 | - | }; |
|
| 30 | - | content: string; |
|
| 31 | - | createdAt: string; |
|
| 32 | - | $type: string; |
|
| 33 | - | } |
|
| 19 | + | // interface Reply { |
|
| 20 | + | // uri: string; |
|
| 21 | + | // cid: string; |
|
| 22 | + | // author: Author; |
|
| 23 | + | // root: { |
|
| 24 | + | // cid: string; |
|
| 25 | + | // uri: string; |
|
| 26 | + | // }; |
|
| 27 | + | // parent: { |
|
| 28 | + | // cid: string; |
|
| 29 | + | // uri: string; |
|
| 30 | + | // }; |
|
| 31 | + | // content: string; |
|
| 32 | + | // createdAt: string; |
|
| 33 | + | // $type: string; |
|
| 34 | + | // } |
|
| 34 | 35 | ||
| 35 | - | interface ReplyListProps { |
|
| 36 | - | atUri: string; |
|
| 37 | - | } |
|
| 36 | + | // interface ReplyListProps { |
|
| 37 | + | // atUri: string; |
|
| 38 | + | // } |
|
| 38 | 39 | ||
| 39 | - | export function ReplyList({ atUri }: ReplyListProps) { |
|
| 40 | - | const [replies, setReplies] = useState<Reply[]>([]); |
|
| 41 | - | const [loading, setLoading] = useState(true); |
|
| 42 | - | const [error, setError] = useState<string | null>(null); |
|
| 40 | + | // export function ReplyList({ atUri }: ReplyListProps) { |
|
| 41 | + | // const [replies, setReplies] = useState<Reply[]>([]); |
|
| 42 | + | // const [loading, setLoading] = useState(true); |
|
| 43 | + | // const [error, setError] = useState<string | null>(null); |
|
| 43 | 44 | ||
| 44 | - | useEffect(() => { |
|
| 45 | - | fetchReplies(); |
|
| 46 | - | }, [atUri]); |
|
| 45 | + | // useEffect(() => { |
|
| 46 | + | // fetchReplies(); |
|
| 47 | + | // }, [atUri]); |
|
| 47 | 48 | ||
| 48 | - | const fetchReplies = async () => { |
|
| 49 | - | try { |
|
| 50 | - | setLoading(true); |
|
| 51 | - | setError(null); |
|
| 49 | + | // const fetchReplies = async () => { |
|
| 50 | + | // try { |
|
| 51 | + | // setLoading(true); |
|
| 52 | + | // setError(null); |
|
| 52 | 53 | ||
| 53 | - | const encodedUri = encodeURIComponent(atUri); |
|
| 54 | - | const response = await fetch(`${API_URL}/now/comments/${encodedUri}`); |
|
| 54 | + | // const encodedUri = encodeURIComponent(atUri); |
|
| 55 | + | // const response = await fetch(`${API_URL}/now/comments/${encodedUri}`); |
|
| 55 | 56 | ||
| 56 | - | if (!response.ok) { |
|
| 57 | - | throw new Error("Failed to fetch comments"); |
|
| 58 | - | } |
|
| 57 | + | // if (!response.ok) { |
|
| 58 | + | // throw new Error("Failed to fetch comments"); |
|
| 59 | + | // } |
|
| 59 | 60 | ||
| 60 | - | const data = await response.json(); |
|
| 61 | - | setReplies(data.replies || []); |
|
| 62 | - | } catch (err) { |
|
| 63 | - | console.error("Error fetching replies:", err); |
|
| 64 | - | setError(err instanceof Error ? err.message : "Failed to load replies"); |
|
| 65 | - | } finally { |
|
| 66 | - | setLoading(false); |
|
| 67 | - | } |
|
| 68 | - | }; |
|
| 61 | + | // const data = await response.json(); |
|
| 62 | + | // setReplies(data.replies || []); |
|
| 63 | + | // } catch (err) { |
|
| 64 | + | // console.error("Error fetching replies:", err); |
|
| 65 | + | // setError(err instanceof Error ? err.message : "Failed to load replies"); |
|
| 66 | + | // } finally { |
|
| 67 | + | // setLoading(false); |
|
| 68 | + | // } |
|
| 69 | + | // }; |
|
| 70 | + | ||
| 71 | + | // const formatDate = (dateString: string) => { |
|
| 72 | + | // const date = new Date(dateString); |
|
| 73 | + | // const now = new Date(); |
|
| 74 | + | // const diffInSeconds = Math.floor((now.getTime() - date.getTime()) / 1000); |
|
| 75 | + | ||
| 76 | + | // if (diffInSeconds < 60) { |
|
| 77 | + | // return `${diffInSeconds}s ago`; |
|
| 78 | + | // } else if (diffInSeconds < 3600) { |
|
| 79 | + | // return `${Math.floor(diffInSeconds / 60)}m ago`; |
|
| 80 | + | // } else if (diffInSeconds < 86400) { |
|
| 81 | + | // return `${Math.floor(diffInSeconds / 3600)}h ago`; |
|
| 82 | + | // } else if (diffInSeconds < 604800) { |
|
| 83 | + | // return `${Math.floor(diffInSeconds / 86400)}d ago`; |
|
| 84 | + | // } else { |
|
| 85 | + | // return date.toLocaleDateString(); |
|
| 86 | + | // } |
|
| 87 | + | // }; |
|
| 69 | 88 | ||
| 70 | - | const formatDate = (dateString: string) => { |
|
| 71 | - | const date = new Date(dateString); |
|
| 72 | - | const now = new Date(); |
|
| 73 | - | const diffInSeconds = Math.floor((now.getTime() - date.getTime()) / 1000); |
|
| 89 | + | // if (loading) { |
|
| 90 | + | // return ( |
|
| 91 | + | // <div className="mt-8"> |
|
| 92 | + | // <h3 className="text-lg font-bold mb-4">Replies</h3> |
|
| 93 | + | // <p className="text-sm text-gray-400">Loading replies...</p> |
|
| 94 | + | // </div> |
|
| 95 | + | // ); |
|
| 96 | + | // } |
|
| 74 | 97 | ||
| 75 | - | if (diffInSeconds < 60) { |
|
| 76 | - | return `${diffInSeconds}s ago`; |
|
| 77 | - | } else if (diffInSeconds < 3600) { |
|
| 78 | - | return `${Math.floor(diffInSeconds / 60)}m ago`; |
|
| 79 | - | } else if (diffInSeconds < 86400) { |
|
| 80 | - | return `${Math.floor(diffInSeconds / 3600)}h ago`; |
|
| 81 | - | } else if (diffInSeconds < 604800) { |
|
| 82 | - | return `${Math.floor(diffInSeconds / 86400)}d ago`; |
|
| 83 | - | } else { |
|
| 84 | - | return date.toLocaleDateString(); |
|
| 85 | - | } |
|
| 86 | - | }; |
|
| 98 | + | // if (error) { |
|
| 99 | + | // return ( |
|
| 100 | + | // <div className="mt-8"> |
|
| 101 | + | // <h3 className="text-lg font-bold mb-4">Replies</h3> |
|
| 102 | + | // <p className="text-sm text-red-500">{error}</p> |
|
| 103 | + | // </div> |
|
| 104 | + | // ); |
|
| 105 | + | // } |
|
| 87 | 106 | ||
| 88 | - | if (loading) { |
|
| 89 | - | return ( |
|
| 90 | - | <div className="mt-8"> |
|
| 91 | - | <h3 className="text-lg font-bold mb-4">Replies</h3> |
|
| 92 | - | <p className="text-sm text-gray-400">Loading replies...</p> |
|
| 93 | - | </div> |
|
| 94 | - | ); |
|
| 95 | - | } |
|
| 107 | + | // if (replies.length === 0) { |
|
| 108 | + | // return ( |
|
| 109 | + | // <div className="mt-8"> |
|
| 110 | + | // <h3 className="text-lg font-bold mb-4">Replies</h3> |
|
| 111 | + | // <p className="text-sm text-gray-400"> |
|
| 112 | + | // No replies yet. Be the first to reply! |
|
| 113 | + | // </p> |
|
| 114 | + | // </div> |
|
| 115 | + | // ); |
|
| 116 | + | // } |
|
| 96 | 117 | ||
| 97 | - | if (error) { |
|
| 98 | - | return ( |
|
| 99 | - | <div className="mt-8"> |
|
| 100 | - | <h3 className="text-lg font-bold mb-4">Replies</h3> |
|
| 101 | - | <p className="text-sm text-red-500">{error}</p> |
|
| 102 | - | </div> |
|
| 103 | - | ); |
|
| 104 | - | } |
|
| 118 | + | // return ( |
|
| 119 | + | // <div className="mt-8"> |
|
| 120 | + | // <h3 className="text-lg font-bold mb-4">Replies</h3> |
|
| 121 | + | // <div className="space-y-6"> |
|
| 122 | + | // {replies.map((reply) => ( |
|
| 123 | + | // <div key={reply.uri}> |
|
| 124 | + | // <div className="flex items-start gap-3"> |
|
| 125 | + | // {reply.author.avatar ? ( |
|
| 126 | + | // <img |
|
| 127 | + | // src={reply.author.avatar} |
|
| 128 | + | // alt={reply.author.handle} |
|
| 129 | + | // className="w-10 h-10 rounded-full" |
|
| 130 | + | // /> |
|
| 131 | + | // ) : ( |
|
| 132 | + | // <div className="w-10 h-10 rounded-full bg-gray-700 flex items-center justify-center"> |
|
| 133 | + | // <span className="text-gray-400 text-sm"> |
|
| 134 | + | // {reply.author.handle.charAt(0).toUpperCase()} |
|
| 135 | + | // </span> |
|
| 136 | + | // </div> |
|
| 137 | + | // )} |
|
| 105 | 138 | ||
| 106 | - | if (replies.length === 0) { |
|
| 107 | - | return ( |
|
| 108 | - | <div className="mt-8"> |
|
| 109 | - | <h3 className="text-lg font-bold mb-4">Replies</h3> |
|
| 110 | - | <p className="text-sm text-gray-400"> |
|
| 111 | - | No replies yet. Be the first to reply! |
|
| 112 | - | </p> |
|
| 113 | - | </div> |
|
| 114 | - | ); |
|
| 115 | - | } |
|
| 139 | + | // <div className="flex-1 min-w-0"> |
|
| 140 | + | // <div className="flex items-center gap-2 flex-wrap"> |
|
| 141 | + | // <span className="font-semibold text-sm"> |
|
| 142 | + | // {reply.author.displayName || reply.author.handle} |
|
| 143 | + | // </span> |
|
| 144 | + | // {reply.author.displayName && ( |
|
| 145 | + | // <a |
|
| 146 | + | // href={`https://pdsls.dev/at://${reply.author.did}`} |
|
| 147 | + | // target="_blank" |
|
| 148 | + | // rel="noopener noreferrer" |
|
| 149 | + | // className="text-xs text-gray-400 hover:text-gray-300" |
|
| 150 | + | // > |
|
| 151 | + | // @{reply.author.handle} |
|
| 152 | + | // </a> |
|
| 153 | + | // )} |
|
| 154 | + | // <a |
|
| 155 | + | // href={`https://pdsls.dev/${reply.uri}`} |
|
| 156 | + | // target="_blank" |
|
| 157 | + | // rel="noopener noreferrer" |
|
| 158 | + | // className="text-xs text-gray-400 hover:text-gray-300" |
|
| 159 | + | // > |
|
| 160 | + | // {formatDate(reply.createdAt)} |
|
| 161 | + | // </a> |
|
| 162 | + | // </div> |
|
| 116 | 163 | ||
| 117 | - | return ( |
|
| 118 | - | <div className="mt-8"> |
|
| 119 | - | <h3 className="text-lg font-bold mb-4">Replies</h3> |
|
| 120 | - | <div className="space-y-6"> |
|
| 121 | - | {replies.map((reply) => ( |
|
| 122 | - | <div key={reply.uri}> |
|
| 123 | - | <div className="flex items-start gap-3"> |
|
| 124 | - | {reply.author.avatar ? ( |
|
| 125 | - | <img |
|
| 126 | - | src={reply.author.avatar} |
|
| 127 | - | alt={reply.author.handle} |
|
| 128 | - | className="w-10 h-10 rounded-full" |
|
| 129 | - | /> |
|
| 130 | - | ) : ( |
|
| 131 | - | <div className="w-10 h-10 rounded-full bg-gray-700 flex items-center justify-center"> |
|
| 132 | - | <span className="text-gray-400 text-sm"> |
|
| 133 | - | {reply.author.handle.charAt(0).toUpperCase()} |
|
| 134 | - | </span> |
|
| 135 | - | </div> |
|
| 136 | - | )} |
|
| 164 | + | // <p className="mt-2 text-sm whitespace-pre-wrap break-words"> |
|
| 165 | + | // {reply.content} |
|
| 166 | + | // </p> |
|
| 167 | + | // </div> |
|
| 168 | + | // </div> |
|
| 169 | + | // </div> |
|
| 170 | + | // ))} |
|
| 171 | + | // </div> |
|
| 172 | + | // </div> |
|
| 173 | + | // ); |
|
| 174 | + | // } |
|
| 137 | 175 | ||
| 138 | - | <div className="flex-1 min-w-0"> |
|
| 139 | - | <div className="flex items-center gap-2 flex-wrap"> |
|
| 140 | - | <span className="font-semibold text-sm"> |
|
| 141 | - | {reply.author.displayName || reply.author.handle} |
|
| 142 | - | </span> |
|
| 143 | - | {reply.author.displayName && ( |
|
| 144 | - | <a |
|
| 145 | - | href={`https://pdsls.dev/at://${reply.author.did}`} |
|
| 146 | - | target="_blank" |
|
| 147 | - | rel="noopener noreferrer" |
|
| 148 | - | className="text-xs text-gray-400 hover:text-gray-300" |
|
| 149 | - | > |
|
| 150 | - | @{reply.author.handle} |
|
| 151 | - | </a> |
|
| 152 | - | )} |
|
| 153 | - | <a |
|
| 154 | - | href={`https://pdsls.dev/${reply.uri}`} |
|
| 155 | - | target="_blank" |
|
| 156 | - | rel="noopener noreferrer" |
|
| 157 | - | className="text-xs text-gray-400 hover:text-gray-300" |
|
| 158 | - | > |
|
| 159 | - | {formatDate(reply.createdAt)} |
|
| 160 | - | </a> |
|
| 161 | - | </div> |
|
| 176 | + | interface ReplyListProps { |
|
| 177 | + | atUri: string; |
|
| 178 | + | } |
|
| 162 | 179 | ||
| 163 | - | <p className="mt-2 text-sm whitespace-pre-wrap break-words"> |
|
| 164 | - | {reply.content} |
|
| 165 | - | </p> |
|
| 166 | - | </div> |
|
| 167 | - | </div> |
|
| 168 | - | </div> |
|
| 169 | - | ))} |
|
| 170 | - | </div> |
|
| 171 | - | </div> |
|
| 172 | - | ); |
|
| 180 | + | export function ReplyList({ atUri }: ReplyListProps) { |
|
| 181 | + | return null; |
|
| 173 | 182 | } |
| 1 | 1 | import { Hono } from "hono"; |
|
| 2 | 2 | import { cors } from "hono/cors"; |
|
| 3 | - | import { home, now, auth, guestAuth } from "./routes"; |
|
| 3 | + | // COMMENTS FUNCTIONALITY DISABLED |
|
| 4 | + | // import { home, now, auth, guestAuth } from "./routes"; |
|
| 5 | + | import { home, now, auth } from "./routes"; |
|
| 4 | 6 | ||
| 5 | 7 | interface Env { |
|
| 6 | 8 | SESSIONS: KVNamespace; |
|
| 32 | 34 | app.route("/", home); |
|
| 33 | 35 | app.route("/now", now); |
|
| 34 | 36 | app.route("/auth", auth); |
|
| 35 | - | app.route("/guest-auth", guestAuth); |
|
| 37 | + | // COMMENTS FUNCTIONALITY DISABLED |
|
| 38 | + | // app.route("/guest-auth", guestAuth); |
|
| 36 | 39 | ||
| 37 | 40 | export default app; |
|
| 1 | 1 | export { default as home } from "./home"; |
|
| 2 | 2 | export { default as now } from "./now"; |
|
| 3 | 3 | export { default as auth } from "./auth"; |
|
| 4 | - | export { default as guestAuth } from "./guest-auth"; |
|
| 4 | + | // COMMENTS FUNCTIONALITY DISABLED |
|
| 5 | + | // export { default as guestAuth } from "./guest-auth"; |
| 22 | 22 | ||
| 23 | 23 | const PDS_URL = "https://andromeda.social"; |
|
| 24 | 24 | ||
| 25 | - | // Helper function to get session for both admin and guest users |
|
| 26 | - | async function getAnySession(c: any, sessionId: string) { |
|
| 27 | - | if (sessionId.startsWith("guest_")) { |
|
| 28 | - | // Guest session |
|
| 29 | - | const originalSessionId = await c.env.SESSIONS.get( |
|
| 30 | - | `guest_session:${sessionId}`, |
|
| 31 | - | ); |
|
| 32 | - | if (!originalSessionId) return null; |
|
| 33 | - | return await getSession(c.env.SESSIONS, originalSessionId); |
|
| 34 | - | } else { |
|
| 35 | - | // Admin session |
|
| 36 | - | return await getSession(c.env.SESSIONS, sessionId); |
|
| 37 | - | } |
|
| 38 | - | } |
|
| 25 | + | // COMMENTS FUNCTIONALITY DISABLED |
|
| 26 | + | // // Helper function to get session for both admin and guest users |
|
| 27 | + | // async function getAnySession(c: any, sessionId: string) { |
|
| 28 | + | // if (sessionId.startsWith("guest_")) { |
|
| 29 | + | // // Guest session |
|
| 30 | + | // const originalSessionId = await c.env.SESSIONS.get( |
|
| 31 | + | // `guest_session:${sessionId}`, |
|
| 32 | + | // ); |
|
| 33 | + | // if (!originalSessionId) return null; |
|
| 34 | + | // return await getSession(c.env.SESSIONS, originalSessionId); |
|
| 35 | + | // } else { |
|
| 36 | + | // // Admin session |
|
| 37 | + | // return await getSession(c.env.SESSIONS, sessionId); |
|
| 38 | + | // } |
|
| 39 | + | // } |
|
| 39 | 40 | ||
| 40 | 41 | // Create a new post |
|
| 41 | 42 | now.post("/post", async (c) => { |
|
| 328 | 329 | } |
|
| 329 | 330 | }); |
|
| 330 | 331 | ||
| 331 | - | // Create a reply to a post (for guests) |
|
| 332 | - | now.post("/reply", async (c) => { |
|
| 333 | - | try { |
|
| 334 | - | // Get session from cookie |
|
| 335 | - | const sessionId = getSessionIdFromCookie(c); |
|
| 336 | - | if (!sessionId) { |
|
| 337 | - | return c.json({ error: "Not authenticated" }, 401); |
|
| 338 | - | } |
|
| 332 | + | // COMMENTS FUNCTIONALITY DISABLED |
|
| 333 | + | // // Create a reply to a post (for guests) |
|
| 334 | + | // now.post("/reply", async (c) => { |
|
| 335 | + | // try { |
|
| 336 | + | // // Get session from cookie |
|
| 337 | + | // const sessionId = getSessionIdFromCookie(c); |
|
| 338 | + | // if (!sessionId) { |
|
| 339 | + | // return c.json({ error: "Not authenticated" }, 401); |
|
| 340 | + | // } |
|
| 339 | 341 | ||
| 340 | - | const sessionData = await getAnySession(c, sessionId); |
|
| 341 | - | if (!sessionData) { |
|
| 342 | - | return c.json({ error: "Session not found" }, 401); |
|
| 343 | - | } |
|
| 342 | + | // const sessionData = await getAnySession(c, sessionId); |
|
| 343 | + | // if (!sessionData) { |
|
| 344 | + | // return c.json({ error: "Session not found" }, 401); |
|
| 345 | + | // } |
|
| 344 | 346 | ||
| 345 | - | let { session, dpopKeyPair } = sessionData; |
|
| 347 | + | // let { session, dpopKeyPair } = sessionData; |
|
| 346 | 348 | ||
| 347 | - | // Determine which PDS to use (user's PDS for guests, env PDS for admin) |
|
| 348 | - | const isGuest = sessionId.startsWith("guest_"); |
|
| 349 | - | const pdsUrl = isGuest && session.pdsUrl ? session.pdsUrl : c.env.PDS_URL; |
|
| 349 | + | // // Determine which PDS to use (user's PDS for guests, env PDS for admin) |
|
| 350 | + | // const isGuest = sessionId.startsWith("guest_"); |
|
| 351 | + | // const pdsUrl = isGuest && session.pdsUrl ? session.pdsUrl : c.env.PDS_URL; |
|
| 350 | 352 | ||
| 351 | - | // Refresh token if expired |
|
| 352 | - | if (isTokenExpired(session.expiresAt) && session.refreshToken) { |
|
| 353 | - | const metadata = await fetchOAuthMetadata(pdsUrl); |
|
| 354 | - | const clientId = isGuest |
|
| 355 | - | ? `${c.env.API_URL}/guest-auth/client-metadata.json` |
|
| 356 | - | : `${c.env.API_URL}/auth/client-metadata.json`; |
|
| 353 | + | // // Refresh token if expired |
|
| 354 | + | // if (isTokenExpired(session.expiresAt) && session.refreshToken) { |
|
| 355 | + | // const metadata = await fetchOAuthMetadata(pdsUrl); |
|
| 356 | + | // const clientId = isGuest |
|
| 357 | + | // ? `${c.env.API_URL}/guest-auth/client-metadata.json` |
|
| 358 | + | // : `${c.env.API_URL}/auth/client-metadata.json`; |
|
| 357 | 359 | ||
| 358 | - | const { tokenResponse, dpopNonce } = await refreshAccessToken( |
|
| 359 | - | metadata, |
|
| 360 | - | session.refreshToken, |
|
| 361 | - | clientId, |
|
| 362 | - | dpopKeyPair, |
|
| 363 | - | session.dpopNonce, |
|
| 364 | - | ); |
|
| 360 | + | // const { tokenResponse, dpopNonce } = await refreshAccessToken( |
|
| 361 | + | // metadata, |
|
| 362 | + | // session.refreshToken, |
|
| 363 | + | // clientId, |
|
| 364 | + | // dpopKeyPair, |
|
| 365 | + | // session.dpopNonce, |
|
| 366 | + | // ); |
|
| 365 | 367 | ||
| 366 | - | // Get the actual session ID for update |
|
| 367 | - | const actualSessionId = isGuest |
|
| 368 | - | ? (await c.env.SESSIONS.get(`guest_session:${sessionId}`)) || "" |
|
| 369 | - | : sessionId; |
|
| 368 | + | // // Get the actual session ID for update |
|
| 369 | + | // const actualSessionId = isGuest |
|
| 370 | + | // ? (await c.env.SESSIONS.get(`guest_session:${sessionId}`)) || "" |
|
| 371 | + | // : sessionId; |
|
| 370 | 372 | ||
| 371 | - | // Update session with new tokens |
|
| 372 | - | await updateSession( |
|
| 373 | - | c.env.SESSIONS, |
|
| 374 | - | actualSessionId, |
|
| 375 | - | tokenResponse.access_token, |
|
| 376 | - | tokenResponse.refresh_token || session.refreshToken, |
|
| 377 | - | dpopNonce, |
|
| 378 | - | tokenResponse.expires_in, |
|
| 379 | - | ); |
|
| 373 | + | // // Update session with new tokens |
|
| 374 | + | // await updateSession( |
|
| 375 | + | // c.env.SESSIONS, |
|
| 376 | + | // actualSessionId, |
|
| 377 | + | // tokenResponse.access_token, |
|
| 378 | + | // tokenResponse.refresh_token || session.refreshToken, |
|
| 379 | + | // dpopNonce, |
|
| 380 | + | // tokenResponse.expires_in, |
|
| 381 | + | // ); |
|
| 380 | 382 | ||
| 381 | - | // Update local session object |
|
| 382 | - | session.accessToken = tokenResponse.access_token; |
|
| 383 | - | session.dpopNonce = dpopNonce; |
|
| 384 | - | } |
|
| 383 | + | // // Update local session object |
|
| 384 | + | // session.accessToken = tokenResponse.access_token; |
|
| 385 | + | // session.dpopNonce = dpopNonce; |
|
| 386 | + | // } |
|
| 385 | 387 | ||
| 386 | - | // Parse request body |
|
| 387 | - | const body = await c.req.json<{ |
|
| 388 | - | parentUri: string; |
|
| 389 | - | content: string; |
|
| 390 | - | }>(); |
|
| 388 | + | // // Parse request body |
|
| 389 | + | // const body = await c.req.json<{ |
|
| 390 | + | // parentUri: string; |
|
| 391 | + | // content: string; |
|
| 392 | + | // }>(); |
|
| 391 | 393 | ||
| 392 | - | if (!body.parentUri || body.parentUri.trim().length === 0) { |
|
| 393 | - | return c.json({ error: "Parent URI is required" }, 400); |
|
| 394 | - | } |
|
| 394 | + | // if (!body.parentUri || body.parentUri.trim().length === 0) { |
|
| 395 | + | // return c.json({ error: "Parent URI is required" }, 400); |
|
| 396 | + | // } |
|
| 395 | 397 | ||
| 396 | - | if (!body.content || body.content.trim().length === 0) { |
|
| 397 | - | return c.json({ error: "Content is required" }, 400); |
|
| 398 | - | } |
|
| 398 | + | // if (!body.content || body.content.trim().length === 0) { |
|
| 399 | + | // return c.json({ error: "Content is required" }, 400); |
|
| 400 | + | // } |
|
| 399 | 401 | ||
| 400 | - | // Fetch the parent post to get its CID (use owner's PDS since that's where the post lives) |
|
| 401 | - | const getRecordUrl = |
|
| 402 | - | `${c.env.PDS_URL}/xrpc/com.atproto.repo.getRecord?` + |
|
| 403 | - | new URLSearchParams({ |
|
| 404 | - | repo: body.parentUri.split("/")[2], // Extract DID from URI |
|
| 405 | - | collection: body.parentUri.split("/")[3], // Extract collection |
|
| 406 | - | rkey: body.parentUri.split("/")[4], // Extract rkey |
|
| 407 | - | }); |
|
| 402 | + | // // Fetch the parent post to get its CID (use owner's PDS since that's where the post lives) |
|
| 403 | + | // const getRecordUrl = |
|
| 404 | + | // `${c.env.PDS_URL}/xrpc/com.atproto.repo.getRecord?` + |
|
| 405 | + | // new URLSearchParams({ |
|
| 406 | + | // repo: body.parentUri.split("/")[2], // Extract DID from URI |
|
| 407 | + | // collection: body.parentUri.split("/")[3], // Extract collection |
|
| 408 | + | // rkey: body.parentUri.split("/")[4], // Extract rkey |
|
| 409 | + | // }); |
|
| 408 | 410 | ||
| 409 | - | const parentResponse = await fetch(getRecordUrl); |
|
| 410 | - | if (!parentResponse.ok) { |
|
| 411 | - | console.error("Failed to fetch parent post"); |
|
| 412 | - | return c.json({ error: "Failed to fetch parent post" }, 400); |
|
| 413 | - | } |
|
| 411 | + | // const parentResponse = await fetch(getRecordUrl); |
|
| 412 | + | // if (!parentResponse.ok) { |
|
| 413 | + | // console.error("Failed to fetch parent post"); |
|
| 414 | + | // return c.json({ error: "Failed to fetch parent post" }, 400); |
|
| 415 | + | // } |
|
| 414 | 416 | ||
| 415 | - | const parentData = (await parentResponse.json()) as { cid: string }; |
|
| 416 | - | const parentCid = parentData.cid; |
|
| 417 | + | // const parentData = (await parentResponse.json()) as { cid: string }; |
|
| 418 | + | // const parentCid = parentData.cid; |
|
| 417 | 419 | ||
| 418 | - | // Fetch author profile to get handle, displayName, and avatar from Bluesky public API |
|
| 419 | - | let authorHandle = session.did; |
|
| 420 | - | let authorDisplayName: string | undefined; |
|
| 421 | - | let authorAvatar: string | undefined; |
|
| 420 | + | // // Fetch author profile to get handle, displayName, and avatar from Bluesky public API |
|
| 421 | + | // let authorHandle = session.did; |
|
| 422 | + | // let authorDisplayName: string | undefined; |
|
| 423 | + | // let authorAvatar: string | undefined; |
|
| 422 | 424 | ||
| 423 | - | try { |
|
| 424 | - | const profileUrl = `https://public.api.bsky.app/xrpc/app.bsky.actor.getProfile?actor=${session.did}`; |
|
| 425 | - | const profileResponse = await fetch(profileUrl); |
|
| 426 | - | if (profileResponse.ok) { |
|
| 427 | - | const profileData = (await profileResponse.json()) as { |
|
| 428 | - | handle?: string; |
|
| 429 | - | displayName?: string; |
|
| 430 | - | avatar?: string; |
|
| 431 | - | }; |
|
| 432 | - | authorHandle = profileData.handle || session.did; |
|
| 433 | - | authorDisplayName = profileData.displayName; |
|
| 434 | - | authorAvatar = profileData.avatar; |
|
| 435 | - | } |
|
| 436 | - | } catch (err) { |
|
| 437 | - | console.error("Failed to fetch author profile:", err); |
|
| 438 | - | } |
|
| 425 | + | // try { |
|
| 426 | + | // const profileUrl = `https://public.api.bsky.app/xrpc/app.bsky.actor.getProfile?actor=${session.did}`; |
|
| 427 | + | // const profileResponse = await fetch(profileUrl); |
|
| 428 | + | // if (profileResponse.ok) { |
|
| 429 | + | // const profileData = (await profileResponse.json()) as { |
|
| 430 | + | // handle?: string; |
|
| 431 | + | // displayName?: string; |
|
| 432 | + | // avatar?: string; |
|
| 433 | + | // }; |
|
| 434 | + | // authorHandle = profileData.handle || session.did; |
|
| 435 | + | // authorDisplayName = profileData.displayName; |
|
| 436 | + | // authorAvatar = profileData.avatar; |
|
| 437 | + | // } |
|
| 438 | + | // } catch (err) { |
|
| 439 | + | // console.error("Failed to fetch author profile:", err); |
|
| 440 | + | // } |
|
| 439 | 441 | ||
| 440 | - | // Create the comment record using site.standard.document.comment lexicon |
|
| 441 | - | // Use the user's PDS URL since the record is stored in THEIR repo |
|
| 442 | - | const createRecordUrl = `${pdsUrl}/xrpc/com.atproto.repo.createRecord`; |
|
| 442 | + | // // Create the comment record using site.standard.document.comment lexicon |
|
| 443 | + | // // Use the user's PDS URL since the record is stored in THEIR repo |
|
| 444 | + | // const createRecordUrl = `${pdsUrl}/xrpc/com.atproto.repo.createRecord`; |
|
| 443 | 445 | ||
| 444 | - | const commentRecord = { |
|
| 445 | - | repo: session.did, |
|
| 446 | - | collection: "site.standard.document.comment", |
|
| 447 | - | record: { |
|
| 448 | - | $type: "site.standard.document.comment", |
|
| 449 | - | parent: { |
|
| 450 | - | uri: body.parentUri, |
|
| 451 | - | cid: parentCid, |
|
| 452 | - | }, |
|
| 453 | - | root: { |
|
| 454 | - | uri: body.parentUri, |
|
| 455 | - | cid: parentCid, |
|
| 456 | - | }, |
|
| 457 | - | content: body.content.trim(), |
|
| 458 | - | author: { |
|
| 459 | - | did: session.did, |
|
| 460 | - | handle: authorHandle, |
|
| 461 | - | ...(authorDisplayName && { displayName: authorDisplayName }), |
|
| 462 | - | ...(authorAvatar && { avatar: authorAvatar }), |
|
| 463 | - | }, |
|
| 464 | - | createdAt: new Date().toISOString(), |
|
| 465 | - | }, |
|
| 466 | - | }; |
|
| 446 | + | // const commentRecord = { |
|
| 447 | + | // repo: session.did, |
|
| 448 | + | // collection: "site.standard.document.comment", |
|
| 449 | + | // record: { |
|
| 450 | + | // $type: "site.standard.document.comment", |
|
| 451 | + | // parent: { |
|
| 452 | + | // uri: body.parentUri, |
|
| 453 | + | // cid: parentCid, |
|
| 454 | + | // }, |
|
| 455 | + | // root: { |
|
| 456 | + | // uri: body.parentUri, |
|
| 457 | + | // cid: parentCid, |
|
| 458 | + | // }, |
|
| 459 | + | // content: body.content.trim(), |
|
| 460 | + | // author: { |
|
| 461 | + | // did: session.did, |
|
| 462 | + | // handle: authorHandle, |
|
| 463 | + | // ...(authorDisplayName && { displayName: authorDisplayName }), |
|
| 464 | + | // ...(authorAvatar && { avatar: authorAvatar }), |
|
| 465 | + | // }, |
|
| 466 | + | // createdAt: new Date().toISOString(), |
|
| 467 | + | // }, |
|
| 468 | + | // }; |
|
| 467 | 469 | ||
| 468 | - | // Make request with DPoP |
|
| 469 | - | const makeRequest = async (nonce?: string): Promise<Response> => { |
|
| 470 | - | const dpopProof = await createDPoPProof(dpopKeyPair, { |
|
| 471 | - | method: "POST", |
|
| 472 | - | url: createRecordUrl, |
|
| 473 | - | nonce: nonce || session.dpopNonce, |
|
| 474 | - | accessToken: session.accessToken, |
|
| 475 | - | }); |
|
| 470 | + | // // Make request with DPoP |
|
| 471 | + | // const makeRequest = async (nonce?: string): Promise<Response> => { |
|
| 472 | + | // const dpopProof = await createDPoPProof(dpopKeyPair, { |
|
| 473 | + | // method: "POST", |
|
| 474 | + | // url: createRecordUrl, |
|
| 475 | + | // nonce: nonce || session.dpopNonce, |
|
| 476 | + | // accessToken: session.accessToken, |
|
| 477 | + | // }); |
|
| 476 | 478 | ||
| 477 | - | return fetch(createRecordUrl, { |
|
| 478 | - | method: "POST", |
|
| 479 | - | headers: { |
|
| 480 | - | "Content-Type": "application/json", |
|
| 481 | - | Authorization: `DPoP ${session.accessToken}`, |
|
| 482 | - | DPoP: dpopProof, |
|
| 483 | - | }, |
|
| 484 | - | body: JSON.stringify(commentRecord), |
|
| 485 | - | }); |
|
| 486 | - | }; |
|
| 479 | + | // return fetch(createRecordUrl, { |
|
| 480 | + | // method: "POST", |
|
| 481 | + | // headers: { |
|
| 482 | + | // "Content-Type": "application/json", |
|
| 483 | + | // Authorization: `DPoP ${session.accessToken}`, |
|
| 484 | + | // DPoP: dpopProof, |
|
| 485 | + | // }, |
|
| 486 | + | // body: JSON.stringify(commentRecord), |
|
| 487 | + | // }); |
|
| 488 | + | // }; |
|
| 487 | 489 | ||
| 488 | - | let response = await makeRequest(); |
|
| 490 | + | // let response = await makeRequest(); |
|
| 489 | 491 | ||
| 490 | - | // Handle DPoP nonce requirement |
|
| 491 | - | if (response.status === 401) { |
|
| 492 | - | const newNonce = extractDPoPNonce(response); |
|
| 493 | - | if (newNonce) { |
|
| 494 | - | // Retry with new nonce |
|
| 495 | - | response = await makeRequest(newNonce); |
|
| 492 | + | // // Handle DPoP nonce requirement |
|
| 493 | + | // if (response.status === 401) { |
|
| 494 | + | // const newNonce = extractDPoPNonce(response); |
|
| 495 | + | // if (newNonce) { |
|
| 496 | + | // // Retry with new nonce |
|
| 497 | + | // response = await makeRequest(newNonce); |
|
| 496 | 498 | ||
| 497 | - | // Get the actual session ID for update |
|
| 498 | - | const actualSessionId = isGuest |
|
| 499 | - | ? (await c.env.SESSIONS.get(`guest_session:${sessionId}`)) || "" |
|
| 500 | - | : sessionId; |
|
| 499 | + | // // Get the actual session ID for update |
|
| 500 | + | // const actualSessionId = isGuest |
|
| 501 | + | // ? (await c.env.SESSIONS.get(`guest_session:${sessionId}`)) || "" |
|
| 502 | + | // : sessionId; |
|
| 501 | 503 | ||
| 502 | - | // Update session with new nonce |
|
| 503 | - | await updateSession( |
|
| 504 | - | c.env.SESSIONS, |
|
| 505 | - | actualSessionId, |
|
| 506 | - | session.accessToken, |
|
| 507 | - | session.refreshToken, |
|
| 508 | - | newNonce, |
|
| 509 | - | Math.floor((session.expiresAt - Date.now()) / 1000), |
|
| 510 | - | ); |
|
| 511 | - | } |
|
| 512 | - | } |
|
| 504 | + | // // Update session with new nonce |
|
| 505 | + | // await updateSession( |
|
| 506 | + | // c.env.SESSIONS, |
|
| 507 | + | // actualSessionId, |
|
| 508 | + | // session.accessToken, |
|
| 509 | + | // session.refreshToken, |
|
| 510 | + | // newNonce, |
|
| 511 | + | // Math.floor((session.expiresAt - Date.now()) / 1000), |
|
| 512 | + | // ); |
|
| 513 | + | // } |
|
| 514 | + | // } |
|
| 513 | 515 | ||
| 514 | - | if (!response.ok) { |
|
| 515 | - | const errorData = await response.json(); |
|
| 516 | - | console.error("Failed to create reply:", errorData); |
|
| 517 | - | return c.json( |
|
| 518 | - | { error: "Failed to create reply", details: errorData }, |
|
| 519 | - | response.status as 400 | 401 | 403 | 500, |
|
| 520 | - | ); |
|
| 521 | - | } |
|
| 516 | + | // if (!response.ok) { |
|
| 517 | + | // const errorData = await response.json(); |
|
| 518 | + | // console.error("Failed to create reply:", errorData); |
|
| 519 | + | // return c.json( |
|
| 520 | + | // { error: "Failed to create reply", details: errorData }, |
|
| 521 | + | // response.status as 400 | 401 | 403 | 500, |
|
| 522 | + | // ); |
|
| 523 | + | // } |
|
| 522 | 524 | ||
| 523 | - | const result = (await response.json()) as { uri: string; cid: string }; |
|
| 524 | - | return c.json({ success: true, uri: result.uri, cid: result.cid }); |
|
| 525 | - | } catch (error) { |
|
| 526 | - | console.error("Error creating reply:", error); |
|
| 527 | - | return c.json({ error: "Internal server error" }, 500); |
|
| 528 | - | } |
|
| 529 | - | }); |
|
| 525 | + | // const result = (await response.json()) as { uri: string; cid: string }; |
|
| 526 | + | // return c.json({ success: true, uri: result.uri, cid: result.cid }); |
|
| 527 | + | // } catch (error) { |
|
| 528 | + | // console.error("Error creating reply:", error); |
|
| 529 | + | // return c.json({ error: "Internal server error" }, 500); |
|
| 530 | + | // } |
|
| 531 | + | // }); |
|
| 530 | 532 | ||
| 531 | - | // Get comments for a post via TAP API |
|
| 532 | - | now.get("/comments/:uri", async (c) => { |
|
| 533 | - | try { |
|
| 534 | - | const encodedUri = c.req.param("uri"); |
|
| 535 | - | const uri = decodeURIComponent(encodedUri); |
|
| 533 | + | // // Get comments for a post via TAP API |
|
| 534 | + | // now.get("/comments/:uri", async (c) => { |
|
| 535 | + | // try { |
|
| 536 | + | // const encodedUri = c.req.param("uri"); |
|
| 537 | + | // const uri = decodeURIComponent(encodedUri); |
|
| 536 | 538 | ||
| 537 | - | // First, get the list of comment URIs from TAP API |
|
| 538 | - | const tapUrl = `https://tap.stevedylan.dev/comments?document=${encodeURIComponent(uri)}`; |
|
| 539 | - | const response = await fetch(tapUrl); |
|
| 539 | + | // // First, get the list of comment URIs from TAP API |
|
| 540 | + | // const tapUrl = `https://tap.stevedylan.dev/comments?document=${encodeURIComponent(uri)}`; |
|
| 541 | + | // const response = await fetch(tapUrl); |
|
| 540 | 542 | ||
| 541 | - | if (!response.ok) { |
|
| 542 | - | console.error("Failed to fetch comment list from TAP:", response.status); |
|
| 543 | - | return c.json({ replies: [] }); |
|
| 544 | - | } |
|
| 543 | + | // if (!response.ok) { |
|
| 544 | + | // console.error("Failed to fetch comment list from TAP:", response.status); |
|
| 545 | + | // return c.json({ replies: [] }); |
|
| 546 | + | // } |
|
| 545 | 547 | ||
| 546 | - | interface CommentReference { |
|
| 547 | - | createdAt: string; |
|
| 548 | - | did: string; |
|
| 549 | - | uri: string; |
|
| 550 | - | } |
|
| 548 | + | // interface CommentReference { |
|
| 549 | + | // createdAt: string; |
|
| 550 | + | // did: string; |
|
| 551 | + | // uri: string; |
|
| 552 | + | // } |
|
| 551 | 553 | ||
| 552 | - | const commentRefs: CommentReference[] = await response.json(); |
|
| 554 | + | // const commentRefs: CommentReference[] = await response.json(); |
|
| 553 | 555 | ||
| 554 | - | // Fetch each individual comment using ATProto getRecord |
|
| 555 | - | const commentPromises = commentRefs.map(async (ref) => { |
|
| 556 | - | try { |
|
| 557 | - | // Parse the AT URI: at://did:plc:.../collection/rkey |
|
| 558 | - | const parts = ref.uri.split("/"); |
|
| 559 | - | const did = parts[2]; |
|
| 560 | - | const collection = parts[3]; |
|
| 561 | - | const rkey = parts[4]; |
|
| 556 | + | // // Fetch each individual comment using ATProto getRecord |
|
| 557 | + | // const commentPromises = commentRefs.map(async (ref) => { |
|
| 558 | + | // try { |
|
| 559 | + | // // Parse the AT URI: at://did:plc:.../collection/rkey |
|
| 560 | + | // const parts = ref.uri.split("/"); |
|
| 561 | + | // const did = parts[2]; |
|
| 562 | + | // const collection = parts[3]; |
|
| 563 | + | // const rkey = parts[4]; |
|
| 562 | 564 | ||
| 563 | - | // Resolve the DID to find the PDS endpoint |
|
| 564 | - | const didDoc = (await fetch(`https://plc.directory/${did}`).then((r) => |
|
| 565 | - | r.json(), |
|
| 566 | - | )) as { |
|
| 567 | - | service?: Array<{ type: string; serviceEndpoint: string }>; |
|
| 568 | - | }; |
|
| 565 | + | // // Resolve the DID to find the PDS endpoint |
|
| 566 | + | // const didDoc = (await fetch(`https://plc.directory/${did}`).then((r) => |
|
| 567 | + | // r.json(), |
|
| 568 | + | // )) as { |
|
| 569 | + | // service?: Array<{ type: string; serviceEndpoint: string }>; |
|
| 570 | + | // }; |
|
| 569 | 571 | ||
| 570 | - | // Find the PDS service endpoint |
|
| 571 | - | const pdsService = didDoc.service?.find( |
|
| 572 | - | (s) => s.type === "AtprotoPersonalDataServer", |
|
| 573 | - | ); |
|
| 572 | + | // // Find the PDS service endpoint |
|
| 573 | + | // const pdsService = didDoc.service?.find( |
|
| 574 | + | // (s) => s.type === "AtprotoPersonalDataServer", |
|
| 575 | + | // ); |
|
| 574 | 576 | ||
| 575 | - | if (!pdsService?.serviceEndpoint) { |
|
| 576 | - | console.error(`No PDS found for DID: ${did}`); |
|
| 577 | - | return null; |
|
| 578 | - | } |
|
| 577 | + | // if (!pdsService?.serviceEndpoint) { |
|
| 578 | + | // console.error(`No PDS found for DID: ${did}`); |
|
| 579 | + | // return null; |
|
| 580 | + | // } |
|
| 579 | 581 | ||
| 580 | - | const pdsUrl = pdsService.serviceEndpoint; |
|
| 582 | + | // const pdsUrl = pdsService.serviceEndpoint; |
|
| 581 | 583 | ||
| 582 | - | // Fetch the record from the user's PDS |
|
| 583 | - | const getRecordUrl = |
|
| 584 | - | `${pdsUrl}/xrpc/com.atproto.repo.getRecord?` + |
|
| 585 | - | new URLSearchParams({ |
|
| 586 | - | repo: did, |
|
| 587 | - | collection: collection, |
|
| 588 | - | rkey: rkey, |
|
| 589 | - | }); |
|
| 584 | + | // // Fetch the record from the user's PDS |
|
| 585 | + | // const getRecordUrl = |
|
| 586 | + | // `${pdsUrl}/xrpc/com.atproto.repo.getRecord?` + |
|
| 587 | + | // new URLSearchParams({ |
|
| 588 | + | // repo: did, |
|
| 589 | + | // collection: collection, |
|
| 590 | + | // rkey: rkey, |
|
| 591 | + | // }); |
|
| 590 | 592 | ||
| 591 | - | const recordResponse = await fetch(getRecordUrl); |
|
| 592 | - | if (!recordResponse.ok) { |
|
| 593 | - | console.error( |
|
| 594 | - | `Failed to fetch comment from PDS ${pdsUrl}: ${ref.uri}`, |
|
| 595 | - | ); |
|
| 596 | - | return null; |
|
| 597 | - | } |
|
| 593 | + | // const recordResponse = await fetch(getRecordUrl); |
|
| 594 | + | // if (!recordResponse.ok) { |
|
| 595 | + | // console.error( |
|
| 596 | + | // `Failed to fetch comment from PDS ${pdsUrl}: ${ref.uri}`, |
|
| 597 | + | // ); |
|
| 598 | + | // return null; |
|
| 599 | + | // } |
|
| 598 | 600 | ||
| 599 | - | const data = (await recordResponse.json()) as { |
|
| 600 | - | value: Record<string, unknown>; |
|
| 601 | - | cid: string; |
|
| 602 | - | }; |
|
| 603 | - | return { |
|
| 604 | - | ...data.value, |
|
| 605 | - | uri: ref.uri, |
|
| 606 | - | cid: data.cid, |
|
| 607 | - | }; |
|
| 608 | - | } catch (err) { |
|
| 609 | - | console.error(`Error fetching comment ${ref.uri}:`, err); |
|
| 610 | - | return null; |
|
| 611 | - | } |
|
| 612 | - | }); |
|
| 601 | + | // const data = (await recordResponse.json()) as { |
|
| 602 | + | // value: Record<string, unknown>; |
|
| 603 | + | // cid: string; |
|
| 604 | + | // }; |
|
| 605 | + | // return { |
|
| 606 | + | // ...data.value, |
|
| 607 | + | // uri: ref.uri, |
|
| 608 | + | // cid: data.cid, |
|
| 609 | + | // }; |
|
| 610 | + | // } catch (err) { |
|
| 611 | + | // console.error(`Error fetching comment ${ref.uri}:`, err); |
|
| 612 | + | // return null; |
|
| 613 | + | // } |
|
| 614 | + | // }); |
|
| 613 | 615 | ||
| 614 | - | const comments = await Promise.all(commentPromises); |
|
| 615 | - | const validComments = comments.filter((comment) => comment !== null); |
|
| 616 | + | // const comments = await Promise.all(commentPromises); |
|
| 617 | + | // const validComments = comments.filter((comment) => comment !== null); |
|
| 616 | 618 | ||
| 617 | - | return c.json({ replies: validComments }); |
|
| 618 | - | } catch (error) { |
|
| 619 | - | console.error("Error fetching comments:", error); |
|
| 620 | - | return c.json({ replies: [] }); |
|
| 621 | - | } |
|
| 622 | - | }); |
|
| 619 | + | // return c.json({ replies: validComments }); |
|
| 620 | + | // } catch (error) { |
|
| 621 | + | // console.error("Error fetching comments:", error); |
|
| 622 | + | // return c.json({ replies: [] }); |
|
| 623 | + | // } |
|
| 624 | + | // }); |
|
| 623 | 625 | ||
| 624 | 626 | export default now; |
|