Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
5 changes: 3 additions & 2 deletions packages/plugins/openapi/src/rest-generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -847,8 +847,9 @@ export class RESTfulOpenAPIGenerator extends OpenAPIGeneratorBase {

private generateModelEntity(model: DataModel, mode: 'read' | 'create' | 'update'): OAPI.SchemaObject {
const idFields = model.fields.filter((f) => isIdField(f));
// For compound ids, each component is also exposed as a separate field
const fields = idFields.length > 1 ? model.fields : model.fields.filter((f) => !isIdField(f));
// For compound ids, each component is also exposed as a separate fields for read operations
const fields =
idFields.length > 1 && mode === 'read' ? model.fields : model.fields.filter((f) => !isIdField(f));

const attributes: Record<string, OAPI.SchemaObject> = {};
const relationships: Record<string, OAPI.ReferenceObject | OAPI.SchemaObject> = {};
Expand Down
15 changes: 0 additions & 15 deletions packages/plugins/openapi/tests/baseline/rest-3.0.0.baseline.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3143,14 +3143,6 @@ components:
type: string
attributes:
type: object
required:
- postId
- userId
properties:
postId:
type: string
userId:
type: string
relationships:
type: object
properties:
Expand Down Expand Up @@ -3178,13 +3170,6 @@ components:
type: string
type:
type: string
attributes:
type: object
properties:
postId:
type: string
userId:
type: string
relationships:
type: object
properties:
Expand Down
17 changes: 0 additions & 17 deletions packages/plugins/openapi/tests/baseline/rest-3.1.0.baseline.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3155,16 +3155,6 @@ components:
properties:
type:
type: string
attributes:
type: object
required:
- postId
- userId
properties:
postId:
type: string
userId:
type: string
relationships:
type: object
properties:
Expand Down Expand Up @@ -3192,13 +3182,6 @@ components:
type: string
type:
type: string
attributes:
type: object
properties:
postId:
type: string
userId:
type: string
relationships:
type: object
properties:
Expand Down
55 changes: 46 additions & 9 deletions packages/server/src/api/rest/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -719,6 +719,18 @@ class RequestHandler extends APIHandlerBase {
const parsed = this.createUpdatePayloadSchema.parse(body);
const attributes: any = parsed.data.attributes;

// Map in the compound id relationships as attributes, as they are expected by the zod schema
if (parsed.data.relationships) {
for (const [key, data] of Object.entries<any>(parsed.data.relationships)) {
const typeInfo = this.typeMap[key];
if (typeInfo && typeInfo.idFields.length > 1) {
typeInfo.idFields.forEach((field, index) => {
attributes[field.name] = this.coerce(field.type, data.data.id.split(this.idDivider)[index]);
});
}
}
}

if (attributes) {
const schemaName = `${upperCaseFirst(type)}${upperCaseFirst(mode)}Schema`;
// zod-parse attributes if a schema is provided
Expand Down Expand Up @@ -756,6 +768,19 @@ class RequestHandler extends APIHandlerBase {
}

const { error, attributes, relationships } = this.processRequestBody(type, requestBody, zodSchemas, 'create');

if (relationships) {
// Remove attributes that are present in compound id relationships, as they are not expected by Prisma
for (const [key] of Object.entries<any>(relationships)) {
const typeInfo = this.typeMap[key];
if (typeInfo && typeInfo.idFields.length > 1) {
typeInfo.idFields.forEach((field) => {
delete attributes[field.name];
});
}
}
}

if (error) {
return error;
}
Expand All @@ -776,18 +801,16 @@ class RequestHandler extends APIHandlerBase {

if (relationInfo.isCollection) {
createPayload.data[key] = {
connect: enumerate(data.data).map((item: any) => ({
[this.makePrismaIdKey(relationInfo.idFields)]: item.id,
})),
connect: enumerate(data.data).map((item: any) =>
this.makeIdConnect(relationInfo.idFields, item.id)
),
};
} else {
if (typeof data.data !== 'object') {
return this.makeError('invalidRelationData');
}
createPayload.data[key] = {
connect: {
[this.makePrismaIdKey(relationInfo.idFields)]: data.data.id,
},
connect: this.makeIdConnect(relationInfo.idFields, data.data.id),
};
}

Expand Down Expand Up @@ -868,9 +891,7 @@ class RequestHandler extends APIHandlerBase {
} else {
updateArgs.data = {
[relationship]: {
connect: {
[this.makePrismaIdKey(relationInfo.idFields)]: parsed.data.data.id,
},
connect: this.makeIdConnect(relationInfo.idFields, parsed.data.data.id),
},
};
}
Expand Down Expand Up @@ -1261,6 +1282,22 @@ class RequestHandler extends APIHandlerBase {
return idFields.reduce((acc, curr) => ({ ...acc, [curr.name]: true }), {});
}

private makeIdConnect(idFields: FieldInfo[], id: string | number) {
if (idFields.length === 1) {
return { [idFields[0].name]: this.coerce(idFields[0].type, id) };
} else {
return {
[this.makePrismaIdKey(idFields)]: idFields.reduce(
(acc, curr, idx) => ({
...acc,
[curr.name]: this.coerce(curr.type, `${id}`.split(this.idDivider)[idx]),
}),
{}
),
};
}
}

private makeIdKey(idFields: FieldInfo[]) {
return idFields.map((idf) => idf.name).join(this.idDivider);
}
Expand Down
35 changes: 35 additions & 0 deletions packages/server/tests/api/rest.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,17 @@ describe('REST server tests', () => {
superLike Boolean
post Post @relation(fields: [postId], references: [id])
user User @relation(fields: [userId], references: [myId])
likeInfos PostLikeInfo[]
@@id([postId, userId])
}

model PostLikeInfo {
id Int @id @default(autoincrement())
text String
postId Int
userId String
postLike PostLike @relation(fields: [postId, userId], references: [postId, userId])
}
`;

beforeAll(async () => {
Expand Down Expand Up @@ -1765,6 +1774,32 @@ describe('REST server tests', () => {

expect(r.status).toBe(201);
});

it('create an entity related to an entity with compound id', async () => {
await prisma.user.create({ data: { myId: 'user1', email: '[email protected]' } });
await prisma.post.create({ data: { id: 1, title: 'Post1' } });
await prisma.postLike.create({ data: { userId: 'user1', postId: 1, superLike: false } });

const r = await handler({
method: 'post',
path: '/postLikeInfo',
query: {},
requestBody: {
data: {
type: 'postLikeInfo',
attributes: { text: 'LikeInfo1' },
relationships: {
postLike: {
data: { type: 'postLike', id: `1${idDivider}user1` },
},
},
},
},
prisma,
});

expect(r.status).toBe(201);
});
});

describe('PUT', () => {
Expand Down
Loading