Skip to content

Commit 0031382

Browse files
committed
#3873 renamed platform logo and implemented get/set platform endpoints + hooks + frontend impl
1 parent c45043f commit 0031382

14 files changed

Lines changed: 257 additions & 15 deletions

File tree

src/backend/src/controllers/organizations.controllers.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,29 @@ export default class OrganizationsController {
111111
}
112112
}
113113

114+
static async setPlatformLogoImage(req: Request, res: Response, next: NextFunction) {
115+
try {
116+
if (!req.file) {
117+
throw new HttpException(400, 'Invalid or undefined image data');
118+
}
119+
120+
const updatedOrg = await OrganizationsService.setPlatformLogoImage(req.file, req.currentUser, req.organization);
121+
122+
res.status(200).json(updatedOrg);
123+
} catch (error: unknown) {
124+
next(error);
125+
}
126+
}
127+
128+
static async getPlatformLogoImage(req: Request, res: Response, next: NextFunction) {
129+
try {
130+
const platformLogoImageId = await OrganizationsService.getPlatformLogoImage(req.organization.organizationId);
131+
res.status(200).json(platformLogoImageId);
132+
} catch (error: unknown) {
133+
next(error);
134+
}
135+
}
136+
114137
static async setNewMemberImage(req: Request, res: Response, next: NextFunction) {
115138
try {
116139
if (!req.file) {

src/backend/src/prisma/migrations/20260215155228_guest_home_page/migration.sql renamed to src/backend/src/prisma/migrations/20260221213604_guest_home_page/migration.sql

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,5 @@
88
-- AlterTable
99
ALTER TABLE "Organization" DROP COLUMN "applyInterestImageId",
1010
DROP COLUMN "exploreAsGuestImageId",
11-
ADD COLUMN "finishlineDescription" TEXT NOT NULL DEFAULT '',
12-
ADD COLUMN "finishlineLogo" TEXT;
11+
ADD COLUMN "platformDescription" TEXT NOT NULL DEFAULT '',
12+
ADD COLUMN "platformLogoImageId" TEXT;

src/backend/src/prisma/schema.prisma

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1320,8 +1320,8 @@ model Organization {
13201320
partReviewSampleImageId String?
13211321
partReviewGuideLink String?
13221322
sponsorshipNotificationsSlackChannelId String?
1323-
finishlineDescription String @default("")
1324-
finishlineLogo String?
1323+
platformDescription String @default("")
1324+
platformLogoImageId String?
13251325
13261326
// Relation references
13271327
wbsElements WBS_Element[]

src/backend/src/prisma/seed.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,9 +127,9 @@ const performSeed: () => Promise<void> = async () => {
127127
'Northeastern Electric Racing is a student-run organization at Northeastern University building all-electric formula-style race cars from scratch to compete in Forumla Hybrid + Electric Formula SAE (FSAE).',
128128
applicationLink:
129129
'https://docs.google.com/forms/d/e/1FAIpQLSeCvG7GqmZm_gmSZiahbVTW9ZFpEWG0YfGQbkSB_whhHzxXpA/closedform',
130-
finishlineDescription:
130+
platformDescription:
131131
'Finishline is a Project Management Dashboard developed by the Software Team at Northeastern Electric Racing.',
132-
finishlineLogo: '1auQO3GYydZOo1-vCn0D2iyCfaxaVFssx'
132+
platformLogoImageId: '1auQO3GYydZOo1-vCn0D2iyCfaxaVFssx'
133133
}
134134
});
135135

src/backend/src/routes/organizations.routes.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,14 @@ organizationRouter.post(
4343
);
4444
organizationRouter.post('/logo/update', upload.single('logo'), OrganizationsController.setLogoImage);
4545
organizationRouter.get('/logo', OrganizationsController.getOrganizationLogoImage);
46+
47+
organizationRouter.post(
48+
'/platform-logo/update',
49+
upload.single('platformLogo'),
50+
OrganizationsController.setPlatformLogoImage
51+
);
52+
organizationRouter.get('/platform-logo', OrganizationsController.getPlatformLogoImage);
53+
4654
organizationRouter.post(
4755
'/new-member-image/update',
4856
upload.single('newMemberImage'),

src/backend/src/services/organizations.services.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -545,4 +545,50 @@ export default class OrganizationsService {
545545

546546
return updatedOrg.financeDelegates.map(userTransformer);
547547
}
548+
549+
/**
550+
* sets an organizations platform image
551+
* @param submitter the user who is setting the images
552+
* @param organizationId the organization which the images will be set up
553+
* @param images the images which are being set
554+
*/
555+
static async setPlatformLogoImage(
556+
platformLogoImageId: Express.Multer.File | null,
557+
submitter: User,
558+
organization: Organization
559+
) {
560+
if (!(await userHasPermission(submitter.userId, organization.organizationId, isAdmin))) {
561+
throw new AccessDeniedAdminOnlyException('update platform logo');
562+
}
563+
564+
const platformLogoImageData = platformLogoImageId ? await uploadFile(platformLogoImageId) : null;
565+
566+
const updateData = {
567+
...(platformLogoImageData && { platformLogoImageId: platformLogoImageData.id })
568+
};
569+
570+
const newImages = await prisma.organization.update({
571+
where: { organizationId: organization.organizationId },
572+
data: updateData
573+
});
574+
575+
return newImages;
576+
}
577+
578+
/**
579+
* Gets platform logo image for the given organization
580+
* @param organizationId organization Id of the milestone
581+
* @returns all the milestones from the given organization
582+
*/
583+
static async getPlatformLogoImage(organizationId: string) {
584+
const organization = await prisma.organization.findUnique({
585+
where: { organizationId }
586+
});
587+
588+
if (!organization) {
589+
throw new NotFoundException('Organization', organizationId);
590+
}
591+
592+
return organization.platformLogoImageId;
593+
}
548594
}

src/backend/src/transformers/organizationTransformer.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ export const organizationTransformer = (organization: Organization): Organizatio
55
return {
66
...organization,
77
applicationLink: organization.applicationLink ?? undefined,
8-
newMemberImageId: organization.newMemberImageId ?? undefined
8+
newMemberImageId: organization.newMemberImageId ?? undefined,
9+
platformLogoImageId: organization.platformLogoImageId ?? undefined
910
};
1011
};

src/backend/tests/unit/organization.test.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,4 +295,64 @@ describe('Organization Tests', () => {
295295
expect(updatedOrganization.partReviewGuideLink).toBe('newlink');
296296
});
297297
});
298+
299+
describe('Set Organization Platform Logo', () => {
300+
const file1 = { originalname: 'image1.png' } as Express.Multer.File;
301+
const file2 = { originalname: 'image2.png' } as Express.Multer.File;
302+
const file3 = { originalname: 'image3.png' } as Express.Multer.File;
303+
it('Fails if user is not an admin', async () => {
304+
await expect(
305+
OrganizationsService.setPlatformLogoImage(file1, await createTestUser(wonderwomanGuest, orgId), organization)
306+
).rejects.toThrow(new AccessDeniedAdminOnlyException('update platform logo'));
307+
});
308+
309+
it('Succeeds and updates all the images', async () => {
310+
const testBatman = await createTestUser(batmanAppAdmin, orgId);
311+
(uploadFile as Mock).mockImplementation((file) => {
312+
return Promise.resolve({ name: `${file.originalname}`, id: `uploaded-${file.originalname}` });
313+
});
314+
315+
await OrganizationsService.setPlatformLogoImage(file2, testBatman, organization);
316+
317+
const oldOrganization = await prisma.organization.findUnique({
318+
where: {
319+
organizationId: orgId
320+
}
321+
});
322+
323+
expect(oldOrganization).not.toBeNull();
324+
expect(oldOrganization?.platformLogoImageId).toBe('uploaded-image2.png');
325+
326+
await OrganizationsService.setPlatformLogoImage(file3, testBatman, organization);
327+
328+
const updatedOrganization = await prisma.organization.findUnique({
329+
where: {
330+
organizationId: orgId
331+
}
332+
});
333+
334+
expect(updatedOrganization?.platformLogoImageId).toBe('uploaded-image3.png');
335+
});
336+
});
337+
338+
describe('Get Organization Platform Logo', () => {
339+
it('Fails if an organization does not exist', async () => {
340+
await expect(async () => await OrganizationsService.getPlatformLogoImage('1')).rejects.toThrow(
341+
new NotFoundException('Organization', '1')
342+
);
343+
});
344+
345+
it('Succeeds and gets the image', async () => {
346+
const testBatman = await createTestUser(batmanAppAdmin, orgId);
347+
await OrganizationsService.setPlatformLogoImage(
348+
{ originalname: 'image1.png' } as Express.Multer.File,
349+
testBatman,
350+
organization
351+
);
352+
const image = await OrganizationsService.getPlatformLogoImage(orgId);
353+
354+
expect(image).not.toBeNull();
355+
expect(image).toBe('uploaded-image1.png');
356+
});
357+
});
298358
});

src/frontend/src/apis/organizations.api.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,18 @@ export const getOrganizationNewMemberImage = async () => {
6666
});
6767
};
6868

69+
export const setOrganizationPlatformLogoImage = async (file: File) => {
70+
const formData = new FormData();
71+
formData.append('platformLogo', file);
72+
return axios.post<Organization>(apiUrls.organizationsSetPlatformLogoImage(), formData);
73+
};
74+
75+
export const getOrganizationPlatformLogoImage = async () => {
76+
return axios.get<string>(apiUrls.organizationsPlatformLogoImage(), {
77+
transformResponse: (data) => JSON.parse(data)
78+
});
79+
};
80+
6981
export const setOrganizationFeaturedProjects = async (featuredProjectIds: string[]) => {
7082
return axios.post<Organization>(apiUrls.organizationsSetFeaturedProjects(), {
7183
projectIds: featuredProjectIds

src/frontend/src/hooks/organizations.hooks.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@ import {
1919
getFinanceDelegates,
2020
setFinanceDelegates,
2121
setOrganizationNewMemberImage,
22-
getOrganizationNewMemberImage
22+
getOrganizationNewMemberImage,
23+
setOrganizationPlatformLogoImage,
24+
getOrganizationPlatformLogoImage
2325
} from '../apis/organizations.api';
2426
import { downloadGoogleImage } from '../apis/organizations.api';
2527

@@ -218,6 +220,25 @@ export const useSetOrganizationNewMemberImage = () => {
218220
});
219221
};
220222

223+
export const useSetOrganizationPlatformLogoImage = () => {
224+
const queryClient = useQueryClient();
225+
return useMutation<Organization, Error, File>(['organizations', 'platform-logo'], async (file: File) => {
226+
const { data } = await setOrganizationPlatformLogoImage(file);
227+
queryClient.invalidateQueries(['organizations']);
228+
return data;
229+
});
230+
};
231+
232+
export const useOrganizationPlatformLogoImage = () => {
233+
return useQuery<Blob | undefined, Error>(['organizations', 'platform-logo'], async () => {
234+
const { data: fileId } = await getOrganizationPlatformLogoImage();
235+
if (!fileId) {
236+
return;
237+
}
238+
return await downloadGoogleImage(fileId);
239+
});
240+
};
241+
221242
/*
222243
* Custom React Hook to fetch confluence guide for current
223244
* organization in backend

0 commit comments

Comments
 (0)