Skip to content

Commit

Permalink
feat(agora): migrate Teams component (AG-1541) (#2809)
Browse files Browse the repository at this point in the history
  • Loading branch information
sagely1 authored Sep 17, 2024
1 parent bdc5f79 commit 0cd939c
Show file tree
Hide file tree
Showing 119 changed files with 10,286 additions and 712 deletions.
3 changes: 3 additions & 0 deletions apps/agora/api/src/api.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import express from 'express';
import mongoose from 'mongoose';
import { dataVersionRoute } from './components/dataversion';
import { teamMemberImageRoute, teamsRoute } from './components/teams';

const mongoUri = process.env.MONGODB_URI;

Expand All @@ -19,6 +20,8 @@ mongoose.connection.on('error', console.error.bind(console, 'MongoDB connection
const router = express.Router();
mongoose.connection.once('open', async () => {
router.get('/dataversion', dataVersionRoute);
router.get('/teams', teamsRoute);
router.get('/team-members/:name/image', teamMemberImageRoute);
});

export default router;
2 changes: 1 addition & 1 deletion apps/agora/api/src/components/dataversion.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { NextFunction, Request, Response } from 'express';
import { setHeaders } from '../helpers';
import { DataVersionCollection } from '../models/dataversion';
import { DataVersionCollection } from '../models';

export async function getDataVersion() {
return await DataVersionCollection.findOne();
Expand Down
2 changes: 2 additions & 0 deletions apps/agora/api/src/components/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './dataversion';
export * from './teams';
125 changes: 125 additions & 0 deletions apps/agora/api/src/components/teams.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
// -------------------------------------------------------------------------- //
// External
// -------------------------------------------------------------------------- //
import { mongo, connection } from 'mongoose';
import { Request, Response, NextFunction } from 'express';

// -------------------------------------------------------------------------- //
// Internal
// -------------------------------------------------------------------------- //
import { setHeaders, cache } from '../helpers';
import { TeamCollection } from '../models';
import { Team } from '@sagebionetworks/agora/api-client-angular';

// -------------------------------------------------------------------------- //
// GridFs
// -------------------------------------------------------------------------- //

let fsBucket: any;

connection.once('open', function () {
fsBucket = new mongo.GridFSBucket(connection.db);
});

// -------------------------------------------------------------------------- //
// Teams
// -------------------------------------------------------------------------- //

export async function getTeams() {
let teams: Team[] | undefined = cache.get('teams');

if (teams) {
return teams;
}

teams = await TeamCollection.find().lean().exec();
// sort null programs last
sortNullProgramsForTeamsLast(teams);

cache.set('teams', teams);
return teams;
}

export function sortNullProgramsForTeamsLast(teams: Team[]): void {
// default sort has null entries first so we will
// sort nulls last since Sage is null and should be last
// as per business requirements
if (!teams) return teams;
teams.sort((a, b) => {
if (!a.program) {
return 1;
} else if (!b.program) {
return -1;
} else {
return (a.program + a.team_full).localeCompare(b.program + b.team_full);
}
});
}

export async function teamsRoute(
req: Request,
res: Response,
next: NextFunction,
) {
try {
const teams = await getTeams();
setHeaders(res);
res.json({ items: teams });
} catch (err) {
next(err);
}
}

// -------------------------------------------------------------------------- //
// Team member images
// -------------------------------------------------------------------------- //

export async function getTeamMemberImage(name: string) {
name = name.toLowerCase().replace(/[- ]/g, '_');

let files = await fsBucket.find({ filename: name + '.jpg' }).toArray();
if (!files.length) {
files = await fsBucket.find({ filename: name + '.jpeg' }).toArray();
if (!files.length) {
files = await fsBucket.find({ filename: name + '.png' }).toArray();
}
}

return files[0] || undefined;
}

export async function teamMemberImageRoute(
req: Request,
res: Response,
next: NextFunction,
) {
if (!req.params || !req.params.name) {
res.status(404).send('Not found');
return;
}

try {
const name = req.params.name.trim();
const file = await getTeamMemberImage(name);

if (file?._id) {
const stream = fsBucket.openDownloadStream(file._id);

stream.on('data', (chunk: Buffer) => {
res.write(chunk);
});

stream.on('error', () => {
res.sendStatus(404);
});

stream.on('end', () => {
res.end();
});
} else {
res.end();
}
} catch (err) {
next(err);
}
}
69 changes: 69 additions & 0 deletions apps/agora/api/src/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,76 @@
import { Response } from 'express';
import debug from 'debug';
import NodeCache from 'node-cache';

export function setHeaders(res: Response) {
res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
res.setHeader('Pragma', 'no-cache');
res.setHeader('Expires', 0);
}

// Normalize a port into a number, string, or false.
export function normalizePort(val: any) {
const tPort = parseInt(val, 10);

if (isNaN(tPort)) {
// named pipe
return val;
}

if (tPort >= 0) {
// port number
return tPort;
}

return false;
}

// Event listener for HTTP server "error" event
export function onError(error: any, port: any) {
if (error.syscall !== 'listen') {
throw error;
}

const bind = typeof port === 'string' ? 'Pipe ' + port : 'Port ' + port;

// handle specific listen errors with friendly messages
switch (error.code) {
case 'EACCES':
console.error(bind + ' requires elevated privileges');
process.exit(1);
break;
case 'EADDRINUSE':
console.error(bind + ' is already in use');
process.exit(1);
break;
default:
throw error;
}
}

// Event listener for HTTP server "listening" event
export function onListening(address: any) {
debug('Listening on ' + typeof address === 'string' ? 'pipe ' + address : 'port ' + address.port);
}

// -------------------------------------------------------------------------- //
// Cache
// -------------------------------------------------------------------------- //
export const cache = new NodeCache();

// TODO: Performance issues with node-cache on large object, this should be revisited when possible.
// For now used AlternativeCache (local variables) to store large set of data.

class AlternativeCache {
data: { [key: string]: any } = {};

set(key: string, data: any) {
this.data[key] = data;
}

get(key: string) {
return this.data[key] || undefined;
}
}

export const altCache = new AlternativeCache();
2 changes: 2 additions & 0 deletions apps/agora/api/src/models/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './dataversion';
export * from './teams';
34 changes: 34 additions & 0 deletions apps/agora/api/src/models/teams.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// -------------------------------------------------------------------------- //
// External
// -------------------------------------------------------------------------- //
import { Schema, model } from 'mongoose';

// -------------------------------------------------------------------------- //
// Internal
// -------------------------------------------------------------------------- //
import { Team, TeamMember } from '@sagebionetworks/agora/api-client-angular';

// -------------------------------------------------------------------------- //
// Schemas
// -------------------------------------------------------------------------- //
const TeamMemberSchema = new Schema<TeamMember>({
name: { type: String, required: true },
isPrimaryInvestigator: { type: Boolean, required: true },
url: String,
});

const TeamSchema = new Schema<Team>(
{
team: { type: String, required: true },
team_full: { type: String, required: true },
program: { type: String, required: true },
description: { type: String, required: true },
members: { type: [TeamMemberSchema], required: true },
},
{ collection: 'teaminfo' },
);

// -------------------------------------------------------------------------- //
// Models
// -------------------------------------------------------------------------- //
export const TeamCollection = model<Team>('TeamCollection', TeamSchema);
6 changes: 6 additions & 0 deletions apps/agora/api/tsconfig.app.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@
"module": "commonjs",
"types": ["node", "express"]
},
"angularCompilerOptions": {
"enableI18nLegacyMessageIdFormat": false,
"strictInjectionParameters": true,
"strictInputAccessModifiers": true,
"strictTemplates": true
},
"exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"],
"include": ["src/**/*.ts"]
}
2 changes: 1 addition & 1 deletion apps/agora/app/.env.example
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
API_DOCS_URL="http://localhost:8000/api-docs"
APP_VERSION="0.0.1"
APP_VERSION="4.0.0"
CSR_API_URL="http://localhost:8000/api/v1"
SSR_API_URL="http://agora-api:3333/v1"
10 changes: 10 additions & 0 deletions apps/agora/app/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,16 @@
"input": "libs/shared/typescript/assets/src/assets",
"glob": "**/*",
"output": "assets"
},
{
"input": "libs/agora/assets",
"glob": "**/*",
"output": "agora-assets"
},
{
"input": "libs/agora/assets/favicon",
"glob": "favicon.ico",
"output": ""
}
],
"styles": [
Expand Down
4 changes: 4 additions & 0 deletions apps/agora/app/src/app/app.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ export const routes: Route[] = [
path: 'not-found',
loadChildren: () => import('@sagebionetworks/agora/not-found').then((routes) => routes.routes),
},
{
path: 'teams',
loadChildren: () => import('@sagebionetworks/agora/teams').then((routes) => routes.teamsRoutes),
},
{
path: '**',
redirectTo: '/not-found',
Expand Down
2 changes: 1 addition & 1 deletion apps/agora/app/src/config/config.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"apiDocsUrl": "http://localhost:8000/api-docs",
"appVersion": "0.0.1",
"appVersion": "4.0.0",
"csrApiUrl": "http://localhost:3333/v1",
"ssrApiUrl": "http://agora-api:3333/v1",
"rollbarToken": "e788198867474855a996485580b08d03"
Expand Down
3 changes: 2 additions & 1 deletion apps/agora/app/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
"noFallthroughCasesInSwitch": true,
"allowSyntheticDefaultImports": true
},
"files": [],
"include": [],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,17 @@ README.md
api.module.ts
api/api.ts
api/dataversion.service.ts
api/team.service.ts
api/teamMember.service.ts
configuration.ts
encoder.ts
git_push.sh
index.ts
model/basicError.ts
model/dataversion.ts
model/models.ts
model/team.ts
model/teamList.ts
model/teamMember.ts
param.ts
variables.ts
Loading

0 comments on commit 0cd939c

Please sign in to comment.