Skip to content

Commit 3499562

Browse files
Runtime Error
Server TypeError: Cannot read properties of null (readin
1 parent 0dea2a8 commit 3499562

1 file changed

Lines changed: 79 additions & 41 deletions

File tree

src/app/(app)/projects/[id]/actions.ts

Lines changed: 79 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ import {
3232
deleteProjectAnnouncement as dbDeleteProjectAnnouncement,
3333
updateProjectGithubRepo as dbUpdateProjectGithubRepo,
3434
getUserGithubOAuthToken as dbGetUserGithubOAuthToken,
35-
getUserGithubInstallation,
35+
// getUserGithubInstallation, // Not used directly, part of GitHub client
3636
} from '@/lib/db';
3737
import { z } from 'zod';
3838
import { auth } from '@/lib/authEdge';
@@ -565,8 +565,8 @@ async function updateReadmeOnGithub(octokit: Octokit, owner: string, repo: strin
565565
message: 'Update README.md from FlowUp',
566566
content: Buffer.from(content).toString('base64'),
567567
committer: {
568-
name: 'FlowUp Bot',
569-
email: 'bot@flowup.app',
568+
name: 'FlowUp Bot', // Or use the user's GitHub name if available
569+
email: 'bot@flowup.app', // Or user's email if allowed and consented
570570
},
571571
};
572572
if (existingSha) {
@@ -579,8 +579,6 @@ async function updateReadmeOnGithub(octokit: Octokit, owner: string, repo: strin
579579
} catch (error: any) {
580580
if (error.status === 404 && !existingSha) {
581581
console.log(`[updateReadmeOnGithub] README.md not found for ${owner}/${repo}, creating it.`);
582-
// If it was a 404 and we didn't have an existing SHA, it means the file doesn't exist.
583-
// We can try creating it without the SHA.
584582
const paramsForCreation = { ...paramsForUpdate };
585583
delete paramsForCreation.sha;
586584
try {
@@ -597,6 +595,7 @@ async function updateReadmeOnGithub(octokit: Octokit, owner: string, repo: strin
597595
}
598596
}
599597

598+
600599
export async function saveProjectReadmeAction(prevState: SaveProjectReadmeFormState, formData: FormData): Promise<SaveProjectReadmeFormState> {
601600
const session = await auth();
602601
if (!session?.user?.uuid) {
@@ -625,7 +624,17 @@ export async function saveProjectReadmeAction(prevState: SaveProjectReadmeFormSt
625624
const oauthToken = await dbGetUserGithubOAuthToken(session.user.uuid);
626625
if (oauthToken?.accessToken) {
627626
const octokit = new Octokit({ auth: oauthToken.accessToken });
628-
const [owner, repo] = updatedProject.githubRepoName.split('/');
627+
628+
const repoParts = updatedProject.githubRepoName.split('/');
629+
if (repoParts.length !== 2) {
630+
console.error(`[saveProjectReadmeAction] Invalid githubRepoName format: ${updatedProject.githubRepoName}`);
631+
return { message: "README saved in FlowUp, but GitHub repo name format is invalid for GitHub update.", project: updatedProject };
632+
}
633+
const [owner, repo] = repoParts;
634+
if (!owner || !repo) {
635+
console.error(`[saveProjectReadmeAction] Invalid owner or repo after splitting githubRepoName: ${updatedProject.githubRepoName}`);
636+
return { message: "README saved in FlowUp, but GitHub owner or repo name is invalid for GitHub update.", project: updatedProject };
637+
}
629638

630639
let existingSha: string | null = null;
631640
try {
@@ -1112,15 +1121,23 @@ export async function fetchUserGithubOAuthTokenAction(): Promise<UserGithubOAuth
11121121
}
11131122
}
11141123

1115-
function sanitizeRepoName(name: string): string {
1116-
let sanitized = name
1117-
.trim()
1118-
.replace(/\s+/g, '-')
1119-
.replace(/[^a-zA-Z0-9_.-]/g, '')
1120-
.replace(/-+/g, '-')
1121-
.replace(/^\.+|\.$|^-+|-+$/g, '');
1124+
function sanitizeRepoName(name: string | null | undefined): string {
1125+
if (!name) {
1126+
return '';
1127+
}
1128+
const trimmedName = name.trim();
1129+
if (trimmedName === '') {
1130+
return '';
1131+
}
11221132

1123-
return sanitized.substring(0, 100);
1133+
let sanitized = trimmedName
1134+
.replace(/\s+/g, '-') // Replace spaces with hyphens
1135+
.replace(/[^a-zA-Z0-9_.-]/g, '') // Remove invalid characters
1136+
.replace(/-+/g, '-') // Replace multiple hyphens with a single one
1137+
.replace(/^\.+|\.$/g, '') // Remove leading/trailing dots
1138+
.replace(/^-+|-+$/g, ''); // Remove leading/trailing hyphens
1139+
1140+
return sanitized.substring(0, 100); // GitHub repo name max length is 100
11241141
}
11251142

11261143

@@ -1140,28 +1157,35 @@ export async function linkProjectToGithubAction(
11401157
}
11411158

11421159
const projectUuid = formData.get('projectUuid') as string;
1143-
const flowUpProjectName = formData.get('flowUpProjectName') as string;
1144-
const githubRepoNameInput = formData.get('githubRepoName') as string;
1160+
const flowUpProjectNameValue = formData.get('flowUpProjectName');
1161+
const githubRepoNameValue = formData.get('githubRepoName');
11451162
const useDefaultRepoName = formData.get('useDefaultRepoName') === 'true';
11461163

1164+
let nameForRepoCreation: string;
11471165

1148-
if (!projectUuid || !flowUpProjectName) {
1149-
return { error: "Project UUID and FlowUp Project Name are required." };
1166+
if (useDefaultRepoName) {
1167+
if (typeof flowUpProjectNameValue === 'string' && flowUpProjectNameValue.trim() !== '') {
1168+
nameForRepoCreation = `FlowUp-${flowUpProjectNameValue}`;
1169+
} else {
1170+
return { error: "FlowUp Project Name is required to generate the default repository name." };
1171+
}
1172+
} else {
1173+
if (typeof githubRepoNameValue === 'string' && githubRepoNameValue.trim() !== '') {
1174+
nameForRepoCreation = githubRepoNameValue;
1175+
} else {
1176+
return { error: "Custom repository name cannot be empty." };
1177+
}
1178+
}
1179+
1180+
const repoSlug = sanitizeRepoName(nameForRepoCreation);
1181+
if (!repoSlug) {
1182+
return { error: "Resulting repository name is invalid after sanitization. Please provide a valid name." };
11501183
}
11511184

11521185
const projectFromDb = await dbGetProjectByUuid(projectUuid);
11531186
if (!projectFromDb) {
11541187
return { error: "FlowUp project not found." };
11551188
}
1156-
1157-
const repoSlug = useDefaultRepoName
1158-
? sanitizeRepoName(`FlowUp-${flowUpProjectName}`)
1159-
: sanitizeRepoName(githubRepoNameInput);
1160-
1161-
if (!repoSlug) {
1162-
return { error: "Invalid repository name. Please provide a valid name or use the default." };
1163-
}
1164-
11651189
const repoIsPrivate = projectFromDb.isPrivate !== undefined ? projectFromDb.isPrivate : true;
11661190

11671191

@@ -1185,28 +1209,35 @@ export async function linkProjectToGithubAction(
11851209
createdRepo = await octokit.rest.repos.createForAuthenticatedUser({
11861210
name: repoSlug,
11871211
private: repoIsPrivate,
1188-
description: `Repository for FlowUp project: ${flowUpProjectName}`,
1212+
description: `Repository for FlowUp project: ${projectFromDb.name}`,
11891213
auto_init: true,
11901214
});
11911215
console.log(`Successfully created repository: ${createdRepo.data.html_url}`);
11921216

11931217
if (projectFromDb.readmeContent && projectFromDb.readmeContent.trim() !== '') {
1194-
const { data: readmeData } = await octokit.rest.repos.getContent({
1195-
owner: createdRepo.data.owner.login,
1196-
repo: createdRepo.data.name,
1197-
path: 'README.md',
1198-
});
1199-
let existingReadmeSha: string | undefined = undefined;
1200-
if ('sha' in readmeData && readmeData.type === 'file') {
1201-
existingReadmeSha = readmeData.sha;
1218+
// Fetch existing README's SHA to avoid errors if it was created by auto_init
1219+
let existingReadmeSha: string | undefined = undefined;
1220+
try {
1221+
const { data: readmeData } = await octokit.rest.repos.getContent({
1222+
owner: createdRepo.data.owner.login,
1223+
repo: createdRepo.data.name,
1224+
path: 'README.md',
1225+
});
1226+
if ('sha' in readmeData && readmeData.type === 'file') { // Check if readmeData is a file object
1227+
existingReadmeSha = readmeData.sha;
1228+
}
1229+
} catch (getContentError: any) {
1230+
if (getContentError.status !== 404) { // Log error if not a "file not found"
1231+
console.warn(`[linkProjectToGithubAction] Could not fetch existing README SHA for ${createdRepo.data.owner.login}/${createdRepo.data.name}:`, getContentError.message);
1232+
}
12021233
}
12031234
await updateReadmeOnGithub(octokit, createdRepo.data.owner.login, createdRepo.data.name, projectFromDb.readmeContent, existingReadmeSha);
12041235
}
12051236

12061237
} catch (apiError: any) {
12071238
console.error(`GitHub API error creating repository: ${apiError.status} ${apiError.message}`, apiError.response?.data);
12081239
const errorMessage = apiError.response?.data?.message || apiError.message || 'Unknown GitHub API error.';
1209-
if (apiError.status === 422) {
1240+
if (apiError.status === 422) { // Unprocessable Entity (e.g., repo already exists)
12101241
return { error: `Failed to create GitHub repository '${repoSlug}'. It might already exist or there's a naming conflict. GitHub's message: ${errorMessage}` };
12111242
}
12121243
return { error: `GitHub API Error (${apiError.status || 'unknown'}): ${errorMessage} - ${apiError.documentation_url || ''}` };
@@ -1215,7 +1246,7 @@ export async function linkProjectToGithubAction(
12151246
const repoUrl = createdRepo.data.html_url;
12161247
const actualRepoName = createdRepo.data.full_name;
12171248

1218-
const updatedProject = await dbUpdateProjectGithubRepo(projectUuid, repoUrl, actualRepoName, null);
1249+
const updatedProject = await dbUpdateProjectGithubRepo(projectUuid, repoUrl, actualRepoName);
12191250

12201251
if (!updatedProject) {
12211252
return { error: "Failed to save GitHub repository details to FlowUp project after creation on GitHub." };
@@ -1229,9 +1260,13 @@ export async function linkProjectToGithubAction(
12291260
}
12301261
}
12311262

1263+
/*
1264+
// This function might be needed if we switch back to GitHub App installation flow for repo creation
1265+
// For now, with OAuth flow, it's not directly used for creating repos but good for fetching installation details
1266+
// if we need to interact with the app installation itself.
12321267
export async function fetchUserGithubAccessDetailsAction(userUuid: string): Promise<{
12331268
oauthToken: UserGithubOAuthToken | null;
1234-
installation: UserGithubInstallation | null;
1269+
installation: UserGithubInstallation | null; // Placeholder, not fully implemented for OAuth flow
12351270
}> {
12361271
const session = await auth();
12371272
if (!session?.user?.uuid || session.user.uuid !== userUuid) {
@@ -1240,10 +1275,13 @@ export async function fetchUserGithubAccessDetailsAction(userUuid: string): Prom
12401275
}
12411276
try {
12421277
const oauthToken = await dbGetUserGithubOAuthToken(userUuid);
1243-
const installation = await getUserGithubInstallation(userUuid);
1244-
return { oauthToken, installation };
1278+
// const installation = await getUserGithubInstallation(userUuid); // This is for GitHub App installs
1279+
return { oauthToken, installation: null }; // Return null for installation in pure OAuth flow
12451280
} catch (error) {
12461281
console.error("[fetchUserGithubAccessDetailsAction] Error fetching GitHub access details:", error);
12471282
return { oauthToken: null, installation: null };
12481283
}
12491284
}
1285+
*/
1286+
1287+
```

0 commit comments

Comments
 (0)