Makes it so: - An `Info`'s `Website` doesn't need to wait for other `Website`s of that same `Info` to load to show up - An `Info`s `Website` not working won't prevent other `Website`s of that same `Info` from showing up - Code is more split and organized Furthermore, the token for the osu! API is now stored, and used for ALL osu! requests for 24 hours instead of being revoked Overall, it's a lot of future-proofing so things on working even if I'm no longer there to maintain them Also so `Info`s can be added, changed, and removed more easily
145 lines
4.1 KiB
TypeScript
145 lines
4.1 KiB
TypeScript
import {type Handler} from "@netlify/functions";
|
|
import {api} from "./shared/api.js";
|
|
import {type WanikaniInfo} from "../../src/components/Info/Japanese/Wanikani.js";
|
|
|
|
type Subject = {
|
|
id: number;
|
|
object: string;
|
|
data: {
|
|
characters: string;
|
|
slug: string;
|
|
document_url: string;
|
|
meanings: Array<{
|
|
meaning: string;
|
|
}>;
|
|
};
|
|
};
|
|
|
|
type StuffToLearn = {
|
|
available_at: string;
|
|
type: string;
|
|
writing: string;
|
|
meanings: Array<{
|
|
meaning: string;
|
|
}>;
|
|
url: string;
|
|
};
|
|
|
|
function addStuffToLearn(ids: number[], data: Array<{available_at: string; subject_ids: number[]}>, subjects: Subject[]): StuffToLearn[] {
|
|
const arr: StuffToLearn[] = [];
|
|
|
|
for (const id of ids) {
|
|
const summaryData = data.find(lesson => lesson.subject_ids.includes(id));
|
|
const subject = subjects.find(subject => subject.id === id);
|
|
if (!summaryData || !subject) {
|
|
console.error("Failed: ", summaryData, subject);
|
|
continue;
|
|
}
|
|
|
|
arr.push({
|
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
available_at: summaryData.available_at,
|
|
type: subject.object,
|
|
writing: subject.data.characters || subject.data.slug || subject.data.meanings[0].meaning,
|
|
meanings: subject.data.meanings,
|
|
url: subject.data.document_url,
|
|
});
|
|
}
|
|
|
|
return arr;
|
|
}
|
|
|
|
const handler: Handler = async () => {
|
|
const data: any[] = await Promise.all([
|
|
new Promise((resolve) => {
|
|
resolve(api("https://api.wanikani.com/v2/level_progressions", process.env.API_WANIKANI));
|
|
}),
|
|
new Promise((resolve) => {
|
|
resolve(api("https://api.wanikani.com/v2/resets", process.env.API_WANIKANI));
|
|
}),
|
|
new Promise((resolve) => {
|
|
resolve(api("https://api.wanikani.com/v2/summary", process.env.API_WANIKANI));
|
|
}),
|
|
]);
|
|
|
|
const progression: {
|
|
total_count: number;
|
|
data: Array<{
|
|
data: {
|
|
level: number;
|
|
unlocked_at: undefined | string;
|
|
completed_at: undefined | string;
|
|
abandoned_at: undefined | string;
|
|
};
|
|
}>;
|
|
} = data[0];
|
|
|
|
const resets: {
|
|
data: [{
|
|
data: {
|
|
created_at: string;
|
|
original_level: number;
|
|
target_level: number;
|
|
};
|
|
}];
|
|
} = data[1];
|
|
|
|
const summary: {
|
|
data: {
|
|
lessons: Array<{
|
|
available_at: string;
|
|
subject_ids: number[];
|
|
}>;
|
|
reviews: Array<{
|
|
available_at: string;
|
|
subject_ids: number[];
|
|
}>;
|
|
next_reviews_at: undefined | string;
|
|
};
|
|
} = data[2];
|
|
|
|
const subjectIdsLessons: number[] = [];
|
|
const subjectIdsReviews: number[] = [];
|
|
for (const lesson of summary.data.lessons) {
|
|
for (const subjectId of lesson.subject_ids) {
|
|
subjectIdsLessons.push(subjectId);
|
|
}
|
|
}
|
|
|
|
for (const review of summary.data.reviews) {
|
|
for (const subjectId of review.subject_ids) {
|
|
subjectIdsReviews.push(subjectId);
|
|
}
|
|
}
|
|
|
|
const now = new Date();
|
|
// next_reviews checks what reviews will be available in the next 23 hours
|
|
// summary.data.next_reviews_at checks beyond that, but will be the current time if a review is already available
|
|
const nextReviews = summary.data.reviews
|
|
.map((r: {subject_ids: number[]; available_at: Date | string}) => {
|
|
r.available_at = new Date(r.available_at); return r;
|
|
})
|
|
.filter((r) => r.available_at > now && r.subject_ids.length) as Array<{subject_ids: number[]; available_at: Date}>;
|
|
const moreThingsToReviewAt = nextReviews[0] ? nextReviews[0].available_at.toISOString() : summary.data.next_reviews_at ? summary.data.next_reviews_at : undefined;
|
|
|
|
const subjectIdsAll = subjectIdsLessons.concat(subjectIdsReviews);
|
|
const subjects = await api<{data: Subject[]}>(`https://api.wanikani.com/v2/subjects?ids=${subjectIdsAll.toString()}`, process.env.API_WANIKANI);
|
|
|
|
const lessons = addStuffToLearn(subjectIdsLessons, summary.data.lessons, subjects.data);
|
|
const reviews = addStuffToLearn(subjectIdsReviews, summary.data.reviews, subjects.data);
|
|
|
|
const info: WanikaniInfo = {
|
|
progression,
|
|
resets: resets.data,
|
|
lessons,
|
|
reviews,
|
|
moreThingsToReviewAt,
|
|
};
|
|
|
|
return {
|
|
statusCode: 200,
|
|
body: JSON.stringify(info),
|
|
};
|
|
};
|
|
|
|
export {handler};
|