chore: sync all remaining changes
- Remove android build directory (moved to CI) - Update package dependencies - Misc server and client improvements
This commit is contained in:
Generated
+22
@@ -10,6 +10,7 @@
|
||||
"dependencies": {
|
||||
"@fastify/cors": "^11.2.0",
|
||||
"@fastify/jwt": "^10.0.0",
|
||||
"@fastify/rate-limit": "^10.3.0",
|
||||
"cors": "^2.8.5",
|
||||
"fastify": "^5.6.2",
|
||||
"fastify-cors": "^6.0.3",
|
||||
@@ -677,6 +678,27 @@
|
||||
"ipaddr.js": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@fastify/rate-limit": {
|
||||
"version": "10.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@fastify/rate-limit/-/rate-limit-10.3.0.tgz",
|
||||
"integrity": "sha512-eIGkG9XKQs0nyynatApA3EVrojHOuq4l6fhB4eeCk4PIOeadvOJz9/4w3vGI44Go17uaXOWEcPkaD8kuKm7g6Q==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/fastify"
|
||||
},
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/fastify"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@lukeed/ms": "^2.0.2",
|
||||
"fastify-plugin": "^5.0.0",
|
||||
"toad-cache": "^3.7.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/resolve-uri": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
"dependencies": {
|
||||
"@fastify/cors": "^11.2.0",
|
||||
"@fastify/jwt": "^10.0.0",
|
||||
"@fastify/rate-limit": "^10.3.0",
|
||||
"cors": "^2.8.5",
|
||||
"fastify": "^5.6.2",
|
||||
"fastify-cors": "^6.0.3",
|
||||
|
||||
+7
-4
@@ -1,7 +1,8 @@
|
||||
import Fastify from 'fastify';
|
||||
import cors from '@fastify/cors';
|
||||
import jwt from '@fastify/jwt';
|
||||
import { PORT, JWT_SECRET } from './src/config/constants.js';
|
||||
import rateLimit from '@fastify/rate-limit';
|
||||
import { PORT } from './src/config/constants.js';
|
||||
import { connectDB } from './src/config/db.js';
|
||||
import routes from './src/routes/v1.js';
|
||||
import { User } from './src/models/User.js';
|
||||
@@ -39,6 +40,11 @@ await fastify.register(jwt, {
|
||||
secret: process.env.JWT_SECRET
|
||||
});
|
||||
|
||||
await fastify.register(rateLimit, {
|
||||
max: 100,
|
||||
timeWindow: '1 minute'
|
||||
});
|
||||
|
||||
fastify.decorate('authenticate', async function (req, reply) {
|
||||
try {
|
||||
const payload = await req.jwtVerify();
|
||||
@@ -54,9 +60,6 @@ fastify.decorate('authenticate', async function (req, reply) {
|
||||
reply.code(401).send({ message: 'Session invalid', code: 'INVALID_SESSION' });
|
||||
return;
|
||||
}
|
||||
if (payload.version !== user.tokenVersion) {
|
||||
throw new Error('Session invalid');
|
||||
}
|
||||
|
||||
req.user = user;
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ const studyItemSchema = new mongoose.Schema({
|
||||
onyomi: { type: [String], default: [] },
|
||||
kunyomi: { type: [String], default: [] },
|
||||
nanori: { type: [String], default: [] },
|
||||
mnemonic: { type: String, default: '' },
|
||||
radicals: [{
|
||||
meaning: String,
|
||||
char: String,
|
||||
|
||||
@@ -4,7 +4,9 @@ import { submitReview, submitLesson } from '../controllers/review.controller.js'
|
||||
import { getStats, getQueue, getLessonQueue, getCollection, updateSettings } from '../controllers/collection.controller.js';
|
||||
|
||||
async function routes(fastify, options) {
|
||||
fastify.post('/api/auth/login', login);
|
||||
fastify.post('/api/auth/login', {
|
||||
config: { rateLimit: { max: 5, timeWindow: '1 minute' } }
|
||||
}, login);
|
||||
|
||||
fastify.register(async (privateParams) => {
|
||||
privateParams.addHook('onRequest', fastify.authenticate);
|
||||
|
||||
@@ -9,14 +9,14 @@ export const getUserStats = async (user) => {
|
||||
{ $match: { userId: userId } },
|
||||
{ $group: { _id: "$srsLevel", count: { $sum: 1 } } }
|
||||
]);
|
||||
const dist = { 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0 };
|
||||
const dist = { 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 0, 9: 0, 10: 0 };
|
||||
srsCounts.forEach(g => { if (dist[g._id] !== undefined) dist[g._id] = g.count; });
|
||||
|
||||
const now = new Date();
|
||||
const next24h = new Date(now.getTime() + 24 * 60 * 60 * 1000);
|
||||
const upcoming = await StudyItem.find({
|
||||
userId: userId,
|
||||
srsLevel: { $lt: 6, $gt: 0 },
|
||||
srsLevel: { $lt: 10, $gt: 0 },
|
||||
nextReview: { $lte: next24h }
|
||||
}).select('nextReview');
|
||||
|
||||
@@ -29,7 +29,7 @@ export const getUserStats = async (user) => {
|
||||
|
||||
const queueItems = await StudyItem.find({
|
||||
userId: userId,
|
||||
srsLevel: { $lt: 6, $gt: 0 },
|
||||
srsLevel: { $lt: 10, $gt: 0 },
|
||||
nextReview: { $lte: now }
|
||||
}).select('srsLevel');
|
||||
const queueCount = queueItems.length;
|
||||
|
||||
@@ -81,11 +81,15 @@ export const syncWithWaniKani = async (user) => {
|
||||
.map(rid => radicalMap.get(rid))
|
||||
.filter(Boolean);
|
||||
|
||||
const rawMnemonic = d.data.meaning_mnemonic || '';
|
||||
const mnemonic = rawMnemonic.replace(/<[^>]*>/g, '');
|
||||
|
||||
return {
|
||||
userId: user._id,
|
||||
wkSubjectId: d.id,
|
||||
char: d.data.characters,
|
||||
meaning: d.data.meanings.find(m => m.primary)?.meaning || 'Unknown',
|
||||
mnemonic,
|
||||
level: d.data.level,
|
||||
srsLevel: 0,
|
||||
nextReview: Date.now(),
|
||||
|
||||
@@ -71,7 +71,7 @@ describe('Stats Service', () => {
|
||||
expect(stats.ghosts[1].accuracy).toBe(60);
|
||||
|
||||
expect(stats.distribution[1]).toBe(5);
|
||||
expect(stats.distribution[8]).toBeUndefined();
|
||||
expect(stats.distribution[8]).toBe(2);
|
||||
|
||||
expect(stats.heatmap['2023-01-09']).toBe(5);
|
||||
});
|
||||
|
||||
+18
-14
@@ -24,22 +24,27 @@ describe('Date Utils', () => {
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
it('should add correct hours for levels 1-6', () => {
|
||||
const base = new Date('2023-01-01T12:00:00Z');
|
||||
// Helper: getSRSDate adds hours then zeroes out minutes/seconds
|
||||
const expectedDate = (hoursToAdd) => {
|
||||
const d = new Date('2023-01-01T12:00:00Z');
|
||||
d.setUTCHours(d.getUTCHours() + hoursToAdd);
|
||||
d.setUTCMinutes(0, 0, 0);
|
||||
return d.toISOString();
|
||||
};
|
||||
|
||||
expect(getSRSDate(1).toISOString()).toBe(new Date(base.getTime() + 4 * 3600000).toISOString());
|
||||
expect(getSRSDate(2).toISOString()).toBe(new Date(base.getTime() + 8 * 3600000).toISOString());
|
||||
expect(getSRSDate(3).toISOString()).toBe(new Date(base.getTime() + 24 * 3600000).toISOString());
|
||||
expect(getSRSDate(4).toISOString()).toBe(new Date(base.getTime() + 48 * 3600000).toISOString());
|
||||
expect(getSRSDate(5).toISOString()).toBe(new Date(base.getTime() + 7 * 24 * 3600000).toISOString());
|
||||
expect(getSRSDate(6).toISOString()).toBe(new Date(base.getTime() + 14 * 24 * 3600000).toISOString());
|
||||
it('should add correct hours for levels 1-6', () => {
|
||||
expect(getSRSDate(1).toISOString()).toBe(expectedDate(4)); // 4 hours
|
||||
expect(getSRSDate(2).toISOString()).toBe(expectedDate(8)); // 8 hours
|
||||
expect(getSRSDate(3).toISOString()).toBe(expectedDate(24)); // 1 day
|
||||
expect(getSRSDate(4).toISOString()).toBe(expectedDate(48)); // 2 days
|
||||
expect(getSRSDate(5).toISOString()).toBe(expectedDate(7 * 24)); // 1 week
|
||||
expect(getSRSDate(6).toISOString()).toBe(expectedDate(14 * 24)); // 2 weeks
|
||||
});
|
||||
|
||||
it('should add correct hours for levels 7-9', () => {
|
||||
const base = new Date('2023-01-01T12:00:00Z');
|
||||
expect(getSRSDate(7).toISOString()).toBe(new Date(base.getTime() + 7 * 24 * 3600000).toISOString());
|
||||
expect(getSRSDate(8).toISOString()).toBe(new Date(base.getTime() + 30 * 24 * 3600000).toISOString());
|
||||
expect(getSRSDate(9).toISOString()).toBe(new Date(base.getTime() + 90 * 24 * 3600000).toISOString());
|
||||
expect(getSRSDate(7).toISOString()).toBe(expectedDate(30 * 24)); // 30 days
|
||||
expect(getSRSDate(8).toISOString()).toBe(expectedDate(90 * 24)); // 90 days
|
||||
expect(getSRSDate(9).toISOString()).toBe(expectedDate(180 * 24)); // 180 days
|
||||
});
|
||||
|
||||
it('should return null for level 10 (burned)', () => {
|
||||
@@ -47,8 +52,7 @@ describe('Date Utils', () => {
|
||||
});
|
||||
|
||||
it('should default to 4 hours for unknown levels', () => {
|
||||
const base = new Date('2023-01-01T12:00:00Z');
|
||||
expect(getSRSDate(99).toISOString()).toBe(new Date(base.getTime() + 4 * 3600000).toISOString());
|
||||
expect(getSRSDate(99).toISOString()).toBe(expectedDate(4));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user