Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fixed repo_config bug on setup #293

Merged
merged 19 commits into from
Apr 8, 2024
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
811d6ee
fixed repo_config bug on setup
MuskanPaliwal Mar 29, 2024
69d6df3
removed unnecessory new lines
MuskanPaliwal Mar 29, 2024
d6ac835
added `on conflict` condition in insert repo_config query
MuskanPaliwal Mar 29, 2024
20ad19a
Addressed pR comments, tested the operation
MuskanPaliwal Apr 1, 2024
f40340e
removed logs
MuskanPaliwal Apr 1, 2024
b730e21
removed unnecessory return statements
MuskanPaliwal Apr 1, 2024
efaaa0c
created function to remove repo_config for repos that are not in the …
MuskanPaliwal Apr 4, 2024
7add7ca
created just one function to save setup repos in db, handled cases wh…
MuskanPaliwal Apr 4, 2024
690cec3
return 0th element of queryReuslt rows in case of multiple users
MuskanPaliwal Apr 4, 2024
18d2bdd
fixed `install_id` error
MuskanPaliwal Apr 4, 2024
b230788
removed unused import
MuskanPaliwal Apr 4, 2024
2d8c447
Add comment in the query of removeRepoconfigForInstallId function to …
avikalpg Apr 8, 2024
01fc8ff
minor: never nesting in `getUserIdByTopicName`
avikalpg Apr 8, 2024
00c4ddb
minor: Formatting changes and snake_case to camelCase conversion for …
avikalpg Apr 8, 2024
707b1b0
addressed pr comments
MuskanPaliwal Apr 8, 2024
65fd488
Merge branch 'mkp/repo_config_bug_fix_on_setup' of github.com:Alokit-…
MuskanPaliwal Apr 8, 2024
84a2d2d
add catch block to insert repos query in setupRepos.ts
avikalpg Apr 8, 2024
8ce86fe
Merge branch 'mkp/repo_config_bug_fix_on_setup' of github.com:Alokit-…
MuskanPaliwal Apr 8, 2024
afc09ff
Merge branch 'main' of github.com:Alokit-Innovations/vibinex-server i…
avikalpg Apr 8, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 21 additions & 8 deletions pages/api/dpu/setup.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { NextApiRequest, NextApiResponse } from 'next';
import { SetupReposArgs, removePreviousInstallations, saveSetupReposInDb } from '../../../utils/db/setupRepos';
import { getUserIdByTopicName } from '../../../utils/db/users';
import { insertRepoConfig, removeRepoconfigForInstallId } from '../../../utils/db/repos';

const setupHandler = async (req: NextApiRequest, res: NextApiResponse) => {
console.info("[setupHandler]Saving setup info in db...");
Expand All @@ -9,28 +11,39 @@ const setupHandler = async (req: NextApiRequest, res: NextApiResponse) => {
res.status(400).json({ "error": "Invalid request body" });
return;
}
try {
await removePreviousInstallations(jsonBody.installationId)
} catch (err) {
console.error(`[setupHandler] Unable to remove previous installations for ${jsonBody.installationId}`, err);
res.status(500).json({"error": "Internal Server Error"});
// get user_id for the given install_id
const userId = await getUserIdByTopicName(jsonBody.installationId).catch((error: any) => {
console.error("[setupHandler/getUserIdByTopicName] Failed to fetch userId from the database.", error);
});
avikalpg marked this conversation as resolved.
Show resolved Hide resolved
if (!userId) {
console.error(`[setupHandler/getUserIdByTopicName] NO userId found for topic name: ${jsonBody.installationId} from database.`);
res.status(404).json({ "error": "No userId found for given installationId" });
return;
}
const allSetupReposPromises = [];
for (const ownerInfo of jsonBody.info) {
let setupReposArgs: SetupReposArgs = {
repo_owner: ownerInfo.owner,
repo_provider: ownerInfo.provider,
repo_provider: ownerInfo.provider,
repo_names: ownerInfo.repos,
install_id: jsonBody.installationId
}
const saveSetupReposPromises = saveSetupReposInDb(setupReposArgs)
try {
await removePreviousInstallations(jsonBody.installationId, ownerInfo.provider);
await removeRepoconfigForInstallId(jsonBody.installationId, ownerInfo.provider, ownerInfo.repos, userId);
} catch (err) {
console.error(`[setupHandler] Unable to remove previous installations for ${jsonBody.installationId}`, err);
res.status(500).json({ "error": "Internal Server Error" });
return;
}

const saveSetupReposPromises = saveSetupReposInDb(setupReposArgs, userId)
avikalpg marked this conversation as resolved.
Show resolved Hide resolved
avikalpg marked this conversation as resolved.
Show resolved Hide resolved
.catch((err) => {
console.error("[setupHandler] Unable to save setup info, ", err);
});
allSetupReposPromises.push(saveSetupReposPromises);
}
await Promise.all(allSetupReposPromises).then((values) => {
await Promise.all(allSetupReposPromises).then(() => {
console.info("[setupHandler] All setup info saved succesfully...")
res.status(200).send("Ok");
return;
Expand Down
47 changes: 47 additions & 0 deletions utils/db/repos.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,3 +170,50 @@ export const getRepoConfigByUserAndRepo = async (provider: string, repoName: str
}
return userRows[0].config;
}

export const insertRepoConfig = async (userId: string, repoIds: number[]) => {
const query = `
INSERT INTO repo_config (repo_id, user_id, auto_assign, comment_setting)
SELECT id, $1, true, true
FROM unnest($2::INT[]) AS t(id)
MuskanPaliwal marked this conversation as resolved.
Show resolved Hide resolved
ON CONFLICT DO NOTHING
MuskanPaliwal marked this conversation as resolved.
Show resolved Hide resolved
`;
const params = [userId, repoIds];

const queryIsSuccessful = await conn.query(query, params)
MuskanPaliwal marked this conversation as resolved.
Show resolved Hide resolved
.then((dbResponse) => {
if (dbResponse.rowCount == 0) {
MuskanPaliwal marked this conversation as resolved.
Show resolved Hide resolved
return false;
}
return true;
})
.catch((err: Error) => {
console.error(`[db/insertRepoConfigOnSetup] Could not insert repo config for the repos: ${repoIds}`, { pg_query: query }, err);
return false;
})
return queryIsSuccessful;
}

export const removeRepoconfigForInstallId = async (install_id: string, repo_names: string[], provider: string, user_id: string) => {
MuskanPaliwal marked this conversation as resolved.
Show resolved Hide resolved
avikalpg marked this conversation as resolved.
Show resolved Hide resolved
const deleteRepoConfigQuery = `
DELETE FROM repo_config
WHERE repo_id IN (
SELECT id
MuskanPaliwal marked this conversation as resolved.
Show resolved Hide resolved
FROM repos
WHERE install_ids && ARRAY[$1]
avikalpg marked this conversation as resolved.
Show resolved Hide resolved
AND repo_name NOT IN (SELECT unnest($2::TEXT[]))
AND repo_provider = $3
)
AND user_id = $4;
`;

const result = await conn.query(deleteRepoConfigQuery, [install_id, repo_names, provider, user_id])
.catch((err) => {
console.error(`[removeRepoConfig] Could not remove repo config for: ${user_id}, ${repo_names}`, { pg_query: deleteRepoConfigQuery }, err);
throw new Error("Error in running the query on the database", err);
});
if (result.rowCount === 0) {
throw new Error('No repo config found to remove');
}
console.debug(`[removeRepoConfig] Previous repoConfig removed for ${install_id} and ${user_id}`);
}
180 changes: 39 additions & 141 deletions utils/db/setupRepos.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import conn from '.';
import { insertRepoConfig } from './repos';

export interface SetupReposArgs {
repo_owner: string;
Expand All @@ -7,155 +8,52 @@ export interface SetupReposArgs {
install_id: string;
}

interface SetupResponse {
message: string;
}

export const saveSetupReposInDb = async (args: SetupReposArgs): Promise<SetupResponse> => {
export const saveSetupReposInDb = async (args: SetupReposArgs, userId: string): Promise<boolean> => {
const { repo_owner, repo_provider, repo_names, install_id } = args;
const query = `SELECT EXISTS(SELECT 1 FROM repos WHERE repo_owner = $1 AND repo_provider = $2)`

return conn.query(query, [repo_owner, repo_provider])
.then(async (orgExistsResult) => {
const orgExists = orgExistsResult.rows[0].exists;

if (!orgExists) {
await addOrganization(repo_owner, repo_provider, repo_names, install_id).catch(error => {
console.error(`[saveSetupReposInDb] Could not add organisation in repos table for: ${repo_provider}/${repo_owner} for install_id ${install_id}`, error );
throw new Error('Failed to add organization', error);
});
} else {
await handleRepoSetupAndRemoveExtra(repo_names, repo_owner, repo_provider, install_id).catch(error => {
console.error(`[saveSetupreposInDb] error in handleReposetupAndRemoveExtra function: ${repo_provider}/${repo_owner} for install_id ${install_id}`, error);
throw new Error('Failed to handle repo setup and remove extra repositories', error);
});
const insertReposQuery = `
INSERT INTO repos (repo_name, install_ids, repo_owner, repo_provider)
SELECT repo_name, ARRAY[$4]::INT[], $1, $2
FROM unnest($3::TEXT[]) AS t(repo_name)
ON CONFLICT (repo_name) DO UPDATE
SET install_ids = CASE
WHEN NOT (repos.install_ids && ARRAY[$4]::INT[]) -- user's install_id not present
THEN repos.install_ids || $4 -- append user's install_id
ELSE repos.install_ids -- no update needed
END,
repo_owner = $1,
repo_provider = $2
RETURNING id AS repo_id;
`;
try {
await conn.query('BEGIN');
const { rowCount: reposRowCount, rows } = await conn.query(insertReposQuery, [repo_owner, repo_provider, repo_names, install_id]);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add .catch clause here and mention the query in it like we always do.

if (reposRowCount === 0) {
await conn.query('ROLLBACK');
avikalpg marked this conversation as resolved.
Show resolved Hide resolved
console.error(`[saveSetupRepos] No repositories were inserted or updated for: ${install_id}, ${repo_names}`);
return false;
}
return { message: 'setup repos are saved in db successfully' };
})
.catch(error => {
console.error(`[saveSetupReposInDb] Could not save setup repos in db for: ${repo_provider}/${repo_owner} for install_id ${install_id}`, { pg_query: query }, error );
throw new Error('Failed to save set up repos in db', error);
});
};


const addOrganization = async (repo_owner: string, repo_provider: string, repo_names: string[], install_id: string) => {
await conn.query('BEGIN').catch(err => {
console.error(`[addOrganisation] Could not start db transaction: ${repo_provider}/${repo_owner} for install_id ${install_id}`, { pg_query: query }, err);
throw new Error('Failed to begin transaction');
})
const query = `INSERT INTO repos (repo_name, repo_owner, repo_provider, install_id) VALUES ($1, $2, $3, $4)`
await Promise.all(repo_names.map(repo_name => conn.query(query, [repo_name, repo_owner, repo_provider, [install_id]])
.catch(error => {
console.error(`[addOrganisation] Could not add repos data in repos table for: ${repo_provider}/${repo_owner} for install_id ${install_id}`, { pg_query: query }, error);
throw new Error('Failed to insert repos data');
})));
await conn.query('COMMIT').catch(err => {
console.error(`[addOrganisation] Could not commit db transaction: ${repo_provider}/${repo_owner} `, { pg_query: query }, err);
throw new Error('Failed to commit transaction');
})
};

// Function to handle setup for a single repository and remove extra repositories
const handleRepoSetupAndRemoveExtra = async (repo_names: string[], repo_owner: string, repo_provider: string, install_id: string) => {
await Promise.all(repo_names.map(repo_name => handleRepoSetup(repo_name, repo_owner, repo_provider, install_id)
.catch(error => {
console.error(`[handleReposetupAndRemoveExtra] Error in handleRepoSetup: ${repo_provider}/${repo_owner} for install_id ${install_id}`, error);
throw new Error('Failed to handle repo setup');
})));
await removeExtraRepositories(repo_names, repo_owner, repo_provider, install_id).catch(error => {
console.error(`[handleReposetupAndRemoveExtra] Error in removeExtraRepositories: ${repo_provider}/${repo_owner} for install_id ${install_id}`, error);
throw new Error('Failed to remove extra repositories');
});
};

// Function to handle setup for a single repository
const handleRepoSetup = async (repo_name: string, repo_owner: string, repo_provider: string, install_id: string) => {
await conn.query('BEGIN').catch(err => {
console.error(`[handleRepoSetup] Could not start db transaction: ${repo_provider}/${repo_owner} for install_id ${install_id}`, { pg_query: query }, err);
throw new Error('Failed to begin transaction');
});

const query = `SELECT install_id FROM repos WHERE repo_name = $1 AND repo_owner = $2 AND repo_provider = $3`;
const existingInstallationsResult = await conn.query(query, [repo_name, repo_owner, repo_provider]).catch(err => {
console.error(`[handleRepoSetup] Could not get the install-id for repository: ${repo_provider}/${repo_owner}/${repo_name} for install_id ${install_id}`, { pg_query: query }, err);
throw new Error("Error in getting install-id from db");
});

const existingInstallations = existingInstallationsResult.rows.map(row => row.install_id);

// Iterate over each row
if (existingInstallations && existingInstallations.length > 0) {
for (const installations of existingInstallations) {
// Check if the install_id is already present in the array
if (!installations.includes(install_id)) {
installations.push(install_id);
const updateQuery = 'UPDATE repos SET install_id = $1 WHERE repo_name = $2 AND repo_owner = $3 AND repo_provider = $4';
await conn.query(updateQuery, [installations, repo_name, repo_owner, repo_provider])
.catch(err => {
console.error(`[handleRepoSetup] Could not update the install-id array for repository: ${repo_provider}/${repo_owner}/${repo_name} for install_id ${install_id}`, { pg_query: query }, err);
throw new Error('Failed to update install-id array for repo');
});
}

const repoIds = rows.map((row) => row.repo_id);
const insertRepoConfigSuccess = await insertRepoConfig(userId, repoIds);

if (!insertRepoConfigSuccess) {
await conn.query('ROLLBACK');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👏🏼

return false;
}
} else {
//If no installation exist that means add that repo into repos table with the upcoming install_id
const query = `INSERT INTO repos (repo_name, repo_owner, repo_provider, install_id) VALUES ($1, $2, $3, $4)`
await conn.query(query, [repo_name, repo_owner, repo_provider, [install_id]]).catch(err => {
console.error(`[handleRepoSetup] could not insert repo into repos table: ${repo_provider}/${repo_owner}/${repo_name} for install_id ${install_id}`, { pg_query: query }, err);
throw new Error("Error in inserting repo in db");
})
}

await conn.query('COMMIT').catch(err => {
console.error(`[handleRepoSetup] Could not commit DB transaction: ${repo_provider}/${repo_owner} for install_id ${install_id}`, { pg_query: query }, err);
throw new Error('Failed to commit db transaction');
});
};

const removeExtraRepositories = async (incomingRepoNames: string[], repo_owner: string, repo_provider: string, install_id: string) => {
await conn.query('BEGIN').catch(err => {
console.error(`[removeExtraRepositories] Could not start db transaction: ${repo_provider}/${repo_owner} for install_id ${install_id}`, { pg_query: query }, err);
throw new Error('Failed to begin db transaction');
});

const query = `SELECT repo_name FROM repos WHERE repo_owner = $1 AND repo_provider = $2 AND $3 = ANY(install_id)`;

const existingReposResult = await conn.query(query, [repo_owner, repo_provider, install_id]).catch(err => {
console.error(`[removeExtraRepositories] Could not get existing repositories from db for: ${repo_provider}/${repo_owner} for install_id: ${install_id}`, { pg_query: query }, err);
throw new Error('Failed to get existing repos from db');
});

const existingRepos = existingReposResult.rows.map(row => row.repo_name);
const extraRepos = existingRepos.filter(repo => !incomingRepoNames.includes(repo));

for (const repo_name of extraRepos) {
// Check if the current extra repo is the only one associated with the install_id
const singleRepoQuery = `SELECT install_id FROM repos WHERE repo_name = $1 AND repo_owner = $2 AND repo_provider = $3`;
const singleRepoResult = await conn.query(singleRepoQuery, [repo_name, repo_owner, repo_provider]).catch(err => {
console.error(`[removeExtraRepositories] Error checking if repo is the only one: ${repo_provider}/${repo_owner}/${repo_name} for install_id ${install_id}`, { pg_query: singleRepoQuery }, err);
throw new Error('Failed to check if repo is the only one associated with install_id');
});

const install_ids = singleRepoResult.rows[0]?.install_id || [];
const updatedInstallIds = install_ids.filter((id: String) => id !== install_id);
await conn.query('UPDATE repos SET install_id = $1 WHERE repo_name = $2 AND repo_owner = $3 AND repo_provider = $4', [updatedInstallIds, repo_name, repo_owner, repo_provider])
.catch(err => {
console.error(`[removeExtraRepositories] Could not remove install_id from extra repo: ${repo_provider}/${repo_owner}/${repo_name} for install_id ${install_id}`, { pg_query: query }, err);
throw new Error('Failed to remove install_id from extra repo');
});
await conn.query('COMMIT');
console.debug(`[saveSetupRepos] setup repos info saved successfully in db`)
return true;
} catch (err) {
await conn.query('ROLLBACK');
console.error(`[saveSetupRepos] Could not save setup repos for: ${install_id}, ${repo_names}`, { pg_query: insertReposQuery }, err);
return false;
}

await conn.query('COMMIT').catch(err => {
console.error(`[removeExtraRepositories] Could not commit DB transaction: ${repo_provider}/${repo_owner} for install_id ${install_id}`, { pg_query: query }, err);
throw new Error('Failed to commit db transaction');
});
};

export const removePreviousInstallations = async (install_id: string) => {
export const removePreviousInstallations = async (install_id: string, provider: string) => {
const query = `UPDATE repos
SET install_id = array_remove(install_id, '${install_id}')
WHERE '${install_id}' = ANY (install_id);
WHERE '${install_id}' = ANY (install_id) AND repo_provider = '${provider}';
avikalpg marked this conversation as resolved.
Show resolved Hide resolved
avikalpg marked this conversation as resolved.
Show resolved Hide resolved
`;
await conn.query(query).catch(err => {
console.error(`[removePreviousInstallations] Could not remove previous repos for ${install_id}`);
Expand Down
22 changes: 21 additions & 1 deletion utils/db/users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -250,4 +250,24 @@ export const getAuthInfoFromDb = async function (user_id: string): Promise<AuthI
return { authInfo: null };
});
return authinfo_promise.authInfo;
}
}

export const getUserIdByTopicName = async function(topic_name: string) {
const query = `SELECT id FROM users WHERE topic_name = $1`;
const params = [topic_name];

const queryResult = await conn.query(query, params).catch((err) => {
console.error(`[getUserIdByTopicName] Query failed: Select from users where topic_name = ${topic_name}`, { pg_query: query }, err);
});

if (queryResult && queryResult.rowCount === 0) {
console.error(`[getUserIdByTopicName] No user found for topicName ${topic_name}`);
return null;
} else if (queryResult && queryResult.rowCount > 1) {
console.error(`[getUserIdByTopicName] Multiple users found for topicName ${topic_name}`);
return null;
MuskanPaliwal marked this conversation as resolved.
Show resolved Hide resolved
} else {
return queryResult?.rows[0].id;
}
}