generated from nhcarrigan/template
feat: Multiple Features, Accessibility, Security, and UX Improvements #59
@@ -32,6 +32,8 @@ model Game {
|
||||
coverImage String?
|
||||
tags String[]
|
||||
links Link[]
|
||||
series String?
|
||||
seriesOrder Int? @db.Int
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
comments Comment[]
|
||||
@@ -58,6 +60,8 @@ model Book {
|
||||
coverImage String?
|
||||
tags String[]
|
||||
links Link[]
|
||||
series String?
|
||||
seriesOrder Int? @db.Int
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
comments Comment[]
|
||||
|
||||
@@ -24,6 +24,17 @@ const booksRoutes: FastifyPluginAsync = async (app) => {
|
||||
return bookService.getAllBooks();
|
||||
});
|
||||
|
||||
/**
|
||||
* Get all books in a series (public route).
|
||||
*/
|
||||
app.get<{ Params: { seriesName: string }; Reply: Book[] }>(
|
||||
"/series/:seriesName",
|
||||
async (request) => {
|
||||
const { seriesName } = request.params;
|
||||
return bookService.getBooksBySeries(seriesName);
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Get single book by ID (public route).
|
||||
*/
|
||||
|
||||
@@ -22,6 +22,15 @@ const gamesRoutes: FastifyPluginAsync = async (app) => {
|
||||
return gameService.getAllGames();
|
||||
});
|
||||
|
||||
// Get all games in a series (public route)
|
||||
app.get<{ Params: { seriesName: string }; Reply: Game[] }>(
|
||||
"/series/:seriesName",
|
||||
async (request) => {
|
||||
const { seriesName } = request.params;
|
||||
return gameService.getGamesBySeries(seriesName);
|
||||
}
|
||||
);
|
||||
|
||||
// Get single game (public route)
|
||||
app.get<{ Params: { id: string }; Reply: Game | null }>(
|
||||
"/:id",
|
||||
|
||||
@@ -56,6 +56,28 @@ export class BookService {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all books in a series, ordered by seriesOrder.
|
||||
*/
|
||||
async getBooksBySeries(seriesName: string): Promise<Book[]> {
|
||||
const books = await this.prisma.book.findMany({
|
||||
where: { series: seriesName },
|
||||
orderBy: { seriesOrder: "asc" },
|
||||
});
|
||||
|
||||
return books.map((book) => ({
|
||||
...book,
|
||||
status: book.status as unknown as BookStatus,
|
||||
dateAdded: book.dateAdded,
|
||||
dateStarted: book.dateStarted || undefined,
|
||||
dateFinished: book.dateFinished || undefined,
|
||||
tags: book.tags ?? [],
|
||||
links: book.links ?? [],
|
||||
createdAt: book.createdAt,
|
||||
updatedAt: book.updatedAt,
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new book.
|
||||
*/
|
||||
|
||||
@@ -58,6 +58,29 @@ export class GameService {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all games in a series, ordered by seriesOrder.
|
||||
*/
|
||||
async getGamesBySeries(seriesName: string): Promise<Game[]> {
|
||||
const games = await this.prisma.game.findMany({
|
||||
where: { series: seriesName },
|
||||
orderBy: { seriesOrder: "asc" },
|
||||
});
|
||||
|
||||
return games.map((game) => ({
|
||||
...game,
|
||||
status: game.status as unknown as GameStatus,
|
||||
dateAdded: game.dateAdded,
|
||||
dateStarted: game.dateStarted || undefined,
|
||||
dateCompleted: game.dateCompleted || undefined,
|
||||
dateFinished: game.dateFinished || undefined,
|
||||
tags: game.tags ?? [],
|
||||
links: game.links ?? [],
|
||||
createdAt: game.createdAt,
|
||||
updatedAt: game.updatedAt,
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new game.
|
||||
*/
|
||||
|
||||
@@ -127,6 +127,29 @@ import { Book, BookStatus, CreateBookDto, UpdateBookDto, Comment, SuggestionEnti
|
||||
></textarea>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="series">Series (optional)</label>
|
||||
<input
|
||||
type="text"
|
||||
id="series"
|
||||
[(ngModel)]="newBook.series"
|
||||
name="series"
|
||||
placeholder="e.g., Harry Potter"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="seriesOrder">Series Order (optional)</label>
|
||||
<input
|
||||
type="number"
|
||||
id="seriesOrder"
|
||||
[(ngModel)]="newBook.seriesOrder"
|
||||
name="seriesOrder"
|
||||
min="1"
|
||||
placeholder="Order in series"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="coverImage">Cover Image (max 500KB)</label>
|
||||
<input
|
||||
@@ -289,6 +312,29 @@ import { Book, BookStatus, CreateBookDto, UpdateBookDto, Comment, SuggestionEnti
|
||||
></textarea>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="edit-series">Series (optional)</label>
|
||||
<input
|
||||
type="text"
|
||||
id="edit-series"
|
||||
[(ngModel)]="editBook.series"
|
||||
name="series"
|
||||
placeholder="e.g., Harry Potter"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="edit-seriesOrder">Series Order (optional)</label>
|
||||
<input
|
||||
type="number"
|
||||
id="edit-seriesOrder"
|
||||
[(ngModel)]="editBook.seriesOrder"
|
||||
name="seriesOrder"
|
||||
min="1"
|
||||
placeholder="Order in series"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="edit-coverImage">Cover Image (max 500KB)</label>
|
||||
<input
|
||||
|
||||
@@ -115,6 +115,29 @@ import { Game, GameStatus, CreateGameDto, UpdateGameDto, Comment, SuggestionEnti
|
||||
></textarea>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="series">Series (optional)</label>
|
||||
<input
|
||||
type="text"
|
||||
id="series"
|
||||
[(ngModel)]="newGame.series"
|
||||
name="series"
|
||||
placeholder="e.g., The Legend of Zelda"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="seriesOrder">Series Order (optional)</label>
|
||||
<input
|
||||
type="number"
|
||||
id="seriesOrder"
|
||||
[(ngModel)]="newGame.seriesOrder"
|
||||
name="seriesOrder"
|
||||
min="1"
|
||||
placeholder="Order in series"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="coverImage">Box Art (max 500KB)</label>
|
||||
<input
|
||||
@@ -265,6 +288,29 @@ import { Game, GameStatus, CreateGameDto, UpdateGameDto, Comment, SuggestionEnti
|
||||
></textarea>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="edit-series">Series (optional)</label>
|
||||
<input
|
||||
type="text"
|
||||
id="edit-series"
|
||||
[(ngModel)]="editGame.series"
|
||||
name="series"
|
||||
placeholder="e.g., The Legend of Zelda"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="edit-seriesOrder">Series Order (optional)</label>
|
||||
<input
|
||||
type="number"
|
||||
id="edit-seriesOrder"
|
||||
[(ngModel)]="editGame.seriesOrder"
|
||||
name="seriesOrder"
|
||||
min="1"
|
||||
placeholder="Order in series"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="edit-coverImage">Box Art (max 500KB)</label>
|
||||
<input
|
||||
|
||||
@@ -27,6 +27,8 @@ interface Book {
|
||||
coverImage?: string;
|
||||
tags: Array<string>;
|
||||
links: Array<Link>;
|
||||
series?: string;
|
||||
seriesOrder?: number;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
@@ -43,6 +45,8 @@ interface CreateBookDto {
|
||||
coverImage?: string;
|
||||
tags?: Array<string>;
|
||||
links?: Array<Link>;
|
||||
series?: string;
|
||||
seriesOrder?: number;
|
||||
}
|
||||
|
||||
interface UpdateBookDto extends Partial<CreateBookDto> {
|
||||
|
||||
@@ -27,6 +27,8 @@ interface Game {
|
||||
coverImage?: string;
|
||||
tags: Array<string>;
|
||||
links: Array<Link>;
|
||||
series?: string;
|
||||
seriesOrder?: number;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
@@ -42,6 +44,8 @@ interface CreateGameDto {
|
||||
coverImage?: string;
|
||||
tags?: Array<string>;
|
||||
links?: Array<Link>;
|
||||
series?: string;
|
||||
seriesOrder?: number;
|
||||
}
|
||||
|
||||
interface UpdateGameDto extends Partial<CreateGameDto> {
|
||||
|
||||
Reference in New Issue
Block a user