app/imports/api/teaser/TeaserCollection.ts
import SimpleSchema from 'simpl-schema';
import _ from 'lodash';
import { Slugs } from '../slug/SlugCollection';
import BaseSlugCollection from '../base/BaseSlugCollection';
import { Interests } from '../interest/InterestCollection';
import { Opportunities } from '../opportunity/OpportunityCollection';
import { TeaserDefine, TeaserUpdate, TeaserUpdateData } from '../../typings/radgrad';
/**
* Represents a teaser instance, such as "ACM Webmasters".
* @extends api/base.BaseSlugCollection
* @memberOf api/teaser
*/
class TeaserCollection extends BaseSlugCollection {
/**
* Creates the Teaser collection.
*/
constructor() {
super('Teaser', new SimpleSchema({
title: String,
slugID: SimpleSchema.RegEx.Id,
author: String,
url: String,
description: String,
interestIDs: [SimpleSchema.RegEx.Id],
opportunityID: { type: SimpleSchema.RegEx.Id, optional: true },
targetSlugID: SimpleSchema.RegEx.Id,
duration: { type: String, optional: true },
retired: { type: Boolean, optional: true },
}));
this.defineSchema = new SimpleSchema({
title: String,
slug: String,
author: String,
url: String,
description: String,
duration: { type: String, optional: true },
interests: Array,
'interests.$': String,
opportunity: { type: String, optional: true },
targetSlug: String,
retired: { type: Boolean, optional: true },
});
this.updateSchema = new SimpleSchema({
title: { type: String, optional: true },
author: { type: String, optional: true },
url: { type: String, optional: true },
description: { type: String, optional: true },
duration: { type: String, optional: true },
interests: { type: Array, optional: true },
'interests.$': String,
opportunity: { type: String, optional: true },
targetSlug: { type: String, optional: true },
retired: { type: Boolean, optional: true },
});
}
/**
* Defines a new Teaser video to provide information about an opportunity.
* @example
* Teaser.define({ title: 'ACM Webmasters',
* slug: 'acm-webmasters-teaser',
* opportunity: 'acm-webmasters'
* interests: ['html', 'javascript', 'css', 'web-development'],
* author: 'Torlief Nielson'
* url: 'https://www.youtube.com/watch?v=OI4CXULK3tw'
* description: 'Learn web development by helping to develop and maintain the ACM Manoa website.',
* duration: '0:39',
* })
* @param { Object } description Object with keys title, slug, URL, description, duration. interestIDs.
* Title is a required string.
* Slug is required and must be previously undefined. By convention, use a '-teaser' suffix.
* Interests is a required array of one or more defined interest slugs or interestIDs.
* Opportunity is a required opportunity slug or opportunityID.
* URL is a required string; the url to the (typically YouTube) video defining this teaser.
* Author is a required string indicating the author of the Teaser.
* Description is a required string providing a short description of this teaser.
* Duration is an optional string indicating the length of the teaser.
* @throws {Meteor.Error} If the interest definition includes a defined slug or undefined interestID,
* if the slug is already defined, or if the opportunity is supplied and not found.
* @returns The newly created docID.
*/
public define({ title, slug, author, url, description, duration, interests, opportunity, targetSlug, retired = false }: TeaserDefine) {
// Get InterestIDs, throw error if any of them are not found.
const interestIDs = Interests.getIDs(interests);
// Get SlugID, throw error if found.
const slugID = Slugs.define({ name: slug, entityName: this.getType() });
let targetSlugID;
if (!_.isUndefined(opportunity)) {
// Get OpportunityID, throw error if not found.
const opportunityID = Opportunities.getID(opportunity);
// if ()
targetSlugID = Slugs.findDoc({ entityID: opportunityID })._id;
}
if (!_.isUndefined(targetSlug)) {
targetSlugID = Slugs.findDoc({ name: targetSlug })._id;
}
const teaserID = this.collection.insert({ title, slugID, author, url, description, duration, interestIDs,
targetSlugID, retired });
// Connect the Slug to this teaser
Slugs.updateEntityID(slugID, teaserID);
return teaserID;
}
/**
* Update a Teaser. Everything can be updated except the slug.
* @param docID The docID to be updated.
* @throws { Meteor.Error } If docID is not defined, or if any interest or opportunity is undefined.
*/
public update(docID, { title, targetSlug, interests, author, url, description, duration, retired }: TeaserUpdate) {
this.assertDefined(docID);
const updateData: TeaserUpdateData = {};
if (title) {
updateData.title = title;
}
if (targetSlug) {
updateData.targetSlugID = Slugs.findDoc({ name: targetSlug })._id;
}
if (interests) {
updateData.interestIDs = Interests.getIDs(interests);
}
if (author) {
updateData.author = author;
}
if (url) {
updateData.url = url;
}
if (description) {
updateData.description = description;
}
if (duration) {
updateData.duration = duration;
}
if (_.isBoolean(retired)) {
updateData.retired = retired;
}
this.collection.update(docID, { $set: updateData });
}
/**
* Remove the Teaser.
* @param instance The docID or slug of the entity to be removed.
* @throws { Meteor.Error } If docID is not a Teaser.
*/
public removeIt(instance: string) {
const docID = this.getID(instance);
// OK, clear to delete.
return super.removeIt(docID);
}
/**
* Returns true if teaser has the specified interest.
* @param teaser The teaser (docID or slug)
* @param interest The Interest (docID or slug).
* @returns {boolean} True if the teaser has the associated Interest.
* @throws { Meteor.Error } If teaser is not a teaser or interest is not a Interest.
*/
public hasInterest(teaser: string, interest: string) {
const interestID = Interests.getID(interest);
const doc = this.findDoc(teaser);
return (doc.interestIDs.includes(interestID));
}
/**
* Returns true if teaser has the specified slug.
* @param teaser The teaser (docID or slug)
* @param target The target (slug).
* @returns {boolean} True if the teaser has the associated slug.
* @throws { Meteor.Error } If teaser is not a teaser or target isn't a defined slug.
*/
hasTarget(teaser, target) {
const targetSlugID = Slugs.findDoc({ name: target })._id;
const doc = this.findDoc(teaser);
return (doc.targetSlugID === targetSlugID);
}
/**
* Returns an array of strings, each one representing an integrity problem with this collection.
* Returns an empty array if no problems were found.
* Checks slugID, interestIDs
* @returns {Array} A (possibly empty) array of strings indicating integrity issues.
*/
public checkIntegrity() {
const problems = [];
this.find().forEach((doc) => {
if (!Slugs.isDefined(doc.slugID)) {
problems.push(`Bad slugID: ${doc.slugID}`);
}
doc.interestIDs.forEach((interestID) => {
if (!Interests.isDefined(interestID)) {
problems.push(`Bad interestID: ${interestID}`);
}
});
if (doc.opportunityID && !Opportunities.isDefined(doc.opportunityID)) {
problems.push(`Bad opportunityID: ${doc.opportunityID}`);
}
if (doc.opportunityID) {
if (!doc.targetSlugID) {
const slugDoc = Slugs.findDoc({ entityID: doc.opportunityID });
const docID = doc._id;
const updateData: any = {};
updateData.targetSlugID = slugDoc._id;
if (slugDoc) {
this.collection.update(docID, { $set: updateData });
}
}
if (!Opportunities.isDefined(doc.opportunityID)) {
problems.push(`Bad opportunityID ${doc.opportunityID}`);
}
}
if (doc.targetSlugID && !Slugs.isDefined(doc.targetSlugID)) {
problems.push(`Bad targetSlugID: ${doc.targetSlugID}`);
}
});
return problems;
}
/**
* Returns an object representing the Teaser docID in a format acceptable to define().
* @param docID The docID of a Teaser.
* @returns { Object } An object representing the definition of docID.
*/
public dumpOne(docID: string): TeaserDefine {
const doc = this.findDoc(docID);
const title = doc.title;
const slug = Slugs.getNameFromID(doc.slugID);
const author = doc.author;
const url = doc.url;
const description = doc.description;
const duration = doc.duration;
const interests = doc.interestIDs.map((interestID) => Interests.findSlugByID(interestID));
let targetSlug;
if (doc.targetSlugID) {
targetSlug = Slugs.getNameFromID(doc.targetSlugID);
}
const retired = doc.retired;
return { title, slug, author, url, description, duration, interests, targetSlug, retired };
}
}
/**
* Provides the singleton instance of this class to all other entities.
* @type {api/teaser.TeaserCollection}
* @memberOf api/teaser
*/
export const Teasers = new TeaserCollection();