+
+
{user.avatar_id ? (

{
)}
-
+
{user.disp_name || user.nickname}
@{user.nickname}
- {/*
- Joined: {new Date(user.creation_date).toLocaleDateString()}
-
*/}
-
-
-
{user.user_desc &&
{user.user_desc}
}
+
+ Joined: {new Date(user.creation_date).toLocaleDateString()}
+
diff --git a/modules/frontend/src/components/cards/TitleCardHorizontal.tsx b/modules/frontend/src/components/cards/TitleCardHorizontal.tsx
deleted file mode 100644
index c3a8159..0000000
--- a/modules/frontend/src/components/cards/TitleCardHorizontal.tsx
+++ /dev/null
@@ -1,22 +0,0 @@
-import type { Title } from "../../api/models/Title";
-
-export function TitleCardHorizontal({ title }: { title: Title }) {
- return (
-
- {title.posterUrl && (
-

- )}
-
-
{title.name}
-
{title.year} · {title.season} · Rating: {title.rating}
-
Status: {title.status}
-
-
- );
-}
diff --git a/modules/frontend/src/components/cards/TitleCardSquare.tsx b/modules/frontend/src/components/cards/TitleCardSquare.tsx
deleted file mode 100644
index 0fc0339..0000000
--- a/modules/frontend/src/components/cards/TitleCardSquare.tsx
+++ /dev/null
@@ -1,22 +0,0 @@
-// TitleCardSquare.tsx
-import type { Title } from "../../api/models/Title";
-
-export function TitleCardSquare({ title }: { title: Title }) {
- return (
-
- {title.posterUrl && (
-

- )}
-
-
{title.name}
- {title.year} • {title.rating}
-
-
- );
-}
diff --git a/modules/frontend/src/pages/TitlePage/TitlePage.module.css b/modules/frontend/src/pages/TitlePage/TitlePage.module.css
deleted file mode 100644
index e69de29..0000000
diff --git a/modules/frontend/src/pages/TitlePage/TitlePage.tsx b/modules/frontend/src/pages/TitlePage/TitlePage.tsx
deleted file mode 100644
index 7fe9de7..0000000
--- a/modules/frontend/src/pages/TitlePage/TitlePage.tsx
+++ /dev/null
@@ -1,64 +0,0 @@
-// import React, { useEffect, useState } from "react";
-// import { useParams } from "react-router-dom";
-// import { DefaultService } from "../../api/services/DefaultService";
-// import type { User } from "../../api/models/User";
-// import styles from "./UserPage.module.css";
-
-// const UserPage: React.FC = () => {
-// const { id } = useParams<{ id: string }>();
-// const [user, setUser] = useState
(null);
-// const [loading, setLoading] = useState(true);
-// const [error, setError] = useState(null);
-
-// useEffect(() => {
-// if (!id) return;
-
-// const getTitleInfo = async () => {
-// try {
-// const userInfo = await DefaultService.getTitle(id, "all");
-// setUser(userInfo);
-// } catch (err) {
-// console.error(err);
-// setError("Failed to fetch user info.");
-// } finally {
-// setLoading(false);
-// }
-// };
-// getTitleInfo();
-// }, [id]);
-
-// if (loading) return Loading...
;
-// if (error) return {error}
;
-// if (!user) return User not found.
;
-
-// return (
-//
-//
-//
-// {user.avatar_id ? (
-//

-// ) : (
-//
-// {user.disp_name?.[0] || "U"}
-//
-// )}
-//
-
-//
-//
{user.disp_name || user.nickname}
-//
@{user.nickname}
-// {user.user_desc &&
{user.user_desc}
}
-//
-// Joined: {new Date(user.creation_date).toLocaleDateString()}
-//
-//
-//
-//
-// );
-// };
-
-// export default UserPage;
diff --git a/modules/frontend/src/pages/TitlesPage/TitlesPage.module.css b/modules/frontend/src/pages/TitlesPage/TitlesPage.module.css
deleted file mode 100644
index 9cc728b..0000000
--- a/modules/frontend/src/pages/TitlesPage/TitlesPage.module.css
+++ /dev/null
@@ -1,59 +0,0 @@
-.container {
- padding: 24px;
-}
-
-.header {
- display: flex;
- justify-content: space-between;
- margin-bottom: 16px;
-}
-
-.searchInput {
- padding: 8px;
- width: 240px;
-}
-
-.list {
- display: grid;
- gap: 12px;
-}
-
-.card {
- display: flex;
- padding: 10px;
- border: 1px solid #ddd;
- border-radius: 8px;
- gap: 12px;
-}
-
-.poster {
- width: 80px;
- height: 120px;
- object-fit: cover;
- border-radius: 4px;
-}
-
-.posterPlaceholder {
- width: 80px;
- height: 120px;
- background: #eee;
- display: flex;
- align-items: center;
- justify-content: center;
-}
-
-.cardInfo {
- display: flex;
- flex-direction: column;
-}
-
-.loadMore {
- margin-top: 16px;
- padding: 8px 16px;
-}
-
-.loader,
-.error {
- padding: 20px;
- text-align: center;
-}
diff --git a/modules/frontend/src/pages/TitlesPage/TitlesPage.tsx b/modules/frontend/src/pages/TitlesPage/TitlesPage.tsx
deleted file mode 100644
index 438d828..0000000
--- a/modules/frontend/src/pages/TitlesPage/TitlesPage.tsx
+++ /dev/null
@@ -1,114 +0,0 @@
-import React, { useEffect, useState } from "react";
-import { DefaultService } from "../../api/services/DefaultService";
-import type { Title } from "../../api/models/Title";
-import styles from "./TitlesPage.module.css";
-
-const LIMIT = 20;
-
-const TitlesPage: React.FC = () => {
- const [titles, setTitles] = useState([]);
- const [search, setSearch] = useState("");
- const [offset, setOffset] = useState(0);
-
- const [loading, setLoading] = useState(true);
- const [loadingMore, setLoadingMore] = useState(false);
- const [error, setError] = useState(null);
-
- const fetchTitles = async (reset: boolean) => {
- try {
- if (reset) {
- setLoading(true);
- setOffset(0);
- } else {
- setLoadingMore(true);
- }
-
- const result = await DefaultService.getTitles(
- search || undefined,
- undefined, // status
- undefined, // rating
- undefined, // release_year
- undefined, // release_season
- LIMIT,
- reset ? 0 : offset,
- "all"
- );
-
- if (reset) {
- setTitles(result);
- } else {
- setTitles(prev => [...prev, ...result]);
- }
-
- if (result.length > 0) {
- setOffset(prev => prev + LIMIT);
- }
-
- } catch (err) {
- console.error(err);
- setError("Failed to fetch titles.");
- } finally {
- setLoading(false);
- setLoadingMore(false);
- }
- };
-
- useEffect(() => {
- fetchTitles(true);
- }, [search]);
-
- if (loading) return Loading...
;
- if (error) return {error}
;
-
- return (
-
-
-
Titles
-
- setSearch(e.target.value)}
- />
-
-
-
- {titles.map((t) => (
-
- {t.poster_id ? (
-

- ) : (
-
No Image
- )}
-
-
-
{t.name}
-
- {t.release_year} • {t.release_season}
-
-
Rating: {t.rating}
-
{t.status}
-
-
- ))}
-
-
- {titles.length > 0 && (
-
- )}
-
- );
-};
-
-export default TitlesPage;
diff --git a/modules/frontend/src/pages/UserPage/UserPage.module.css b/modules/frontend/src/pages/UserPage/UserPage.module.css
deleted file mode 100644
index 7f350c8..0000000
--- a/modules/frontend/src/pages/UserPage/UserPage.module.css
+++ /dev/null
@@ -1,103 +0,0 @@
-body,
-html {
- width: 100%;
- margin: 0;
- background-color: #777;
- color: #fff;
-}
-
-html,
-body,
-#root {
- height: 100%;
-}
-
-.header {
- width: 100vw;
- padding: 30px 40px;
- background: #f7f7f7;
- display: flex;
- align-items: center;
- gap: 25px;
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
- border-bottom: 1px solid #e5e5e5;
- color: #000000;
-}
-
-.avatarWrapper {
- width: 120px;
- height: 120px;
- min-width: 120px;
- border-radius: 50%;
- overflow: hidden;
- display: flex;
- align-items: center;
- justify-content: center;
- background: #ddd;
-}
-
-.avatarImg {
- width: 100%;
- height: 100%;
- object-fit: cover;
-}
-
-.avatarPlaceholder {
- width: 100%;
- height: 100%;
- border-radius: 50%;
- background: #ccc;
- font-size: 42px;
- font-weight: bold;
- color: #555;
- display: flex;
- align-items: center;
- justify-content: center;
-}
-
-.userInfo {
- display: flex;
- flex-direction: column;
-}
-
-.name {
- font-size: 32px;
- font-weight: 700;
- margin: 0;
-}
-
-.nickname {
- font-size: 18px;
- color: #666;
- margin-top: 6px;
-}
-
-.container {
- max-width: 100vw;
- width: 100%;
- position: absolute;
- top: 0%;
- /* margin: 25px auto; */
- /* padding: 0 20px; */
-}
-
-.content {
- margin-top: 20px;
-}
-
-.desc {
- font-size: 18px;
- margin-bottom: 10px;
-}
-
-.created {
- font-size: 16px;
- color: #888;
-}
-
-.loader,
-.error {
- text-align: center;
- margin-top: 40px;
- font-size: 18px;
-}
diff --git a/modules/frontend/src/types/list.ts b/modules/frontend/src/types/list.ts
deleted file mode 100644
index 582da39..0000000
--- a/modules/frontend/src/types/list.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-export interface PaginatedResult {
- items: TItem[];
- nextCursor?: string;
-}
-
-export interface FetchParams {
- search: string;
- cursor?: string;
-}
-
-export type FetchFunction =
- (params: FetchParams) => Promise>;
diff --git a/modules/frontend/tsconfig.app.json b/modules/frontend/tsconfig.app.json
index 2f416e5..a9b5a59 100644
--- a/modules/frontend/tsconfig.app.json
+++ b/modules/frontend/tsconfig.app.json
@@ -20,7 +20,7 @@
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
- "erasableSyntaxOnly": false,
+ "erasableSyntaxOnly": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
diff --git a/modules/frontend/tsconfig.node.json b/modules/frontend/tsconfig.node.json
index 3439137..8a67f62 100644
--- a/modules/frontend/tsconfig.node.json
+++ b/modules/frontend/tsconfig.node.json
@@ -18,7 +18,7 @@
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
- "erasableSyntaxOnly": false,
+ "erasableSyntaxOnly": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
diff --git a/sql/queries.sql.go b/sql/queries.sql.go
index 7d970cb..4e28f40 100644
--- a/sql/queries.sql.go
+++ b/sql/queries.sql.go
@@ -179,7 +179,7 @@ func (q *Queries) GetTitleTags(ctx context.Context, titleID int64) ([][]byte, er
return nil, err
}
defer rows.Close()
- items := [][]byte{}
+ var items [][]byte
for rows.Next() {
var tag_names []byte
if err := rows.Scan(&tag_names); err != nil {
@@ -289,131 +289,75 @@ const searchTitles = `-- name: SearchTitles :many
SELECT
id, title_names, studio_id, poster_id, title_status, rating, rating_count, release_year, release_season, season, episodes_aired, episodes_all, episodes_len
FROM titles
-WHERE
- CASE
- WHEN $1::boolean THEN
- -- forward: greater than cursor (next page)
- CASE $2::text
- WHEN 'year' THEN
- ($3::int IS NULL) OR
- (release_year > $3::int) OR
- (release_year = $3::int AND id > $4::bigint)
-
- WHEN 'rating' THEN
- ($5::float IS NULL) OR
- (rating > $5::float) OR
- (rating = $5::float AND id > $4::bigint)
-
- WHEN 'id' THEN
- ($4::bigint IS NULL) OR
- (id > $4::bigint)
-
- ELSE true -- fallback
- END
-
- ELSE
- -- backward: less than cursor (prev page)
- CASE $2::text
- WHEN 'year' THEN
- ($3::int IS NULL) OR
- (release_year < $3::int) OR
- (release_year = $3::int AND id < $4::bigint)
-
- WHEN 'rating' THEN
- ($5::float IS NULL) OR
- (rating < $5::float) OR
- (rating = $5::float AND id < $4::bigint)
-
- WHEN 'id' THEN
- ($4::bigint IS NULL) OR
- (id < $4::bigint)
-
- ELSE true
- END
+WHERE
+ CASE
+ WHEN $1::text IS NOT NULL THEN
+ (
+ SELECT bool_and(
+ EXISTS (
+ SELECT 1
+ FROM jsonb_each_text(title_names) AS t(key, val)
+ WHERE val ILIKE pattern
+ )
+ )
+ FROM unnest(
+ ARRAY(
+ SELECT '%' || trim(w) || '%'
+ FROM unnest(string_to_array($1::text, ' ')) AS w
+ WHERE trim(w) <> ''
+ )
+ ) AS pattern
+ )
+ ELSE true
END
- AND (
- CASE
- WHEN $6::text IS NOT NULL THEN
- (
- SELECT bool_and(
- EXISTS (
- SELECT 1
- FROM jsonb_each_text(title_names) AS t(key, val)
- WHERE val ILIKE pattern
- )
- )
- FROM unnest(
- ARRAY(
- SELECT '%' || trim(w) || '%'
- FROM unnest(string_to_array($6::text, ' ')) AS w
- WHERE trim(w) <> ''
- )
- ) AS pattern
- )
- ELSE true
- END
- )
-
- AND ($7::title_status_t IS NULL OR title_status = $7::title_status_t)
- AND ($8::float IS NULL OR rating >= $8::float)
- AND ($9::int IS NULL OR release_year = $9::int)
- AND ($10::release_season_t IS NULL OR release_season = $10::release_season_t)
-
-ORDER BY
- CASE WHEN $1::boolean THEN
- CASE
- WHEN $2::text = 'id' THEN id
- WHEN $2::text = 'year' THEN release_year
- WHEN $2::text = 'rating' THEN rating
- END
- END ASC,
- CASE WHEN NOT $1::boolean THEN
- CASE
- WHEN $2::text = 'id' THEN id
- WHEN $2::text = 'year' THEN release_year
- WHEN $2::text = 'rating' THEN rating
- END
- END DESC,
-
- CASE WHEN $2::text <> 'id' THEN id END ASC
-
-LIMIT COALESCE($11::int, 100)
+ AND ($2::title_status_t IS NULL OR title_status = $2::title_status_t)
+ AND ($3::float IS NULL OR rating >= $3::float)
+ AND ($4::int IS NULL OR release_year = $4::int)
+ AND ($5::release_season_t IS NULL OR release_season = $5::release_season_t)
+ORDER BY CASE
+ WHEN $6::boolean AND $7::text = 'name' THEN name
+ WHEN $8::boolean AND $7::text = 'id' THEN id
+ WHEN $8::boolean AND $7::text = 'name' THEN name
+ WHEN $8::boolean AND $7::text = 'id' THEN id
+END ASC, CASE
+ WHEN NOT $8::boolean AND $7::text = 'name' THEN name
+ WHEN NOT $8::boolean AND $7::text = 'id' THEN id
+ WHEN NOT $8::boolean AND $7::text = 'name' THEN name
+ WHEN NOT $8::boolean AND $7::text = 'id' THEN id
+END DESC
+LIMIT COALESCE($9::int, 100)
`
type SearchTitlesParams struct {
- Forward bool `json:"forward"`
- SortBy string `json:"sort_by"`
- CursorYear *int32 `json:"cursor_year"`
- CursorID *int64 `json:"cursor_id"`
- CursorRating *float64 `json:"cursor_rating"`
Word *string `json:"word"`
Status *TitleStatusT `json:"status"`
Rating *float64 `json:"rating"`
ReleaseYear *int32 `json:"release_year"`
ReleaseSeason *ReleaseSeasonT `json:"release_season"`
+ Forward bool `json:"forward"`
+ OrderBy string `json:"order_by"`
+ Reverse bool `json:"reverse"`
Limit *int32 `json:"limit"`
}
func (q *Queries) SearchTitles(ctx context.Context, arg SearchTitlesParams) ([]Title, error) {
rows, err := q.db.Query(ctx, searchTitles,
- arg.Forward,
- arg.SortBy,
- arg.CursorYear,
- arg.CursorID,
- arg.CursorRating,
arg.Word,
arg.Status,
arg.Rating,
arg.ReleaseYear,
arg.ReleaseSeason,
+ arg.Forward,
+ arg.OrderBy,
+ arg.Reverse,
arg.Limit,
)
if err != nil {
return nil, err
}
defer rows.Close()
- items := []Title{}
+ var items []Title
for rows.Next() {
var i Title
if err := rows.Scan(
@@ -511,6 +455,7 @@ type SearchUserTitlesRow struct {
}
// 100 is default limit
+// OFFSET sqlc.narg('offset')::int;
func (q *Queries) SearchUserTitles(ctx context.Context, arg SearchUserTitlesParams) ([]SearchUserTitlesRow, error) {
rows, err := q.db.Query(ctx, searchUserTitles,
arg.Word,
@@ -525,7 +470,7 @@ func (q *Queries) SearchUserTitles(ctx context.Context, arg SearchUserTitlesPara
return nil, err
}
defer rows.Close()
- items := []SearchUserTitlesRow{}
+ var items []SearchUserTitlesRow
for rows.Next() {
var i SearchUserTitlesRow
if err := rows.Scan(
diff --git a/sql/sqlc.yaml b/sql/sqlc.yaml
index 94b9fb4..f74d2ad 100644
--- a/sql/sqlc.yaml
+++ b/sql/sqlc.yaml
@@ -12,7 +12,6 @@ sql:
sql_driver: "github.com/jackc/pgx/v5"
emit_json_tags: true
emit_pointers_for_null_types: true
- emit_empty_slices: true #slices returned by :many queries will be empty instead of nil
overrides:
- db_type: "uuid"
nullable: false