6 Commits

Author SHA1 Message Date
Mathis HERRIOT
d613a89e63 chore: bump version to 1.1.0
All checks were successful
CI/CD Pipeline / Valider frontend (push) Successful in 1m40s
CI/CD Pipeline / Valider documentation (push) Successful in 1m46s
CI/CD Pipeline / Valider backend (push) Successful in 1m54s
CI/CD Pipeline / Déploiement en Production (push) Successful in 1m38s
2026-01-21 09:54:34 +01:00
Mathis HERRIOT
67a10ad7d8 feat(layout): add metadata base URL configuration for dynamic app URL
- Configured `metadataBase` in `RootLayout` using `NEXT_PUBLIC_APP_URL` with a fallback to the default URL.
2026-01-21 09:47:49 +01:00
Mathis HERRIOT
82e98f4fce feat(config): add support for remote image domains in Next.js config
- Enabled `images.remotePatterns` to allow loading images from `memegoat.fr` and `api.memegoat.fr`.
2026-01-21 09:47:19 +01:00
Mathis HERRIOT
70a4249e41 fix(content): update conditional checks to use mimeType for content rendering
- Replaced `type` field checks with `mimeType.startsWith("image/")` for improved accuracy in `content-card` and admin content page components.
- Adjusted `CardContent` background color for better visual consistency.
2026-01-21 09:41:11 +01:00
Mathis HERRIOT
de7d41f4a1 fix(auth): prevent login redirect loop and concurrent refresh calls
- Added check to avoid redirecting to `/login` if already on the login page.
- Prevented multiple simultaneous `refreshUser` calls by adding an `isLoading` guard.
- Improved `useEffect` cleanup in `auth-provider` to handle components unmounting.
2026-01-21 09:40:47 +01:00
Mathis HERRIOT
2da1142866 chore(docker): restrict Postgres port exposure to localhost in production configuration
All checks were successful
CI/CD Pipeline / Valider backend (push) Successful in 1m39s
CI/CD Pipeline / Valider frontend (push) Successful in 1m44s
CI/CD Pipeline / Valider documentation (push) Successful in 1m47s
CI/CD Pipeline / Déploiement en Production (push) Successful in 1m17s
2026-01-20 22:35:42 +01:00
10 changed files with 47 additions and 10 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "@memegoat/backend",
"version": "1.0.8",
"version": "1.1.0",
"description": "",
"author": "",
"private": true,

View File

@@ -9,6 +9,8 @@ services:
POSTGRES_DB: ${POSTGRES_DB:-app}
networks:
- nw_memegoat
ports:
- "127.0.0.1:5432:5432" # not exposed to WAN, LAN only for administration checkup
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:

View File

@@ -3,6 +3,18 @@ import type { NextConfig } from "next";
const nextConfig: NextConfig = {
/* config options here */
reactCompiler: true,
images: {
remotePatterns: [
{
protocol: "https",
hostname: "memegoat.fr",
},
{
protocol: "https",
hostname: "api.memegoat.fr",
},
],
},
output: "standalone",
};

View File

@@ -1,6 +1,6 @@
{
"name": "@memegoat/frontend",
"version": "1.0.8",
"version": "1.1.0",
"private": true,
"scripts": {
"dev": "next dev",

View File

@@ -98,7 +98,7 @@ export default function AdminContentsPage() {
<TableCell className="font-medium">
<div className="flex items-center gap-3">
<div className="flex h-10 w-10 items-center justify-center rounded bg-muted">
{content.type === "meme" ? (
{content.mimeType.startsWith("image/") ? (
<ImageIcon className="h-5 w-5 text-muted-foreground" />
) : (
<Video className="h-5 w-5 text-muted-foreground" />

View File

@@ -49,6 +49,9 @@ export const metadata: Metadata = {
images: ["/memegoat-og.png"],
},
icons: "/memegoat-color.svg",
metadataBase: new URL(
process.env.NEXT_PUBLIC_APP_URL || "https://memegoat.fr",
),
};
export default function RootLayout({

View File

@@ -93,9 +93,9 @@ export function ContentCard({ content }: ContentCardProps) {
<MoreHorizontal className="h-4 w-4" />
</Button>
</CardHeader>
<CardContent className="p-0 relative bg-zinc-100 dark:bg-zinc-900 aspect-square flex items-center justify-center">
<CardContent className="p-0 relative bg-zinc-200 dark:bg-zinc-900 aspect-square flex items-center justify-center">
<Link href={`/meme/${content.slug}`} className="w-full h-full relative">
{content.type === "meme" ? (
{content.mimeType.startsWith("image/") ? (
<Image
src={content.url}
alt={content.title}

View File

@@ -53,7 +53,10 @@ api.interceptors.response.use(
} catch (refreshError) {
// If refresh fails, we might want to redirect to login on the client
if (typeof window !== "undefined") {
window.location.href = "/login";
// On évite de rediriger vers login si on y est déjà pour éviter les boucles
if (!window.location.pathname.includes("/login")) {
window.location.href = "/login";
}
}
return Promise.reject(refreshError);
}

View File

@@ -26,6 +26,8 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
const router = useRouter();
const refreshUser = React.useCallback(async () => {
// Éviter de lancer plusieurs refresh en même temps
if (!isLoading) setIsLoading(true);
try {
const userData = await UserService.getMe();
setUser(userData);
@@ -34,11 +36,26 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
} finally {
setIsLoading(false);
}
}, []);
}, [isLoading]);
React.useEffect(() => {
refreshUser();
}, [refreshUser]);
let isMounted = true;
const initAuth = async () => {
try {
const userData = await UserService.getMe();
if (isMounted) setUser(userData);
} catch (_error) {
if (isMounted) setUser(null);
} finally {
if (isMounted) setIsLoading(false);
}
};
initAuth();
return () => {
isMounted = false;
};
}, []);
const login = async (email: string, password: string) => {
try {

View File

@@ -1,6 +1,6 @@
{
"name": "@memegoat/source",
"version": "1.0.8",
"version": "1.1.0",
"description": "",
"scripts": {
"version:get": "cmake -P version.cmake GET",