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: 1 addition & 1 deletion .github/pull_request_template.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@

### Issue

* Closes: #<issue number>
- Closes: #<issue number>
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,10 @@ The initial user interface was designed and prototyped in Figma. These images sh
<img src="docs/images/phone_view.png" width="250" alt="Mobile interface prototype">

## Sustainability

To make our project more sustainable we have made sure to not load any information in before it is asked for by the user. We have also used pictures of pokemon instead of GIFs.

However, this is a subject where you are expected to use AI to assist you in the project, and we have used AI. It would have been environmentally beneficial to not use AI, but the way the subject and it's requirements are now, we did not see that as a possibility.
However, this is a subject where you are expected to use AI to assist you in the project, and we have used AI. It would have been environmentally beneficial to not use AI, but the way the subject and it's requirements are now, we did not see that as a possibility.

## Architecture

Expand Down
5 changes: 4 additions & 1 deletion backend/scripts/import/extract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,10 @@ export async function extractShowdownData(): Promise<ShowdownData> {
if (Gen2Dex.species.get(s.id).exists && s.gen <= 2) {
entries.push(...Gen2Dex.species.getFullLearnset(s.id));
}
learnsets.set(s.id, entries.map((entry) => entry.learnset));
learnsets.set(
s.id,
entries.map((entry) => entry.learnset),
);
}

return {
Expand Down
6 changes: 5 additions & 1 deletion backend/scripts/import/load/batch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@ import type { Connection } from 'mariadb';
// Keeps each batch well below MariaDB's max_allowed_packet of 16 MB
const CHUNK_SIZE = 5000;

export async function batchInsert(conn: Connection, sql: string, values: unknown[][]): Promise<void> {
export async function batchInsert(
conn: Connection,
sql: string,
values: unknown[][],
): Promise<void> {
for (let i = 0; i < values.length; i += CHUNK_SIZE) {
await conn.batch(sql, values.slice(i, i + CHUNK_SIZE));
}
Expand Down
15 changes: 13 additions & 2 deletions backend/scripts/import/load/moves.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,19 @@ export async function loadMoves(conn: Connection, rows: MoveRow[]): Promise<void
gen = VALUES(gen),
is_nonstandard = VALUES(is_nonstandard)`,
rows.map((m) => [
m.id, m.num, m.name, m.typeId, m.category, m.basePower, m.accuracy,
m.pp, m.priority, m.target, m.shortDesc, m.gen, m.isNonstandard,
m.id,
m.num,
m.name,
m.typeId,
m.category,
m.basePower,
m.accuracy,
m.pp,
m.priority,
m.target,
m.shortDesc,
m.gen,
m.isNonstandard,
]),
);
}
26 changes: 23 additions & 3 deletions backend/scripts/import/load/pokemon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@ import { batchInsert } from './batch.ts';

export async function loadPokemon(
conn: Connection,
rows: { pokemon: PokemonRow[]; pokemonTypes: PokemonTypeRow[]; pokemonAbilities: PokemonAbilityRow[] },
rows: {
pokemon: PokemonRow[];
pokemonTypes: PokemonTypeRow[];
pokemonAbilities: PokemonAbilityRow[];
},
): Promise<void> {
// We update if the primary already exists, as hypothetically, if the pokemon showdown dataset was changed
// our application would break, as published teams would use the wrong ID's
Expand Down Expand Up @@ -36,8 +40,24 @@ export async function loadPokemon(
is_nonstandard = VALUES(is_nonstandard),
sprite = VALUES(sprite)`,
rows.pokemon.map((p) => [
p.id, p.num, p.name, p.baseSpecies, p.forme, p.gen, p.hp, p.atk, p.def, p.spa, p.spd, p.spe,
p.weightKg, p.tier, p.natdexTier, p.doublesTier, p.isNonstandard, p.sprite,
p.id,
p.num,
p.name,
p.baseSpecies,
p.forme,
p.gen,
p.hp,
p.atk,
p.def,
p.spa,
p.spd,
p.spe,
p.weightKg,
p.tier,
p.natdexTier,
p.doublesTier,
p.isNonstandard,
p.sprite,
]),
);

Expand Down
2 changes: 1 addition & 1 deletion backend/scripts/import/load/users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@ export async function loadUsers(conn: Connection): Promise<void> {
ON DUPLICATE KEY UPDATE username = VALUES(username)`,
DEMO_USERS.map((username) => [username]),
);
}
}
3 changes: 2 additions & 1 deletion backend/scripts/import/sprites.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ export async function downloadSprites(species: Species[]): Promise<Map<string, s
if (await ensure(s.spriteid)) return [s.id, `${s.spriteid}.png`];

const baseSpriteId = s.baseSpecies.toLowerCase().replace(/[^a-z0-9]+/g, '');
if (baseSpriteId !== s.spriteid && (await ensure(baseSpriteId))) return [s.id, `${baseSpriteId}.png`];
if (baseSpriteId !== s.spriteid && (await ensure(baseSpriteId)))
return [s.id, `${baseSpriteId}.png`];

return [s.id, null];
}),
Expand Down
4 changes: 3 additions & 1 deletion backend/scripts/import/transform/pokemon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ export function transformPokemon(
pokemonTypes: PokemonTypeRow[];
pokemonAbilities: PokemonAbilityRow[];
} {
const abilityIdByName = new Map(data.abilities.map((ability) => [ability.name, ability.id as string]));
const abilityIdByName = new Map(
data.abilities.map((ability) => [ability.name, ability.id as string]),
);

const pokemon: PokemonRow[] = [];
const pokemonTypes: PokemonTypeRow[] = [];
Expand Down
2 changes: 1 addition & 1 deletion backend/scripts/import/transform/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const DAMAGE_TAKEN_MULTIPLIER: Record<number, number> = {
0: 1,
1: 2,
2: 0.5,
3: 0
3: 0,
};

export function transformTypes(data: ShowdownData): {
Expand Down
56 changes: 25 additions & 31 deletions backend/src/graphql/api.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { describe, expect, it } from "vitest";
import { describe, expect, it } from 'vitest';

const endpoint =
process.env.GRAPHQL_TEST_URL ?? "http://localhost:3001/";
const endpoint = process.env.GRAPHQL_TEST_URL ?? 'http://localhost:3001/';

type Pokemon = {
id: string;
Expand All @@ -22,14 +21,11 @@ type GraphQLResponse<T> = {
errors?: { message: string }[];
};

async function sendQuery<T>(
query: string,
variables?: Record<string, unknown>,
) {
async function sendQuery<T>(query: string, variables?: Record<string, unknown>) {
const response = await fetch(endpoint, {
method: "POST",
method: 'POST',
headers: {
"Content-Type": "application/json",
'Content-Type': 'application/json',
},
body: JSON.stringify({ query, variables }),
});
Expand All @@ -39,8 +35,8 @@ async function sendQuery<T>(
return { response, body };
}

describe("GraphQL API integration", () => {
it("returns a list of Pokémon with their fields", async () => {
describe('GraphQL API integration', () => {
it('returns a list of Pokémon with their fields', async () => {
const { response, body } = await sendQuery<{
pokemons: Pokemon[];
}>(`
Expand All @@ -62,10 +58,10 @@ describe("GraphQL API integration", () => {
expect(firstPokemon).toBeDefined();
expect(firstPokemon.id).toBeTruthy();
expect(firstPokemon.name).toBeTruthy();
expect(typeof firstPokemon.type).toBe("string");
expect(typeof firstPokemon.type).toBe('string');
});

it("returns a Pokémon when queried by its ID", async () => {
it('returns a Pokémon when queried by its ID', async () => {
const listResult = await sendQuery<{
pokemons: Pokemon[];
}>(`
Expand Down Expand Up @@ -104,7 +100,7 @@ describe("GraphQL API integration", () => {
expect(body.data?.pokemon).toEqual(firstPokemon);
});

it("returns the sprite file name for a Pokémon", async () => {
it('returns the sprite file name for a Pokémon', async () => {
const { response, body } = await sendQuery<{
pokemon: { id: string; sprite: string | null } | null;
}>(
Expand All @@ -116,15 +112,15 @@ describe("GraphQL API integration", () => {
}
}
`,
{ id: "bulbasaur" },
{ id: 'bulbasaur' },
);

expect(response.status).toBe(200);
expect(body.errors).toBeUndefined();
expect(body.data?.pokemon?.sprite).toBe("bulbasaur.png");
expect(body.data?.pokemon?.sprite).toBe('bulbasaur.png');
});

it("returns null for an unknown Pokémon ID", async () => {
it('returns null for an unknown Pokémon ID', async () => {
const { response, body } = await sendQuery<{
pokemon: Pokemon | null;
}>(
Expand All @@ -137,15 +133,15 @@ describe("GraphQL API integration", () => {
}
}
`,
{ id: "__integration_test_missing_pokemon__" },
{ id: '__integration_test_missing_pokemon__' },
);

expect(response.status).toBe(200);
expect(body.errors).toBeUndefined();
expect(body.data?.pokemon).toBeNull();
});

it("returns an error for an invalid GraphQL field", async () => {
it('returns an error for an invalid GraphQL field', async () => {
const { body } = await sendQuery(`
query {
pokemons {
Expand All @@ -158,7 +154,7 @@ describe("GraphQL API integration", () => {
expect(body.errors!.length).toBeGreaterThan(0);
});

it("respects the requested result limit", async () => {
it('respects the requested result limit', async () => {
const { response, body } = await sendQuery<{
searchPokemons: SearchPokemon[];
}>(`
Expand All @@ -176,7 +172,7 @@ describe("GraphQL API integration", () => {
expect(body.data!.searchPokemons).toHaveLength(5);
});

it("returns different results when the offset changes", async () => {
it('returns different results when the offset changes', async () => {
const query = `
query Search($limit: Int, $offset: Int) {
searchPokemons(
Expand Down Expand Up @@ -216,7 +212,7 @@ describe("GraphQL API integration", () => {
expect(secondIds.every((id) => !firstIds.includes(id))).toBe(true);
});

it("returns an empty list when the offset is beyond the results", async () => {
it('returns an empty list when the offset is beyond the results', async () => {
const { response, body } = await sendQuery<{
searchPokemons: SearchPokemon[];
}>(`
Expand All @@ -238,7 +234,7 @@ describe("GraphQL API integration", () => {
expect(body.data!.searchPokemons).toEqual([]);
});

it("sorts Pokémon by name in ascending order", async () => {
it('sorts Pokémon by name in ascending order', async () => {
const { response, body } = await sendQuery<{
searchPokemons: SearchPokemon[];
}>(`
Expand All @@ -265,7 +261,7 @@ describe("GraphQL API integration", () => {
expect(names).toEqual(sortedNames);
});

it("sorts Pokémon by name in descending order", async () => {
it('sorts Pokémon by name in descending order', async () => {
const { response, body } = await sendQuery<{
searchPokemons: SearchPokemon[];
}>(`
Expand All @@ -292,7 +288,7 @@ describe("GraphQL API integration", () => {
expect(names).toEqual(sortedNames);
});

it("sorts Pokémon by number in descending order", async () => {
it('sorts Pokémon by number in descending order', async () => {
const { response, body } = await sendQuery<{
searchPokemons: SearchPokemon[];
}>(`
Expand Down Expand Up @@ -326,14 +322,12 @@ describe("GraphQL API integration", () => {

for (let i = 0; i < results.length - 1; i++) {
if (numbers[i] === numbers[i + 1]) {
expect(
results[i].name.localeCompare(results[i + 1].name),
).toBeLessThanOrEqual(0);
expect(results[i].name.localeCompare(results[i + 1].name)).toBeLessThanOrEqual(0);
}
}
});

it("caps the requested result limit at 100", async () => {
it('caps the requested result limit at 100', async () => {
const { response, body } = await sendQuery<{
searchPokemons: SearchPokemon[];
}>(`
Expand All @@ -351,7 +345,7 @@ describe("GraphQL API integration", () => {
expect(body.data!.searchPokemons.length).toBeLessThanOrEqual(100);
});

it("rejects an invalid sort field", async () => {
it('rejects an invalid sort field', async () => {
const { body } = await sendQuery(`
query {
searchPokemons(format: "gen9", sortBy: "invalid") {
Expand All @@ -364,7 +358,7 @@ describe("GraphQL API integration", () => {
expect(body.errors!.length).toBeGreaterThan(0);
});

it("rejects an invalid sort direction", async () => {
it('rejects an invalid sort direction', async () => {
const { body } = await sendQuery(`
query {
searchPokemons(format: "gen9", sortDirection: "SIDEWAYS") {
Expand Down
Loading