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:
2025-07-20 15:15:30 +02:00
parent 27e9119123
commit c5de84c013
9 changed files with 95 additions and 75 deletions

View File

@@ -24,3 +24,10 @@ tower-http = { version = "0.6.6", features = ["cors", "fs", "trace"] }
tracing = "0.1.41"
tracing-log = "0.2.0"
tracing-subscriber = { version = "0.3.19", features = ["env-filter", "fmt"] }
[profile.release]
opt-level = 3
lto = true
codegen-units = 1
panic = "abort"
strip = true

View File

@@ -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 mkdir -p /app/data
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=frontend-builder /app/dist ./frontend/dist
EXPOSE 8000

View File

@@ -3,7 +3,7 @@ services:
build: .
container_name: rick_and_morty_app
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
ports:
- "8000:8000"

Binary file not shown.

View File

@@ -4,21 +4,22 @@ import { getCharacters, rateCharacters } from "./api";
import type { Character } from "./types";
import { Card } from "./components/card";
import { Table } from "./components/table";
import { getId } from "./utils";
function getRandomIndex(length: number) {
return Math.floor(Math.random() * length);
}
function getRandomPair(characters: Character[], lastPair: [number, number]) {
if (characters.length < 2) return [null, null];
// Optionally: Use a lastPairRef to avoid same characters twice in a row
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 secondIndex = getRandomIndex(characters.length);
while (
firstIndex === lastPair[0] ||
secondIndex === lastPair[1] ||
(lastPair && firstIndex === lastPair[0] && secondIndex === lastPair[1]) ||
firstIndex === secondIndex
) {
firstIndex = getRandomIndex(characters.length);
@@ -57,8 +58,8 @@ const App: React.FC = () => {
if (voting || !rivals[winnerIdx] || !rivals[loserIdx]) return;
setVoting(true);
setVotedLeft(winnerIdx === 0);
const winnerId = getId(rivals[winnerIdx]);
const loserId = getId(rivals[loserIdx]);
const winnerId = rivals[winnerIdx]!.id;
const loserId = rivals[loserIdx]!.id;
if (!winnerId || !loserId) return;
await rateCharacters(winnerId, loserId);
const chars = await getCharacters();

View File

@@ -2,13 +2,19 @@ import type { Character } from './types';
export const getCharacters = async (): Promise<Character[]> => {
const res = await fetch('/characters');
if (!res.ok) {
throw new Error('Failed to fetch characters');
}
return res.json();
};
export const rateCharacters = async (winnerId: string, loserId: string) => {
await fetch('/rate', {
export const rateCharacters = async (winnerId: number, loserId: number) => {
const res = await fetch('/rate', {
method: 'POST',
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');
}
};

View File

@@ -1,5 +1,5 @@
import React from 'react';
import type { Character } from '../types';
import React from "react";
import type { Character } from "../types";
interface CardProps {
data: Character;
@@ -16,9 +16,11 @@ export const Card: React.FC<CardProps> = ({
onClick,
}) => (
<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}
style={{ cursor: 'pointer' }}
style={{ cursor: "pointer" }}
>
<div className="card-inner">
{/* FRONT */}
@@ -43,11 +45,11 @@ export const Card: React.FC<CardProps> = ({
</tr>
<tr>
<th>Origin</th>
<td>{data.origin.name}</td>
<td>{data.origin_name}</td>
</tr>
<tr>
<th>Last location</th>
<td>{data.location.name}</td>
<td>{data.location_name}</td>
</tr>
</tbody>
</table>

View File

@@ -4,17 +4,19 @@ export interface OriginOrLocation {
}
export interface Character {
_id: string | { $oid: string };
id: number;
rmid: number;
name: string;
status: string;
species: string;
type: string;
gender: string;
origin: OriginOrLocation;
location: OriginOrLocation;
origin_name: string;
origin_url: string;
location_name: string;
location_url: string;
image: string;
episode: string[];
episode: string; // This will be a stringified JSON array!
url: string;
created: string;
elo_rating: number;

View File

@@ -1,9 +1,10 @@
import type { Character } from "./types";
export function getId(character: Character | null) {
if (!character) return undefined;
const id = character._id as string | { $oid: string } | undefined;
if (typeof id === 'string') return id;
if (id && typeof id.$oid === 'string') return id.$oid;
return undefined;
export function getEpisodes(character: Character): string[] {
if (Array.isArray(character.episode)) return character.episode;
try {
return JSON.parse(character.episode);
} catch {
return [];
}
}