Skip to content

Commit

Permalink
Added Holiday Park and Hellendoorn (temporarily), added Shops support
Browse files Browse the repository at this point in the history
  • Loading branch information
timyboy12345 committed Mar 21, 2021
1 parent b159be8 commit 7b9b74c
Show file tree
Hide file tree
Showing 41 changed files with 14,760 additions and 159 deletions.
81 changes: 48 additions & 33 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,57 +10,72 @@ To view the API, please go to [the example API](https://tp.arendz.nl/api) at htt
### Supported parks
In the table below you will find the parks that are currently supported and the functions that they support. This list of included and non-included functions is also included in the API.

| Park | Ride Support | Restaurants Support | Shows Support |
| ------------- | ------------- | ------------- | ------------- |
| 🇫🇷 Bellewaerde | Yes | Yes | Yes |
| 🇫🇷 Disneyland Paris | Yes | Yes | Yes |
| 🇫🇷 Disneyland Studios Park | Yes | Yes | Yes |
| 🇫🇷 Parc Asterix | Yes | Yes | Yes |
| 🇩🇪 Phantasialand | Yes | Yes | Yes |
| 🇪🇸 Portaventura Park | Yes | Yes | No |
| 🇪🇸 Ferrariland | Yes | Yes | No |
| 🇳🇱 Efteling | Yes | Yes | No |
| 🇳🇱 Toverland | Yes | Yes | No |
| 🇳🇱 Walibi Holland | Yes | No | No |
| 🇳🇱 Walibi Holland | Yes | Yes | Yes |
| 🇳🇱 DippieDoe | Yes | No | No |

| Park | Ride Support | Restaurants Support | Shows Support | Shops Support |
| ------------- | ------------- | ------------- | ------------- | ------------- |
| 🇫🇷 Bellewaerde | Yes | Yes | Yes | Yes |
| 🇫🇷 Disneyland Paris | Yes | Yes | Yes | Yes |
| 🇫🇷 Disneyland Studios Park | Yes | Yes | Yes | Yes |
| 🇫🇷 Parc Asterix | Yes | Yes | Yes | No |
| 🇩🇪 Phantasialand | Yes | Yes | Yes | Yes |
| 🇪🇸 Portaventura Park | Yes | Yes | No | No |
| 🇪🇸 Ferrariland | Yes | Yes | No | No |
| 🇧🇪 Walibi Belgium | Yes | No | No | Yes |
| 🇳🇱 Efteling | Yes | Yes | No | Yes |
| 🇳🇱 Toverland | Yes | Yes | No | No |
| 🇳🇱 Walibi Holland | Yes | Yes | Yes | No |
| 🇳🇱 DippieDoe | Yes | No | No | No |
| 🇳🇱 Avonturenpark Hellendoorn | Yes | Yes | Yes | Yes |

## Description

This API was build using the [Nest](https://github.com/nestjs/nest) framework. Find the docs at the [NestJS Documentation](https://docs.nestjs.com/)

## Installation

```bash
$ npm install
```

## Running the app

```bash
# development
$ npm run start
npm run start

# watch mode
$ npm run start:dev
npm run start:dev

# production mode
$ npm run start:prod
npm run start:prod
```

## Test
## Adding a new park
If you have access to an API of a theme park/resort that is not yet included, you can include it yourself and create a pull request.

### Structure
To keep the code easy to understand, there is a structure to the files. This is used for all parks. If the park you're adding is part of a resort, the contents is placed within a folder named after the resort (for example `disney`)
```text
...
src
+-- _dtos # The dto files for API documentation
+-- ...
+-- _interfaces # The global interface and enum files
+-- ...
+-- _services # The global services
+-- ...
+-- {PARK_NAME} # The folder in which all files will be located
+-- interfaces # All interfaces for this park
+-- ...
+-- {PARK_NAME}-transfer # A service which is used to translate park-provided objects to the POI interface structure
+-- {PARK_NAME}-transfer.service.spec.ts
+-- {PARK_NAME}-transfer.service.ts
+-- data # A folder which contains static assets if no API is available for this data
+-- ...
+-- {PARK_NAME}.service.spec.ts # All tests for this park
+-- {PARK_NAME}.service.ts # The service for this park, which extends theme-park.service.ts
...
```

```bash
# unit tests
$ npm run test
After the right files have been added, the park has to be added to the constructor of `parks.service.ts` so Nest knows the park exists and is ready to be included with the other parks.

# e2e tests
$ npm run test:e2e
### Using other services
Some parks use a single API URL to return all data. For these parks, the `through-pois-theme-park.service.ts` file was created. This service can be usefull if a park returns data from for example `restaurants`, `rides` and `shows` in a single response. This way, you don't have to create all the methods by hand, but just implement the `getPois()` method.

# test coverage
$ npm run test:cov
```
Some parks use services provided by [themeparks.io](https://attractions.io). For these parks, a specific service was created called `themeparks-io-theme-park.service.ts`. This service can easily implement new themeparks.io parks by providing some basic information, since all data is returned in a standardized format.

## License

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
"start": "nest start",
"start:dev": "nest start --watch",
"dev": "npm run start:dev",
"start:debug": "nest start --debug --watch",
"start:prod": "node dist/main",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export interface AttractionsIoCategoryInterface {
'_id': number,
'Name': {
'nl-NL': string,
'en-GB': string,
'de-DE': string
},
'Icon': string,
'Parent': string
}
34 changes: 34 additions & 0 deletions src/_interfaces/attractions-io/attractions-io-item.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
export interface AttractionsIoItemInterface {
'_id': number,
'Name': {
'nl-NL': string,
'de-DE': string,
'en-GB': string
},
'Summary': {
'nl-NL': string,
'de-DE': string,
'en-GB': string
},
'Keywords': string,
'DefaultImage': number,
'Location': string,
'Featured': boolean,
'WayfindingEnabled': boolean,
'VisibleOnMap': boolean,
'Category': number,
'Parent': string,
'entityIds': [],
'Classifications': [],
'MinimumHeightRequirement': number,
'MinimumUnaccompaniedHeightRequirement': number,
'MaximumHeightRequirement': number,
'MinimumAgeRequirement': number,
'MinimumUnaccompaniedAgeRequirement': number,
'MaximumAgeRequirement': number,
'RestrictionSummary': {
'nl-NL': string,
'de-DE': string,
'en-GB': string
}
}
2 changes: 2 additions & 0 deletions src/_interfaces/park-supports.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ export interface ThemeParkSupports {
supportsPois: boolean;
supportsRestaurants: boolean;
supportsRestaurantOpeningTimes: boolean;
supportsShops: boolean;
supportsShopOpeningTimes: boolean;
supportsRides: boolean;
supportsRideWaitTimes: boolean;
supportsShows: boolean;
Expand Down
1 change: 1 addition & 0 deletions src/_interfaces/poi-categories.enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,5 @@ export enum PoiCategory {
FIRSTAID = 'FIRSTAID',
PHOTO_SHOP = 'PHOTO_SHOP',
ANIMAL = 'ANIMAL',
SMOKING_AREA = 'SMOKING_AREA',
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { AttractionsIoThemeParkService } from './attractions-io-theme-park.service';

describe('AttractionsIoThemeParkService', () => {
let service: AttractionsIoThemeParkService;

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [AttractionsIoThemeParkService],
}).compile();

service = module.get<AttractionsIoThemeParkService>(AttractionsIoThemeParkService);
});

it('should be defined', () => {
expect(service).toBeDefined();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import { HttpService, Injectable } from '@nestjs/common';
import { ThemeParkService } from '../themepark/theme-park.service';
import { ConfigService } from '@nestjs/config';
import { AxiosError, AxiosRequestConfig } from 'axios';
import { Poi } from '../../_interfaces/poi.interface';
import { AttractionsIoItemInterface } from '../../_interfaces/attractions-io/attractions-io-item.interface';
import { PoiCategory } from '../../_interfaces/poi-categories.enum';

@Injectable()
export class AttractionsIoThemeParkService extends ThemeParkService {
private readonly _attractionsIoApiUrl: string;
private readonly _attractionsIoApiInstallationBody: string;

private _tempToken: string;

constructor(private readonly httpService: HttpService,
private readonly configService: ConfigService) {
super();

this._attractionsIoApiUrl = 'https://api.attractions.io/v1';
this._attractionsIoApiInstallationBody = '\n' +
'--s47UC4ujBvwu4tUZny16oB9EYPIK2lYen2gqiaI3cG8N2xg2xG4CuZ88uVFUzeVBcHglTSA5twz4fJCrDwgWt1vy0Ff8gIwp3DPc\n' +
'Content-Disposition: form-data; name="device_identifier"\n' +
'\n' +
'6FE3A85A-B6EF-4D19-A199-15EE46386BB6\n' +
'--s47UC4ujBvwu4tUZny16oB9EYPIK2lYen2gqiaI3cG8N2xg2xG4CuZ88uVFUzeVBcHglTSA5twz4fJCrDwgWt1vy0Ff8gIwp3DPc\n' +
'Content-Disposition: form-data; name="user_identifier"\n' +
'\n' +
'D1982D4C-FF0C-4FE8-BDA3-2DE392E54544\n' +
'--s47UC4ujBvwu4tUZny16oB9EYPIK2lYen2gqiaI3cG8N2xg2xG4CuZ88uVFUzeVBcHglTSA5twz4fJCrDwgWt1vy0Ff8gIwp3DPc\n' +
'Content-Disposition: form-data; name="app_version"\n' +
'\n' +
'1.2\n' +
'--s47UC4ujBvwu4tUZny16oB9EYPIK2lYen2gqiaI3cG8N2xg2xG4CuZ88uVFUzeVBcHglTSA5twz4fJCrDwgWt1vy0Ff8gIwp3DPc\n' +
'Content-Disposition: form-data; name="app_build"\n' +
'\n' +
'23\n' +
'--s47UC4ujBvwu4tUZny16oB9EYPIK2lYen2gqiaI3cG8N2xg2xG4CuZ88uVFUzeVBcHglTSA5twz4fJCrDwgWt1vy0Ff8gIwp3DPc--\n' +
'\n';
}

private async getTempToken() {
return this._tempToken ?? await this.getToken();
}

protected async getToken(): Promise<string> {
const headers = {
'Authorization': 'Attractions-Io api-key="3acb983d-a451-4700-b607-aac8ab1bedee"',
'User-Agent': 'Avonturenpark/23 CFNetwork/1220.1 Darwin/20.3.0',
'Occasio-Platform-Version': '14.4',
'Occasio-Platform': 'iOS',
'Occasio-App-Build': '23',
'Content-Type': 'multipart/form-data; boundary=s47UC4ujBvwu4tUZny16oB9EYPIK2lYen2gqiaI3cG8N2xg2xG4CuZ88uVFUzeVBcHglTSA5twz4fJCrDwgWt1vy0Ff8gIwp3DPc',
};

const config: AxiosRequestConfig = { headers: headers };

return await this.httpService
.post(
this._attractionsIoApiUrl + '/installation',
this._attractionsIoApiInstallationBody,
config,
)
.toPromise()
.then(value => {
this._tempToken = value.data.token;
return this._tempToken;
});
}

protected async getData() {
const token = await this.getToken();

const headers = {
'Authorization': 'Attractions-Io api-key="3acb983d-a451-4700-b607-aac8ab1bedee", installation-token="' + token + '"',
'User-Agent': 'Avonturenpark/23 CFNetwork/1220.1 Darwin/20.3.0',
'Occasio-Platform-Version': '14.4',
'Occasio-Platform': 'iOS',
'Occasio-App-Build': '23',
'Content-Type': 'multipart/form-data; boundary=s47UC4ujBvwu4tUZny16oB9EYPIK2lYen2gqiaI3cG8N2xg2xG4CuZ88uVFUzeVBcHglTSA5twz4fJCrDwgWt1vy0Ff8gIwp3DPc',
'Date': '2021-03-15',
};

const config: AxiosRequestConfig = {
headers: headers,
params: {
'version': '2021-03-05T20:10:46%2B01:00',
},
};

return await this.httpService
.post(
this._attractionsIoApiUrl + '/data',
null,
config,
)
.toPromise()
.then((value) => {
console.log(value);
console.log(value.data);
})
.catch((reason: AxiosError) => {
console.log(reason.response.data);
console.log(reason.config.headers);
});
}

public getFileItems(file: any): Poi[] {
return file.Item.map((item: AttractionsIoItemInterface) => {
let category: PoiCategory = this.getCategory(item.Category);

const poi: Poi = {
id: item._id + '',
title: item.Name['en-GB'],
description: item.Summary ? item.Summary['en-GB'] : undefined,
category: category,
original: item,
minSize: item.MinimumHeightRequirement ? item.MinimumHeightRequirement * 100 : undefined,
minSizeEscort: item.MinimumUnaccompaniedHeightRequirement ? item.MinimumUnaccompaniedHeightRequirement : undefined,
maxSize: item.MaximumHeightRequirement ? item.MaximumHeightRequirement * 100 : undefined,
minAge: item.MinimumAgeRequirement ?? undefined,
maxAge: item.MaximumAgeRequirement ?? undefined,
};

if (item.Location) {
const lat = parseFloat(item.Location.split(',')[0]);
const lng = parseFloat(item.Location.split(',')[1]);

poi.location = {
lat: lat,
lng: lng,
};
}

return poi;
});
}

public getCategory(category: number): PoiCategory {
return PoiCategory.UNDEFINED;
}
}
8 changes: 7 additions & 1 deletion src/_services/parks/parks.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import { PortaventuraService } from '../../portaventura/portaventura.service';
import { FerrariLandService } from '../../portaventura/ferrariland.service';
import { BellewaerdeService } from '../../bellewaerde/bellewaerde.service';
import { DippieDoeService } from '../../dippiedoe/dippie-doe.service';
import { HolidayParkService } from '../../holiday-park/holiday-park.service';
import { HellendoornService } from '../../hellendoorn/hellendoorn.service';

@Injectable()
export class ParksService {
Expand All @@ -28,7 +30,9 @@ export class ParksService {
private readonly _portaVenturaService: PortaventuraService,
private readonly _ferrariLandService: FerrariLandService,
private readonly _bellewaerdeService: BellewaerdeService,
private readonly _dippieDoeService: DippieDoeService) {
private readonly _dippieDoeService: DippieDoeService,
private readonly _holidayParkService: HolidayParkService,
private readonly _hellendoornService: HellendoornService) {
this._parks = [];
this._parks.push(_eftelingService);
this._parks.push(_toverlandService);
Expand All @@ -42,6 +46,8 @@ export class ParksService {
this._parks.push(_ferrariLandService);
this._parks.push(_bellewaerdeService);
this._parks.push(_dippieDoeService);
this._parks.push(_holidayParkService);
this._parks.push(_hellendoornService);
}

public getParks() {
Expand Down
11 changes: 11 additions & 0 deletions src/_services/themepark/theme-park.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,15 @@ export class ThemeParkService {
return shows.find(show => show.id === id);
})
}

async getShops(): Promise<Poi[]> {
throw new NotImplementedException();
}

async getShop(id: string): Promise<Poi> {
return await this.getShops()
.then((shops) => {
return shops.find(shop => shop.id === id);
})
}
}
Loading

0 comments on commit 7b9b74c

Please sign in to comment.