feat(pokemon): add opposite relationships and redesign detail view
Add description and opposite relationships to environments and favorite things Move pokedex reference data (stats, dimensions, types) to a separate tab Highlight core mechanics (skills, habitat, favorite things) in detail view Update related pokemon scoring to account for opposite relationships
This commit is contained in:
@@ -109,11 +109,14 @@ CREATE INDEX IF NOT EXISTS user_follows_followed_created_idx
|
||||
CREATE TABLE IF NOT EXISTS environments (
|
||||
id integer GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
name text NOT NULL UNIQUE,
|
||||
description text NOT NULL DEFAULT '',
|
||||
opposite_environment_id integer REFERENCES environments(id) ON DELETE SET NULL,
|
||||
sort_order integer NOT NULL DEFAULT 0 CHECK (sort_order >= 0),
|
||||
created_by_user_id integer REFERENCES users(id) ON DELETE SET NULL,
|
||||
updated_by_user_id integer REFERENCES users(id) ON DELETE SET NULL,
|
||||
created_at timestamptz NOT NULL DEFAULT now(),
|
||||
updated_at timestamptz NOT NULL DEFAULT now()
|
||||
updated_at timestamptz NOT NULL DEFAULT now(),
|
||||
CHECK (opposite_environment_id IS NULL OR opposite_environment_id <> id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS roles (
|
||||
@@ -1071,11 +1074,13 @@ CREATE TABLE IF NOT EXISTS skills (
|
||||
CREATE TABLE IF NOT EXISTS favorite_things (
|
||||
id integer GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
name text NOT NULL UNIQUE,
|
||||
opposite_favorite_thing_id integer REFERENCES favorite_things(id) ON DELETE SET NULL,
|
||||
sort_order integer NOT NULL DEFAULT 0 CHECK (sort_order >= 0),
|
||||
created_by_user_id integer REFERENCES users(id) ON DELETE SET NULL,
|
||||
updated_by_user_id integer REFERENCES users(id) ON DELETE SET NULL,
|
||||
created_at timestamptz NOT NULL DEFAULT now(),
|
||||
updated_at timestamptz NOT NULL DEFAULT now()
|
||||
updated_at timestamptz NOT NULL DEFAULT now(),
|
||||
CHECK (opposite_favorite_thing_id IS NULL OR opposite_favorite_thing_id <> id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS pokemon_types (
|
||||
@@ -1526,6 +1531,50 @@ ALTER TABLE skills
|
||||
ALTER TABLE items
|
||||
ADD COLUMN IF NOT EXISTS dyeability integer NOT NULL DEFAULT 0 CHECK (dyeability IN (0, 1, 2, 3));
|
||||
|
||||
ALTER TABLE environments
|
||||
ADD COLUMN IF NOT EXISTS description text NOT NULL DEFAULT '';
|
||||
|
||||
ALTER TABLE environments
|
||||
ADD COLUMN IF NOT EXISTS opposite_environment_id integer;
|
||||
|
||||
ALTER TABLE favorite_things
|
||||
ADD COLUMN IF NOT EXISTS opposite_favorite_thing_id integer;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM pg_constraint WHERE conname = 'environments_opposite_environment_id_fkey'
|
||||
) THEN
|
||||
ALTER TABLE environments
|
||||
ADD CONSTRAINT environments_opposite_environment_id_fkey
|
||||
FOREIGN KEY (opposite_environment_id) REFERENCES environments(id) ON DELETE SET NULL;
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM pg_constraint WHERE conname = 'environments_opposite_environment_id_check'
|
||||
) THEN
|
||||
ALTER TABLE environments
|
||||
ADD CONSTRAINT environments_opposite_environment_id_check
|
||||
CHECK (opposite_environment_id IS NULL OR opposite_environment_id <> id);
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM pg_constraint WHERE conname = 'favorite_things_opposite_favorite_thing_id_fkey'
|
||||
) THEN
|
||||
ALTER TABLE favorite_things
|
||||
ADD CONSTRAINT favorite_things_opposite_favorite_thing_id_fkey
|
||||
FOREIGN KEY (opposite_favorite_thing_id) REFERENCES favorite_things(id) ON DELETE SET NULL;
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM pg_constraint WHERE conname = 'favorite_things_opposite_favorite_thing_id_check'
|
||||
) THEN
|
||||
ALTER TABLE favorite_things
|
||||
ADD CONSTRAINT favorite_things_opposite_favorite_thing_id_check
|
||||
CHECK (opposite_favorite_thing_id IS NULL OR opposite_favorite_thing_id <> id);
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
UPDATE items
|
||||
SET dyeability = CASE
|
||||
WHEN dual_dyeable THEN 2
|
||||
|
||||
@@ -157,6 +157,8 @@ type ConfigDefinition = {
|
||||
hasItemDrop?: boolean;
|
||||
hasTrading?: boolean;
|
||||
hasChangeLog?: boolean;
|
||||
hasDescription?: boolean;
|
||||
oppositeColumn?: string;
|
||||
};
|
||||
type SortableContentType = 'items' | 'ancient-artifacts' | 'recipes' | 'habitats';
|
||||
type SortableContentDefinition = {
|
||||
@@ -656,6 +658,8 @@ type DailyChecklistChangeSource = {
|
||||
} & TranslationChangeSource;
|
||||
type ConfigChangeSource = {
|
||||
name: string;
|
||||
description?: string;
|
||||
opposite?: { name: string } | null;
|
||||
hasItemDrop?: boolean;
|
||||
hasTrading?: boolean;
|
||||
changeLog?: string;
|
||||
@@ -722,8 +726,8 @@ const ancientArtifactCategoryOptions = [
|
||||
const configDefinitions: Record<ConfigType, ConfigDefinition> = {
|
||||
'pokemon-types': { table: 'pokemon_types', entityType: 'pokemon-types' },
|
||||
skills: { table: 'skills', entityType: 'skills', hasItemDrop: true, hasTrading: true },
|
||||
environments: { table: 'environments', entityType: 'environments' },
|
||||
'favorite-things': { table: 'favorite_things', entityType: 'favorite-things' },
|
||||
environments: { table: 'environments', entityType: 'environments', hasDescription: true, oppositeColumn: 'opposite_environment_id' },
|
||||
'favorite-things': { table: 'favorite_things', entityType: 'favorite-things', oppositeColumn: 'opposite_favorite_thing_id' },
|
||||
'acquisition-methods': { table: 'acquisition_methods', entityType: 'acquisition-methods' },
|
||||
maps: { table: 'maps', entityType: 'maps' },
|
||||
'game-versions': { table: 'game_versions', entityType: 'game-versions', hasChangeLog: true },
|
||||
@@ -1108,8 +1112,22 @@ function configOrder(): string {
|
||||
|
||||
function configSelect(definition: ConfigDefinition, locale: string): string {
|
||||
const name = localizedName(definition.entityType, 'c', locale);
|
||||
const oppositeName = localizedName(definition.entityType, 'opposite_config', locale);
|
||||
const translations = translationsSelect(definition.entityType, 'c.id');
|
||||
const columns = [`c.id`, `${name} AS name`, `c.name AS "baseName"`, `${translations} AS translations`];
|
||||
if (definition.hasDescription) {
|
||||
columns.push(`c.description`);
|
||||
}
|
||||
if (definition.oppositeColumn) {
|
||||
columns.push(
|
||||
`
|
||||
CASE
|
||||
WHEN opposite_config.id IS NULL THEN NULL
|
||||
ELSE json_build_object('id', opposite_config.id, 'name', ${oppositeName})
|
||||
END AS opposite
|
||||
`
|
||||
);
|
||||
}
|
||||
if (definition.hasItemDrop) {
|
||||
columns.push(`c.has_item_drop AS "hasItemDrop"`);
|
||||
}
|
||||
@@ -1122,6 +1140,14 @@ function configSelect(definition: ConfigDefinition, locale: string): string {
|
||||
return columns.join(', ');
|
||||
}
|
||||
|
||||
function configRelationJoins(definition: ConfigDefinition): string {
|
||||
if (!definition.oppositeColumn) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return `LEFT JOIN ${definition.table} opposite_config ON opposite_config.id = c.${definition.oppositeColumn}`;
|
||||
}
|
||||
|
||||
function validationError(message: string): ValidationError {
|
||||
const error = new Error(message) as ValidationError;
|
||||
error.statusCode = 400;
|
||||
@@ -2070,7 +2096,7 @@ async function ensurePokemonTypeCatalog(
|
||||
const changes = configEditChanges(
|
||||
{ table: 'pokemon_types', entityType: 'pokemon-types' },
|
||||
existing.rows[0],
|
||||
{ name, translations, hasItemDrop: false, hasTrading: false, changeLog: '' }
|
||||
{ name, description: '', translations, oppositeName: '', hasItemDrop: false, hasTrading: false, changeLog: '' }
|
||||
);
|
||||
if (changes.length) {
|
||||
await client.query(
|
||||
@@ -2636,7 +2662,9 @@ function configEditChanges(
|
||||
before: ConfigChangeSource,
|
||||
after: {
|
||||
name: string;
|
||||
description: string;
|
||||
translations: TranslationInput;
|
||||
oppositeName: string;
|
||||
hasItemDrop: boolean;
|
||||
hasTrading: boolean;
|
||||
changeLog: string;
|
||||
@@ -2645,6 +2673,12 @@ function configEditChanges(
|
||||
const changes: EditChange[] = [];
|
||||
pushChange(changes, 'Name', before.name, after.name);
|
||||
pushTranslationChanges(changes, before.translations, after.translations, ['name']);
|
||||
if (definition.hasDescription) {
|
||||
pushChange(changes, 'Description', before.description, after.description);
|
||||
}
|
||||
if (definition.oppositeColumn) {
|
||||
pushChange(changes, 'Opposite', before.opposite?.name ?? '', after.oppositeName);
|
||||
}
|
||||
if (definition.hasItemDrop) {
|
||||
pushChange(changes, 'Has item drop', boolValue(Boolean(before.hasItemDrop)), boolValue(after.hasItemDrop));
|
||||
}
|
||||
@@ -2684,8 +2718,10 @@ function pokemonProjection(locale: string): string {
|
||||
const pokemonDetails = localizedField('pokemon', 'p.id', 'p.details', 'details', locale);
|
||||
const typeName = localizedName('pokemon-types', 'pt', locale);
|
||||
const environmentName = localizedName('environments', 'e', locale);
|
||||
const environmentOppositeName = localizedName('environments', 'environment_opposite', locale);
|
||||
const skillName = localizedName('skills', 's', locale);
|
||||
const favoriteThingName = localizedName('favorite-things', 'ft', locale);
|
||||
const favoriteThingOppositeName = localizedName('favorite-things', 'opposite_ft', locale);
|
||||
|
||||
return `
|
||||
SELECT
|
||||
@@ -2715,7 +2751,16 @@ function pokemonProjection(locale: string): string {
|
||||
) AS stats,
|
||||
${translationsSelect('pokemon', 'p.id')} AS translations,
|
||||
${auditSelect('p', 'pokemon_created_user', 'pokemon_updated_user')},
|
||||
json_build_object('id', e.id, 'name', ${environmentName}) AS environment,
|
||||
json_build_object(
|
||||
'id', e.id,
|
||||
'name', ${environmentName},
|
||||
'description', e.description,
|
||||
'opposite',
|
||||
CASE
|
||||
WHEN environment_opposite.id IS NULL THEN NULL
|
||||
ELSE json_build_object('id', environment_opposite.id, 'name', ${environmentOppositeName})
|
||||
END
|
||||
) AS environment,
|
||||
COALESCE((
|
||||
SELECT json_agg(json_build_object('id', pt.id, 'name', ${typeName}) ORDER BY ppt.slot_order)
|
||||
FROM pokemon_pokemon_types ppt
|
||||
@@ -2729,13 +2774,26 @@ function pokemonProjection(locale: string): string {
|
||||
WHERE ps.pokemon_id = p.id
|
||||
), '[]'::json) AS skills,
|
||||
COALESCE((
|
||||
SELECT json_agg(json_build_object('id', ft.id, 'name', ${favoriteThingName}) ORDER BY ${orderByEntity('ft')})
|
||||
SELECT json_agg(
|
||||
json_build_object(
|
||||
'id', ft.id,
|
||||
'name', ${favoriteThingName},
|
||||
'opposite',
|
||||
CASE
|
||||
WHEN opposite_ft.id IS NULL THEN NULL
|
||||
ELSE json_build_object('id', opposite_ft.id, 'name', ${favoriteThingOppositeName})
|
||||
END
|
||||
)
|
||||
ORDER BY ${orderByEntity('ft')}
|
||||
)
|
||||
FROM pokemon_favorite_things pft
|
||||
JOIN favorite_things ft ON ft.id = pft.favorite_thing_id
|
||||
LEFT JOIN favorite_things opposite_ft ON opposite_ft.id = ft.opposite_favorite_thing_id
|
||||
WHERE pft.pokemon_id = p.id
|
||||
), '[]'::json) AS favorite_things
|
||||
FROM pokemon p
|
||||
JOIN environments e ON e.id = p.environment_id
|
||||
LEFT JOIN environments environment_opposite ON environment_opposite.id = e.opposite_environment_id
|
||||
${auditJoins('p', 'pokemon_created_user', 'pokemon_updated_user')}
|
||||
`;
|
||||
}
|
||||
@@ -5363,6 +5421,7 @@ export async function listConfig(type: ConfigType, locale = defaultLocale) {
|
||||
`
|
||||
SELECT ${configSelect(definition, locale)}, ${auditSelect('c')}
|
||||
FROM ${definition.table} c
|
||||
${configRelationJoins(definition)}
|
||||
${auditJoins('c')}
|
||||
ORDER BY ${configOrder()}
|
||||
`
|
||||
@@ -5375,6 +5434,7 @@ async function getConfigById(type: ConfigType, id: number, locale = defaultLocal
|
||||
`
|
||||
SELECT ${configSelect(definition, locale)}, ${auditSelect('c')}
|
||||
FROM ${definition.table} c
|
||||
${configRelationJoins(definition)}
|
||||
${auditJoins('c')}
|
||||
WHERE c.id = $1
|
||||
`,
|
||||
@@ -5386,6 +5446,8 @@ export async function createConfig(type: ConfigType, payload: Record<string, unk
|
||||
const definition = configDefinitions[type];
|
||||
const name = cleanName(payload.name);
|
||||
const translations = cleanTranslations(payload.translations, ['name']);
|
||||
const description = definition.hasDescription ? cleanOptionalText(payload.description) : '';
|
||||
const oppositeId = definition.oppositeColumn ? cleanOptionalPositiveInteger(payload.oppositeId) : null;
|
||||
const hasItemDrop = definition.hasItemDrop ? Boolean(payload.hasItemDrop) : false;
|
||||
const hasTrading = definition.hasTrading ? Boolean(payload.hasTrading) : false;
|
||||
const changeLog = definition.hasChangeLog ? cleanOptionalText(payload.changeLog) : '';
|
||||
@@ -5394,6 +5456,14 @@ export async function createConfig(type: ConfigType, payload: Record<string, unk
|
||||
const sortOrder = await nextSortOrder(client, definition.table);
|
||||
const columns = ['name'];
|
||||
const values: unknown[] = [name];
|
||||
if (definition.hasDescription) {
|
||||
columns.push('description');
|
||||
values.push(description);
|
||||
}
|
||||
if (definition.oppositeColumn) {
|
||||
columns.push(definition.oppositeColumn);
|
||||
values.push(oppositeId);
|
||||
}
|
||||
if (definition.hasItemDrop) {
|
||||
columns.push('has_item_drop');
|
||||
values.push(hasItemDrop);
|
||||
@@ -5451,14 +5521,28 @@ export async function updateConfig(
|
||||
const definition = configDefinitions[type];
|
||||
const name = cleanName(payload.name);
|
||||
const translations = cleanTranslations(payload.translations, ['name']);
|
||||
const description = definition.hasDescription ? cleanOptionalText(payload.description) : '';
|
||||
const oppositeId = definition.oppositeColumn ? cleanOptionalPositiveInteger(payload.oppositeId) : null;
|
||||
const hasItemDrop = definition.hasItemDrop ? Boolean(payload.hasItemDrop) : false;
|
||||
const hasTrading = definition.hasTrading ? Boolean(payload.hasTrading) : false;
|
||||
const changeLog = definition.hasChangeLog ? cleanOptionalText(payload.changeLog) : '';
|
||||
const before = await getConfigById(type, id, defaultLocale);
|
||||
|
||||
if (oppositeId === id) {
|
||||
throw validationError('server.validation.invalidField');
|
||||
}
|
||||
|
||||
const updated = await withTransaction(async (client) => {
|
||||
const assignments = ['name = $1'];
|
||||
const values: unknown[] = [name];
|
||||
if (definition.hasDescription) {
|
||||
values.push(description);
|
||||
assignments.push(`description = $${values.length}`);
|
||||
}
|
||||
if (definition.oppositeColumn) {
|
||||
values.push(oppositeId);
|
||||
assignments.push(`${definition.oppositeColumn} = $${values.length}`);
|
||||
}
|
||||
if (definition.hasItemDrop) {
|
||||
values.push(hasItemDrop);
|
||||
assignments.push(`has_item_drop = $${values.length}`);
|
||||
@@ -5513,8 +5597,17 @@ export async function updateConfig(
|
||||
}
|
||||
|
||||
await replaceEntityTranslations(client, definition.entityType, id, translations, ['name']);
|
||||
const oppositeNames = definition.oppositeColumn && oppositeId ? await entityNameMap(client, definition.table, [oppositeId]) : new Map<number, string>();
|
||||
const changes = before
|
||||
? configEditChanges(definition, before as ConfigChangeSource, { name, translations, hasItemDrop, hasTrading, changeLog })
|
||||
? configEditChanges(definition, before as ConfigChangeSource, {
|
||||
name,
|
||||
description,
|
||||
translations,
|
||||
oppositeName: oppositeId ? oppositeNames.get(oppositeId) ?? '' : '',
|
||||
hasItemDrop,
|
||||
hasTrading,
|
||||
changeLog
|
||||
})
|
||||
: [];
|
||||
await recordEditLog(client, type, id, 'update', userId, changes);
|
||||
return true;
|
||||
@@ -5637,6 +5730,7 @@ export async function getPokemon(id: number, locale = defaultLocale) {
|
||||
const relatedEnvironmentName = localizedName('environments', 'related_environment', locale);
|
||||
const relatedSkillName = localizedName('skills', 'related_skill', locale);
|
||||
const relatedFavoriteThingName = localizedName('favorite-things', 'related_favorite_thing', locale);
|
||||
const relatedFavoriteThingOppositeName = localizedName('favorite-things', 'related_favorite_thing_opposite', locale);
|
||||
const tradingItemName = localizedName('items', 'trading_item', locale);
|
||||
|
||||
const [habitats, itemDrops, favoriteThingItems, tradingItems, relatedPokemon, editHistory, imageHistory] = await Promise.all([
|
||||
@@ -5718,24 +5812,56 @@ export async function getPokemon(id: number, locale = defaultLocale) {
|
||||
WHERE p.id = $1
|
||||
),
|
||||
current_favourites AS (
|
||||
SELECT pft.favorite_thing_id
|
||||
SELECT
|
||||
pft.favorite_thing_id,
|
||||
ft.opposite_favorite_thing_id
|
||||
FROM pokemon_favorite_things pft
|
||||
JOIN favorite_things ft ON ft.id = pft.favorite_thing_id
|
||||
WHERE pft.pokemon_id = $1
|
||||
),
|
||||
scored_pokemon AS (
|
||||
SELECT
|
||||
related_pokemon.id,
|
||||
(related_pokemon.environment_id = current_pokemon.environment_id) AS "environmentMatches",
|
||||
COUNT(current_favourites.favorite_thing_id)::integer AS "favoriteThingMatchCount"
|
||||
(
|
||||
related_pokemon.environment_id = current_environment.opposite_environment_id
|
||||
OR related_environment.opposite_environment_id = current_pokemon.environment_id
|
||||
) AS "environmentIsOpposite",
|
||||
COUNT(current_favourites.favorite_thing_id) FILTER (
|
||||
WHERE current_favourites.favorite_thing_id = related_pokemon_favourite.favorite_thing_id
|
||||
)::integer AS "favoriteThingMatchCount",
|
||||
COUNT(current_favourites.favorite_thing_id) FILTER (
|
||||
WHERE current_favourites.opposite_favorite_thing_id = related_pokemon_favourite.favorite_thing_id
|
||||
OR related_favorite_thing.opposite_favorite_thing_id = current_favourites.favorite_thing_id
|
||||
)::integer AS "favoriteThingOppositeCount"
|
||||
FROM current_pokemon
|
||||
JOIN environments current_environment ON current_environment.id = current_pokemon.environment_id
|
||||
JOIN pokemon related_pokemon ON related_pokemon.id <> current_pokemon.id
|
||||
JOIN environments related_environment ON related_environment.id = related_pokemon.environment_id
|
||||
LEFT JOIN pokemon_favorite_things related_pokemon_favourite
|
||||
ON related_pokemon_favourite.pokemon_id = related_pokemon.id
|
||||
LEFT JOIN favorite_things related_favorite_thing
|
||||
ON related_favorite_thing.id = related_pokemon_favourite.favorite_thing_id
|
||||
LEFT JOIN current_favourites
|
||||
ON current_favourites.favorite_thing_id = related_pokemon_favourite.favorite_thing_id
|
||||
GROUP BY related_pokemon.id, related_pokemon.environment_id, current_pokemon.environment_id
|
||||
OR current_favourites.opposite_favorite_thing_id = related_pokemon_favourite.favorite_thing_id
|
||||
OR related_favorite_thing.opposite_favorite_thing_id = current_favourites.favorite_thing_id
|
||||
GROUP BY
|
||||
related_pokemon.id,
|
||||
related_pokemon.environment_id,
|
||||
related_environment.opposite_environment_id,
|
||||
current_pokemon.environment_id,
|
||||
current_environment.opposite_environment_id
|
||||
HAVING related_pokemon.environment_id = current_pokemon.environment_id
|
||||
OR COUNT(current_favourites.favorite_thing_id) > 0
|
||||
OR related_pokemon.environment_id = current_environment.opposite_environment_id
|
||||
OR related_environment.opposite_environment_id = current_pokemon.environment_id
|
||||
OR COUNT(current_favourites.favorite_thing_id) FILTER (
|
||||
WHERE current_favourites.favorite_thing_id = related_pokemon_favourite.favorite_thing_id
|
||||
) > 0
|
||||
OR COUNT(current_favourites.favorite_thing_id) FILTER (
|
||||
WHERE current_favourites.opposite_favorite_thing_id = related_pokemon_favourite.favorite_thing_id
|
||||
OR related_favorite_thing.opposite_favorite_thing_id = current_favourites.favorite_thing_id
|
||||
) > 0
|
||||
)
|
||||
SELECT
|
||||
related_pokemon.id,
|
||||
@@ -5743,7 +5869,12 @@ export async function getPokemon(id: number, locale = defaultLocale) {
|
||||
${relatedPokemonName} AS name,
|
||||
related_pokemon.is_event_item AS "isEventItem",
|
||||
${pokemonImageJson('related_pokemon')} AS image,
|
||||
json_build_object('id', related_environment.id, 'name', ${relatedEnvironmentName}) AS environment,
|
||||
json_build_object(
|
||||
'id', related_environment.id,
|
||||
'name', ${relatedEnvironmentName},
|
||||
'matches', scored_pokemon."environmentMatches",
|
||||
'isOpposite', scored_pokemon."environmentIsOpposite"
|
||||
) AS environment,
|
||||
COALESCE((
|
||||
SELECT json_agg(
|
||||
json_build_object(
|
||||
@@ -5763,22 +5894,40 @@ export async function getPokemon(id: number, locale = defaultLocale) {
|
||||
json_build_object(
|
||||
'id', related_favorite_thing.id,
|
||||
'name', ${relatedFavoriteThingName},
|
||||
'opposite',
|
||||
CASE
|
||||
WHEN related_favorite_thing_opposite.id IS NULL THEN NULL
|
||||
ELSE json_build_object('id', related_favorite_thing_opposite.id, 'name', ${relatedFavoriteThingOppositeName})
|
||||
END,
|
||||
'matches', EXISTS (
|
||||
SELECT 1
|
||||
FROM current_favourites
|
||||
WHERE current_favourites.favorite_thing_id = related_favorite_thing.id
|
||||
),
|
||||
'isOpposite', EXISTS (
|
||||
SELECT 1
|
||||
FROM current_favourites
|
||||
WHERE current_favourites.opposite_favorite_thing_id = related_favorite_thing.id
|
||||
OR related_favorite_thing.opposite_favorite_thing_id = current_favourites.favorite_thing_id
|
||||
)
|
||||
)
|
||||
ORDER BY ${orderByEntity('related_favorite_thing')}
|
||||
)
|
||||
FROM pokemon_favorite_things related_pokemon_favourite
|
||||
JOIN favorite_things related_favorite_thing ON related_favorite_thing.id = related_pokemon_favourite.favorite_thing_id
|
||||
LEFT JOIN favorite_things related_favorite_thing_opposite
|
||||
ON related_favorite_thing_opposite.id = related_favorite_thing.opposite_favorite_thing_id
|
||||
WHERE related_pokemon_favourite.pokemon_id = related_pokemon.id
|
||||
), '[]'::json) AS favorite_things
|
||||
FROM scored_pokemon
|
||||
JOIN pokemon related_pokemon ON related_pokemon.id = scored_pokemon.id
|
||||
JOIN environments related_environment ON related_environment.id = related_pokemon.environment_id
|
||||
ORDER BY scored_pokemon."environmentMatches" DESC, scored_pokemon."favoriteThingMatchCount" DESC, related_pokemon.id
|
||||
ORDER BY
|
||||
scored_pokemon."environmentMatches" DESC,
|
||||
scored_pokemon."favoriteThingMatchCount" DESC,
|
||||
scored_pokemon."environmentIsOpposite" DESC,
|
||||
scored_pokemon."favoriteThingOppositeCount" DESC,
|
||||
related_pokemon.id
|
||||
`,
|
||||
[id]
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user