Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
101 changes: 1 addition & 100 deletions netlify/functions/generateCv.mts
Original file line number Diff line number Diff line change
@@ -1,104 +1,5 @@
import * as yup from 'yup';
import enhanceWithAi from '../functions/enhanceWithAi.mjs';
import { isValidMonthYear, isAfter } from '../utils/date.js';
import validateApiKey from '../utils/validations.js';

const cvSchema = yup.object().shape({
apiKey: yup
.string()
.required('API key is required')
.test('is-valid-api-key', 'Invalid API key format', (value) => {
if (!value) return false;
return validateApiKey(value);
}),
personalInfo: yup.object().shape({
fullName: yup.string().required('Full name is required'),
email: yup.string().email().required('Email is required'),
phone: yup.string().required('Phone number is required'),
github: yup.string().url().required('GitHub profile is required'),
linkedin: yup.string().url().required('LinkedIn profile is required'),
portfolio: yup.string().url().required('Portfolio is required'),
}),
professionalSummary: yup.object().shape({
summary: yup.string().required('Professional summary is required'),
}),
transferableExperience: yup.object().shape({
experience: yup.string().required('Experience is required'),
}),
projects: yup
.array()
.of(
yup.object().shape({
name: yup.string().required('Project name is required'),
description: yup
.string()
.required('Description is required')
.test(
'charCount',
'Description must be more than 150 characters',
(value) => typeof value === 'string' && value.trim().length > 150
),
deployedWebsite: yup
.string()
.url('Deployed site must be a valid URL')
.required('Deployed website is required'),
githubLink: yup
.string()
.url('GitHub link must be a valid URL')
.matches(
/^https:\/\/github\.com\/.+/,
'Must be a GitHub repository URL'
)
.required('GitHub link is required'),
})
)
.required('Projects are required'),
education: yup
.array()
.of(
yup.object().shape({
institution: yup.string().required('Institution is required'),
program: yup.string().required('Program is required'),
startDate: yup
.string()
.required('Start date is required')
.test(
'valid-start-format',
"Start date must be in 'Month YYYY' format",
isValidMonthYear
),
endDate: yup
.string()
.required('End date is required')
.test(
'valid-or-current',
'End date must be a valid date or "current"',
function (value) {
return (
value?.toLowerCase() === 'current' || isValidMonthYear(value)
);
}
)
.test(
'after-start',
'End date must be after start date',
function (value) {
const { startDate } = this.parent;
if (!startDate || !value) return true;
if (value.toLowerCase() === 'current') return true;

return isAfter(startDate, value);
}
),
})
)
.required('Education is required'),
profileVsJobCriteria: yup.object().shape({
jobcriteria: yup
.string()
.required('Comparison with job criteria is required'),
}),
});
import cvSchema from '../utils/schemaValidation';

const generateCv = async (event) => {
try {
Expand Down
102 changes: 102 additions & 0 deletions netlify/utils/schemaValidation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import * as yup from 'yup';
import { isValidMonthYear, isAfter } from '../utils/date.js';
import validateApiKey from '../utils/validations.js';

const cvSchema = yup.object().shape({
apiKey: yup
.string()
.required('API key is required')
.test('is-valid-api-key', 'Invalid API key format', (value) => {
if (!value) return false;
return validateApiKey(value);
}),
personalInfo: yup.object().shape({
fullName: yup.string().required('Full name is required'),
email: yup.string().email().required('Email is required'),
phone: yup.string().required('Phone number is required'),
github: yup.string().url().required('GitHub profile is required'),
linkedin: yup.string().url().required('LinkedIn profile is required'),
portfolio: yup.string().url().required('Portfolio is required'),
}),
professionalSummary: yup.object().shape({
summary: yup.string().required('Professional summary is required'),
}),
transferableExperience: yup.object().shape({
experience: yup.string().required('Experience is required'),
}),
projects: yup
.array()
.of(
yup.object().shape({
name: yup.string().required('Project name is required'),
description: yup
.string()
.required('Description is required')
.test(
'charCount',
'Description must be more than 150 characters',
(value) => typeof value === 'string' && value.trim().length > 150
),
deployedWebsite: yup
.string()
.url('Deployed site must be a valid URL')
.required('Deployed website is required'),
githubLink: yup
.string()
.url('GitHub link must be a valid URL')
.matches(
/^https:\/\/github\.com\/.+/,
'Must be a GitHub repository URL'
)
.required('GitHub link is required'),
})
)
.required('Projects are required'),
education: yup
.array()
.of(
yup.object().shape({
institution: yup.string().required('Institution is required'),
program: yup.string().required('Program is required'),
startDate: yup
.string()
.required('Start date is required')
.test(
'valid-start-format',
"Start date must be in 'Month YYYY' format",
isValidMonthYear
),
endDate: yup
.string()
.required('End date is required')
.test(
'valid-or-current',
'End date must be a valid date or "current"',
function (value) {
return (
value?.toLowerCase() === 'current' || isValidMonthYear(value)
);
}
)
.test(
'after-start',
'End date must be after start date',
function (value) {
const { startDate } = this.parent;
if (!startDate || !value) return true;
if (value.toLowerCase() === 'current') return true;

return isAfter(startDate, value);
}
),
})
)
.required('Education is required'),
profileVsJobCriteria: yup.object().shape({
jobcriteria: yup
.string()
.required('Comparison with job criteria is required'),
}),
});

export default cvSchema;
55 changes: 52 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,16 @@
},
"dependencies": {
"@google/generative-ai": "^0.24.1",
"@hookform/resolvers": "^5.1.1",
"@netlify/functions": "^4.1.5",
"date-fns": "^4.1.0",
"dotenv": "^16.5.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-hook-form": "^7.58.1",
"react-icons": "^5.5.0",
"react-router": "^7.5.3",
"react-router-dom": "^7.6.3",
"react-to-print": "^3.1.0",
"yup": "^1.6.1"
},
Expand Down
8 changes: 8 additions & 0 deletions smart-cv-builder.code-workspace
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"folders": [
{
"path": "."
}
],
"settings": {}
}
Loading