How to render a type based on another type in typescript? - typescript-typings

export enum TemplateType {
Email = "Email",
Sms = "Sms",
}
export type SmsBinding = {
date: string;
first_name: string;
last_name: string;
};
export type EmailBinding = {
recipients: string[];
templateBindings: SmsBinding;
};
export interface CrmMessagesDataType {
id: string;
subject: string;
bindings: SmsBinding | EmailBinding;
createdAt: string;
isArchived: boolean;
isDraft: boolean;
isRead: boolean;
status: CrmMessageStatus;
type: TemplateType;
}
How do I make "bindings" in interface "CrmMessagesDataType" of type SmsBinding or EmailBinding based on if "type" is of "Email" or "Sms" in a destructuring?

1. Template arguments
export interface CrmMessagesDataType<T extends SmsBinding | EmailBinding> {
id: string;
subject: string;
bindings: T;
createdAt: string;
isArchived: boolean;
isDraft: boolean;
isRead: boolean;
type: TemplateType;
}
const data1: CrmMessagesDataType<SmsBinding> = {
id: '',
subject: '',
bindings: { date: '', first_name: '', last_name: '' },
createdAt: undefined,
isArchived: true,
isDraft: true,
isRead: true,
type: TemplateType.Sms,
};
const data2: CrmMessagesDataType<EmailBinding> = {
id: '',
subject: '',
bindings: { recipients: [''], templateBindings: { date: '', first_name: '', last_name: '' } },
createdAt: undefined,
isArchived: true,
isDraft: true,
isRead: true,
type: TemplateType.Email,
};
const { bindings } = data1; // bindings is of type SmsBinding
const { bindings } = data2; // bindings is of type EmailBinding
2. Type Casting
const data: CrmMessagesDataType = {
id: '',
subject: '',
bindings: { date: '', first_name: '', last_name: '' },
createdAt: undefined,
isArchived: true,
isDraft: true,
isRead: true,
type: TemplateType.Sms
};
const { bindings } = data;
if (data.type === TemplateType.Email) {
(<EmailBinding>bindings).templateBindings;
}
if (data.type === TemplateType.Sms) {
(<SmsBinding>bindings).first_name;
}

Related

How to update a sub document in two levels in MongoDB (mongoose)

Purpose: Update a comment
"mongoose": "^6.7.0",
I've searched a few places for different ways to solve this problem, but to no avail.
My schema: (GalleryModule)
Here is the mongoose model creation
{
user: {
type: {
_id: {
type: String,
required: true,
},
name: {
type: String,
required: true,
},
},
required: true,
},
list: {
type: Array<{
_id: {
type: String;
required: true;
};
name: {
type: String;
required: true;
};
references: {
type: Array<{
type: {
type: string;
required: true;
};
body: {
type: string;
required: true;
};
}>;
};
comments: {
type: Array<{
_id: {
type: string;
required: true;
};
body: {
type: string;
required: true;
};
createdAt: {
type: Number;
required: true;
};
updatedAt: {
type: Number;
required: true;
};
}>;
};
createdAt: {
type: Number;
required: true;
};
updatedAt: {
type: Number;
required: true;
};
}>,
},
}
`The execution:
I look for a user with the ID, then I update some data
I only update the data that came in the body of the request
const User = await GetOneId(UserModule, req.body.UserId);
if (!User.body) {
return res.status(404).json({
message: "Usuário não existe no banco de dados",
});
}
var body = {};
if (req.body?.body) {
body["list.$[item].comments.$[score].body"] = req.body.body;
}
body["list.$[item].comments.$[score].user.name"] = User.body.name;
body["list.$[item].comments.$[score].user.image"] = User.body.image_small;
const response = await UpdateSubDoc<GalleryResponse>(
GalleryModule,
{
"list.$[item].comments.$[score].updatedAt": Date.now(),
},
[
{ "item._id": req.params.itemId },
{ "score._id": req.params.commentId },
],
{ _id: req.params.galleryId }
);
return res.status(response.status).json(response.body);
function: (UpdateSubDc)
Here is a reusable function in several pieces of code to update sub documents
export const UpdateSubDoc = async <S, TSchema = any, T = any>(
Schema: Model<S>,
Body: AnyKeys<TSchema> & AnyObject,
Filter: { [key: string]: any }[],
Id?: FilterQuery<T>
) => {
const response = await Schema.updateMany(
Id || {},
{ $set: Body },
{ arrayFilters: Filter }
);
return {
status: 200,
body: response,
};
};
Error:
Error: Could not find path "list.0.comments.0._id" in schema

Infer object with Pothos without to call implement

I have a z object created like this:
import z from 'zod';
import { ObjectId } from 'mongodb';
interface IAppMenuItem {
folder: string;
unit: string;
items: IAppMenuItem[];
}
const zAppMenuItem: z.ZodType<IAppMenuItem> = z.lazy(() =>
z.object({
folder: z.string().trim().min(1).max(20),
unit: z.string().trim().min(1).max(20),
items: z.array(zAppMenuItem)
})
);
export const zApp = z.object({
orgId: z.instanceof(ObjectId),
name: z.string().trim().min(1).max(50),
caption: z.string().trim().min(1).max(50),
menu: z.array(zAppMenuItem).optional(),
});
....
import { z } from 'zod';
import { zApp } from '../validators';
import { ChangeLog } from './ChangeLog';
export type App = z.infer<typeof zApp> & { changeLog: ChangeLog };
then I have a builder file
const builder = new SchemaBuilder<{
Objects: {
AppObject: WithId<App>;
};
Scalars: {
Id: { Input: Id; Output: Id };
Date: { Input: Date; Output: Date };
DateTime: { Input: Date; Output: Date };
};
Context: Context;
}>({});
export const AppObject = builder.objectRef<WithId<App>>('App');
export const ChangeLogObject = builder.objectRef<ChangeLog>('ChangeLog');
ChangeLogObject.implement({
fields: t => ({
createdAt: t.expose('createdAt', { type: 'DateTime', nullable: true }),
createdById: t.expose('createdById', { type: 'Id', nullable: true }),
createdByName: t.expose('createdByName', { type: 'String', nullable: true }),
updatedAt: t.expose('updatedAt', { type: 'DateTime', nullable: true }),
updatedById: t.expose('updatedById', { type: 'Id', nullable: true }),
updatedByName: t.expose('updatedByName', { type: 'String', nullable: true })
})
});
AppObject.implement({
fields: t => ({
_id: t.expose('_id', { type: 'Id' }),
orgId: t.expose('orgId', { type: 'Id' }),
name: t.expose('name', { type: 'String' }),
caption: t.expose('caption', { type: 'String' }),
// menu should be implemented here...
changeLog: t.expose('changeLog', { type: ChangeLogObject })
})
});
export { builder };
Because the zApp object will have even more nested objects like menu and units, it's not practical to implement for each the corresponding model under pothos.
Is there any way to infer the App type, so that Pothos will understand the model?

Any better way for me to select between three models based on some variable?

I am currently working on an Express.js API that handles getting and saving various content types, storing one record per piece of content. My issue that I encountered a while ago was how do I select which Mongoose model to use for queries (mainly find and save) depending on the content type specified to the route handler. I want to do this the right way (or rather, I want to use best practices).
Long story short I looked for help and I used an object instead of a map, which led to more issues due to the following object having the following type:
const contentTypeModels = {
"image": imageModel,
"video": videoModel,
"letter": letterModel
};
Type info of contentTypeModels as declared and initialized
const contentTypeModels: {
image: Model<IImage, {}, {}, {}, any>;
video: Model<IVideo, {}, {}, {}, any>;
letter: Model<ILetter, {}, {}, {}, any>;
}
The issue here was that the type expected in the route handler was
Model<IImage | IVideo | ILetter>
Figured this out sometime yesterday (don’t remember exactly how tbh)
I was able to get a working solution with further assistance but I want to know if there’s a better way to dynamically select a model to perform queries on, or how to resolve the type issues I’m currently having if there is no better way than to use an object.
Here’s the interfaces and models for each content type, as well as one route handler and some accompanying code from the top of the file (imports excluded)
const contentTypeModels = {
'image': imageModel,
'video': videoModel,
'letter': letterModel
};
type contentNames = 'image' | 'video' | 'letter' | null;
// other code
// …
const getOneFromDb = (req: Request, res: Response, next: NextFunction) => {
// get object from Db
/*
make request at the URL <contentType>/<mongoId>
*/
const mongoId = req.params.mongoId;
// was getting an error before with this cause i didn’t limit the values of strings that could be accepted by the rest of the code
/*
Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ image: Model<IImage, {}, {}, {}, any>; video: Model<IVideo, {}, {}, {}, any>; letter: Model<ILetter, {}, {}, {}, any>; }'.
*/
const content = req.params.contentType;
// uncommenting the line below yields the error ‘string’ is not assignable to contentNames
// const content:contentNames = req.params.contentType;
// resolved that error with the following
/*
let content:contentNames;
if (req.params.contentType === 'image' || req.params.contentType === 'video' || req.params.contentType === 'letter') {
content = req.params.contentType;
} else {
content = null;
throw 'bad request: please specify a content type';
}
*/
const contentType = contentTypeModels[content];
if (mongoId.length > 0) {
// the error i was getting before was on the findById() function, it was something like:
/* This expression is not callable.
Each member of the union type '{ <ResultDoc = Document<unknown, any, ILetter> & ILetter & { _id: ObjectId; }>(id: any, projection?: ProjectionType<ILetter> | null | undefined, options?: QueryOptions<...> | ... 1 more ... | undefined, callback?: Callback<...> | undefined): Query<...>; <ResultDoc = Document<...> & ... 1 more ... & { ...; }>(id: any...' has signatures, but none of those signatures are compatible with each other.
*/
contentType.findById(mongoId)
.then(fileFound => {
if (fileFound) {
res.status(200).send(fileFound);
} else {
next(createHttpError(404, 'file not found in db'));
}
})
.catch((err: HttpError) => {
next(createHttpError(500, err));
});
} else {
next(createHttpError(400, 'please make sure your URL is of the format <contentType>/<mongoId>'));
}
};
The interfaces:
IImage
interface IImage {
// interface IImage extends IPieceofContent {
firstName: string;
lastName: string;
email: string;
phoneNumber: string;
IP: string;
customData?: object;
URL: string;
caption: string;
}
export {
IImage
};
ILetter
interface ILetter {
// interface ILetter extends IPieceofContent {
firstName: string;
lastName: string;
email: string;
phoneNumber: string;
IP: string;
customData?: object;
twitterHandle: string;
title: string;
description: string;
town: string;
}
export {
ILetter
};
IVideo
interface IVideo {
// interface IVideo extends IPieceofContent {
firstName: string;
lastName: string;
email: string;
phoneNumber: string;
IP: string;
customData?: object;
URL: string;
caption: string;
}
export {
IVideo
};
And the schemas:
imageModel
import {Schema, model} from 'mongoose';
import { IImage } from '../interfaces/IImage';
// For video and image they can use the same schema, but they are different models
const imageSchema = new Schema<IImage>({
firstName: {
required: true,
type: String
},
lastName: {
required: true,
type: String
},
email: {
required: true,
type: String
},
phoneNumber: {
required: true,
type: String
},
URL: {
required:true,
type: String
},
caption: {
required:true,
type: String
},
IP: {
required:true,
type: String
},
// has a type of Mixed
customData: {},
},
{ timestamps: true }
);
const imageModel = model<IImage>('images', imageSchema);
export {
imageModel
};
letterModel
import { ILetter } from '../interfaces/ILetter';
import { Schema, model } from 'mongoose';
const letterSchema = new Schema<ILetter>({
firstName: {
required: true,
type: String
},
lastName: {
required: true,
type: String
},
email: {
required: true,
type: String
},
phoneNumber: {
required: true,
type: String
},
twitterHandle: {
required: false,
type: String
},
title: {
required: true,
type: String
},
description: {
required: true,
type: String
},
town: {
required: true,
type: String
},
IP: {
required: true,
type: String
},
// has a type of Mixed
customData: {},
},
{ timestamps: true }
);
const letterModel = model<ILetter>('letters', letterSchema);
export {
letterModel
};
videoModel
import {Schema, model} from 'mongoose';
import { IVideo } from '../interfaces/IVideo';
const videoSchema = new Schema<IVideo>({
firstName: {
required: true,
type: String
},
lastName: {
required: true,
type: String
},
email: {
required: true,
type: String
},
phoneNumber: {
required: true,
type: String
},
URL: {
required:true,
type: String
},
caption: {
required:true,
type: String
},
IP: {
required:true,
type: String
},
// has a type of Mixed
customData: {},
},
{ timestamps: true }
);
const videoModel = model<IVideo>('videos', videoSchema);
export {
videoModel
};
TL:DR: trying to select between three content types based on info in request, idk how best to do this, plz help.

Property 'save' does not exist on type 'Ipub[]' | mongoose - typescript

i'm trying to update a schema in my backend app (with node/express/typescript), but when i do .save() it breaks because the type is not like type it's type[]
And in my app is happening the following error
Property 'save' does not exist on type 'Ipub[]'
This is the schema
import { Schema, model } from "mongoose";
import { Ipub } from "../helpers/PublicationInterfaces";
const publication = new Schema<Ipub>({
body: { type: String },
photo: { type: String },
creator: {
name: { required: true, type: String },
perfil: { type: String },
identifier: { required: true, type: String }
},
likes: [
{
identifier: { type: String }
}
],
comments: [
{
body: { type: String },
name: { type: String },
perfil: { type: String },
identifier: { type: String },
createdAt: { type: Date, default: new Date() },
likesComments: [
{
identifier: { type: String }
}
]
}
],
createdAt: { required: true, type: Date, default: new Date() }
});
export default model("Publications", publication);
This is schema's interface
import { Document } from "mongoose";
// Publication's representation
export interface Ipub extends Document {
body: string;
photo: string;
creator: {
name: string;
perfil?: string;
identifier: string;
};
likes?: [
{
identifier: string;
}
];
comments?: [
{
body: string;
name: string;
perfil: string;
identifier: string;
createdAt: Date;
likesComments: [
{
identifier: string;
}
];
}
];
createdAt: Date;
}
And this is where the code breaks, look the line of code where i put await updateSub.save()
// Look for user's publications
const usersPub: Ipub[] = await Publication.find();
const updatePub = usersPub.filter(
(p: Ipub) => p.creator.identifier === specificUser.id
);
// update photo
try {
if (banner !== "") {
specificUser.banner = banner;
} else if (perfil !== "") {
specificUser.perfil = perfil;
updatePub.map((pub: Ipub) => (pub.creator.perfil = perfil));
await updatePub.save();
}
await specificUser.save();
return res.json(specificUser);
} catch (err) {
console.log(err);
return res.status(500).json({ Error: "The API Failed" });
}
It's really weird, it looks like it's because of the Ipub[] type definition, what can i do about it !
Thanks for your help !

Express.js, Mongoose: Populate returns null

I'm learning express.js and mongoDB and want to create a functionality, where user should be able add products.
My code contains 2 models, User and Product. User has a reference to Product. In User query when i try to populate Product', null is returned.
I'm testing it with postman, and the result is null in cart.
I am a beginner and don't understand how to solve the issue.
user schema
import mongoose, { Document } from 'mongoose'
import { ProductDocument } from './Products'
export type UserDocument = Document & {
firstName: string;
lastName: string;
password: string;
email: string;
isAdmin: boolean;
cart: ProductDocument[];
resetLink: string;
}
const userSchema = new mongoose.Schema({
firstName: {
type: String,
required: true,
max: 64,
},
lastName: {
type: String,
required: true,
max: 64,
},
password: {
type: String,
required: true,
min: 6,
},
email: {
type: String,
required: true,
lowercase: true,
trim: true,
},
isAdmin: {
type: Boolean,
default: false,
},
cart: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'Products',
},
],
resetLink: {
data: String,
default: '',
},
})
export default mongoose.model<UserDocument>('Users', userSchema)
product schema
import mongoose, { Document } from 'mongoose'
export type ProductDocument = Document & {
name: string;
description: string;
categories: string[];
variants: string[];
sizes: number[];
}
const productSchema = new mongoose.Schema({
name: {
type: String,
required: true,
index: true,
},
description: {
type: String,
},
categories: [String],
variants: [String],
sizes: [Number],
})
export default mongoose.model<ProductDocument>('Products', productSchema)
and the controller for responsible for populating
export const getProduct = async (req: Request, res: Response) => {
try {
const { productId } = await req.body
const productBought = await Products.findOne({ _id: productId }).then(
(product) => {
return product
}
)
console.log(productBought)
const updated = await Users.findOneAndUpdate(
{ _id: req.params.userId },
{ $push: { cart: productBought } },
{ new: true }
)
.populate('cart')
.exec()
return res.json(updated)
} catch (error) {
return res.status(404).json({ message: 'does not work' })
}
}
output from postman
{
"isAdmin": false,
"cart": [
null,
null
],
"_id": "5f894b3c06b4a108f8d9a7ab",
"firstName": "John",
"lastName": "Doe",
"password": "$2b$08$rA0/r8iVBWeSyyerPDpPWO.ztgouQoseX0QFBZ.mlPgb6tELlrhpy",
"email": "john#gmail.com",
"__v": 0
}
your "cart" like[ObjectId,ObjectId], but your "productBought" like[{},{}].
try let cart = [];productBought.forEach((v,k) => {cart[k] = v._id});
and use { $push: { cart: { $each: cart } },

Resources