Skip to content
Merged
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
55 changes: 49 additions & 6 deletions src/database/controller/progressions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,10 @@ export namespace ProgressionController {
{ name: criteriaName },
increase
);
const updatedAchievements = await Progression.achieveCompletedAchievements(
updatedProgressionsIds.map((progression) => progression.id)
);
const updatedAchievements =
await Progression.achieveCompletedAchievements(
updatedProgressionsIds.map((progression) => progression.id)
);
let awardedPoints = 0;
for (let achievement of updatedAchievements) {
// Notify the user of the unlocked achievement
Expand Down Expand Up @@ -98,9 +99,10 @@ export namespace ProgressionController {
value.toString(),
maximize
);
const updatedAchievements = await Progression.achieveCompletedAchievements(
updatedProgressionsIds.map((progression) => progression.id)
);
const updatedAchievements =
await Progression.achieveCompletedAchievements(
updatedProgressionsIds.map((progression) => progression.id)
);
for (let achievement of updatedAchievements) {
// Notify the user of the unlocked achievement
awardAchievement(achievement);
Expand All @@ -109,4 +111,45 @@ export namespace ProgressionController {
logger.error(`Failed to update progression: ${(error as Error).message}`);
}
}

/**
* Update multiple progression values and check if any achievements have been unlocked
*
* @memberof achievements
* @function updateProgressions
*
* @param {Array<{name: string, value: string | number | Date | boolean}>} updates - The list of progressions to update
* @returns {Promise<void>}
*/
export async function updateProgressions(
updates: Array<{
name: string;
value: string | number | Date | boolean;
maximize?: boolean;
}>
): Promise<void> {
try {
const allUpdatedIds: number[] = [];
for (const update of updates) {
const updatedProgressionsIds = await Progression.updateValue(
{ name: update.name },
update.value.toString(),
update.maximize
);
allUpdatedIds.push(...updatedProgressionsIds.map((p) => p.id));
}

if (allUpdatedIds.length > 0) {
const updatedAchievements =
await Progression.achieveCompletedAchievements(allUpdatedIds);
for (let achievement of updatedAchievements) {
awardAchievement(achievement);
}
}
} catch (error) {
logger.error(
`Failed to update progressions: ${(error as Error).message}`
);
}
}
}
85 changes: 34 additions & 51 deletions src/database/controller/timespent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,72 +17,53 @@ export namespace TimeSpentController {
export async function updateTimeSpentFromSessions(): Promise<void> {
const currentDate = new Date();
const currentDateString = currentDate.toISOString().split("T")[0];
// Update the daily counter
logger.debug("JOB: Updating daily time spent");
const dailyTimeSpent = await DailySession.calculateDuration(
currentDateString,
currentDateString
);
await ProgressionController.updateProgression(
constants.criteria.DAILY_TIME_SPENT,
dailyTimeSpent.toString()
);

// Update the bi-monthly counter
logger.debug("JOB: Updating bi-monthly time spent");
const fourteenDaysAgo = new Date(
currentDate.getTime() - 14 * 24 * 60 * 60 * 1000
);
const fourteenDaysAgoString = fourteenDaysAgo.toISOString().split("T")[0];
const biMonthlyTimeSpent = await DailySession.calculateDuration(
fourteenDaysAgoString,
currentDateString
);
await ProgressionController.updateProgression(
constants.criteria.TWO_WEEKS_TIME_SPENT,
biMonthlyTimeSpent.toString()
);

// Update the monthly counter
logger.debug("JOB: Updating monthly time spent");
const firstDayOfMonth = new Date(
currentDate.getFullYear(),
currentDate.getMonth(),
1
);
const firstDayOfMonthString = firstDayOfMonth.toISOString().split("T")[0];
const monthlyTimeSpent = await DailySession.calculateDuration(
firstDayOfMonthString,
currentDateString
);
await ProgressionController.updateProgression(
constants.criteria.MONTHLY_TIME_SPENT,
monthlyTimeSpent.toString()
);

// Update the yearly counter
logger.debug("JOB: Updating yearly time spent");
const firstDayOfYear = new Date(currentDate.getFullYear(), 0, 1);
const firstDayOfYearString = firstDayOfYear.toISOString().split("T")[0];
const yearlyTimeSpent = await DailySession.calculateDuration(
firstDayOfYearString,
currentDateString
);
await ProgressionController.updateProgression(
constants.criteria.YEARLY_TIME_SPENT,
yearlyTimeSpent.toString()
);

// Update the total counter
logger.debug("JOB: Updating total time spent");
const totalDuration = await DailySession.calculateDuration(
"1970-01-01",
currentDateString
);
await ProgressionController.updateProgression(
constants.criteria.TOTAL_TIME_SPENT,
totalDuration.toString()
);
logger.debug("JOB: Updating time spent counters");

const stats = await DailySession.getStatsSummary(
currentDateString,
fourteenDaysAgoString,
firstDayOfMonthString,
firstDayOfYearString
);

await ProgressionController.updateProgressions([
{
name: constants.criteria.DAILY_TIME_SPENT,
value: stats.daily.toString(),
},
{
name: constants.criteria.TWO_WEEKS_TIME_SPENT,
value: stats.twoWeeks.toString(),
},
{
name: constants.criteria.MONTHLY_TIME_SPENT,
value: stats.monthly.toString(),
},
{
name: constants.criteria.YEARLY_TIME_SPENT,
value: stats.yearly.toString(),
},
{
name: constants.criteria.TOTAL_TIME_SPENT,
value: stats.total.toString(),
},
]);

logger.debug("JOB: Time spent counters updated");
}
Expand Down Expand Up @@ -140,7 +121,9 @@ export namespace TimeSpentController {
yesterdayDate.setDate(yesterdayDate.getDate() - 1);
const yesterdayDateString = yesterdayDate.toISOString().split("T")[0];

const yesterdaySession = await DailySession.getOrCreate(yesterdayDateString);
const yesterdaySession = await DailySession.getOrCreate(
yesterdayDateString
);

let newStreak = 1;

Expand Down
56 changes: 49 additions & 7 deletions src/database/model/migrations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,22 +130,64 @@ export async function applyMigration(
duration INTEGER NOT NULL
)`
);

const indexes_to_create: { [key: string]: string } = {
// Index for achievement_criterias progression_id (Critical for checkAchievements)
idx_achievement_criterias_progression_id:
"achievement_criterias(progression_id)",
// Index for achievements category filtering (UI)
idx_achievements_category: "achievements(category)",
// Index for achievements group filtering (UI)
idx_achievements_group: 'achievements("group")',
// Index for achievements achieved filtering (UI)
idx_achievements_achieved: "achievements(achieved)",
// Index for achievement_labels filtering (UI)
idx_achievement_labels_label: "achievement_labels(label)",
// Index for achievement_requirements requirement_id (Critical for achievable filter)
idx_achievement_requirements_requirement_id:
"achievement_requirements(requirement_id)",
};

for (const [indexName, indexColumns] of Object.entries(
indexes_to_create
)) {
db.run(
`CREATE INDEX IF NOT EXISTS ${indexName} ON ${indexColumns}`
);
}

db.run("COMMIT");
} catch (error) {
db.run("ROLLBACK");
throw error;
}
},
down: () => {
const tables_to_drop = [
"schema_version",
"achievements",
"achievement_requirements",
"progressions",
"achievement_criterias",
"achievement_labels",
"daily_sessions",
];
const indexes_to_drop = [
"idx_achievement_criterias_progression_id",
"idx_achievements_category",
"idx_achievements_group",
"idx_achievements_achieved",
"idx_achievement_labels_label",
"idx_achievement_requirements_requirement_id",
];
db.run("BEGIN TRANSACTION");
try {
db.run("DROP TABLE IF EXISTS schema_version");
db.run("DROP TABLE IF EXISTS achievements");
db.run("DROP TABLE IF EXISTS achievement_requirements");
db.run("DROP TABLE IF EXISTS progressions");
db.run("DROP TABLE IF EXISTS achievement_criterias");
db.run("DROP TABLE IF EXISTS achievement_labels");
db.run("DROP TABLE IF EXISTS daily_sessions");
for (const index of indexes_to_drop) {
db.run(`DROP INDEX IF EXISTS ${index}`);
}
for (const table of tables_to_drop) {
db.run(`DROP TABLE IF EXISTS ${table}`);
}
db.run("COMMIT");
} catch (error) {
db.run("ROLLBACK");
Expand Down
10 changes: 10 additions & 0 deletions src/database/model/tables/Achievement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -592,6 +592,16 @@ class Achievement {
conditions.push(`(${labelConditions.join(" AND ")})`);
values.push(...filters.labels);
}
if (filters.criterias && filters.criterias.length > 0) {
conditions.push(`EXISTS (
SELECT 1 FROM achievement_criterias ac
JOIN progressions p ON ac.progression_id = p.id
WHERE ac.achievement_id = a.id AND p.name IN (${filters.criterias
.map(() => "?")
.join(", ")})
)`);
values.push(...filters.criterias);
}
if (filters.title) {
conditions.push("a.title LIKE ?");
values.push(`%${filters.title}%`);
Expand Down
37 changes: 37 additions & 0 deletions src/database/model/tables/DailySession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,4 +126,41 @@ export class DailySession {
);
return res.total_duration as number;
}

static async getStatsSummary(
today: string,
twoWeeksAgo: string,
monthStart: string,
yearStart: string
): Promise<{
daily: number;
twoWeeks: number;
monthly: number;
yearly: number;
total: number;
}> {
const db = await db_model.getDB();
const query = `
SELECT
SUM(CASE WHEN date = ? THEN duration ELSE 0 END) as daily,
SUM(CASE WHEN date >= ? THEN duration ELSE 0 END) as twoWeeks,
SUM(CASE WHEN date >= ? THEN duration ELSE 0 END) as monthly,
SUM(CASE WHEN date >= ? THEN duration ELSE 0 END) as yearly,
SUM(duration) as total
FROM daily_sessions
`;
const result = db_model.get(db, query, [
today,
twoWeeksAgo,
monthStart,
yearStart,
]);
return {
daily: result.daily || 0,
twoWeeks: result.twoWeeks || 0,
monthly: result.monthly || 0,
yearly: result.yearly || 0,
total: result.total || 0,
};
}
}
54 changes: 24 additions & 30 deletions src/listeners/files.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,47 +154,41 @@ export namespace fileListeners {
path.extname(event.document.fileName)
];
if (language) {
let totalLinesAdded = 0;
for (const change of event.contentChanges) {
await processContentChange(change, language);
totalLinesAdded += countLinesAdded(change);
}
}
}

async function processContentChange(
change: vscode.TextDocumentContentChangeEvent,
language: string
): Promise<void> {
// Check if the change involves adding new lines
if (change.text.includes("\n")) {
if (change.range.isSingleLine) {
// Increment progression for the added line
if (totalLinesAdded > 0) {
// Increment progression for the added lines
await ProgressionController.increaseProgression(
constants.criteria.LINES_OF_CODE_LANGUAGE.replace("%s", language)
constants.criteria.LINES_OF_CODE_LANGUAGE.replace("%s", language),
totalLinesAdded
);
// Increment generic lines of code progression
await ProgressionController.increaseProgression(
constants.criteria.LINES_OF_CODE
constants.criteria.LINES_OF_CODE,
totalLinesAdded
);
} else {
// Count non-empty lines added in the change
const nonEmptyLinesCount = change.text
.split(/\r?\n/)
.filter((line) => line.trim().length > 0).length;
}
}
}

// Increment progression for the added lines
if (nonEmptyLinesCount > 0) {
await ProgressionController.increaseProgression(
constants.criteria.LINES_OF_CODE_LANGUAGE.replace("%s", language),
nonEmptyLinesCount
);
// Increment generic lines of code progression
await ProgressionController.increaseProgression(
constants.criteria.LINES_OF_CODE,
nonEmptyLinesCount
);
}
function countLinesAdded(
change: vscode.TextDocumentContentChangeEvent
): number {
// Check if the change involves adding new lines
if (change.text.includes("\n")) {
const nonEmptyLinesCount = change.text
.split(/\r?\n/)
.filter((line) => line.trim().length > 0).length;

if (nonEmptyLinesCount > 0) {
return nonEmptyLinesCount;
}
return 1;
}
return 0;
}

let fileErrorCounts = new Map<string, number>();
Expand Down
Loading