Enhance performance and error handling in the application
- Optimize release profile settings in Cargo.toml - Update Dockerfile to include additional binary - Modify compose.yml to adjust database URL for read/write mode - Improve error handling in API calls within api.ts - Refactor character properties in types.ts for clarity - Update Card component to reflect new character properties - Revise utility functions for better episode handling
This commit is contained in:
@@ -24,3 +24,10 @@ tower-http = { version = "0.6.6", features = ["cors", "fs", "trace"] }
|
|||||||
tracing = "0.1.41"
|
tracing = "0.1.41"
|
||||||
tracing-log = "0.2.0"
|
tracing-log = "0.2.0"
|
||||||
tracing-subscriber = { version = "0.3.19", features = ["env-filter", "fmt"] }
|
tracing-subscriber = { version = "0.3.19", features = ["env-filter", "fmt"] }
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
opt-level = 3
|
||||||
|
lto = true
|
||||||
|
codegen-units = 1
|
||||||
|
panic = "abort"
|
||||||
|
strip = true
|
||||||
|
@@ -20,6 +20,7 @@ WORKDIR /app
|
|||||||
RUN apt-get update && apt-get install -y ca-certificates openssl sqlite3 && rm -rf /var/lib/apt/lists/*
|
RUN apt-get update && apt-get install -y ca-certificates openssl sqlite3 && rm -rf /var/lib/apt/lists/*
|
||||||
RUN mkdir -p /app/data
|
RUN mkdir -p /app/data
|
||||||
COPY --from=backend-builder /app/target/release/rick-and-morty .
|
COPY --from=backend-builder /app/target/release/rick-and-morty .
|
||||||
|
COPY --from=backend-builder /app/target/release/fetch_characters .
|
||||||
COPY --from=backend-builder /app/migrations ./migrations
|
COPY --from=backend-builder /app/migrations ./migrations
|
||||||
COPY --from=frontend-builder /app/dist ./frontend/dist
|
COPY --from=frontend-builder /app/dist ./frontend/dist
|
||||||
EXPOSE 8000
|
EXPOSE 8000
|
||||||
|
@@ -3,7 +3,7 @@ services:
|
|||||||
build: .
|
build: .
|
||||||
container_name: rick_and_morty_app
|
container_name: rick_and_morty_app
|
||||||
environment:
|
environment:
|
||||||
DATABASE_URL: "sqlite:///app/data/rick_and_morty.db"
|
DATABASE_URL: "sqlite:///app/data/rick_and_morty.db?mode=rwc"
|
||||||
BIND_ADDR: 0.0.0.0:8000
|
BIND_ADDR: 0.0.0.0:8000
|
||||||
ports:
|
ports:
|
||||||
- "8000:8000"
|
- "8000:8000"
|
||||||
|
Binary file not shown.
@@ -4,21 +4,22 @@ import { getCharacters, rateCharacters } from "./api";
|
|||||||
import type { Character } from "./types";
|
import type { Character } from "./types";
|
||||||
import { Card } from "./components/card";
|
import { Card } from "./components/card";
|
||||||
import { Table } from "./components/table";
|
import { Table } from "./components/table";
|
||||||
import { getId } from "./utils";
|
|
||||||
|
|
||||||
function getRandomIndex(length: number) {
|
function getRandomIndex(length: number) {
|
||||||
return Math.floor(Math.random() * length);
|
return Math.floor(Math.random() * length);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getRandomPair(characters: Character[], lastPair: [number, number]) {
|
// Optionally: Use a lastPairRef to avoid same characters twice in a row
|
||||||
if (characters.length < 2) return [null, null];
|
function getRandomPair(
|
||||||
|
characters: Character[],
|
||||||
|
lastPair: [number, number] | null
|
||||||
|
): [Character, Character] {
|
||||||
|
if (characters.length < 2) return [null, null] as never;
|
||||||
let firstIndex = getRandomIndex(characters.length);
|
let firstIndex = getRandomIndex(characters.length);
|
||||||
let secondIndex = getRandomIndex(characters.length);
|
let secondIndex = getRandomIndex(characters.length);
|
||||||
|
|
||||||
while (
|
while (
|
||||||
firstIndex === lastPair[0] ||
|
(lastPair && firstIndex === lastPair[0] && secondIndex === lastPair[1]) ||
|
||||||
secondIndex === lastPair[1] ||
|
|
||||||
firstIndex === secondIndex
|
firstIndex === secondIndex
|
||||||
) {
|
) {
|
||||||
firstIndex = getRandomIndex(characters.length);
|
firstIndex = getRandomIndex(characters.length);
|
||||||
@@ -57,8 +58,8 @@ const App: React.FC = () => {
|
|||||||
if (voting || !rivals[winnerIdx] || !rivals[loserIdx]) return;
|
if (voting || !rivals[winnerIdx] || !rivals[loserIdx]) return;
|
||||||
setVoting(true);
|
setVoting(true);
|
||||||
setVotedLeft(winnerIdx === 0);
|
setVotedLeft(winnerIdx === 0);
|
||||||
const winnerId = getId(rivals[winnerIdx]);
|
const winnerId = rivals[winnerIdx]!.id;
|
||||||
const loserId = getId(rivals[loserIdx]);
|
const loserId = rivals[loserIdx]!.id;
|
||||||
if (!winnerId || !loserId) return;
|
if (!winnerId || !loserId) return;
|
||||||
await rateCharacters(winnerId, loserId);
|
await rateCharacters(winnerId, loserId);
|
||||||
const chars = await getCharacters();
|
const chars = await getCharacters();
|
||||||
|
@@ -2,13 +2,19 @@ import type { Character } from './types';
|
|||||||
|
|
||||||
export const getCharacters = async (): Promise<Character[]> => {
|
export const getCharacters = async (): Promise<Character[]> => {
|
||||||
const res = await fetch('/characters');
|
const res = await fetch('/characters');
|
||||||
|
if (!res.ok) {
|
||||||
|
throw new Error('Failed to fetch characters');
|
||||||
|
}
|
||||||
return res.json();
|
return res.json();
|
||||||
};
|
};
|
||||||
|
|
||||||
export const rateCharacters = async (winnerId: string, loserId: string) => {
|
export const rateCharacters = async (winnerId: number, loserId: number) => {
|
||||||
await fetch('/rate', {
|
const res = await fetch('/rate', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ winner_id: winnerId, loser_id: loserId }),
|
body: JSON.stringify({ winner_id: winnerId.toString(), loser_id: loserId.toString() }),
|
||||||
});
|
});
|
||||||
|
if (!res.ok) {
|
||||||
|
throw new Error('Failed to rate characters');
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React from "react";
|
||||||
import type { Character } from '../types';
|
import type { Character } from "../types";
|
||||||
|
|
||||||
interface CardProps {
|
interface CardProps {
|
||||||
data: Character;
|
data: Character;
|
||||||
@@ -16,9 +16,11 @@ export const Card: React.FC<CardProps> = ({
|
|||||||
onClick,
|
onClick,
|
||||||
}) => (
|
}) => (
|
||||||
<div
|
<div
|
||||||
className={`card bg-gray-300 flex flex-col items-center shadow-lg rounded p-1 transform transition ease-in-out ${isClicked || skipped ? 'flip-card' : ''} md:hover:scale-105`}
|
className={`card bg-gray-300 flex flex-col items-center shadow-lg rounded p-1 transform transition ease-in-out ${
|
||||||
|
isClicked || skipped ? "flip-card" : ""
|
||||||
|
} md:hover:scale-105`}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
style={{ cursor: 'pointer' }}
|
style={{ cursor: "pointer" }}
|
||||||
>
|
>
|
||||||
<div className="card-inner">
|
<div className="card-inner">
|
||||||
{/* FRONT */}
|
{/* FRONT */}
|
||||||
@@ -43,11 +45,11 @@ export const Card: React.FC<CardProps> = ({
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Origin</th>
|
<th>Origin</th>
|
||||||
<td>{data.origin.name}</td>
|
<td>{data.origin_name}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Last location</th>
|
<th>Last location</th>
|
||||||
<td>{data.location.name}</td>
|
<td>{data.location_name}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
@@ -4,17 +4,19 @@ export interface OriginOrLocation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface Character {
|
export interface Character {
|
||||||
_id: string | { $oid: string };
|
id: number;
|
||||||
rmid: number;
|
rmid: number;
|
||||||
name: string;
|
name: string;
|
||||||
status: string;
|
status: string;
|
||||||
species: string;
|
species: string;
|
||||||
type: string;
|
type: string;
|
||||||
gender: string;
|
gender: string;
|
||||||
origin: OriginOrLocation;
|
origin_name: string;
|
||||||
location: OriginOrLocation;
|
origin_url: string;
|
||||||
|
location_name: string;
|
||||||
|
location_url: string;
|
||||||
image: string;
|
image: string;
|
||||||
episode: string[];
|
episode: string; // This will be a stringified JSON array!
|
||||||
url: string;
|
url: string;
|
||||||
created: string;
|
created: string;
|
||||||
elo_rating: number;
|
elo_rating: number;
|
||||||
|
@@ -1,9 +1,10 @@
|
|||||||
import type { Character } from "./types";
|
import type { Character } from "./types";
|
||||||
|
|
||||||
export function getId(character: Character | null) {
|
export function getEpisodes(character: Character): string[] {
|
||||||
if (!character) return undefined;
|
if (Array.isArray(character.episode)) return character.episode;
|
||||||
const id = character._id as string | { $oid: string } | undefined;
|
try {
|
||||||
if (typeof id === 'string') return id;
|
return JSON.parse(character.episode);
|
||||||
if (id && typeof id.$oid === 'string') return id.$oid;
|
} catch {
|
||||||
return undefined;
|
return [];
|
||||||
|
}
|
||||||
}
|
}
|
Reference in New Issue
Block a user