Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions config/custom-environment-variables.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ module.exports = {

githubAccessToken: "GITHUB_PERSONAL_ACCESS_TOKEN",

googleGenerativeAiApiKey: "GOOGLE_GENERATIVE_AI_API_KEY",

firestore: "FIRESTORE_CONFIG",

services: {
Expand Down
2 changes: 2 additions & 0 deletions config/default.js
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,8 @@ module.exports = {
},
githubAccessToken: "GITHUB_PERSONAL_ACCESS_TOKEN",

googleGenerativeAiApiKey: "<GOOGLE_GENERATIVE_AI_API_KEY>",

externalServices: {
EXTERNAL_SERVICE_PUBLIC_KEY: "EXTERNAL_SERVICE_PUBLIC_KEY",
},
Expand Down
37 changes: 36 additions & 1 deletion constants/application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,39 @@ const API_RESPONSE_MESSAGES = {
APPLICATION_RETURN_SUCCESS: "Applications returned successfully",
};

module.exports = { APPLICATION_STATUS_TYPES, API_RESPONSE_MESSAGES };
const APPLICATION_REVIEW_SYSTEM_PROMPT = `You are an expert application reviewer for a developer community. Your task is to analyze applications and provide structured, fair, and constructive feedback.

Evaluate each application based on the following criteria:

1. **Honesty**: Assess the authenticity and genuineness of the applicant's responses. Look for signs of sincerity, personal experiences, and genuine interest rather than generic or copied content.

2. **Curiosity**: Evaluate the applicant's intellectual curiosity, willingness to learn, and engagement with technology and the community. Look for evidence of self-directed learning, questions asked, and exploration of new concepts.

3. **Hard Work**: Assess the applicant's work ethic, dedication, and commitment. Look for examples of perseverance, completion of projects, overcoming challenges, and consistent effort.

4. **Motivation**: Evaluate the applicant's drive, goals, and reasons for joining. Assess whether their motivation aligns with the community's values and whether they demonstrate clear purpose and direction.

5. **Sloppiness/AI Detection**: Identify signs of carelessness, lack of attention to detail, or potential use of AI-generated content. Look for:
- Generic or templated responses
- Lack of personal details or specific examples
- Inconsistencies in writing style
- Overly polished or formulaic language that suggests AI assistance
- Missing or incomplete information

6. **Grammar and Language**: Assess the quality of written communication. Note:
- Spelling and grammar errors
- Call out any attempt of profanity as one of negatives
- Clarity and coherence of writing
- Professional communication skills
- Attention to detail in written responses

Provide a comprehensive review that includes:
- A numerical score (0-10) reflecting overall assessment
- A brief message summarizing your evaluation
- A detailed review explaining your assessment across all criteria
- Specific strengths identified in the application
- Constructive improvement suggestions

Be fair, objective, and supportive in your feedback.`;

module.exports = { APPLICATION_STATUS_TYPES, API_RESPONSE_MESSAGES, APPLICATION_REVIEW_SYSTEM_PROMPT };
92 changes: 91 additions & 1 deletion controllers/applications.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,15 @@ const { logType } = require("../constants/logs");
import { CustomRequest, CustomResponse } from "../types/global";
const { INTERNAL_SERVER_ERROR } = require("../constants/errorMessages");
const ApplicationModel = require("../models/applications");
const { API_RESPONSE_MESSAGES } = require("../constants/application");
const { API_RESPONSE_MESSAGES, APPLICATION_REVIEW_SYSTEM_PROMPT } = require("../constants/application");
const { getUserApplicationObject } = require("../utils/application");
const admin = require("firebase-admin");
const logger = require("../utils/logger");
const config = require("config");
const { generateObject } = require("ai");
const { google } = require("@ai-sdk/google");
const joiToJsonSchema = require("joi-to-json-schema");
const { applicationReviewSchema } = require("../middlewares/validators/application");

const getAllOrUserApplication = async (req: CustomRequest, res: CustomResponse): Promise<any> => {
try {
Expand Down Expand Up @@ -149,9 +155,93 @@ const getApplicationById = async (req: CustomRequest, res: CustomResponse) => {
}
};

const generateReview = async (
introduction: string,
whyRds: string,
forFun: string,
funFact: string
): Promise<any> => {
try {
const apiKey = config.get("googleGenerativeAiApiKey");
if (!apiKey) {
throw new Error("GOOGLE_GENERATIVE_AI_API_KEY is not configured");
}

const jsonSchema = joiToJsonSchema(applicationReviewSchema);
const userPrompt = `Please review the following application:
Introduction:
${introduction}

Why RDS:
${whyRds}

Hobbies/Interests (forFun):
${forFun}

Fun Fact:
${funFact}

Provide a comprehensive review based on the evaluation criteria.`;

const { object } = await generateObject({
model: google("gemini-2.5-flash-lite", {
apiKey: apiKey,
}),
schema: jsonSchema,
system: APPLICATION_REVIEW_SYSTEM_PROMPT,
prompt: userPrompt,
});

const { error, value } = applicationReviewSchema.validate(object);

if (error) {
throw new Error(`AI output validation failed: ${error.details[0].message}`);
}

return value;
} catch (err) {
logger.error(`Error in generating review: ${err}`);
throw err;
}
};

const reviewApplication = async (req: CustomRequest, res: CustomResponse) => {
try {
const { applicationId } = req.body;
const application = await ApplicationModel.getApplicationById(applicationId);

if (application.notFound) {
return res.boom.notFound("Application not found");
}

const { introduction, whyRds, forFun, funFact } = application.intro;

if (!introduction || !whyRds || !forFun || !funFact) {
return res.boom.badRequest("Application is missing required fields for review");
}

const review = await generateReview(introduction, whyRds, forFun, funFact);

return res.json({
message: "Application review generated successfully",
review,
});
} catch (err) {
logger.error(`Error in reviewing application: ${err}`);
if (err.message && err.message.includes("not found")) {
return res.boom.notFound(err.message);
}
if (err.message && err.message.includes("missing required")) {
return res.boom.badRequest(err.message);
}
return res.boom.badImplementation(INTERNAL_SERVER_ERROR);
}
};

module.exports = {
getAllOrUserApplication,
addApplication,
updateApplication,
getApplicationById,
reviewApplication,
};
30 changes: 30 additions & 0 deletions middlewares/validators/application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,38 @@ const validateApplicationQueryParam = async (req: CustomRequest, res: CustomResp
}
};

const applicationReviewSchema = joi
.object()
.strict()
.keys({
score: joi.number().required(),
message: joi.string().required(),
detailedReview: joi.string().required(),
strength: joi.array().items(joi.string()).required(),
improvement: joi.array().items(joi.string()).required(),
});

const validateReviewApplicationRequest = async (req: CustomRequest, res: CustomResponse, next: NextFunction) => {
const schema = joi
.object()
.strict()
.keys({
applicationId: joi.string().required(),
});

try {
await schema.validateAsync(req.body);
next();
} catch (error) {
logger.error(`Error in validating review application request: ${error}`);
res.boom.badRequest(error.details[0].message);
}
};

module.exports = {
validateApplicationData,
validateApplicationUpdateData,
validateApplicationQueryParam,
applicationReviewSchema,
validateReviewApplicationRequest,
};
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@
"tdd:watch": "sh scripts/tests/tdd.sh"
},
"dependencies": {
"@ai-sdk/google": "^2.0.42",
"@aws-sdk/client-identitystore": "^3.665.0",
"@types/nodemailer": "^6.4.15",
"ai": "^5.0.100",
"axios": "1.7.4",
"cloudinary": "2.0.3",
"config": "3.3.7",
Expand All @@ -31,6 +33,7 @@
"helmet": "7.1.0",
"http-errors": "~2.0.0",
"joi": "17.12.2",
"joi-to-json-schema": "^5.1.0",
"jsdoc": "4.0.2",
"jsonwebtoken": "^8.5.1",
"morgan": "1.10.0",
Expand All @@ -42,7 +45,8 @@
"passport-github2": "0.1.12",
"passport-google-oauth20": "^2.0.0",
"rate-limiter-flexible": "5.0.3",
"winston": "3.13.0"
"winston": "3.13.0",
"zod": "^4.1.12"
},
"devDependencies": {
"@types/chai": "4.3.16",
Expand Down
7 changes: 7 additions & 0 deletions routes/applications.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,12 @@ router.patch(
applicationValidator.validateApplicationUpdateData,
applications.updateApplication
);
router.post(
"/review-application",
// authenticate,
// authorizeRoles([SUPERUSER]),
applicationValidator.validateReviewApplicationRequest,
applications.reviewApplication
);

module.exports = router;
Loading
Loading