Skip to content

Commit

Permalink
Add magazines to weekly debrief (#66)
Browse files Browse the repository at this point in the history
* Implemented community board models

- implemented Flyer and Organization models for community board feature
- modified User model
- updated typescript version to 4.0.5 to resolve es lint issue

* Implemented weekly debrief magazines

- added magazines to weekly debrief feature
- updated user and weekly debrief models to allow tracking of read magazines
- updated logic in user and weekly debrief controllers to enable reading of magazines

* Revert package.json changes

- reverted package.json changes back to the original file
- added additional rules to eslintrc to suppress es lint warnings

* Create jest testing for magazines

- added jest test cases for magazines

* Implement jest testing for User

- implemented unit testing for UserRepo
- indirectly test Weekly Debrief with User unit tests

* Update gitignore

- updated gitignore file to include secrets folder
  • Loading branch information
isaachan100 authored Mar 20, 2023
1 parent bf2774c commit 6505521
Show file tree
Hide file tree
Showing 11 changed files with 486 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ module.exports = {
'@typescript-eslint/explicit-module-boundary-types': 0,
'@typescript-eslint/no-unused-vars': 0,
'semi-style': ['error', 'last'],
'import/named': 0,
'import/prefer-default-export': 0,
},
// This gets rid of weird react error for non-react project
settings: {
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ nohup.out
*src/certificates*
Makefile
start.sh
*secrets*

# ignore database dumps
dump/
5 changes: 5 additions & 0 deletions src/entities/User.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import WeeklyDebrief from './WeeklyDebrief';
import { PublicationSlug } from '../common/types';
import { Flyer } from './Flyer';
import { Organization } from './Organization';
import { Magazine } from './Magazine';

@ObjectType({ description: 'The User Model' })
export class User {
Expand Down Expand Up @@ -42,6 +43,10 @@ export class User {
@Property({ required: true, type: () => Article, default: [] })
readArticles: mongoose.Types.DocumentArray<DocumentType<Article>>;

@Field((type) => [Magazine])
@Property({ required: true, type: () => Magazine, default: [] })
readMagazines: mongoose.Types.DocumentArray<DocumentType<Magazine>>;

@Field((type) => [Flyer])
@Property({ required: true, type: () => Flyer, default: [] })
readFlyers: mongoose.Types.DocumentArray<DocumentType<Flyer>>;
Expand Down
5 changes: 5 additions & 0 deletions src/entities/WeeklyDebrief.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Field, ID, ObjectType } from 'type-graphql';
import { prop as Property, DocumentType } from '@typegoose/typegoose';
import mongoose from 'mongoose';
import { Article } from './Article';
import { Magazine } from './Magazine';

@ObjectType({ description: 'The Weekly Debrief Model' })
export default class WeeklyDebrief {
Expand Down Expand Up @@ -36,6 +37,10 @@ export default class WeeklyDebrief {
@Property({ required: true, type: () => Article, default: [] })
readArticles: mongoose.Types.DocumentArray<DocumentType<Article>>;

@Field((type) => [Magazine])
@Property({ required: true, type: () => Magazine, default: [] })
readMagazines: mongoose.Types.DocumentArray<DocumentType<Magazine>>;

@Field((type) => [Article])
@Property({ required: true, type: () => Article, default: [] })
randomArticles: mongoose.Types.DocumentArray<DocumentType<Article>>;
Expand Down
22 changes: 22 additions & 0 deletions src/repos/UserRepo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { Article } from '../entities/Article';
import ArticleRepo from './ArticleRepo';
import { PublicationSlug } from '../common/types';
import { User, UserModel } from '../entities/User';
import { Magazine } from '../entities/Magazine';
import MagazineRepo from './MagazineRepo';

/**
* Create new user associated with deviceToken and followedPublicationsSlugs of deviceType.
Expand Down Expand Up @@ -96,6 +98,25 @@ const appendReadArticle = async (uuid: string, articleID: string): Promise<User>
return user;
};

/**
* Add a magazine to a user's readMagazines
*/
const appendReadMagazine = async (uuid: string, magazineID: string): Promise<User> => {
const user = await UserModel.findOne({ uuid });

if (!user) return user;

const magazine = await MagazineRepo.getMagazineByID(magazineID);
const checkDuplicates = (prev: boolean, cur: Magazine) => prev || cur.id === magazineID;

if (magazine && !user.readMagazines.reduce(checkDuplicates, false)) {
user.readMagazines.push(magazine);
}

user.save();
return user;
};

/**
* Increment shoutouts in user's numShoutouts
*/
Expand Down Expand Up @@ -126,6 +147,7 @@ const incrementBookmarks = async (uuid: string): Promise<User> => {

export default {
appendReadArticle,
appendReadMagazine,
createUser,
followPublication,
getUserByUUID,
Expand Down
3 changes: 3 additions & 0 deletions src/repos/WeeklyDebriefRepo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,17 @@ const createWeeklyDebrief = async (
numShoutouts: user.numShoutouts,
numBookmarkedArticles: user.numBookmarkedArticles,
readArticles: user.readArticles.slice(0, 2),
readMagazines: user.readMagazines.slice(0, 2),
numReadArticles: user.readArticles.length,
numReadMagazines: user.readMagazines.length,
randomArticles: await articleAggregate.sample(2).exec(),
});
return UserModel.findOneAndUpdate(
{ uuid },
{
$set: {
readArticles: [],
readMagazines: [],
numShoutouts: 0,
numBookmarkedArticles: 0,
weeklyDebrief,
Expand Down
8 changes: 8 additions & 0 deletions src/resolvers/UserResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,14 @@ class UserResolver {
return await UserRepo.appendReadArticle(uuid, articleID);
}

@Mutation((_returns) => User, {
nullable: true,
description: "Adds the <Magazine> given by the <magazineID> to the <User's> read magazines",
})
async readMagazine(@Arg('uuid') uuid: string, @Arg('magazineID') magazineID: string) {
return await UserRepo.appendReadMagazine(uuid, magazineID);
}

@Mutation((_returns) => User, {
nullable: true,
description: 'Increments the number of bookmarks for the <User> given by <uuid>',
Expand Down
56 changes: 56 additions & 0 deletions src/tests/data/MagazineFactory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/* eslint-disable no-underscore-dangle */
import { faker } from '@faker-js/faker';
import { _ } from 'underscore';
import PublicationFactory from './PublicationFactory';
import { Magazine } from '../../entities/Magazine';
import FactoryUtils from './FactoryUtils';

class MagazineFactory {
public static async create(n: number): Promise<Magazine[]> {
/**
* Returns a list of n number of random Magazine objects
*/
return Promise.all(FactoryUtils.create(n, MagazineFactory.fake));
}

public static async createSpecific(
n: number,
newMappings: { [key: string]: any },
): Promise<Magazine[]> {
/**
* Returns a list of n number of random Magazine objects with specified
* field values in new Mappings
*/
const arr = await MagazineFactory.create(n);
return arr.map((x) => {
const newDoc = x;
Object.entries(newMappings).forEach(([k, v]) => {
newDoc[k] = v;
});
return newDoc;
});
}

public static async fake(): Promise<Magazine> {
/**
* Returns a Magazine with random values in its instance variables
*/
const fakeMagazine = new Magazine();
const examplePublication = await PublicationFactory.getRandomPublication();

fakeMagazine.date = faker.date.past();
fakeMagazine.semester = _.sample(['FA22', 'SP23']);
fakeMagazine.pdfURL = faker.image.cats();
fakeMagazine.publication = examplePublication;
fakeMagazine.publicationSlug = examplePublication.slug;
fakeMagazine.shoutouts = _.random(0, 50);
fakeMagazine.title = faker.commerce.productDescription();
fakeMagazine.nsfw = _.sample([true, false]);
fakeMagazine.isFeatured = _.sample([true, false]);
fakeMagazine.trendiness = 0;
fakeMagazine.isFiltered = _.sample([true, false]);

return fakeMagazine;
}
}
export default MagazineFactory;
55 changes: 55 additions & 0 deletions src/tests/data/UserFactory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/* eslint-disable no-underscore-dangle */
import { faker } from '@faker-js/faker';
import { _ } from 'underscore';
import { User } from '../../entities/User';
import FactoryUtils from './FactoryUtils';

class UserFactory {
public static async create(n: number): Promise<User[]> {
/**
* Returns a list of n number of random User objects
*
* @param n The number of desired random User objects
* @returns A Promise of the list of n number of random User objects
*/
return Promise.all(FactoryUtils.create(n, UserFactory.fake));
}

public static async createSpecific(
n: number,
newMappings: { [key: string]: any },
): Promise<User[]> {
/**
* Returns a list of n number of random User objects with specified
* fields values in newMappings
*
* @param n The number of desired random User objects
* @param newMappings The specified values for User parameters [key]
* @returns A Promise of the list of n number of random User objects
*/
const arr = await UserFactory.create(n);
return arr.map((x) => {
const newDoc = x;
Object.entries(newMappings).forEach(([k, v]) => {
newDoc[k] = v;
});
return newDoc;
});
}

public static async fake(): Promise<User> {
/**
* Returns a User with random values in its instance variables
*
* @returns The User object with random values in its instance variables
*/
const fakeUser = new User();

fakeUser.deviceToken = faker.datatype.string();
fakeUser.deviceType = _.sample(['IOS', 'ANDROID']);
fakeUser.uuid = faker.datatype.uuid();

return fakeUser;
}
}
export default UserFactory;
Loading

0 comments on commit 6505521

Please sign in to comment.