Skip to content

Commit

Permalink
Merge pull request #54 from devsoc-unsw/features/society-dropdown
Browse files Browse the repository at this point in the history
Features/society dropdown
  • Loading branch information
Gyoumi authored Dec 15, 2024
2 parents 3cfd919 + 788a229 commit 6b4c2bd
Show file tree
Hide file tree
Showing 19 changed files with 715 additions and 122 deletions.
2 changes: 2 additions & 0 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
"license": "ISC",
"dependencies": {
"@prisma/client": "^5.22.0",
"@supabase/storage-js": "^2.7.1",
"base64-arraybuffer": "^1.0.2",
"bcrypt": "^5.1.1",
"connect-redis": "^7.1.1",
"cors": "^2.8.5",
Expand Down
68 changes: 68 additions & 0 deletions backend/src/config/storage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { StorageClient } from '@supabase/storage-js';
import { decode } from 'base64-arraybuffer';

const STORAGE_URL = process.env['STORAGE_URL'];
const SERVICE_KEY = process.env['SERVICE_ROLE'];

if (process.env['NODE_ENV'] !== 'test' && !STORAGE_URL) {
throw new Error('Storage URL not found.');
}
if (process.env['NODE_ENV'] !== 'test' && !SERVICE_KEY) {
throw new Error('Service key not found.');
}

export let storageClient: StorageClient | null = null;
if (STORAGE_URL && SERVICE_KEY) {
storageClient = new StorageClient(STORAGE_URL, {
apikey: SERVICE_KEY,
Authorization: `Bearer ${SERVICE_KEY}`,
});
}

export const uploadFile = async (
file: string,
fileType: string,
societyId: number,
eventName: string
) => {
if (!storageClient) {
throw new Error('Storage client not initialised.');
}
const { data, error } = await storageClient
.from('images')
.upload(
`${societyId}/${eventName}/banner.${fileType.split('/')[1]}`,
decode(file),
{
contentType: fileType,
upsert: true,
}
);
if (error) {
throw new Error(error.message);
}
console.log(data);
return data.path;
};

export const getFile = async (path: string) => {
if (!storageClient) {
throw new Error('Storage client not initialised.');
}
const { data, error } = await storageClient.from('images').download(path);
if (error) {
throw new Error(error.message);
}

return data;
};

export const getFileUrl = async (path: string) => {
if (!storageClient) {
throw new Error('Storage client not initialised.');
}

const { data } = await storageClient.from('images').getPublicUrl(path);

return data;
};
118 changes: 92 additions & 26 deletions backend/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ import {
findUserFromId,
updateUserPasswordFromEmail,
} from './routes/User/user';
import {
getFile,
getFileUrl,
storageClient,
uploadFile,
} from './config/storage';

declare module 'express-session' {
interface SessionData {
Expand Down Expand Up @@ -78,7 +84,7 @@ app.use(
credentials: true,
})
);
app.use(express.json());
app.use(express.json({ limit: '20mb' }));

if (process.env['SESSION_SECRET'] === undefined) {
console.error('Session secret not provided in .env file');
Expand Down Expand Up @@ -191,7 +197,7 @@ app.post('/auth/otp/generate', async (req: Request, res: Response) => {
});
}

if (process.env['CI']) {
if (process.env['NODE_ENV'] === 'test') {
return res.status(200).json({ message: token });
}
return res.status(200).json({ message: 'ok' });
Expand Down Expand Up @@ -256,6 +262,8 @@ app.post('/auth/password/forgot', async (req: Request, res: Response) => {

await updateUserPasswordFromEmail(email, password, SALT_ROUNDS);

await redisClient.del(email);

return res.status(200).json({ message: 'ok' });
} catch (error) {
return res.status(400).json({
Expand Down Expand Up @@ -626,10 +634,33 @@ app.post(
return res.status(400).json({ message: 'Invalid date' });
}

//console.log(event);

// upload image to storage and get link
let imagePath = '';
try {
if (event.banner && storageClient) {
const metaData = event.banner.metaData;
const base64Data = event.banner.buffer.split(',')[1];
if (base64Data) {
imagePath = await uploadFile(
base64Data,
metaData.type,
event.societyId,
event.name
);
} else {
throw new Error('Invalid base64 string.');
}
}
} catch (error) {
return res.status(400).json({ message: `Unable to upload image.` });
}

try {
const eventRes = await prisma.event.create({
data: {
banner: event.banner,
banner: imagePath,
name: event.name,
startDateTime: dayjs(event.startDateTime).toISOString(),
endDateTime: dayjs(event.endDateTime).toISOString(),
Expand All @@ -644,6 +675,7 @@ app.post(
});
return res.status(200).json(eventRes);
} catch (e) {
console.log(e);
return res.status(400).json({ message: 'Invalid event input' });
}
}
Expand Down Expand Up @@ -675,21 +707,53 @@ app.put('/event', async (req: TypedRequest<UpdateEventBody>, res: Response) => {
return res.status(400).json({ message: 'Invalid date' });
}

// upload image to storage and get link
let imagePath;
try {
if (Object.keys(event.banner).length > 0) {
const base64Data = req.body.banner.buffer.split(',')[1];
if (base64Data) {
const metaData = req.body.banner.metaData;

imagePath = await uploadFile(
base64Data,
metaData.type,
event.societyId,
event.name
);
} else {
throw new Error('Invalid base64 string.');
}
} else {
throw new Error('No banner submitted.');
}
} catch (error) {
return res.status(400).json({ error: (error as Error).message });
}

try {
const eventRes = await prisma.event.update({
where: {
id: eventID,
},
data: {
banner: req.body.banner,
banner: imagePath,
name: req.body.name,
startDateTime: dayjs(req.body.startDateTime).toISOString(),
endDateTime: dayjs(req.body.endDateTime).toISOString(),
location: req.body.location,
description: req.body.description,
},
});
return res.status(200).json(eventRes);
// we are choosing to send the image back as a url
let imageFile;
try {
imageFile = await getFileUrl(event.banner); // getFile(event.banner) if we wanted raw file
} catch (error) {
return res.status(400).json({ error: (error as Error).message });
}

return res.status(200).json({ ...event, banner: imageFile });
} catch (e) {
return res.status(400).json({ message: 'Invalid event input' });
}
Expand Down Expand Up @@ -1175,16 +1239,16 @@ app.get('/user/keywords', async (req, res: Response) => {

const userID = sessionFromDB.userId;

let userKeywords = await prisma.user.findFirst({
where: {
id: userID,
},
select: {
keywords: true,
}
});
if (userKeywords === null) return res.status(404).json([]);
let userKeywords = await prisma.user.findFirst({
where: {
id: userID,
},
select: {
keywords: true,
},
});

if (userKeywords === null) return res.status(404).json([]);

return res.status(200).json(userKeywords.keywords);
});
Expand Down Expand Up @@ -1224,21 +1288,21 @@ app.post(
});
return res.status(200).json(newKeyword);
} catch (e) {
return res.status(400).json({ message: "Invalid keyword input." });
return res.status(400).json({ message: 'Invalid keyword input.' });
}
}
);

// associates a user with a keyword
app.post(
"/user/keyword",
'/user/keyword',
async (req: TypedRequest<keywordIdBody>, res: Response) => {
//get userid from session
const sessionFromDB = await validateSession(
req.session ? req.session : null
);
if (!sessionFromDB) {
return res.status(401).json({ message: "Invalid session provided." });
return res.status(401).json({ message: 'Invalid session provided.' });
}
const userID = sessionFromDB.userId;

Expand All @@ -1253,7 +1317,7 @@ app.post(
});

if (!keywordExists) {
return res.status(404).json({ message: "Invalid keyword." });
return res.status(404).json({ message: 'Invalid keyword.' });
}

//Connect keyword and user
Expand Down Expand Up @@ -1283,19 +1347,20 @@ app.post(
},
});

return res.status(200).json({ message: "ok" });
});
return res.status(200).json({ message: 'ok' });
}
);

// disassociates a user with a keyword
app.delete(
"/user/keyword",
'/user/keyword',
async (req: TypedRequest<keywordIdBody>, res: Response) => {
//get userid from session
const sessionFromDB = await validateSession(
req.session ? req.session : null
);
if (!sessionFromDB) {
return res.status(401).json({ message: "Invalid session provided." });
return res.status(401).json({ message: 'Invalid session provided.' });
}
const userID = sessionFromDB.userId;

Expand All @@ -1310,7 +1375,7 @@ app.delete(
});

if (!societyId) {
return res.status(400).json({ message: "Invalid keyword." });
return res.status(400).json({ message: 'Invalid keyword.' });
}

//Disconnect keyword and user
Expand Down Expand Up @@ -1340,8 +1405,9 @@ app.delete(
},
});

return res.status(200).json({ message: "ok" });
});
return res.status(200).json({ message: 'ok' });
}
);

app.get('/hello', () => {
console.log('Hello World!');
Expand Down
11 changes: 10 additions & 1 deletion backend/src/requestTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export interface UserIdBody {
}

export interface CreateEventBody {
banner: string;
banner: Base64Image;
name: string;
startDateTime: Date;
endDateTime: Date;
Expand All @@ -33,6 +33,15 @@ export interface CreateEventBody {
societyId: number;
}

interface Base64Image {
buffer: string,
metaData: {
name: string,
type: string,
size: number,
}
}

export interface UpdateEventBody extends CreateEventBody {
id: number;
}
Expand Down
10 changes: 4 additions & 6 deletions backend/tests/otp.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import app from '../src/index';
import { createClient } from 'redis';
import prisma from '../src/prisma';

describe.skip('Password change', () => {
describe('Password change', () => {
test('Forgot password OTP', async () => {
const redisClient = createClient({
url: `redis://localhost:${process.env['REDIS_PORT']}`,
Expand Down Expand Up @@ -45,6 +45,7 @@ describe.skip('Password change', () => {
const expToken = await redisClient.get(newUser.email);

expect(expToken).not.toBeNull();

expect(expToken).toEqual(expResToken);

await new Promise((resolve) => setTimeout(resolve, 1000));
Expand Down Expand Up @@ -106,14 +107,11 @@ describe.skip('Password change', () => {
const forgotRes = await request(app).post('/auth/password/forgot').send({
email: '[email protected]',
token: fResToken,
newPassword: 'oraclefan1',
password: 'oraclefan1',
});

//console.log(forgotRes.error.text);
expect(forgotRes.status).toBe(200);

await new Promise((resolve) => setTimeout(resolve, 1000));

const fCheckVerTokens = await redisClient.get(newUser.email);
expect(fCheckVerTokens).toBeNull();

Expand Down Expand Up @@ -194,7 +192,7 @@ describe.skip('Password change', () => {
username: 'richard grayson',
password: 'iheartkori',
});
expect(updateResFail.status).toBe(400);
expect(oldPassResFail.status).toBe(400);

const newPassRes = await request(app).post('/auth/login').send({
username: 'richard grayson',
Expand Down
Loading

0 comments on commit 6b4c2bd

Please sign in to comment.