Skip to content

Commit 5076724

Browse files
committed
Improvement/On mobile, the filter(Format,decade,added) are in vertical instead of horizontal
Improvement/On mobile, reset password have a weird design. It will bere more polish to add button and reset password on a new page Feature/Share collection with other users
1 parent b88a6c2 commit 5076724

File tree

17 files changed

+505
-90
lines changed

17 files changed

+505
-90
lines changed

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1.2.0
1+
1.3.0

backend/package-lock.json

Lines changed: 24 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

backend/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
"jsonwebtoken": "^9.0.2",
2727
"mongoose": "^8.16.0",
2828
"multer": "^2.0.2",
29+
"uuid": "^13.0.0",
2930
"winston": "^3.18.3"
3031
},
3132
"devDependencies": {
@@ -35,8 +36,9 @@
3536
"@types/express": "^5.0.3",
3637
"@types/jsonwebtoken": "^9.0.10",
3738
"@types/node": "^24.0.3",
39+
"@types/uuid": "^10.0.0",
3840
"nodemon": "^3.1.10",
3941
"ts-node": "^10.9.2",
4042
"typescript": "^5.8.3"
4143
}
42-
}
44+
}

backend/src/controllers/collection.controller.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,7 @@ export async function updateCollectionItem(req: Request, res: Response) {
292292

293293
const updatedItem = await CollectionItem.findOneAndUpdate(
294294
{ _id: itemId, user: req.user._id },
295-
{ $set: { "format.name": format.name } },
295+
{ $set: { "format.name": format.name, "format.text": format.name } },
296296
{ new: true }
297297
);
298298

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { Request, Response } from 'express';
2+
import User from '../models/User';
3+
import CollectionItem from '../models/CollectionItem';
4+
import { IAlbum } from '../models/Album';
5+
6+
export async function getPublicCollection(req: Request, res: Response) {
7+
try {
8+
const { shareId } = req.params;
9+
10+
// Find user by publicShareId
11+
const user = await User.findOne({ publicShareId: shareId });
12+
13+
if (!user) {
14+
res.status(404).json({ message: 'Collection not found' });
15+
return;
16+
}
17+
18+
// Check if collection is public
19+
if (!user.preferences?.isPublic) {
20+
res.status(404).json({ message: 'Collection not found' });
21+
return;
22+
}
23+
24+
// Fetch collection items for this user
25+
const collection = await CollectionItem.find({ user: user._id })
26+
.populate<{ album: IAlbum }>('album');
27+
28+
// Sort by artist
29+
collection.sort((a, b) => {
30+
if (a.album && b.album) {
31+
return a.album.artist.localeCompare(b.album.artist);
32+
}
33+
return 0;
34+
});
35+
36+
res.status(200).json({
37+
username: user.username,
38+
collection: collection,
39+
total: collection.length
40+
});
41+
} catch (error) {
42+
console.error('Error fetching public collection:', error);
43+
res.status(500).json({ message: 'Internal server error' });
44+
}
45+
}

backend/src/controllers/users.controller.ts

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -84,17 +84,20 @@ export async function createAdminUser(req: Request, res: Response) {
8484
export async function getPreferences(req: Request, res: Response) {
8585
try {
8686
if (!req.user) {
87-
res.status(401).json({ message: "Non autorisé" });
87+
res.status(401).json({ message: "Unauthorized" });
8888
return;
8989
}
9090

91-
const user = await User.findById(req.user._id).select('preferences');
91+
const user = await User.findById(req.user._id).select('preferences publicShareId');
9292
if (!user) {
9393
res.status(404).json({ message: "User not found" });
9494
return;
9595
}
9696

97-
res.status(200).json(user.preferences);
97+
res.status(200).json({
98+
...user.preferences,
99+
publicShareId: user.preferences?.isPublic ? user.publicShareId : null
100+
});
98101
} catch (error) {
99102
console.error("Error in getPreferences controller", error);
100103
res.status(500).json({ message: "Internal server error" });
@@ -104,28 +107,32 @@ export async function getPreferences(req: Request, res: Response) {
104107
export async function updatePreferences(req: Request, res: Response) {
105108
try {
106109
if (!req.user) {
107-
res.status(401).json({ message: "Non autorisé" });
110+
res.status(401).json({ message: "Unauthorized" });
108111
return;
109112
}
110113

111-
const { theme } = req.body;
114+
const { theme, isPublic } = req.body;
112115

113116
const user = await User.findById(req.user._id);
114117
if (!user) {
115118
res.status(404).json({ message: "User not found" });
116119
return;
117120
}
118121

119-
// Mettre à jour les préférences
122+
// Update preferences
120123
if (theme !== undefined) {
121124
user.preferences = { ...user.preferences, theme };
122125
}
126+
if (isPublic !== undefined) {
127+
user.preferences = { ...user.preferences, isPublic };
128+
}
123129

124130
await user.save();
125131

126132
res.status(200).json({
127133
message: "Preferences updated successfully",
128-
preferences: user.preferences
134+
preferences: user.preferences,
135+
publicShareId: user.preferences.isPublic ? user.publicShareId : null
129136
});
130137
} catch (error) {
131138
console.error("Error in updatePreferences controller", error);

backend/src/models/User.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import mongoose, { Schema, Document } from "mongoose"
22
import bcrypt from "bcryptjs"
3+
import { v4 as uuidv4 } from 'uuid'
34

45
export interface IUserPreferences {
56
theme: string
7+
isPublic: boolean
68
}
79

810
export interface IUser extends Document {
@@ -11,6 +13,7 @@ export interface IUser extends Document {
1113
password: string
1214
isAdmin: boolean
1315
preferences: IUserPreferences
16+
publicShareId: string
1417
createdAt: Date
1518
lastLogin?: Date
1619
comparePassword(password: string): Promise<boolean>
@@ -39,8 +42,18 @@ const userSchema = new Schema<IUser>({
3942
theme: {
4043
type: String,
4144
default: 'dark'
45+
},
46+
isPublic: {
47+
type: Boolean,
48+
default: false
4249
}
4350
},
51+
publicShareId: {
52+
type: String,
53+
unique: true,
54+
sparse: true, // Allows null/undefined while maintaining uniqueness
55+
default: () => uuidv4()
56+
},
4457
createdAt: {
4558
type: Date,
4659
default: Date.now,

backend/src/routes/public.route.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { Router } from 'express';
2+
import { getPublicCollection } from '../controllers/public.controller';
3+
4+
const router = Router();
5+
6+
// Public collection endpoint - no authentication required
7+
router.get('/:shareId', getPublicCollection);
8+
9+
export default router;

backend/src/server.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import usersRoute from "./routes/users.route"
1414
import discogsRoute from './routes/discogs.route'
1515
import authRoute from './routes/auth.route'
1616
import collectionRoute from './routes/collection.route'
17+
import publicRoute from './routes/public.route'
1718
dotenv.config()
1819

1920
// Read version from environment variable (Docker) or VERSION file (development)
@@ -108,6 +109,7 @@ app.use('/api/auth', rateLimit({
108109
max: 100 // limit each IP to 100 requests per windowMs
109110
}), authRoute);
110111
app.use('/api/collection', collectionRoute)
112+
app.use('/api/public', publicRoute)
111113

112114
// Version middleware - adds version header to all responses
113115
app.use((req, res, next) => {

frontend/src/App.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import AdminPage from './pages/AdminPage';
1313
import HomePage from './pages/HomePage';
1414
import LandingPage from './pages/LandingPage';
1515
import AlbumDetailPage from './pages/AlbumDetailPage';
16+
import PublicCollectionPage from './pages/PublicCollectionPage';
1617
import { ThemeProvider } from './context/ThemeContext';
1718
import PrivateLayout from './components/Layout/PrivateLayout';
1819

@@ -32,6 +33,9 @@ const App = () => {
3233
<Route path='/signup' element={<SignupPage />} />
3334
</Route>
3435

36+
{/* Public Collection Route - No Auth Required */}
37+
<Route path="/collection/:shareId" element={<PublicCollectionPage />} />
38+
3539
{/* Protected Routes - User Theme */}
3640
<Route path="/app" element={<PrivateLayout />}>
3741
<Route index element={<HomePage />} />

0 commit comments

Comments
 (0)