diff --git a/backend/alembic/versions/0020_created_and_updated.py b/backend/alembic/versions/0020_created_and_updated.py
new file mode 100644
index 000000000..e43074190
--- /dev/null
+++ b/backend/alembic/versions/0020_created_and_updated.py
@@ -0,0 +1,145 @@
+"""empty message
+
+Revision ID: 0020_created_and_updated
+Revises: 0019_resources_refactor
+Create Date: 2024-06-27 12:08:13.886766
+
+"""
+
+import sqlalchemy as sa
+from alembic import op
+from sqlalchemy.dialects import mysql
+
+# revision identifiers, used by Alembic.
+revision = "0020_created_and_updated"
+down_revision = "0019_resources_refactor"
+branch_labels = None
+depends_on = None
+
+
+def upgrade() -> None:
+ # ### commands auto generated by Alembic - please adjust! ###
+ with op.batch_alter_table("firmware", schema=None) as batch_op:
+ batch_op.add_column(
+ sa.Column(
+ "created_at",
+ sa.DateTime(timezone=True),
+ server_default=sa.text("now()"),
+ nullable=False,
+ )
+ )
+ batch_op.add_column(
+ sa.Column(
+ "updated_at",
+ sa.DateTime(timezone=True),
+ server_default=sa.text("now()"),
+ nullable=False,
+ )
+ )
+
+ with op.batch_alter_table("platforms", schema=None) as batch_op:
+ batch_op.add_column(
+ sa.Column(
+ "created_at",
+ sa.DateTime(timezone=True),
+ server_default=sa.text("now()"),
+ nullable=False,
+ )
+ )
+ batch_op.add_column(
+ sa.Column(
+ "updated_at",
+ sa.DateTime(timezone=True),
+ server_default=sa.text("now()"),
+ nullable=False,
+ )
+ )
+
+ with op.batch_alter_table("rom_notes", schema=None) as batch_op:
+ batch_op.add_column(
+ sa.Column(
+ "created_at",
+ sa.DateTime(timezone=True),
+ server_default=sa.text("now()"),
+ nullable=False,
+ )
+ )
+ batch_op.add_column(
+ sa.Column(
+ "updated_at",
+ sa.DateTime(timezone=True),
+ server_default=sa.text("now()"),
+ nullable=False,
+ )
+ )
+ batch_op.drop_column("last_edited_at")
+
+ with op.batch_alter_table("roms", schema=None) as batch_op:
+ batch_op.add_column(
+ sa.Column(
+ "created_at",
+ sa.DateTime(timezone=True),
+ server_default=sa.text("now()"),
+ nullable=False,
+ )
+ )
+ batch_op.add_column(
+ sa.Column(
+ "updated_at",
+ sa.DateTime(timezone=True),
+ server_default=sa.text("now()"),
+ nullable=False,
+ )
+ )
+
+ with op.batch_alter_table("users", schema=None) as batch_op:
+ batch_op.add_column(
+ sa.Column(
+ "created_at",
+ sa.DateTime(timezone=True),
+ server_default=sa.text("now()"),
+ nullable=False,
+ )
+ )
+ batch_op.add_column(
+ sa.Column(
+ "updated_at",
+ sa.DateTime(timezone=True),
+ server_default=sa.text("now()"),
+ nullable=False,
+ )
+ )
+
+ # ### end Alembic commands ###
+
+
+def downgrade() -> None:
+ # ### commands auto generated by Alembic - please adjust! ###
+ with op.batch_alter_table("users", schema=None) as batch_op:
+ batch_op.drop_column("updated_at")
+ batch_op.drop_column("created_at")
+
+ with op.batch_alter_table("roms", schema=None) as batch_op:
+ batch_op.drop_column("updated_at")
+ batch_op.drop_column("created_at")
+
+ with op.batch_alter_table("rom_notes", schema=None) as batch_op:
+ batch_op.add_column(
+ sa.Column(
+ "last_edited_at",
+ mysql.DATETIME(),
+ server_default=sa.text("current_timestamp()"),
+ nullable=False,
+ )
+ )
+ batch_op.drop_column("updated_at")
+
+ with op.batch_alter_table("platforms", schema=None) as batch_op:
+ batch_op.drop_column("updated_at")
+ batch_op.drop_column("created_at")
+
+ with op.batch_alter_table("firmware", schema=None) as batch_op:
+ batch_op.drop_column("updated_at")
+ batch_op.drop_column("created_at")
+
+ # ### end Alembic commands ###
diff --git a/backend/endpoints/heartbeat.py b/backend/endpoints/heartbeat.py
index 3f90f822e..10e06a11b 100644
--- a/backend/endpoints/heartbeat.py
+++ b/backend/endpoints/heartbeat.py
@@ -11,6 +11,7 @@
from handler.filesystem import fs_platform_handler
from handler.metadata.igdb_handler import IGDB_API_ENABLED
from handler.metadata.moby_handler import MOBY_API_ENABLED
+from handler.metadata.sgdb_handler import STEAMGRIDDB_API_ENABLED
from utils import get_version
router = APIRouter()
@@ -27,6 +28,7 @@ def heartbeat() -> HeartbeatResponse:
return {
"VERSION": get_version(),
"ANY_SOURCE_ENABLED": IGDB_API_ENABLED or MOBY_API_ENABLED,
+ "STEAMGRIDDB_ENABLED": STEAMGRIDDB_API_ENABLED,
"METADATA_SOURCES": {
"IGDB_API_ENABLED": IGDB_API_ENABLED,
"MOBY_API_ENABLED": MOBY_API_ENABLED,
diff --git a/backend/endpoints/responses/firmware.py b/backend/endpoints/responses/firmware.py
index bf40fb759..8faa3a306 100644
--- a/backend/endpoints/responses/firmware.py
+++ b/backend/endpoints/responses/firmware.py
@@ -1,3 +1,5 @@
+from datetime import datetime
+
from pydantic import BaseModel
from typing_extensions import TypedDict
@@ -18,6 +20,9 @@ class FirmwareSchema(BaseModel):
md5_hash: str
sha1_hash: str
+ created_at: datetime
+ updated_at: datetime
+
class Config:
from_attributes = True
diff --git a/backend/endpoints/responses/heartbeat.py b/backend/endpoints/responses/heartbeat.py
index cacdf55a2..208f15367 100644
--- a/backend/endpoints/responses/heartbeat.py
+++ b/backend/endpoints/responses/heartbeat.py
@@ -27,4 +27,5 @@ class HeartbeatResponse(TypedDict):
SCHEDULER: SchedulerDict
ANY_SOURCE_ENABLED: bool
METADATA_SOURCES: MetadataSourcesDict
+ STEAMGRIDDB_ENABLED: bool
FS_PLATFORMS: list
diff --git a/backend/endpoints/responses/identity.py b/backend/endpoints/responses/identity.py
index eba4589f5..07005495d 100644
--- a/backend/endpoints/responses/identity.py
+++ b/backend/endpoints/responses/identity.py
@@ -14,5 +14,8 @@ class UserSchema(BaseModel):
last_login: datetime | None
last_active: datetime | None
+ created_at: datetime
+ updated_at: datetime
+
class Config:
from_attributes = True
diff --git a/backend/endpoints/responses/platform.py b/backend/endpoints/responses/platform.py
index f4177ff01..5d7126794 100644
--- a/backend/endpoints/responses/platform.py
+++ b/backend/endpoints/responses/platform.py
@@ -1,3 +1,5 @@
+from datetime import datetime
+
from pydantic import BaseModel, Field
from .firmware import FirmwareSchema
@@ -15,5 +17,8 @@ class PlatformSchema(BaseModel):
logo_path: str | None = ""
firmware: list[FirmwareSchema] = Field(default_factory=list)
+ created_at: datetime
+ updated_at: datetime
+
class Config:
from_attributes = True
diff --git a/backend/endpoints/responses/rom.py b/backend/endpoints/responses/rom.py
index 063012b6f..4e5280db8 100644
--- a/backend/endpoints/responses/rom.py
+++ b/backend/endpoints/responses/rom.py
@@ -30,7 +30,7 @@ class RomNoteSchema(BaseModel):
id: int
user_id: int
rom_id: int
- last_edited_at: datetime
+ updated_at: datetime
raw_markdown: str
is_public: bool
user__username: str
@@ -94,6 +94,9 @@ class RomSchema(BaseModel):
files: list[str]
full_path: str
+ created_at: datetime
+ updated_at: datetime
+
class Config:
from_attributes = True
diff --git a/backend/endpoints/responses/search.py b/backend/endpoints/responses/search.py
index 4219410da..0e3011ad9 100644
--- a/backend/endpoints/responses/search.py
+++ b/backend/endpoints/responses/search.py
@@ -9,3 +9,8 @@ class SearchRomSchema(BaseModel):
summary: str
igdb_url_cover: str = ""
moby_url_cover: str = ""
+
+
+class SearchCoverSchema(BaseModel):
+ name: str
+ resources: list
diff --git a/backend/endpoints/search.py b/backend/endpoints/search.py
index 26fa5ec07..63653cc0e 100644
--- a/backend/endpoints/search.py
+++ b/backend/endpoints/search.py
@@ -1,9 +1,9 @@
import emoji
from decorators.auth import protected_route
-from endpoints.responses.search import SearchRomSchema
+from endpoints.responses.search import SearchCoverSchema, SearchRomSchema
from fastapi import APIRouter, HTTPException, Request, status
from handler.database import db_rom_handler
-from handler.metadata import meta_igdb_handler, meta_moby_handler
+from handler.metadata import meta_igdb_handler, meta_moby_handler, meta_sgdb_handler
from handler.metadata.igdb_handler import IGDB_API_ENABLED
from handler.metadata.moby_handler import MOBY_API_ENABLED
from handler.scan_handler import _get_main_platform_igdb_id
@@ -107,3 +107,12 @@ async def search_rom(
log.info(f"\t - {m_rom['name']}")
return matched_roms
+
+
+@protected_route(router.get, "/search/cover", ["roms.read"])
+async def search_cover(
+ request: Request,
+ search_term: str | None = None,
+) -> list[SearchCoverSchema]:
+
+ return meta_sgdb_handler.get_details(search_term)
diff --git a/backend/handler/filesystem/resources_handler.py b/backend/handler/filesystem/resources_handler.py
index 5fcbcba9c..4c13d0c54 100644
--- a/backend/handler/filesystem/resources_handler.py
+++ b/backend/handler/filesystem/resources_handler.py
@@ -38,8 +38,12 @@ def _cover_exists(rom: Rom, size: CoverSize):
def resize_cover_to_small(cover_path: str):
"""Path of the cover image to resize"""
cover = Image.open(cover_path)
- small_width = int(cover.width * 0.1)
- small_height = int(cover.height * 0.1)
+ if cover.height >= 1000:
+ ratio = 0.2
+ else:
+ ratio = 0.4
+ small_width = int(cover.width * ratio)
+ small_height = int(cover.height * ratio)
small_size = (small_width, small_height)
small_img = cover.resize(small_size)
small_img.save(cover_path)
diff --git a/backend/handler/metadata/igdb_handler.py b/backend/handler/metadata/igdb_handler.py
index 98488b978..59b36c35d 100644
--- a/backend/handler/metadata/igdb_handler.py
+++ b/backend/handler/metadata/igdb_handler.py
@@ -179,13 +179,14 @@ def extract_metadata_from_igdb_rom(rom: dict) -> IGDBMetadata:
class IGDBBaseHandler(MetadataHandler):
def __init__(self) -> None:
- self.platform_endpoint = "https://api.igdb.com/v4/platforms"
- self.platform_version_endpoint = "https://api.igdb.com/v4/platform_versions"
- self.platforms_fields = ["id", "name"]
- self.games_endpoint = "https://api.igdb.com/v4/games"
+ self.BASE_URL = "https://api.igdb.com/v4"
+ self.platform_endpoint = f"{self.BASE_URL}/platforms"
+ self.platform_version_endpoint = f"{self.BASE_URL}/platform_versions"
+ self.platforms_fields = PLATFORMS_FIELDS
+ self.games_endpoint = f"{self.BASE_URL}/games"
self.games_fields = GAMES_FIELDS
- self.search_endpoint = "https://api.igdb.com/v4/search"
- self.search_fields = ["game.id", "name"]
+ self.search_endpoint = f"{self.BASE_URL}/search"
+ self.search_fields = SEARCH_FIELDS
self.pagination_limit = 200
self.twitch_auth = TwitchAuth()
self.headers = {
@@ -617,6 +618,8 @@ def get_oauth_token(self) -> str:
return token
+PLATFORMS_FIELDS = ["id", "name"]
+
GAMES_FIELDS = [
"id",
"name",
@@ -667,6 +670,8 @@ def get_oauth_token(self) -> str:
"similar_games.cover.url",
]
+SEARCH_FIELDS = ["game.id", "name"]
+
# Generated from the following code on https://www.igdb.com/platforms/:
# Array.from(document.querySelectorAll(".media-body a")).map(a => ({
# slug: a.href.split("/")[4],
diff --git a/backend/handler/metadata/sgdb_handler.py b/backend/handler/metadata/sgdb_handler.py
index c75ff29fb..f690bfc5c 100644
--- a/backend/handler/metadata/sgdb_handler.py
+++ b/backend/handler/metadata/sgdb_handler.py
@@ -1,42 +1,99 @@
+from typing import Final
+
import requests
from config import STEAMGRIDDB_API_KEY
from logger.logger import log
+# Used to display the Mobygames API status in the frontend
+STEAMGRIDDB_API_ENABLED: Final = bool(STEAMGRIDDB_API_KEY)
+
+# SteamGridDB dimensions
+STEAMVERTICAL: Final = "600x900"
+GALAXY342: Final = "342x482"
+GALAXY660: Final = "660x930"
+SQUARE512: Final = "512x512"
+SQUARE1024: Final = "1024x1024"
+
+# SteamGridDB types
+STATIC: Final = "static"
+ANIMATED: Final = "animated"
+
+SGDB_API_COVER_LIMIT: Final = 50
+
class SGDBBaseHandler:
def __init__(self) -> None:
+ self.BASE_URL = "https://www.steamgriddb.com/api/v2"
+ self.search_endpoint = f"{self.BASE_URL}/search/autocomplete"
+ self.grid_endpoint = f"{self.BASE_URL}/grids/game"
self.headers = {
"Authorization": f"Bearer {STEAMGRIDDB_API_KEY}",
"Accept": "*/*",
}
- self.BASE_URL = "https://www.steamgriddb.com/api/v2"
- self.DEFAULT_IMAGE_URL = "https://www.steamgriddb.com/static/img/logo-512.png"
- def get_details(self, term):
+ def get_details(self, search_term):
search_response = requests.get(
- f"{self.BASE_URL}/search/autocomplete/{term}",
+ f"{self.search_endpoint}/{search_term}",
headers=self.headers,
timeout=120,
).json()
if len(search_response["data"]) == 0:
- log.info(f"Could not find {term} on SteamGridDB")
- return ("", "", self.DEFAULT_IMAGE_URL)
+ log.warning(f"Could not find '{search_term}' on SteamGridDB")
+ return []
- game_id = search_response["data"][0]["id"]
- game_name = search_response["data"][0]["name"]
-
- game_response = requests.get(
- f"{self.BASE_URL}/grid/game/{game_id}", headers=self.headers, timeout=120
- ).json()
+ games = []
+ for game in search_response["data"]:
+ page = 0
+ covers_response = requests.get(
+ f"{self.grid_endpoint}/{game['id']}",
+ headers=self.headers,
+ timeout=120,
+ params={
+ "dimensions": f"{STEAMVERTICAL},{GALAXY342},{GALAXY660},{SQUARE512},{SQUARE1024}",
+ "types": f"{STATIC},{ANIMATED}",
+ "page": page,
+ },
+ ).json()
- if len(game_response["data"]) == 0:
- log.info(f"Could not find {game_name} image on SteamGridDB")
- return (game_id, game_name, self.DEFAULT_IMAGE_URL)
+ while (
+ len(covers_response["data"]) < covers_response["total"]
+ and covers_response["total"] > SGDB_API_COVER_LIMIT
+ ):
+ page += 1
+ covers_response["data"].extend(
+ requests.get(
+ f"{self.grid_endpoint}/{game['id']}",
+ headers=self.headers,
+ timeout=120,
+ params={
+ "dimensions": f"{STEAMVERTICAL},{GALAXY342},{GALAXY660},{SQUARE512},{SQUARE1024}",
+ "types": f"{STATIC},{ANIMATED}",
+ "page": page,
+ },
+ ).json()["data"]
+ )
- game_image_url = game_response["data"][0]["url"]
+ if len(covers_response["data"]) > 0:
+ games.append(
+ {
+ "name": game["name"],
+ "resources": [
+ {
+ "thumb": cover["thumb"],
+ "url": cover["url"],
+ "type": (
+ "animated"
+ if cover["thumb"].split(".")[-1] == "webm"
+ else "static"
+ ),
+ }
+ for cover in covers_response["data"]
+ ],
+ }
+ )
- return (game_id, game_name, game_image_url)
+ return games
sgdb_handler = SGDBBaseHandler()
diff --git a/backend/models/assets.py b/backend/models/assets.py
index 0403fa2b9..292c0d127 100644
--- a/backend/models/assets.py
+++ b/backend/models/assets.py
@@ -1,9 +1,8 @@
-from datetime import datetime
from functools import cached_property
from typing import TYPE_CHECKING, Optional
from models.base import BaseModel
-from sqlalchemy import BigInteger, DateTime, ForeignKey, String, func
+from sqlalchemy import BigInteger, ForeignKey, String
from sqlalchemy.orm import Mapped, mapped_column, relationship
if TYPE_CHECKING:
@@ -15,13 +14,6 @@ class BaseAsset(BaseModel):
__abstract__ = True
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
- created_at: Mapped[datetime] = mapped_column(
- DateTime(timezone=True), server_default=func.now()
- )
- updated_at: Mapped[datetime] = mapped_column(
- DateTime(timezone=True), server_default=func.now(), onupdate=func.now()
- )
-
file_name: Mapped[str] = mapped_column(String(length=450))
file_name_no_tags: Mapped[str] = mapped_column(String(length=450))
file_name_no_ext: Mapped[str] = mapped_column(String(length=450))
diff --git a/backend/models/base.py b/backend/models/base.py
index f56251667..286af85cf 100644
--- a/backend/models/base.py
+++ b/backend/models/base.py
@@ -1,4 +1,13 @@
-from sqlalchemy.orm import DeclarativeBase
+from datetime import datetime
+from sqlalchemy import DateTime, func
+from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
-class BaseModel(DeclarativeBase): ...
+
+class BaseModel(DeclarativeBase):
+ created_at: Mapped[datetime] = mapped_column(
+ DateTime(timezone=True), server_default=func.now()
+ )
+ updated_at: Mapped[datetime] = mapped_column(
+ DateTime(timezone=True), server_default=func.now(), onupdate=func.now()
+ )
diff --git a/backend/models/rom.py b/backend/models/rom.py
index 27218a9fe..745d16161 100644
--- a/backend/models/rom.py
+++ b/backend/models/rom.py
@@ -186,7 +186,7 @@ class RomNote(BaseModel):
)
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
- last_edited_at: Mapped[datetime] = mapped_column(
+ updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), server_default=func.now()
)
raw_markdown: Mapped[str] = mapped_column(Text, default="")
diff --git a/frontend/assets/scrappers/sgdb.svg b/frontend/assets/scrappers/sgdb.svg
new file mode 100644
index 000000000..b1084e1a4
--- /dev/null
+++ b/frontend/assets/scrappers/sgdb.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/frontend/src/__generated__/index.ts b/frontend/src/__generated__/index.ts
index 68dd6dcc6..b4c5aafa9 100644
--- a/frontend/src/__generated__/index.ts
+++ b/frontend/src/__generated__/index.ts
@@ -32,6 +32,7 @@ export type { RomSchema } from "./models/RomSchema";
export type { SaveSchema } from "./models/SaveSchema";
export type { SchedulerDict } from "./models/SchedulerDict";
export type { ScreenshotSchema } from "./models/ScreenshotSchema";
+export type { SearchCoverSchema } from "./models/SearchCoverSchema";
export type { SearchRomSchema } from "./models/SearchRomSchema";
export type { StateSchema } from "./models/StateSchema";
export type { StatsReturn } from "./models/StatsReturn";
diff --git a/frontend/src/__generated__/models/DetailedRomSchema.ts b/frontend/src/__generated__/models/DetailedRomSchema.ts
index 206c4fa1f..10ad1628c 100644
--- a/frontend/src/__generated__/models/DetailedRomSchema.ts
+++ b/frontend/src/__generated__/models/DetailedRomSchema.ts
@@ -48,6 +48,8 @@ export type DetailedRomSchema = {
multi: boolean;
files: Array;
full_path: string;
+ created_at: string;
+ updated_at: string;
merged_screenshots: Array;
sibling_roms?: Array;
user_saves?: Array;
diff --git a/frontend/src/__generated__/models/FirmwareSchema.ts b/frontend/src/__generated__/models/FirmwareSchema.ts
index 7d58b577d..6f3b91ea5 100644
--- a/frontend/src/__generated__/models/FirmwareSchema.ts
+++ b/frontend/src/__generated__/models/FirmwareSchema.ts
@@ -16,4 +16,6 @@ export type FirmwareSchema = {
crc_hash: string;
md5_hash: string;
sha1_hash: string;
+ created_at: string;
+ updated_at: string;
};
diff --git a/frontend/src/__generated__/models/HeartbeatResponse.ts b/frontend/src/__generated__/models/HeartbeatResponse.ts
index 01a3e2c49..15f70be8b 100644
--- a/frontend/src/__generated__/models/HeartbeatResponse.ts
+++ b/frontend/src/__generated__/models/HeartbeatResponse.ts
@@ -13,5 +13,6 @@ export type HeartbeatResponse = {
SCHEDULER: SchedulerDict;
ANY_SOURCE_ENABLED: boolean;
METADATA_SOURCES: MetadataSourcesDict;
+ STEAMGRIDDB_ENABLED: boolean;
FS_PLATFORMS: Array;
};
diff --git a/frontend/src/__generated__/models/PlatformSchema.ts b/frontend/src/__generated__/models/PlatformSchema.ts
index 3a0358eb1..9edc5c46d 100644
--- a/frontend/src/__generated__/models/PlatformSchema.ts
+++ b/frontend/src/__generated__/models/PlatformSchema.ts
@@ -16,4 +16,6 @@ export type PlatformSchema = {
moby_id?: number | null;
logo_path?: string | null;
firmware?: Array;
+ created_at: string;
+ updated_at: string;
};
diff --git a/frontend/src/__generated__/models/RomNoteSchema.ts b/frontend/src/__generated__/models/RomNoteSchema.ts
index 3dfb826a9..2c5b4d12e 100644
--- a/frontend/src/__generated__/models/RomNoteSchema.ts
+++ b/frontend/src/__generated__/models/RomNoteSchema.ts
@@ -7,7 +7,7 @@ export type RomNoteSchema = {
id: number;
user_id: number;
rom_id: number;
- last_edited_at: string;
+ updated_at: string;
raw_markdown: string;
is_public: boolean;
user__username: string;
diff --git a/frontend/src/__generated__/models/RomSchema.ts b/frontend/src/__generated__/models/RomSchema.ts
index cf5dea9af..80f8b6d3a 100644
--- a/frontend/src/__generated__/models/RomSchema.ts
+++ b/frontend/src/__generated__/models/RomSchema.ts
@@ -43,5 +43,7 @@ export type RomSchema = {
multi: boolean;
files: Array;
full_path: string;
+ created_at: string;
+ updated_at: string;
readonly sort_comparator: string;
};
diff --git a/frontend/src/__generated__/models/SearchCoverSchema.ts b/frontend/src/__generated__/models/SearchCoverSchema.ts
new file mode 100644
index 000000000..7915dcb20
--- /dev/null
+++ b/frontend/src/__generated__/models/SearchCoverSchema.ts
@@ -0,0 +1,9 @@
+/* generated using openapi-typescript-codegen -- do no edit */
+/* istanbul ignore file */
+/* tslint:disable */
+/* eslint-disable */
+
+export type SearchCoverSchema = {
+ name: string;
+ resources: Array;
+};
diff --git a/frontend/src/__generated__/models/UserSchema.ts b/frontend/src/__generated__/models/UserSchema.ts
index 2a3e550c3..4117c59fa 100644
--- a/frontend/src/__generated__/models/UserSchema.ts
+++ b/frontend/src/__generated__/models/UserSchema.ts
@@ -14,4 +14,6 @@ export type UserSchema = {
avatar_path: string;
last_login: string | null;
last_active: string | null;
+ created_at: string;
+ updated_at: string;
};
diff --git a/frontend/src/components/Administration/Users/Dialog/DeleteUser.vue b/frontend/src/components/Administration/Users/Dialog/DeleteUser.vue
index d05a634b9..e49fc418d 100644
--- a/frontend/src/components/Administration/Users/Dialog/DeleteUser.vue
+++ b/frontend/src/components/Administration/Users/Dialog/DeleteUser.vue
@@ -66,7 +66,7 @@ function closeDialog() {
diff --git a/frontend/src/components/Administration/Users/Dialog/EditUser.vue b/frontend/src/components/Administration/Users/Dialog/EditUser.vue
index b8f149581..f9868a59f 100644
--- a/frontend/src/components/Administration/Users/Dialog/EditUser.vue
+++ b/frontend/src/components/Administration/Users/Dialog/EditUser.vue
@@ -133,7 +133,7 @@ function closeDialog() {
imagePreviewUrl
? imagePreviewUrl
: user.avatar_path
- ? `/assets/romm/assets/${user.avatar_path}`
+ ? `/assets/romm/assets/${user.avatar_path}?ts=${user.updated_at}`
: defaultAvatarPath
"
>
diff --git a/frontend/src/components/Administration/Users/Table.vue b/frontend/src/components/Administration/Users/Table.vue
index 922caa732..9b9ea1bfd 100644
--- a/frontend/src/components/Administration/Users/Table.vue
+++ b/frontend/src/components/Administration/Users/Table.vue
@@ -127,7 +127,7 @@ onMounted(() => {
diff --git a/frontend/src/components/Details/BackgroundHeader.vue b/frontend/src/components/Details/BackgroundHeader.vue
index 8bc798e0c..53a289c9c 100644
--- a/frontend/src/components/Details/BackgroundHeader.vue
+++ b/frontend/src/components/Details/BackgroundHeader.vue
@@ -11,16 +11,16 @@ const theme = useTheme();