mirror of
https://github.com/Drop-OSS/drop.git
synced 2026-07-24 00:42:51 +10:00
feat: ratings ui, import giantbomb ratings
This commit is contained in:
@@ -120,12 +120,11 @@ import {
|
|||||||
ArrowUpRightIcon,
|
ArrowUpRightIcon,
|
||||||
} from "@heroicons/vue/20/solid";
|
} from "@heroicons/vue/20/solid";
|
||||||
import { micromark } from "micromark";
|
import { micromark } from "micromark";
|
||||||
import type { Game } from "~/prisma/client";
|
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const id = route.params.id.toString();
|
const id = route.params.id.toString();
|
||||||
|
|
||||||
const rawGame = await $dropFetch<Game>(`/api/v1/games/${id}`);
|
const { game: rawGame } = await $dropFetch(`/api/v1/games/${id}`);
|
||||||
const game = computed(() => {
|
const game = computed(() => {
|
||||||
if (!rawGame) {
|
if (!rawGame) {
|
||||||
throw createError({ statusCode: 404, message: "Game not found" });
|
throw createError({ statusCode: 404, message: "Game not found" });
|
||||||
|
|||||||
@@ -103,7 +103,9 @@
|
|||||||
'w-4 h-4',
|
'w-4 h-4',
|
||||||
]"
|
]"
|
||||||
/>
|
/>
|
||||||
<span class="text-zinc-600">({{ 0 }} reviews)</span>
|
<span class="text-zinc-600"
|
||||||
|
>({{ rating._sum.mReviewCount ?? 0 }} reviews)</span
|
||||||
|
>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -174,10 +176,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ArrowTopRightOnSquareIcon } from "@heroicons/vue/24/outline";
|
import { ArrowTopRightOnSquareIcon } from "@heroicons/vue/24/outline";
|
||||||
import { StarIcon } from "@heroicons/vue/24/solid";
|
import { StarIcon } from "@heroicons/vue/24/solid";
|
||||||
import type { Game, GameVersion } from "~/prisma/client";
|
|
||||||
import { micromark } from "micromark";
|
import { micromark } from "micromark";
|
||||||
import { DateTime } from "luxon";
|
import { DateTime } from "luxon";
|
||||||
import type { SerializeObject } from "nitropack";
|
|
||||||
import type { PlatformClient } from "~/composables/types";
|
import type { PlatformClient } from "~/composables/types";
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
@@ -185,9 +185,7 @@ const gameId = route.params.id.toString();
|
|||||||
|
|
||||||
const user = useUser();
|
const user = useUser();
|
||||||
|
|
||||||
const game = await $dropFetch<
|
const { game, rating } = await $dropFetch(`/api/v1/games/${gameId}`);
|
||||||
SerializeObject<Game> & { versions: GameVersion[] }
|
|
||||||
>(`/api/v1/games/${gameId}`);
|
|
||||||
|
|
||||||
// Preview description (first 30 lines)
|
// Preview description (first 30 lines)
|
||||||
const showPreview = ref(true);
|
const showPreview = ref(true);
|
||||||
@@ -219,10 +217,10 @@ const platforms = game.versions
|
|||||||
.filter((e, i, u) => u.indexOf(e) === i);
|
.filter((e, i, u) => u.indexOf(e) === i);
|
||||||
|
|
||||||
// const rating = Math.round(game.mReviewRating * 5);
|
// const rating = Math.round(game.mReviewRating * 5);
|
||||||
const rating = Math.round(0 * 5);
|
const averageRating = Math.round((rating._avg.mReviewRating ?? 0) * 5);
|
||||||
const ratingArray = Array(5)
|
const ratingArray = Array(5)
|
||||||
.fill(null)
|
.fill(null)
|
||||||
.map((_, i) => i + 1 <= rating);
|
.map((_, i) => i + 1 <= averageRating);
|
||||||
|
|
||||||
useHead({
|
useHead({
|
||||||
title: game.mName,
|
title: game.mName,
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ model GameRating {
|
|||||||
|
|
||||||
mReviewHref String?
|
mReviewHref String?
|
||||||
|
|
||||||
Game Game @relation(fields: [gameId], references: [id], onDelete: Cascade)
|
game Game @relation(fields: [gameId], references: [id], onDelete: Cascade)
|
||||||
gameId String
|
gameId String
|
||||||
|
|
||||||
@@unique([metadataSource, metadataId], name: "metadataKey")
|
@@unique([metadataSource, metadataId], name: "metadataKey")
|
||||||
|
|||||||
@@ -22,5 +22,17 @@ export default defineEventHandler(async (h3) => {
|
|||||||
if (!game)
|
if (!game)
|
||||||
throw createError({ statusCode: 404, statusMessage: "Game not found" });
|
throw createError({ statusCode: 404, statusMessage: "Game not found" });
|
||||||
|
|
||||||
return game;
|
const rating = await prisma.gameRating.aggregate({
|
||||||
|
where: {
|
||||||
|
gameId: game.id,
|
||||||
|
},
|
||||||
|
_avg: {
|
||||||
|
mReviewRating: true,
|
||||||
|
},
|
||||||
|
_sum: {
|
||||||
|
mReviewCount: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return { game, rating };
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import type {
|
|||||||
GameMetadata,
|
GameMetadata,
|
||||||
_FetchCompanyMetadataParams,
|
_FetchCompanyMetadataParams,
|
||||||
CompanyMetadata,
|
CompanyMetadata,
|
||||||
|
GameMetadataRating,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
import axios, { type AxiosRequestConfig } from "axios";
|
import axios, { type AxiosRequestConfig } from "axios";
|
||||||
import TurndownService from "turndown";
|
import TurndownService from "turndown";
|
||||||
@@ -58,6 +59,17 @@ interface GameResult {
|
|||||||
tags: string; // If it's "All Images", art, otherwise screenshot
|
tags: string; // If it's "All Images", art, otherwise screenshot
|
||||||
original: string;
|
original: string;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
reviews: Array<{
|
||||||
|
api_detail_url: string;
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ReviewResult {
|
||||||
|
deck: string;
|
||||||
|
score: number; // Out of 5
|
||||||
|
reviewer: string;
|
||||||
|
site_detail_url: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CompanySearchResult {
|
interface CompanySearchResult {
|
||||||
@@ -198,6 +210,21 @@ export class GiantBombProvider implements MetadataProvider {
|
|||||||
}-${gameData.expected_release_day ?? 1}`,
|
}-${gameData.expected_release_day ?? 1}`,
|
||||||
).toJSDate();
|
).toJSDate();
|
||||||
|
|
||||||
|
const reviews: GameMetadataRating[] = [];
|
||||||
|
for (const { api_detail_url } of gameData.reviews) {
|
||||||
|
const reviewId = api_detail_url.split("/").at(-2);
|
||||||
|
if (!reviewId) continue;
|
||||||
|
const review = await this.request<ReviewResult>("review", reviewId, {});
|
||||||
|
console.log(review.data);
|
||||||
|
reviews.push({
|
||||||
|
metadataSource: MetadataSource.GiantBomb,
|
||||||
|
metadataId: reviewId,
|
||||||
|
mReviewCount: 1,
|
||||||
|
mReviewRating: review.data.results.score / 5,
|
||||||
|
mReviewHref: review.data.results.site_detail_url,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const metadata: GameMetadata = {
|
const metadata: GameMetadata = {
|
||||||
id: gameData.guid,
|
id: gameData.guid,
|
||||||
name: gameData.name,
|
name: gameData.name,
|
||||||
@@ -207,7 +234,7 @@ export class GiantBombProvider implements MetadataProvider {
|
|||||||
|
|
||||||
tags: [],
|
tags: [],
|
||||||
|
|
||||||
reviews: [],
|
reviews,
|
||||||
|
|
||||||
publishers,
|
publishers,
|
||||||
developers,
|
developers,
|
||||||
|
|||||||
@@ -355,8 +355,11 @@ export class IGDBProvider implements MetadataProvider {
|
|||||||
const response = await this.request<IGDBGameFull>("games", body);
|
const response = await this.request<IGDBGameFull>("games", body);
|
||||||
|
|
||||||
for (let i = 0; i < response.length; i++) {
|
for (let i = 0; i < response.length; i++) {
|
||||||
|
const currentGame = response[i];
|
||||||
|
if(!currentGame) continue;
|
||||||
|
|
||||||
let iconRaw;
|
let iconRaw;
|
||||||
const cover = response[i].cover;
|
const cover = currentGame.cover;
|
||||||
if (cover !== undefined) {
|
if (cover !== undefined) {
|
||||||
iconRaw = await this.getCoverURL(cover);
|
iconRaw = await this.getCoverURL(cover);
|
||||||
} else {
|
} else {
|
||||||
@@ -366,7 +369,7 @@ export class IGDBProvider implements MetadataProvider {
|
|||||||
let banner = "";
|
let banner = "";
|
||||||
|
|
||||||
const images = [icon];
|
const images = [icon];
|
||||||
for (const art of response[i]?.artworks ?? []) {
|
for (const art of currentGame.artworks ?? []) {
|
||||||
// if banner not set
|
// if banner not set
|
||||||
if (banner.length <= 0) {
|
if (banner.length <= 0) {
|
||||||
banner = createObject(await this.getArtworkURL(art));
|
banner = createObject(await this.getArtworkURL(art));
|
||||||
@@ -378,7 +381,7 @@ export class IGDBProvider implements MetadataProvider {
|
|||||||
|
|
||||||
const publishers: Company[] = [];
|
const publishers: Company[] = [];
|
||||||
const developers: Company[] = [];
|
const developers: Company[] = [];
|
||||||
for (const involvedCompany of response[i]?.involved_companies ?? []) {
|
for (const involvedCompany of currentGame.involved_companies ?? []) {
|
||||||
// get details about the involved company
|
// get details about the involved company
|
||||||
const involved_company_response =
|
const involved_company_response =
|
||||||
await this.request<IGDBInvolvedCompany>(
|
await this.request<IGDBInvolvedCompany>(
|
||||||
@@ -408,13 +411,13 @@ export class IGDBProvider implements MetadataProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const firstReleaseDate = response[i].first_release_date;
|
const firstReleaseDate = currentGame.first_release_date;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: "" + response[i].id,
|
id: "" + response[i].id,
|
||||||
name: response[i].name,
|
name: response[i].name,
|
||||||
shortDescription: this.trimMessage(response[i].summary, 280),
|
shortDescription: this.trimMessage(currentGame.summary, 280),
|
||||||
description: response[i].summary,
|
description: currentGame.summary,
|
||||||
released:
|
released:
|
||||||
firstReleaseDate === undefined
|
firstReleaseDate === undefined
|
||||||
? new Date()
|
? new Date()
|
||||||
@@ -422,18 +425,18 @@ export class IGDBProvider implements MetadataProvider {
|
|||||||
|
|
||||||
reviews: [
|
reviews: [
|
||||||
{
|
{
|
||||||
metadataId: "" + response[i].id,
|
metadataId: "" + currentGame.id,
|
||||||
metadataSource: MetadataSource.IGDB,
|
metadataSource: MetadataSource.IGDB,
|
||||||
mReviewCount: response[i]?.total_rating_count ?? 0,
|
mReviewCount: currentGame.total_rating_count ?? 0,
|
||||||
mReviewRating: (response[i]?.total_rating ?? 0) / 100,
|
mReviewRating: (currentGame.total_rating ?? 0) / 100,
|
||||||
mReviewHref: response[i].url,
|
mReviewHref: currentGame.url,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
||||||
publishers: [],
|
publishers: [],
|
||||||
developers: [],
|
developers: [],
|
||||||
|
|
||||||
tags: await this.getGenres(response[i].genres),
|
tags: await this.getGenres(currentGame.genres),
|
||||||
|
|
||||||
icon,
|
icon,
|
||||||
bannerId: banner,
|
bannerId: banner,
|
||||||
|
|||||||
@@ -764,7 +764,7 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@kwsites/promise-deferred/-/promise-deferred-1.1.1.tgz#8ace5259254426ccef57f3175bc64ed7095ed919"
|
resolved "https://registry.yarnpkg.com/@kwsites/promise-deferred/-/promise-deferred-1.1.1.tgz#8ace5259254426ccef57f3175bc64ed7095ed919"
|
||||||
integrity sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==
|
integrity sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==
|
||||||
|
|
||||||
"@lobomfz/prismark@^0.0.3":
|
"@lobomfz/prismark@0.0.3":
|
||||||
version "0.0.3"
|
version "0.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/@lobomfz/prismark/-/prismark-0.0.3.tgz#8b4eb34963e96cf3fff01432e62de22f184f2f2d"
|
resolved "https://registry.yarnpkg.com/@lobomfz/prismark/-/prismark-0.0.3.tgz#8b4eb34963e96cf3fff01432e62de22f184f2f2d"
|
||||||
integrity sha512-g2xfR/F+sRBRUhWYlpUkafqZjqsQBetjfzdWvQndRU4wdoavn3zblM3OQwb7vrsrKB6Wmbs+DtLGaD5XBQ2v8A==
|
integrity sha512-g2xfR/F+sRBRUhWYlpUkafqZjqsQBetjfzdWvQndRU4wdoavn3zblM3OQwb7vrsrKB6Wmbs+DtLGaD5XBQ2v8A==
|
||||||
|
|||||||
Reference in New Issue
Block a user