Front: Api models validation (#245)
* Front: Model: Write Validators * Front: Plage response validator * Front: API: Typing 'fetch' return * Front: Basic Models: Response Handlers * Front: API: Validate authentication response * Front: Validate Search History * Front: Validate Responses of User updates * Front: On Validation Error, more verbose console error
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
import * as yup from 'yup';
|
||||
import ResponseHandler from './ResponseHandler';
|
||||
|
||||
const AccessTokenResponseValidator = yup.object({
|
||||
access_token: yup.string().required(),
|
||||
});
|
||||
|
||||
type AccessTokenResponse = yup.InferType<typeof AccessTokenResponseValidator>;
|
||||
|
||||
export const AccessTokenResponseHandler: ResponseHandler<AccessTokenResponse> = {
|
||||
validator: AccessTokenResponseValidator,
|
||||
transformer: (value) => value,
|
||||
};
|
||||
@@ -1,4 +1,12 @@
|
||||
import Model from './Model';
|
||||
import Model, { ModelValidator } from './Model';
|
||||
import * as yup from 'yup';
|
||||
|
||||
export const AlbumValidator = yup
|
||||
.object({
|
||||
name: yup.string().required(),
|
||||
artistId: yup.number().required(),
|
||||
})
|
||||
.concat(ModelValidator);
|
||||
|
||||
interface Album extends Model {
|
||||
name: string;
|
||||
|
||||
+14
-2
@@ -1,8 +1,20 @@
|
||||
import Model from './Model';
|
||||
import Model, { ModelValidator } from './Model';
|
||||
import * as yup from 'yup';
|
||||
import ResponseHandler from './ResponseHandler';
|
||||
|
||||
export const ArtistValidator = yup
|
||||
.object({
|
||||
name: yup.string().required(),
|
||||
})
|
||||
.concat(ModelValidator);
|
||||
|
||||
export const ArtistHandler: ResponseHandler<Artist> = {
|
||||
validator: ArtistValidator,
|
||||
transformer: (value) => value,
|
||||
};
|
||||
|
||||
interface Artist extends Model {
|
||||
name: string;
|
||||
picture?: string;
|
||||
}
|
||||
|
||||
export default Artist;
|
||||
|
||||
+14
-1
@@ -1,4 +1,17 @@
|
||||
import Model from './Model';
|
||||
import Model, { ModelValidator } from './Model';
|
||||
import * as yup from 'yup';
|
||||
import ResponseHandler from './ResponseHandler';
|
||||
|
||||
export const GenreValidator = yup
|
||||
.object({
|
||||
name: yup.string().required(),
|
||||
})
|
||||
.concat(ModelValidator);
|
||||
|
||||
export const GenreHandler: ResponseHandler<Genre> = {
|
||||
validator: GenreValidator,
|
||||
transformer: (value) => value,
|
||||
};
|
||||
|
||||
interface Genre extends Model {
|
||||
name: string;
|
||||
|
||||
+13
-20
@@ -1,26 +1,19 @@
|
||||
import Skill from './Skill';
|
||||
import Model from './Model';
|
||||
import { SkillValidator } from './Skill';
|
||||
import { ModelValidator } from './Model';
|
||||
import * as yup from 'yup';
|
||||
|
||||
export const LessonValidator = yup
|
||||
.object({
|
||||
name: yup.string().required(),
|
||||
description: yup.string().required(),
|
||||
requiredLevel: yup.number().required(),
|
||||
mainSkill: SkillValidator.required(),
|
||||
})
|
||||
.concat(ModelValidator);
|
||||
|
||||
/**
|
||||
* A Lesson is an exercice that the user can try to practice a skill
|
||||
*/
|
||||
interface Lesson extends Model {
|
||||
/**
|
||||
* The title of the lesson
|
||||
*/
|
||||
title: string;
|
||||
/**
|
||||
* Short description of the lesson
|
||||
*/
|
||||
description: string;
|
||||
/**
|
||||
* The minimum level required for the user to access this lesson
|
||||
*/
|
||||
requiredLevel: number;
|
||||
/**
|
||||
* The main skill learnt in this lesson
|
||||
*/
|
||||
mainSkill: Skill;
|
||||
}
|
||||
type Lesson = yup.InferType<typeof LessonValidator>;
|
||||
|
||||
export default Lesson;
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import * as yup from 'yup';
|
||||
import ResponseHandler from './ResponseHandler';
|
||||
|
||||
const ListValidator = <T>(itemType: yup.Schema<T>) => yup.array(itemType).required();
|
||||
|
||||
export const ListHandler = <A, R>(
|
||||
itemHandler: ResponseHandler<A, R>
|
||||
): ResponseHandler<A[], R[]> => ({
|
||||
validator: ListValidator(itemHandler.validator),
|
||||
transformer: (plage) => plage.map((item) => itemHandler.transformer(item)),
|
||||
});
|
||||
@@ -1,5 +1,9 @@
|
||||
interface Model {
|
||||
id: number;
|
||||
}
|
||||
import * as yup from 'yup';
|
||||
|
||||
export const ModelValidator = yup.object({
|
||||
id: yup.number().required(),
|
||||
});
|
||||
|
||||
type Model = yup.InferType<typeof ModelValidator>;
|
||||
|
||||
export default Model;
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import * as yup from 'yup';
|
||||
import ResponseHandler from './ResponseHandler';
|
||||
|
||||
// Ty https://github.com/Arthi-chaud/Meelo/blob/master/front/src/models/pagination.ts
|
||||
export const PlageValidator = <T>(itemType: yup.Schema<T>) =>
|
||||
yup.object({
|
||||
data: yup.array(itemType).required(),
|
||||
metadata: yup.object({
|
||||
/**
|
||||
* Current route
|
||||
*/
|
||||
this: yup.string().required(),
|
||||
/**
|
||||
* route to use for the next items
|
||||
*/
|
||||
next: yup.string().required().nullable(),
|
||||
/**
|
||||
* route to use for the previous items
|
||||
*/
|
||||
previous: yup.string().required().nullable(),
|
||||
}),
|
||||
});
|
||||
|
||||
type Plage<T> = yup.InferType<ReturnType<typeof PlageValidator<T>>>;
|
||||
|
||||
export const PlageHandler = <A, R>(
|
||||
itemHandler: ResponseHandler<A, R>
|
||||
): ResponseHandler<Plage<A>, Plage<R>> => ({
|
||||
validator: PlageValidator(itemHandler.validator),
|
||||
transformer: (plage) => ({
|
||||
...plage,
|
||||
data: plage.data.map((item) => itemHandler.transformer(item)),
|
||||
}),
|
||||
});
|
||||
@@ -0,0 +1,8 @@
|
||||
import * as yup from 'yup';
|
||||
|
||||
type ResponseHandler<APIType, ModelType = APIType> = {
|
||||
validator: yup.Schema<APIType>;
|
||||
transformer: (value: APIType) => ModelType;
|
||||
};
|
||||
|
||||
export default ResponseHandler;
|
||||
@@ -1,4 +1,29 @@
|
||||
import Model from './Model';
|
||||
import Model, { ModelValidator } from './Model';
|
||||
import * as yup from 'yup';
|
||||
import ResponseHandler from './ResponseHandler';
|
||||
|
||||
export const SearchType = ['song', 'artist', 'album'] as const;
|
||||
export type SearchType = (typeof SearchType)[number];
|
||||
|
||||
const SearchHistoryValidator = yup
|
||||
.object({
|
||||
query: yup.string().required(),
|
||||
type: yup.mixed<SearchType>().oneOf(SearchType).required(),
|
||||
userId: yup.number().required(),
|
||||
searchDate: yup.date().required(),
|
||||
})
|
||||
.concat(ModelValidator);
|
||||
|
||||
export const SearchHistoryHandler: ResponseHandler<
|
||||
yup.InferType<typeof SearchHistoryValidator>,
|
||||
SearchHistory
|
||||
> = {
|
||||
validator: SearchHistoryValidator,
|
||||
transformer: (value) => ({
|
||||
...value,
|
||||
timestamp: value.searchDate,
|
||||
}),
|
||||
};
|
||||
|
||||
interface SearchHistory extends Model {
|
||||
query: string;
|
||||
|
||||
+19
-13
@@ -1,15 +1,21 @@
|
||||
type Skill =
|
||||
| 'rhythm'
|
||||
| 'two-hands'
|
||||
| 'combos'
|
||||
| 'arpeggio'
|
||||
| 'distance'
|
||||
| 'left-hand'
|
||||
| 'right-hand'
|
||||
| 'lead-head-change'
|
||||
| 'chord-complexity'
|
||||
| 'chord-timing'
|
||||
| 'pedal'
|
||||
| 'precision';
|
||||
import * as yup from 'yup';
|
||||
|
||||
const Skills = [
|
||||
'rhythm',
|
||||
'two-hands',
|
||||
'combos',
|
||||
'arpeggio',
|
||||
'distance',
|
||||
'left-hand',
|
||||
'right-hand',
|
||||
'lead-head-change',
|
||||
'chord-complexity',
|
||||
'chord-timing',
|
||||
'pedal',
|
||||
'precision',
|
||||
] as const;
|
||||
type Skill = (typeof Skills)[number];
|
||||
|
||||
export const SkillValidator = yup.mixed<Skill>().oneOf(Skills);
|
||||
|
||||
export default Skill;
|
||||
|
||||
+31
-2
@@ -1,6 +1,35 @@
|
||||
import Model from './Model';
|
||||
import SongDetails from './SongDetails';
|
||||
import Model, { ModelValidator } from './Model';
|
||||
import SongDetails, { SongDetailsHandler, SongDetailsValidator } from './SongDetails';
|
||||
import Artist from './Artist';
|
||||
import * as yup from 'yup';
|
||||
import ResponseHandler from './ResponseHandler';
|
||||
import API from '../API';
|
||||
|
||||
export const SongValidator = yup
|
||||
.object({
|
||||
name: yup.string().required(),
|
||||
midiPath: yup.string().required(),
|
||||
musicXmlPath: yup.string().required(),
|
||||
artistId: yup.number().required(),
|
||||
albumId: yup.number().required().nullable(),
|
||||
genreId: yup.number().required().nullable(),
|
||||
difficulties: SongDetailsValidator.required(),
|
||||
illustrationPath: yup.string().required(),
|
||||
})
|
||||
.concat(ModelValidator);
|
||||
|
||||
export const SongHandler: ResponseHandler<yup.InferType<typeof SongValidator>, Song> = {
|
||||
validator: SongValidator,
|
||||
transformer: (song) => ({
|
||||
id: song.id,
|
||||
name: song.name,
|
||||
artistId: song.artistId,
|
||||
albumId: song.albumId,
|
||||
genreId: song.genreId,
|
||||
details: SongDetailsHandler.transformer(song.difficulties),
|
||||
cover: `${API.baseUrl}/song/${song.id}/illustration`,
|
||||
}),
|
||||
};
|
||||
|
||||
interface Song extends Model {
|
||||
id: number;
|
||||
|
||||
@@ -1,7 +1,34 @@
|
||||
import * as yup from 'yup';
|
||||
import ResponseHandler from './ResponseHandler';
|
||||
|
||||
export const SongDetailsValidator = yup.object({
|
||||
length: yup.number().required(),
|
||||
rhythm: yup.number().required(),
|
||||
arpeggio: yup.number().required(),
|
||||
distance: yup.number().required(),
|
||||
lefthand: yup.number().required(),
|
||||
twohands: yup.number().required(),
|
||||
notecombo: yup.number().required(),
|
||||
precision: yup.number().required(),
|
||||
righthand: yup.number().required(),
|
||||
pedalpoint: yup.number().required(),
|
||||
chordtiming: yup.number().required(),
|
||||
leadhandchange: yup.number().required(),
|
||||
chordcomplexity: yup.number().required(),
|
||||
});
|
||||
|
||||
export const SongDetailsHandler: ResponseHandler<
|
||||
yup.InferType<typeof SongDetailsValidator>,
|
||||
SongDetails
|
||||
> = {
|
||||
validator: SongDetailsValidator,
|
||||
transformer: (value) => value,
|
||||
};
|
||||
|
||||
interface SongDetails {
|
||||
length: number;
|
||||
rhythm: number;
|
||||
arppegio: number;
|
||||
arpeggio: number;
|
||||
distance: number;
|
||||
lefthand: number;
|
||||
righthand: number;
|
||||
@@ -10,7 +37,7 @@ interface SongDetails {
|
||||
precision: number;
|
||||
pedalpoint: number;
|
||||
chordtiming: number;
|
||||
leadheadchange: number;
|
||||
leadhandchange: number;
|
||||
chordcomplexity: number;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,44 @@
|
||||
interface SongHistory {
|
||||
import * as yup from 'yup';
|
||||
import ResponseHandler from './ResponseHandler';
|
||||
|
||||
export const SongHistoryItemValidator = yup.object({
|
||||
songID: yup.number().required(),
|
||||
userID: yup.number().required(),
|
||||
score: yup.number().required(),
|
||||
difficulties: yup.mixed().required(),
|
||||
});
|
||||
|
||||
export const SongHistoryItemHandler: ResponseHandler<
|
||||
yup.InferType<typeof SongHistoryItemValidator>,
|
||||
SongHistoryItem
|
||||
> = {
|
||||
validator: SongHistoryItemValidator,
|
||||
transformer: (value) => ({
|
||||
...value,
|
||||
difficulties: value.difficulties,
|
||||
}),
|
||||
};
|
||||
|
||||
export const SongHistoryValidator = yup.object({
|
||||
best: yup.number().required().nullable(),
|
||||
history: yup.array(SongHistoryItemValidator).required(),
|
||||
});
|
||||
|
||||
export type SongHistory = yup.InferType<typeof SongHistoryValidator>;
|
||||
|
||||
export const SongHistoryHandler: ResponseHandler<SongHistory> = {
|
||||
validator: SongHistoryValidator,
|
||||
transformer: (value) => ({
|
||||
...value,
|
||||
history: value.history.map((item) => SongHistoryItemHandler.transformer(item)),
|
||||
}),
|
||||
};
|
||||
|
||||
export type SongHistoryItem = {
|
||||
songID: number;
|
||||
userID: number;
|
||||
score: number;
|
||||
difficulties: JSON;
|
||||
}
|
||||
difficulties: object;
|
||||
};
|
||||
|
||||
export default SongHistory;
|
||||
|
||||
+35
-4
@@ -1,6 +1,31 @@
|
||||
import UserData from './UserData';
|
||||
import Model from './Model';
|
||||
import UserSettings from './UserSettings';
|
||||
import Model, { ModelValidator } from './Model';
|
||||
import * as yup from 'yup';
|
||||
import ResponseHandler from './ResponseHandler';
|
||||
|
||||
export const UserValidator = yup
|
||||
.object({
|
||||
username: yup.string().required(),
|
||||
password: yup.string().required(),
|
||||
email: yup.string().required(),
|
||||
isGuest: yup.boolean().required(),
|
||||
partyPlayed: yup.number().required(),
|
||||
})
|
||||
.concat(ModelValidator);
|
||||
|
||||
export const UserHandler: ResponseHandler<yup.InferType<typeof UserValidator>, User> = {
|
||||
validator: UserValidator,
|
||||
transformer: (value) => ({
|
||||
...value,
|
||||
name: value.username,
|
||||
premium: false,
|
||||
data: {
|
||||
gamesPlayed: value.partyPlayed as number,
|
||||
xp: 0,
|
||||
createdAt: new Date('2023-04-09T00:00:00.000Z'),
|
||||
avatar: 'https://imgs.search.brave.com/RnQpFhmAFvuQsN_xTw7V-CN61VeHDBg2tkEXnKRYHAE/rs:fit:768:512:1/g:ce/aHR0cHM6Ly96b29h/c3Ryby5jb20vd3At/Y29udGVudC91cGxv/YWRzLzIwMjEvMDIv/Q2FzdG9yLTc2OHg1/MTIuanBn',
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
interface User extends Model {
|
||||
name: string;
|
||||
@@ -8,7 +33,13 @@ interface User extends Model {
|
||||
isGuest: boolean;
|
||||
premium: boolean;
|
||||
data: UserData;
|
||||
settings: UserSettings;
|
||||
}
|
||||
|
||||
interface UserData {
|
||||
gamesPlayed: number;
|
||||
xp: number;
|
||||
avatar: string | undefined;
|
||||
createdAt: Date;
|
||||
}
|
||||
|
||||
export default User;
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
interface UserData {
|
||||
gamesPlayed: number;
|
||||
xp: number;
|
||||
avatar: string | undefined;
|
||||
createdAt: Date;
|
||||
}
|
||||
|
||||
export default UserData;
|
||||
@@ -1,3 +1,40 @@
|
||||
import * as yup from 'yup';
|
||||
import { ModelValidator } from './Model';
|
||||
import ResponseHandler from './ResponseHandler';
|
||||
|
||||
export const UserSettingsValidator = yup
|
||||
.object({
|
||||
userId: yup.number().required(),
|
||||
pushNotification: yup.boolean().required(),
|
||||
emailNotification: yup.boolean().required(),
|
||||
trainingNotification: yup.boolean().required(),
|
||||
newSongNotification: yup.boolean().required(),
|
||||
recommendations: yup.boolean().required(),
|
||||
weeklyReport: yup.boolean().required(),
|
||||
leaderBoard: yup.boolean().required(),
|
||||
showActivity: yup.boolean().required(),
|
||||
})
|
||||
.concat(ModelValidator);
|
||||
|
||||
export const UserSettingsHandler: ResponseHandler<
|
||||
yup.InferType<typeof UserSettingsValidator>,
|
||||
UserSettings
|
||||
> = {
|
||||
validator: UserSettingsValidator,
|
||||
transformer: (settings) => ({
|
||||
notifications: {
|
||||
pushNotif: settings.pushNotification,
|
||||
emailNotif: settings.emailNotification,
|
||||
trainNotif: settings.trainingNotification,
|
||||
newSongNotif: settings.newSongNotification,
|
||||
},
|
||||
recommendations: settings.recommendations,
|
||||
weeklyReport: settings.weeklyReport,
|
||||
leaderBoard: settings.leaderBoard,
|
||||
showActivity: settings.showActivity,
|
||||
}),
|
||||
};
|
||||
|
||||
interface UserSettings {
|
||||
notifications: {
|
||||
pushNotif: boolean;
|
||||
|
||||
Reference in New Issue
Block a user