-
-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
added resource access, started work on zod validation.
- Loading branch information
Showing
13 changed files
with
291 additions
and
139 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
// --------------------------------------------------------------------------------------------------------------------- | ||
// Character Validation Model | ||
// --------------------------------------------------------------------------------------------------------------------- | ||
|
||
import { z } from 'zod'; | ||
|
||
// Models | ||
import { HashID } from './common'; | ||
|
||
// Utils | ||
import { cssColorRegEx, jsonSchema } from '../utils'; | ||
import { AccountID } from './account'; | ||
|
||
// --------------------------------------------------------------------------------------------------------------------- | ||
|
||
export const CharacterID = HashID; | ||
|
||
export const Character = z.object({ | ||
id: CharacterID, | ||
system: z.string().min(1), // This could be an enum of known systems? How can I generate it? | ||
name: z.string(), | ||
description: z.string().optional(), | ||
portrait: z.string().url() | ||
.optional(), | ||
thumbnail: z.string().url() | ||
.optional(), | ||
color: z.string().regex(cssColorRegEx) | ||
.optional(), | ||
campaign: z.string().optional(), | ||
accountID: z.string(), | ||
noteID: z.string(), | ||
details: jsonSchema.optional() // This will need to be based on the system. | ||
}); | ||
|
||
// --------------------------------------------------------------------------------------------------------------------- | ||
// Request Validations | ||
// --------------------------------------------------------------------------------------------------------------------- | ||
|
||
export const RouteParams = z.object({ | ||
charID: CharacterID | ||
}); | ||
|
||
export const CharFilter = z.object({ | ||
id: z.union([ AccountID, z.array(AccountID) ]).optional(), | ||
email: z.union([ z.string().email(), z.array(z.string().email()) ]) | ||
.optional(), | ||
name: z.union([ z.string().min(1), z.array(z.string().min(1)) ]) | ||
.optional() | ||
}); | ||
|
||
// --------------------------------------------------------------------------------------------------------------------- |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
// --------------------------------------------------------------------------------------------------------------------- | ||
// Validation Utils | ||
// --------------------------------------------------------------------------------------------------------------------- | ||
|
||
import { z } from 'zod'; | ||
|
||
// --------------------------------------------------------------------------------------------------------------------- | ||
|
||
export const cssColorRegEx = /(#(?:[0-9a-f]{2}){2,4}$|(#[0-9a-f]{3}$)|(rgb|hsl)a?\((-?\d+%?[,\s]+){2,3}\s*[\d.]+%?\)$|black$|silver$|gray$|whitesmoke$|maroon$|red$|purple$|fuchsia$|green$|lime$|olivedrab$|yellow$|navy$|blue$|teal$|aquamarine$|orange$|aliceblue$|antiquewhite$|aqua$|azure$|beige$|bisque$|blanchedalmond$|blueviolet$|brown$|burlywood$|cadetblue$|chartreuse$|chocolate$|coral$|cornflowerblue$|cornsilk$|crimson$|currentcolor$|darkblue$|darkcyan$|darkgoldenrod$|darkgray$|darkgreen$|darkgrey$|darkkhaki$|darkmagenta$|darkolivegreen$|darkorange$|darkorchid$|darkred$|darksalmon$|darkseagreen$|darkslateblue$|darkslategray$|darkslategrey$|darkturquoise$|darkviolet$|deeppink$|deepskyblue$|dimgray$|dimgrey$|dodgerblue$|firebrick$|floralwhite$|forestgreen$|gainsboro$|ghostwhite$|goldenrod$|gold$|greenyellow$|grey$|honeydew$|hotpink$|indianred$|indigo$|ivory$|khaki$|lavenderblush$|lavender$|lawngreen$|lemonchiffon$|lightblue$|lightcoral$|lightcyan$|lightgoldenrodyellow$|lightgray$|lightgreen$|lightgrey$|lightpink$|lightsalmon$|lightseagreen$|lightskyblue$|lightslategray$|lightslategrey$|lightsteelblue$|lightyellow$|limegreen$|linen$|mediumaquamarine$|mediumblue$|mediumorchid$|mediumpurple$|mediumseagreen$|mediumslateblue$|mediumspringgreen$|mediumturquoise$|mediumvioletred$|midnightblue$|mintcream$|mistyrose$|moccasin$|navajowhite$|oldlace$|olive$|orangered$|orchid$|palegoldenrod$|palegreen$|paleturquoise$|palevioletred$|papayawhip$|peachpuff$|peru$|pink$|plum$|powderblue$|rosybrown$|royalblue$|saddlebrown$|salmon$|sandybrown$|seagreen$|seashell$|sienna$|skyblue$|slateblue$|slategray$|slategrey$|snow$|springgreen$|steelblue$|tan$|thistle$|tomato$|transparent$|turquoise$|violet$|wheat$|white$|yellowgreen$|rebeccapurple$)/i; | ||
|
||
// --------------------------------------------------------------------------------------------------------------------- | ||
|
||
export const literalSchema = z.union([ z.string(), z.number(), z.boolean(), z.null() ]); | ||
export type Literal = z.infer<typeof literalSchema>; | ||
export type Json = Literal | { [key : string] : Json } | Json[]; | ||
export const jsonSchema : z.ZodType<Json> = z.lazy(() => | ||
z.union([ literalSchema, z.array(jsonSchema), z.record(jsonSchema) ])); | ||
|
||
// --------------------------------------------------------------------------------------------------------------------- |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
//---------------------------------------------------------------------------------------------------------------------- | ||
// Character Manager | ||
//---------------------------------------------------------------------------------------------------------------------- | ||
|
||
// Models | ||
import { Character } from '../../common/interfaces/models/character'; | ||
|
||
// Transforms | ||
import * as CharTransforms from './transforms/character'; | ||
|
||
// Resource Access | ||
import systemRA from './system'; | ||
|
||
// Utils | ||
import { getDB } from '../utils/database'; | ||
import { FilterToken } from '../routes/utils'; | ||
import { applyFilters } from '../knex/utils'; | ||
import { shortID, snakeCaseKeys } from '../utils/misc'; | ||
import { broadcast } from '../utils/sio'; | ||
|
||
import { MultipleResultsError, NotFoundError } from '../errors'; | ||
|
||
//---------------------------------------------------------------------------------------------------------------------- | ||
|
||
export async function get(id : string) : Promise<Character> | ||
{ | ||
const db = await getDB(); | ||
const characters = await db('character as char') | ||
.select() | ||
.where({ 'char.character_id': id }); | ||
|
||
if(characters.length > 1) | ||
{ | ||
throw new MultipleResultsError('character'); | ||
} | ||
else if(characters.length === 0) | ||
{ | ||
throw new NotFoundError(`No character with id '${ id }' found.`); | ||
} | ||
else | ||
{ | ||
const char = CharTransforms.fromDB(characters[0]); | ||
return systemRA.validateCharacterDetails(char); | ||
} | ||
} | ||
|
||
export async function list(filters : Record<string, FilterToken> = {}) : Promise<Character[]> | ||
{ | ||
const db = await getDB(); | ||
let query = db('character as char') | ||
.select(); | ||
|
||
// Snake case the filters | ||
filters = snakeCaseKeys(filters); | ||
|
||
// Apply any filters | ||
query = applyFilters(query, filters); | ||
|
||
return Promise.all((await query) | ||
.map(CharTransforms.fromDB) | ||
.map(async(char) => systemRA.validateCharacterDetails(char))); | ||
} | ||
|
||
export async function add(accountID : string, newCharacter : Omit<Character, 'id'>) : Promise<Character> | ||
{ | ||
const char = CharTransforms.toDB({ ...newCharacter, id: shortID(), accountID }); | ||
|
||
const db = await getDB(); | ||
await db('character') | ||
.insert(char); | ||
|
||
// We know this is a string since it's set above. | ||
return get(char.character_id); | ||
} | ||
|
||
export async function update(charID : string, updateChar : Partial<Character>) : Promise<Character> | ||
{ | ||
const char = await get(charID); | ||
|
||
// Mix the current character with the allowed updates. | ||
const allowedUpdate = { | ||
...char, | ||
name: updateChar.name ?? char.name, | ||
description: updateChar.description ?? char.description, | ||
portrait: updateChar.portrait ?? char.portrait, | ||
thumbnail: updateChar.thumbnail ?? char.thumbnail, | ||
color: updateChar.color ?? char.color, | ||
campaign: updateChar.campaign ?? char.campaign, | ||
details: updateChar.details ?? char.details | ||
}; | ||
|
||
// Make a new character object | ||
const newCharacter = CharTransforms.toDB(allowedUpdate); | ||
|
||
// Update the database | ||
const db = await getDB(); | ||
await db('character') | ||
.update(newCharacter) | ||
.where({ character_id: charID }); | ||
|
||
const newChar = await get(charID); | ||
|
||
// Broadcast the update | ||
await broadcast('/characters', { | ||
type: 'update', | ||
resource: charID, | ||
payload: newChar | ||
}); | ||
|
||
// Return the updated record | ||
return newChar; | ||
} | ||
|
||
export async function remove(charID : string) : Promise<{ status : 'ok' }> | ||
{ | ||
const db = await getDB(); | ||
await db('character') | ||
.where({ character_id: charID }) | ||
.delete(); | ||
|
||
// Broadcast the update | ||
await broadcast('/characters', { | ||
type: 'remove', | ||
resource: charID | ||
}); | ||
|
||
return { status: 'ok' }; | ||
} | ||
|
||
//---------------------------------------------------------------------------------------------------------------------- |
Oops, something went wrong.