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

designs to polymorphic model 1 #180

Merged
merged 1 commit into from
Jun 17, 2024
Merged
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
7 changes: 6 additions & 1 deletion app/models/design/design.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ import {
type IDesignAttributesFill,
type IDesignFill,
} from './fill/fill.server'
import {
type IDesignStroke,
type IDesignAttributesStroke,
} from './stroke/stroke.server'

// Omitting 'createdAt' and 'updatedAt' from the Design interface
// prisma query returns a string for these fields
Expand All @@ -47,7 +51,7 @@ export interface IDesign extends BaseDesign {
// when adding attributes to a design type,
// make sure it starts as optional or is set to a default value
// for when parsing the design from the deserializer
export type IDesignAttributes = IDesignAttributesFill
export type IDesignAttributes = IDesignAttributesFill | IDesignAttributesStroke

export interface IDesignParsed extends BaseDesign {
type: designTypeEnum
Expand All @@ -60,6 +64,7 @@ export interface IDesignParsed extends BaseDesign {
// TODO: replace with this ^^
export type IDesignByType = {
designFills: IDesignFill[]
designStroke: IDesignStroke[]
}

// export interface IDesignsByTypeWithType {
Expand Down
13 changes: 13 additions & 0 deletions app/models/design/stroke/stroke.delete.server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { prisma } from '#app/utils/db.server'
import { type IDesignStroke } from './stroke.server'

export interface IDesignStrokeDeletedResponse {
success: boolean
message?: string
}

export const deleteDesignStroke = ({ id }: { id: IDesignStroke['id'] }) => {
return prisma.design.delete({
where: { id },
})
}
52 changes: 52 additions & 0 deletions app/models/design/stroke/stroke.get.server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { invariant } from '@epic-web/invariant'
import { z } from 'zod'
import { DesignTypeEnum } from '#app/schema/design'
import { prisma } from '#app/utils/db.server'
import { deserializeDesign } from '../utils'
import { type IDesignStroke } from './stroke.server'

export type queryWhereArgsType = z.infer<typeof whereArgs>
const whereArgs = z.object({
id: z.string().optional(),
ownerId: z.string().optional(),
artworkVersionId: z.string().optional(),
layerId: z.string().optional(),
})

// TODO: Add schemas for each type of query and parse with zod
// aka if by id that should be present, if by slug that should be present
// owner id should be present unless admin (not set up yet)
const validateQueryWhereArgsPresent = (where: queryWhereArgsType) => {
const nullValuesAllowed: string[] = []
const missingValues: Record<string, any> = {}
for (const [key, value] of Object.entries(where)) {
const valueIsNull = value === null || value === undefined
const nullValueAllowed = nullValuesAllowed.includes(key)
if (valueIsNull && !nullValueAllowed) {
missingValues[key] = value
}
}

if (Object.keys(missingValues).length > 0) {
console.log('Missing values:', missingValues)
throw new Error(
'Null or undefined values are not allowed in query parameters for design stroke.',
)
}
}

export const getDesignStroke = async ({
where,
}: {
where: queryWhereArgsType
}): Promise<IDesignStroke | null> => {
validateQueryWhereArgsPresent(where)
const design = await prisma.design.findFirst({
where: {
...where,
type: DesignTypeEnum.STROKE,
},
})
invariant(design, 'Design Stroke Not found')
return deserializeDesign({ design }) as IDesignStroke
}
31 changes: 31 additions & 0 deletions app/models/design/stroke/stroke.server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { type DesignTypeEnum } from '#app/schema/design'
import { type IDesignSubmission, type IDesignParsed } from '../design.server'

export interface IDesignStroke extends IDesignParsed {
type: typeof DesignTypeEnum.STROKE
attributes: IDesignAttributesStroke
}

export type IDesignStrokeBasis =
| 'defined'
| 'random'
| 'palette-selected'
| 'palette-random'
| 'palette-loop'
| 'palette-loop-reverse'
| 'pixel'

export type IDesignStrokeStyle = 'solid'

// when adding attributes to an design type,
// make sure it starts as optional or is set to a default value
// for when parsing the design from the deserializer
export interface IDesignAttributesStroke {
basis?: IDesignStrokeBasis
style?: IDesignStrokeStyle
value?: string
}

export interface IDesignStrokeSubmission
extends IDesignSubmission,
IDesignAttributesStroke {}
53 changes: 53 additions & 0 deletions app/models/design/stroke/stroke.update.basis.server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { type IntentActionArgs } from '#app/definitions/intent-action-args'
import { type IUser } from '#app/models/user/user.server'
import { EditDesignStrokeBasisSchema } from '#app/schema/stroke'
import { ValidateDesignSubmissionStrategy } from '#app/strategies/validate-submission.strategy'
import { validateEntitySubmission } from '#app/utils/conform-utils'
import { prisma } from '#app/utils/db.server'
import {
type IDesignAttributesStroke,
type IDesignStroke,
type IDesignStrokeBasis,
} from './stroke.server'
import { stringifyDesignStrokeAttributes } from './utils'

export const validateEditBasisDesignStrokeSubmission = async ({
userId,
formData,
}: IntentActionArgs) => {
const strategy = new ValidateDesignSubmissionStrategy()

return await validateEntitySubmission({
userId,
formData,
schema: EditDesignStrokeBasisSchema,
strategy,
})
}

export interface IDesignStrokeUpdateBasisSubmission {
userId: IUser['id']
id: IDesignStroke['id']
basis: IDesignStrokeBasis
}

interface IDesignStrokeUpdateBasisData {
attributes: IDesignAttributesStroke
}

export const updateDesignStrokeBasis = ({
id,
data,
}: {
id: IDesignStroke['id']
data: IDesignStrokeUpdateBasisData
}) => {
const { attributes } = data
const jsonAttributes = stringifyDesignStrokeAttributes(attributes)
return prisma.design.update({
where: { id },
data: {
attributes: jsonAttributes,
},
})
}
20 changes: 20 additions & 0 deletions app/models/design/stroke/stroke.update.server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { type IDesign, type IDesignUpdateData } from '../design.server'
import {
type IDesignStrokeSubmission,
type IDesignAttributesStroke,
type IDesignStroke,
} from './stroke.server'

export interface IDesignStrokeUpdatedResponse {
success: boolean
message?: string
updatedDesignStroke?: IDesign
}

export interface IDesignStrokeUpdateSubmission extends IDesignStrokeSubmission {
id: IDesignStroke['id']
}

export interface IDesignStrokeUpdateData extends IDesignUpdateData {
attributes: IDesignAttributesStroke
}
39 changes: 39 additions & 0 deletions app/models/design/stroke/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { ZodError } from 'zod'
import { DesignAttributesStrokeSchema } from '#app/schema/design/stroke'
import { type IDesignAttributesStroke } from './stroke.server'

export const parseDesignStrokeAttributes = (
attributes: string,
): IDesignAttributesStroke => {
try {
return DesignAttributesStrokeSchema.parse(JSON.parse(attributes))
} catch (error: any) {
if (error instanceof ZodError) {
throw new Error(
`Validation failed for asset image: ${error.errors.map(e => e.message).join(', ')}`,
)
} else {
throw new Error(
`Unexpected error during validation for asset image: ${error.message}`,
)
}
}
}

export const stringifyDesignStrokeAttributes = (
attributes: IDesignAttributesStroke,
): string => {
try {
return JSON.stringify(DesignAttributesStrokeSchema.parse(attributes))
} catch (error: any) {
if (error instanceof ZodError) {
throw new Error(
`Validation failed for asset image: ${error.errors.map(e => e.message).join(', ')}`,
)
} else {
throw new Error(
`Unexpected error during validation for asset image: ${error.message}`,
)
}
}
}
3 changes: 3 additions & 0 deletions app/models/design/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { ZodError } from 'zod'
import { DesignTypeEnum, type designTypeEnum } from '#app/schema/design'
import { type IDesign, type IDesignParsed } from './design.server'
import { parseDesignFillAttributes } from './fill/utils'
import { parseDesignStrokeAttributes } from './stroke/utils'

export const deserializeDesigns = ({
designs,
Expand Down Expand Up @@ -42,6 +43,8 @@ export const validateDesignAttributes = ({
switch (type) {
case DesignTypeEnum.FILL:
return parseDesignFillAttributes(attributes)
case DesignTypeEnum.STROKE:
return parseDesignStrokeAttributes(attributes)
default:
throw new Error(`Unsupported design type: ${type}`)
}
Expand Down
60 changes: 60 additions & 0 deletions app/schema/design/stroke.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { z } from 'zod'
import { type ObjectValues } from '#app/utils/typescript-helpers'
import { HexcodeSchema } from '../colors'

export const StrokeBasisTypeEnum = {
DEFINED: 'defined', // exact hex value
RANDOM: 'random', // random hex value
PALETTE_SELECTED: 'palette-selected', // first palette in array
PALETTE_RANDOM: 'palette-random', // random palette in array
PALETTE_LOOP: 'palette-loop', // loop palette array by index
PALETTE_LOOP_REVERSE: 'palette-loop-reverse', // loop reversed palette array by index
PIXEL: 'pixel', // pixel color
// add more basis types here
} as const
export const StrokeStyleTypeEnum = {
SOLID: 'solid', // flat color
// add more styles here, like gradient, pattern, etc.
} as const
export type strokeBasisTypeEnum = ObjectValues<typeof StrokeBasisTypeEnum>
export type strokeStyleTypeEnum = ObjectValues<typeof StrokeStyleTypeEnum>

const StrokeBasisSchema = z.nativeEnum(StrokeBasisTypeEnum)
const StrokeStyleSchema = z.nativeEnum(StrokeStyleTypeEnum)

// use this to (de)serealize data to/from the db
// when adding attributes to an design type,
// make sure it starts as optional or is set to a default value
// for when parsing the design from the deserializer
export const DesignAttributesStrokeSchema = z.object({
basis: StrokeBasisSchema.optional(),
style: StrokeStyleSchema.optional(),
value: HexcodeSchema.optional(),
})

export const NewDesignStrokeSchema = z.object({
visible: z.boolean(),
selected: z.boolean(),
basis: StrokeBasisSchema.default(StrokeBasisTypeEnum.DEFINED),
style: StrokeStyleSchema.default(StrokeStyleTypeEnum.SOLID),
value: HexcodeSchema.default('000000'),
})

export const EditDesignStrokeBasisSchema = z.object({
id: z.string(),
basis: StrokeBasisSchema,
})

export const EditDesignStrokeStyleSchema = z.object({
id: z.string(),
style: StrokeStyleSchema,
})

export const EditDesignStrokeValueSchema = z.object({
id: z.string(),
value: HexcodeSchema,
})

export const DeleteDesignStrokeSchema = z.object({
id: z.string(),
})
Loading
Loading