Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add event cover in create route #425

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions api/controllers/EventController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,8 @@ export class EventController {
async createEvent(@Body() createEventRequest: CreateEventRequest,
@AuthenticatedUser() user: UserModel): Promise<CreateEventResponse> {
if (!PermissionsService.canEditEvents(user)) throw new ForbiddenError();
// const fileName = file.originalname.substring(0, file.originalname.lastIndexOf('.'));
// createEventRequest.event.cover = await this.storageService.upload(file, MediaType.EVENT_COVER, fileName);
const event = await this.eventService.create(createEventRequest.event);
return { error: null, event };
}
Expand Down
11 changes: 7 additions & 4 deletions api/validators/EventControllerRequests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ export class OptionalEventProperties implements IOptionalEventProperties {
}

export class Event extends OptionalEventProperties implements IEvent {
@IsNotEmpty()
cover: string;
// @IsNotEmpty()
// cover: string;

@IsNotEmpty()
title: string;
Expand All @@ -59,8 +59,8 @@ export class Event extends OptionalEventProperties implements IEvent {
}

export class EventPatches extends OptionalEventProperties implements IEvent {
@IsNotEmpty()
cover: string;
// @IsNotEmpty()
// cover: string;

@IsNotEmpty()
title: string;
Expand All @@ -82,6 +82,9 @@ export class EventPatches extends OptionalEventProperties implements IEvent {

@Allow()
pointValue: number;

@Allow()
published?: boolean;
}

export class EventSearchOptions implements IEventSearchOptions {
Expand Down
22 changes: 22 additions & 0 deletions migrations/0044-add-event-drafting.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { MigrationInterface, QueryRunner, TableColumn } from 'typeorm';

const TABLE_NAME = 'Events';

export class AddEventDrafting1716419261341 implements MigrationInterface {

public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.addColumn(
TABLE_NAME,
new TableColumn({
name: 'published',
type: 'boolean',
default: true, // default to true for backward compadibility
}),
);
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.dropColumn(TABLE_NAME, 'published');
}

}
3 changes: 3 additions & 0 deletions models/EventModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ export class EventModel extends BaseEntity {
@Column('integer')
pointValue: number;

@Column('boolean', { default: true }) // default to true for backward compadibility
published: boolean;

@Column('boolean', { default: false })
deleted: boolean;

Expand Down
18 changes: 14 additions & 4 deletions services/EventService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,17 @@ import { EventModel } from '../models/EventModel';
import { Uuid, PublicEvent, Event, EventSearchOptions } from '../types';
import Repositories, { TransactionsManager } from '../repositories';
import { UserError } from '../utils/Errors';
import StorageService from './StorageService';

@Service()
export default class EventService {
private transactions: TransactionsManager;

constructor(@InjectManager() entityManager: EntityManager) {
private storageService: StorageService;

constructor(@InjectManager() entityManager: EntityManager, storageService: StorageService) {
this.transactions = new TransactionsManager(entityManager);
this.storageService = storageService;
}

/**
Expand All @@ -25,9 +29,15 @@ export default class EventService {
const eventCreated = await this.transactions.readWrite(async (txn) => {
const eventRepository = Repositories.event(txn);
const isUnusedAttendanceCode = await eventRepository.isUnusedAttendanceCode(event.attendanceCode);
if (!isUnusedAttendanceCode) throw new UserError('Attendance code has already been used');
if (event.start > event.end) throw new UserError('Start date after end date');
return eventRepository.upsertEvent(EventModel.create(event));
if (!isUnusedAttendanceCode) {
// this.storageService.deleteAtUrl(event.cover);
throw new UserError('Attendance code has already been used');
}
if (event.start > event.end) {
// this.storageService.deleteAtUrl(event.cover);
throw new UserError('Start date after end date');
}
return eventRepository.upsertEvent(EventModel.create({ ...event, published: false }));
});
return eventCreated.getPublicEvent();
}
Expand Down
5 changes: 2 additions & 3 deletions tests/controllers/ControllerFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,8 @@ export class ControllerFactory {
return new AuthController(userAccountService, userAuthService, emailService);
}

public static event(conn: Connection): EventController {
const eventService = new EventService(conn.manager);
const storageService = new StorageService();
public static event(conn: Connection, storageService = new StorageService()): EventController {
const eventService = new EventService(conn.manager, storageService);
const attendanceService = new AttendanceService(conn.manager);
return new EventController(eventService, storageService, attendanceService);
}
Expand Down
38 changes: 30 additions & 8 deletions tests/event.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import * as moment from 'moment';
import { ForbiddenError } from 'routing-controllers';
import { UserAccessType } from '../types';
import { anything, instance, verify } from 'ts-mockito';
import { MediaType, UserAccessType } from '../types';
import { ControllerFactory } from './controllers';
import { DatabaseConnection, EventFactory, PortalState, UserFactory } from './data';
import { CreateEventRequest } from '../api/validators/EventControllerRequests';
import { EventModel } from '../models/EventModel';
import Mocks from './mocks/MockFactory';
import { FileFactory } from './data/FileFactory';
import { Config } from '../config';

beforeAll(async () => {
await DatabaseConnection.connect();
Expand All @@ -20,6 +24,8 @@ afterAll(async () => {
});

describe('event creation', () => {
const fileLocation = 'https://s3.amazonaws.com/event-cover.jpg';

test('successful event creation', async () => {
// setting up inputs
const conn = await DatabaseConnection.get();
Expand Down Expand Up @@ -47,18 +53,29 @@ describe('event creation', () => {
};

// creating the event
const eventController = ControllerFactory.event(conn);
const eventResponse = await eventController.createEvent(createEventRequest, admin);
const cover = FileFactory.image(Config.file.MAX_EVENT_COVER_FILE_SIZE / 2);
const storageService = Mocks.storage(fileLocation);
const eventController = ControllerFactory.event(conn, instance(storageService));
const eventResponse = await eventController.createEvent(cover, createEventRequest, admin);

// verifying response from event creation
expect(eventResponse.event.cover).toEqual(event.cover);
expect(eventResponse.event.cover).toEqual(fileLocation);
expect(eventResponse.event.title).toEqual(event.title);
expect(eventResponse.event.location).toEqual(event.location);
expect(eventResponse.event.committee).toEqual(event.committee);
expect(eventResponse.event.start).toEqual(event.start);
expect(eventResponse.event.end).toEqual(event.end);
expect(eventResponse.event.pointValue).toEqual(event.pointValue);

verify(storageService.deleteAtUrl(fileLocation)).never();
verify(
storageService.upload(
cover,
MediaType.EVENT_COVER,
anything(),
),
).called();

// verifying response from event lookup
const lookupEventResponse = await eventController.getOneEvent({ uuid: eventResponse.event.uuid }, user);
expect(lookupEventResponse.error).toEqual(null);
Expand Down Expand Up @@ -91,8 +108,10 @@ describe('event creation', () => {
};

// verifying correct error
const eventController = ControllerFactory.event(conn);
await expect(eventController.createEvent(createEventRequest, user))
const cover = FileFactory.image(Config.file.MAX_EVENT_COVER_FILE_SIZE / 2);
const storageService = Mocks.storage(fileLocation);
const eventController = ControllerFactory.event(conn, instance(storageService));
await expect(eventController.createEvent(cover, createEventRequest, user))
.rejects.toThrow(ForbiddenError);
});

Expand Down Expand Up @@ -122,9 +141,12 @@ describe('event creation', () => {
};

// verifying correct error thrown
const eventController = ControllerFactory.event(conn);
await expect(eventController.createEvent(createEventRequest, admin))
const cover = FileFactory.image(Config.file.MAX_EVENT_COVER_FILE_SIZE / 2);
const storageService = Mocks.storage(fileLocation);
const eventController = ControllerFactory.event(conn, instance(storageService));
await expect(eventController.createEvent(cover, createEventRequest, admin))
.rejects.toThrow('Start date after end date');
verify(storageService.deleteAtUrl(fileLocation)).called();
});
});

Expand Down
13 changes: 10 additions & 3 deletions tests/merchOrder.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ import { ControllerFactory } from './controllers';
import { DatabaseConnection, EventFactory, MerchFactory, PortalState, UserFactory } from './data';
import { MerchStoreControllerWrapper } from './controllers/MerchStoreControllerWrapper';
import { UserModel } from '../models/UserModel';
import { FileFactory } from './data/FileFactory';
import Mocks from './mocks/MockFactory';
import { Config } from '../config';

beforeAll(async () => {
await DatabaseConnection.connect();
Expand Down Expand Up @@ -905,6 +908,8 @@ describe('merch orders', () => {
});

describe('merch order pickup events', () => {
const fileLocation = 'https://s3.amazonaws.com/event-cover.jpg';

test('past, future, and individual pickup events can be retrieved', async () => {
const conn = await DatabaseConnection.get();
const merchDistributor = UserFactory.fake({ accessType: UserAccessType.MERCH_STORE_DISTRIBUTOR });
Expand Down Expand Up @@ -975,8 +980,10 @@ describe('merch order pickup events', () => {
const merchDistributor = UserFactory.fake({ accessType: UserAccessType.MERCH_STORE_DISTRIBUTOR });
const admin = UserFactory.fake({ accessType: UserAccessType.ADMIN });
const linkedEvent = EventFactory.fake();
const eventController = ControllerFactory.event(conn);
await eventController.createEvent({ event: linkedEvent }, admin);
const cover = FileFactory.image(Config.file.MAX_EVENT_COVER_FILE_SIZE / 2);
const storageService = Mocks.storage(fileLocation);
const eventController = ControllerFactory.event(conn, instance(storageService));
await eventController.createEvent(cover, { event: linkedEvent }, admin);

const pickupEvent = MerchFactory.fakeFutureOrderPickupEvent({ linkedEvent });

Expand All @@ -999,7 +1006,7 @@ describe('merch order pickup events', () => {
// edit a linked event

const newLinkedEvent = EventFactory.fake();
await eventController.createEvent({ event: newLinkedEvent }, admin);
await eventController.createEvent(cover, { event: newLinkedEvent }, admin);

const editPickupEventRequest = { pickupEvent: { linkedEventUuid: newLinkedEvent.uuid } };
const params = { uuid: pickupEvent.uuid };
Expand Down
2 changes: 1 addition & 1 deletion types/ApiRequests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ export interface OptionalEventProperties {
}

export interface Event extends OptionalEventProperties {
cover: string;
cover?: string;
title: string;
description: string;
location: string;
Expand Down
Loading