@@ -545,8 +545,8 @@ async function updateReadmeOnGithub(octokit: Octokit, owner: string, repo: strin
545545 } catch ( error : any ) {
546546 if ( error . status === 404 && ! existingSha ) {
547547 console . log ( `[updateReadmeOnGithub] README.md not found for ${ owner } /${ repo } , creating it.` ) ;
548- const paramsForCreation = { ...paramsForUpdate } ; // Re-create params for creation
549- delete paramsForCreation . sha ; // Ensure SHA is not in creation params
548+ const paramsForCreation = { ...paramsForUpdate } ;
549+ delete paramsForCreation . sha ;
550550 try {
551551 await octokit . rest . repos . createOrUpdateFileContents ( paramsForCreation ) ;
552552 console . log ( `[updateReadmeOnGithub] README.md created successfully on GitHub for ${ owner } /${ repo } after initial 404.` ) ;
@@ -609,7 +609,7 @@ export async function saveProjectReadmeAction(prevState: SaveProjectReadmeFormSt
609609 repo,
610610 path : 'README.md' ,
611611 } ) ;
612- // @ts -ignore // Octokit types can be tricky with union types for content
612+ // @ts -ignore
613613 if ( 'sha' in readmeData && readmeData . type === 'file' ) {
614614 // @ts -ignore
615615 existingSha = readmeData . sha ;
@@ -712,7 +712,7 @@ export async function toggleProjectVisibilityAction(prevState: ToggleProjectVisi
712712 if ( oauthToken ?. accessToken ) {
713713 const octokit = new Octokit ( { auth : oauthToken . accessToken } ) ;
714714 const repoParts = updatedProjectInDb . githubRepoName . split ( '/' ) ;
715- if ( repoParts . length !== 2 || ! repoParts [ 0 ] || ! repoParts [ 1 ] ) { // Added check for empty parts
715+ if ( repoParts . length !== 2 || ! repoParts [ 0 ] || ! repoParts [ 1 ] ) {
716716 console . error ( `[toggleProjectVisibilityAction] Invalid githubRepoName format: ${ updatedProjectInDb . githubRepoName } ` ) ;
717717 return { error : `Project visibility updated in FlowUp, but GitHub repo name format is invalid.` , project : updatedProjectInDb } ;
718718 }
@@ -1169,7 +1169,7 @@ export async function linkProjectToGithubAction(
11691169
11701170 let createdRepo ;
11711171 try {
1172- console . log ( `Attempting to create repository '${ repoSlug } ' for authenticated user (OAuth) . Private: ${ repoIsPrivate } ` ) ;
1172+ console . log ( `Attempting to create repository '${ repoSlug } ' for authenticated user. Private: ${ repoIsPrivate } ` ) ;
11731173 createdRepo = await octokit . rest . repos . createForAuthenticatedUser ( {
11741174 name : repoSlug ,
11751175 private : repoIsPrivate ,
@@ -1326,6 +1326,74 @@ export async function getFileContentAction(
13261326 }
13271327}
13281328
1329+
1330+ export async function saveFileContentAction (
1331+ projectUuid : string ,
1332+ filePath : string ,
1333+ content : string ,
1334+ sha : string ,
1335+ commitMessage ?: string
1336+ ) : Promise < { success : boolean ; newSha ?: string ; error ?: string } > {
1337+ const session = await auth ( ) ;
1338+ if ( ! session ?. user ?. uuid ) {
1339+ return { success : false , error : "Authentication required." } ;
1340+ }
1341+
1342+ const project = await dbGetProjectByUuid ( projectUuid ) ;
1343+ if ( ! project || ! project . githubRepoName ) {
1344+ return { success : false , error : "Project not found or not linked to GitHub." } ;
1345+ }
1346+
1347+ const oauthToken = await dbGetUserGithubOAuthToken ( session . user . uuid ) ;
1348+ if ( ! oauthToken || ! oauthToken . accessToken ) {
1349+ return { success : false , error : "GitHub account not linked or token missing." } ;
1350+ }
1351+
1352+ const [ owner , repo ] = project . githubRepoName . split ( '/' ) ;
1353+ if ( ! owner || ! repo ) {
1354+ return { success : false , error : "Invalid GitHub repository name format." } ;
1355+ }
1356+
1357+ const octokit = new Octokit ( { auth : oauthToken . accessToken } ) ;
1358+
1359+ try {
1360+ const response = await octokit . rest . repos . createOrUpdateFileContents ( {
1361+ owner,
1362+ repo,
1363+ path : filePath ,
1364+ message : commitMessage || `Update ${ filePath } via FlowUp` ,
1365+ content : Buffer . from ( content ) . toString ( 'base64' ) ,
1366+ sha,
1367+ committer : { // Optional: use user's GitHub details if available
1368+ name : session . user . name || 'FlowUp User' ,
1369+ email : session . user . email || 'user@flowup.app' ,
1370+ } ,
1371+ author : {
1372+ name : session . user . name || 'FlowUp User' ,
1373+ email : session . user . email || 'user@flowup.app' ,
1374+ }
1375+ } ) ;
1376+
1377+ if ( response . status === 200 || response . status === 201 ) { // 200 for update, 201 for create
1378+ return { success : true , newSha : response . data . content ?. sha } ;
1379+ } else {
1380+ return { success : false , error : `GitHub API returned status ${ response . status } ` } ;
1381+ }
1382+ } catch ( error : any ) {
1383+ console . error ( `[saveFileContentAction] Error saving file ${ filePath } :` , error . status , error . message , error . response ?. data ) ;
1384+ let userMessage = `Failed to save file: ${ error . message || 'Unknown error' } ` ;
1385+ if ( error . status === 409 ) { // Conflict, SHA mismatch
1386+ userMessage = "File has been modified since you opened it. Please refresh and try again." ;
1387+ } else if ( error . status === 403 ) {
1388+ userMessage = "Permission denied. Ensure you have write access to this repository." ;
1389+ } else if ( error . status === 404 ) {
1390+ userMessage = "File not found. It might have been deleted or moved." ;
1391+ }
1392+ return { success : false , error : userMessage } ;
1393+ }
1394+ }
1395+
1396+
13291397// Placeholder for future GitHub App related auth, if needed
13301398// export async function fetchUserGithubAccessDetailsAction(): Promise<{
13311399// installation: UserGithubInstallation | null;
@@ -1345,3 +1413,4 @@ export async function getFileContentAction(
13451413// }
13461414
13471415
1416+
0 commit comments