@@ -81,3 +81,186 @@ export async function fetchIssues(params: FetchIssuesParams): Promise<unknown> {
8181}
8282
8383
84+ export interface FetchIssueCommentsParams {
85+ apiKey : string ;
86+ apiBaseUrl : string ;
87+ issueId : string ;
88+ debug ?: boolean ;
89+ }
90+
91+ export async function fetchIssueComments ( params : FetchIssueCommentsParams ) : Promise < unknown > {
92+ const { apiKey, apiBaseUrl, issueId, debug } = params ;
93+ if ( ! apiKey ) {
94+ throw new Error ( "API key is required" ) ;
95+ }
96+ if ( ! issueId ) {
97+ throw new Error ( "issueId is required" ) ;
98+ }
99+
100+ const base = normalizeBaseUrl ( apiBaseUrl ) ;
101+ const url = new URL ( `${ base } /issue_comments?issue_id=eq.${ encodeURIComponent ( issueId ) } ` ) ;
102+
103+ const headers : Record < string , string > = {
104+ "access-token" : apiKey ,
105+ "Prefer" : "return=representation" ,
106+ "Content-Type" : "application/json" ,
107+ } ;
108+
109+ if ( debug ) {
110+ const debugHeaders : Record < string , string > = { ...headers , "access-token" : maskSecret ( apiKey ) } ;
111+ // eslint-disable-next-line no-console
112+ console . log ( `Debug: Resolved API base URL: ${ base } ` ) ;
113+ // eslint-disable-next-line no-console
114+ console . log ( `Debug: GET URL: ${ url . toString ( ) } ` ) ;
115+ // eslint-disable-next-line no-console
116+ console . log ( `Debug: Auth scheme: access-token` ) ;
117+ // eslint-disable-next-line no-console
118+ console . log ( `Debug: Request headers: ${ JSON . stringify ( debugHeaders ) } ` ) ;
119+ }
120+
121+ return new Promise ( ( resolve , reject ) => {
122+ const req = https . request (
123+ url ,
124+ {
125+ method : "GET" ,
126+ headers,
127+ } ,
128+ ( res ) => {
129+ let data = "" ;
130+ res . on ( "data" , ( chunk ) => ( data += chunk ) ) ;
131+ res . on ( "end" , ( ) => {
132+ if ( debug ) {
133+ // eslint-disable-next-line no-console
134+ console . log ( `Debug: Response status: ${ res . statusCode } ` ) ;
135+ // eslint-disable-next-line no-console
136+ console . log ( `Debug: Response headers: ${ JSON . stringify ( res . headers ) } ` ) ;
137+ }
138+ if ( res . statusCode && res . statusCode >= 200 && res . statusCode < 300 ) {
139+ try {
140+ const parsed = JSON . parse ( data ) ;
141+ resolve ( parsed ) ;
142+ } catch {
143+ resolve ( data ) ;
144+ }
145+ } else {
146+ let errMsg = `Failed to fetch issue comments: HTTP ${ res . statusCode } ` ;
147+ if ( data ) {
148+ try {
149+ const errObj = JSON . parse ( data ) ;
150+ errMsg += `\n${ JSON . stringify ( errObj , null , 2 ) } ` ;
151+ } catch {
152+ errMsg += `\n${ data } ` ;
153+ }
154+ }
155+ reject ( new Error ( errMsg ) ) ;
156+ }
157+ } ) ;
158+ }
159+ ) ;
160+
161+ req . on ( "error" , ( err : Error ) => reject ( err ) ) ;
162+ req . end ( ) ;
163+ } ) ;
164+ }
165+
166+ export interface CreateIssueCommentParams {
167+ apiKey : string ;
168+ apiBaseUrl : string ;
169+ issueId : string ;
170+ content : string ;
171+ parentCommentId ?: string ;
172+ debug ?: boolean ;
173+ }
174+
175+ export async function createIssueComment ( params : CreateIssueCommentParams ) : Promise < unknown > {
176+ const { apiKey, apiBaseUrl, issueId, content, parentCommentId, debug } = params ;
177+ if ( ! apiKey ) {
178+ throw new Error ( "API key is required" ) ;
179+ }
180+ if ( ! issueId ) {
181+ throw new Error ( "issueId is required" ) ;
182+ }
183+ if ( ! content ) {
184+ throw new Error ( "content is required" ) ;
185+ }
186+
187+ const base = normalizeBaseUrl ( apiBaseUrl ) ;
188+ const url = new URL ( `${ base } /rpc/issue_comment_create` ) ;
189+
190+ const bodyObj : Record < string , unknown > = {
191+ issue_id : issueId ,
192+ content : content ,
193+ } ;
194+ if ( parentCommentId ) {
195+ bodyObj . parent_comment_id = parentCommentId ;
196+ }
197+ const body = JSON . stringify ( bodyObj ) ;
198+
199+ const headers : Record < string , string > = {
200+ "access-token" : apiKey ,
201+ "Prefer" : "return=representation" ,
202+ "Content-Type" : "application/json" ,
203+ "Content-Length" : Buffer . byteLength ( body ) . toString ( ) ,
204+ } ;
205+
206+ if ( debug ) {
207+ const debugHeaders : Record < string , string > = { ...headers , "access-token" : maskSecret ( apiKey ) } ;
208+ // eslint-disable-next-line no-console
209+ console . log ( `Debug: Resolved API base URL: ${ base } ` ) ;
210+ // eslint-disable-next-line no-console
211+ console . log ( `Debug: POST URL: ${ url . toString ( ) } ` ) ;
212+ // eslint-disable-next-line no-console
213+ console . log ( `Debug: Auth scheme: access-token` ) ;
214+ // eslint-disable-next-line no-console
215+ console . log ( `Debug: Request headers: ${ JSON . stringify ( debugHeaders ) } ` ) ;
216+ // eslint-disable-next-line no-console
217+ console . log ( `Debug: Request body: ${ body } ` ) ;
218+ }
219+
220+ return new Promise ( ( resolve , reject ) => {
221+ const req = https . request (
222+ url ,
223+ {
224+ method : "POST" ,
225+ headers,
226+ } ,
227+ ( res ) => {
228+ let data = "" ;
229+ res . on ( "data" , ( chunk ) => ( data += chunk ) ) ;
230+ res . on ( "end" , ( ) => {
231+ if ( debug ) {
232+ // eslint-disable-next-line no-console
233+ console . log ( `Debug: Response status: ${ res . statusCode } ` ) ;
234+ // eslint-disable-next-line no-console
235+ console . log ( `Debug: Response headers: ${ JSON . stringify ( res . headers ) } ` ) ;
236+ }
237+ if ( res . statusCode && res . statusCode >= 200 && res . statusCode < 300 ) {
238+ try {
239+ const parsed = JSON . parse ( data ) ;
240+ resolve ( parsed ) ;
241+ } catch {
242+ resolve ( data ) ;
243+ }
244+ } else {
245+ let errMsg = `Failed to create issue comment: HTTP ${ res . statusCode } ` ;
246+ if ( data ) {
247+ try {
248+ const errObj = JSON . parse ( data ) ;
249+ errMsg += `\n${ JSON . stringify ( errObj , null , 2 ) } ` ;
250+ } catch {
251+ errMsg += `\n${ data } ` ;
252+ }
253+ }
254+ reject ( new Error ( errMsg ) ) ;
255+ }
256+ } ) ;
257+ }
258+ ) ;
259+
260+ req . on ( "error" , ( err : Error ) => reject ( err ) ) ;
261+ req . write ( body ) ;
262+ req . end ( ) ;
263+ } ) ;
264+ }
265+
266+
0 commit comments