Skip to content

Commit

Permalink
In progress: Add Database seeding for auth, todo and ecommerce apis
Browse files Browse the repository at this point in the history
- Add logic to generate random data to populate the database in one go
- Add logic to store randomly generated credentials in local json file so developer can use those
  • Loading branch information
wajeshubham committed Jun 29, 2023
1 parent f214aa7 commit fee85b0
Show file tree
Hide file tree
Showing 15 changed files with 378 additions and 20 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,10 @@ logs

# ignore the image files uploaded in the public/images folder
/public/images/*
/public/temp/*

# except .gitkeep to push empty folder to the git repo
!/public/images/.gitkeep
!/public/temp/.gitkeep

NOTES.md
1 change: 1 addition & 0 deletions nodemon.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{ "ignore": ["seed-credentials.json"] }
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
},
"homepage": "https://github.com/hiteshchoudhary/apihub#readme",
"dependencies": {
"@faker-js/faker": "^8.0.2",
"bcrypt": "^5.1.0",
"cookie-parser": "^1.4.6",
"cors": "^2.8.5",
Expand Down
Empty file added public/temp/.gitkeep
Empty file.
9 changes: 9 additions & 0 deletions src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,9 @@ import redirectRouter from "./routes/kitchen-sink/redirect.routes.js";
import requestinspectionRouter from "./routes/kitchen-sink/requestinspection.routes.js";
import responseinspectionRouter from "./routes/kitchen-sink/responseinspection.routes.js";
import statuscodeRouter from "./routes/kitchen-sink/statuscode.routes.js";
import { getGeneratedCredentials, seedUsers } from "./seeds/user.seeds.js";
import { seedTodos } from "./seeds/todo.seeds.js";
import { seedEcommerce } from "./seeds/ecommerce.seeds.js";

// * API DOCS
app.use("/api/v1/docs", swaggerUi.serve, swaggerUi.setup(swaggerDocument));
Expand Down Expand Up @@ -153,6 +156,12 @@ app.use("/api/v1/kitchen-sink/cookies", cookieRouter);
app.use("/api/v1/kitchen-sink/redirect", redirectRouter);
app.use("/api/v1/kitchen-sink/image", imageRouter);

// * Seeding
app.get("/api/v1/seed/generated-credentials", getGeneratedCredentials);
app.post("/api/v1/seed/users", seedUsers(false));
app.post("/api/v1/seed/todos", seedTodos);
app.post("/api/v1/seed/ecommerce", seedUsers(true), seedEcommerce);

app.delete("/api/v1/reset-db", async (req, res) => {
if (dbInstance) {
// Drop the whole DB
Expand Down
4 changes: 2 additions & 2 deletions src/controllers/apps/auth/user.controllers.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { asyncHandler } from "../../../utils/asyncHandler.js";
import {
getLocalPath,
getStaticFilePath,
removeImageFile,
removeLocalFile,
} from "../../../utils/helpers.js";
import {
emailVerificationMailgenContent,
Expand Down Expand Up @@ -490,7 +490,7 @@ const updateUserAvatar = asyncHandler(async (req, res) => {
);

// remove the old avatar
removeImageFile(user.avatar.localPath);
removeLocalFile(user.avatar.localPath);

return res
.status(200)
Expand Down
12 changes: 6 additions & 6 deletions src/controllers/apps/ecommerce/product.controllers.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
getLocalPath,
getMongoosePaginationOptions,
getStaticFilePath,
removeImageFile,
removeLocalFile,
} from "../../../utils/helpers.js";
import { MAXIMUM_SUB_IMAGE_COUNT } from "../../../constants.js";
import { Category } from "../../../models/apps/ecommerce/category.models.js";
Expand Down Expand Up @@ -131,10 +131,10 @@ const updateProduct = asyncHandler(async (req, res) => {
// Before throwing an error we need to do some cleanup

// remove the newly uploaded sub images by multer as there is not updation happening
subImages?.map((img) => removeImageFile(img.localPath));
subImages?.map((img) => removeLocalFile(img.localPath));
if (product.mainImage.url !== mainImage.url) {
// If use has uploaded new main image remove the newly uploaded main image as there is no updation happening
removeImageFile(mainImage.localPath);
removeLocalFile(mainImage.localPath);
}
throw new ApiError(
400,
Expand Down Expand Up @@ -170,7 +170,7 @@ const updateProduct = asyncHandler(async (req, res) => {
// Once the product is updated. Do some cleanup
if (product.mainImage.url !== mainImage.url) {
// If user is uploading new main image remove the previous one because we don't need that anymore
removeImageFile(product.mainImage.localPath);
removeLocalFile(product.mainImage.localPath);
}

return res
Expand Down Expand Up @@ -263,7 +263,7 @@ const removeProductSubImage = asyncHandler(async (req, res) => {

if (removedSubImage) {
// remove the file from file system as well
removeImageFile(removedSubImage.localPath);
removeLocalFile(removedSubImage.localPath);
}

return res
Expand All @@ -288,7 +288,7 @@ const deleteProduct = asyncHandler(async (req, res) => {

productImages.map((image) => {
// remove images associated with the product that is being deleted
removeImageFile(image.localPath);
removeLocalFile(image.localPath);
});

return res
Expand Down
8 changes: 4 additions & 4 deletions src/controllers/apps/social-media/post.controllers.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
getLocalPath,
getMongoosePaginationOptions,
getStaticFilePath,
removeImageFile,
removeLocalFile,
} from "../../../utils/helpers.js";
import { ApiError } from "../../../utils/ApiError.js";
import { MAXIMUM_SOCIAL_POST_IMAGE_COUNT } from "../../../constants.js";
Expand Down Expand Up @@ -215,7 +215,7 @@ const updatePost = asyncHandler(async (req, res) => {
// Before throwing an error we need to do some cleanup

// remove the newly uploaded images by multer as there is not updation happening
images?.map((img) => removeImageFile(img.localPath));
images?.map((img) => removeLocalFile(img.localPath));

throw new ApiError(
400,
Expand Down Expand Up @@ -290,7 +290,7 @@ const removePostImage = asyncHandler(async (req, res) => {

if (removedImage) {
// remove the file from file system as well
removeImageFile(removedImage.localPath);
removeLocalFile(removedImage.localPath);
}

const aggregatedPost = await SocialPost.aggregate([
Expand Down Expand Up @@ -444,7 +444,7 @@ const deletePost = asyncHandler(async (req, res) => {

postImages.map((image) => {
// remove images associated with the post that is being deleted
removeImageFile(image.localPath);
removeLocalFile(image.localPath);
});

return res
Expand Down
4 changes: 2 additions & 2 deletions src/controllers/apps/social-media/profile.controllers.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { asyncHandler } from "../../../utils/asyncHandler.js";
import {
getLocalPath,
getStaticFilePath,
removeImageFile,
removeLocalFile,
} from "../../../utils/helpers.js";

/**
Expand Down Expand Up @@ -184,7 +184,7 @@ const updateCoverImage = asyncHandler(async (req, res) => {
);

// remove the old cover image
removeImageFile(profile.coverImage.localPath);
removeLocalFile(profile.coverImage.localPath);

updatedProfile = await getUserSocialProfile(req.user._id, req);

Expand Down
212 changes: 212 additions & 0 deletions src/seeds/ecommerce.seeds.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
import { faker } from "@faker-js/faker";
import { ApiResponse } from "../utils/ApiResponse.js";
import { asyncHandler } from "../utils/asyncHandler.js";
import { Category } from "../models/apps/ecommerce/category.models.js";
import { User } from "../models/apps/auth/user.models.js";
import {
AvailableOrderStatuses,
AvailablePaymentProviders,
UserRolesEnum,
} from "../constants.js";
import { Address } from "../models/apps/ecommerce/address.models.js";
import { getRandomNumber } from "../utils/helpers.js";
import { Coupon } from "../models/apps/ecommerce/coupon.models.js";
import { ApiError } from "../utils/ApiError.js";
import { Product } from "../models/apps/ecommerce/product.models.js";
import { EcomOrder } from "../models/apps/ecommerce/order.models.js";

const categories = new Array(20).fill("_").map(() => ({
name: faker.commerce.productAdjective().toLowerCase(),
}));

const addresses = new Array(100).fill("_").map(() => ({
addressLine1: faker.location.streetAddress(),
addressLine2: faker.location.street(),
city: faker.location.city(),
country: faker.location.country(),
pincode: faker.location.zipCode("######"),
state: faker.location.state(),
}));

const coupons = new Array(15).fill("_").map(() => {
const discountValue = faker.number.int({
max: 1000,
min: 100,
});

return {
name: faker.lorem.word({
length: {
max: 15,
min: 8,
},
}),
couponCode:
faker.lorem.word({
length: {
max: 8,
min: 5,
},
}) + `${discountValue}`,
discountValue: discountValue,
isActive: faker.datatype.boolean(),
minimumCartValue: discountValue + 300,
startDate: faker.date.anytime(),
expiryDate: faker.date.future({
years: 3,
}),
};
});

const products = new Array(50).fill("_").map(() => {
return {
name: faker.commerce.productName(),
description: faker.commerce.productDescription(),
mainImage: {
url: faker.image.urlLoremFlickr({
category: "product",
}),
localPath: "",
},
price: +faker.commerce.price({ dec: 0, min: 200, max: 2000 }),
stock: +faker.commerce.price({ dec: 0, min: 10, max: 200 }),
subImages: new Array(4).fill("_").map(() => ({
url: faker.image.urlLoremFlickr({
category: "product",
}),
localPath: "",
})),
};
});

const orders = new Array(15).fill("_").map(() => {
const paymentProvider =
AvailablePaymentProviders[
getRandomNumber(AvailablePaymentProviders.length)
];
return {
status:
AvailableOrderStatuses[getRandomNumber(AvailableOrderStatuses.length)],
paymentProvider: paymentProvider === "UNKNOWN" ? "PAYPAL" : paymentProvider,
paymentId: faker.string.alphanumeric({
casing: "mixed",
length: 24,
}),
isPaymentDone: true,
};
});

const seedCategories = async (owner) => {
await Category.deleteMany({});
await Category.insertMany(
categories.map((cat) => ({ ...cat, owner: owner }))
);
};

const seedAddresses = async () => {
const users = await User.find();
await Address.deleteMany({});
await Address.insertMany(
addresses.map((add) => ({
...add,
owner: users[getRandomNumber(users.length)],
}))
);
};

const seedCoupons = async (owner) => {
await Coupon.deleteMany({});
await Coupon.insertMany(
coupons.map((coupon) => ({ ...coupon, owner: owner }))
);
};

const seedProducts = async () => {
const users = await User.find();
const categories = await Category.find();

await Product.deleteMany({});
await Product.insertMany(
products.map((product) => ({
...product,
owner: users[getRandomNumber(users.length)],
category: categories[getRandomNumber(categories.length)],
}))
);
};

const seedOrders = async () => {
const customers = await User.find();
const coupons = await Coupon.find();
const products = await Product.find();
const addresses = await Address.find();

const orderItems = products
.slice(0, getRandomNumber(products.length - 10))
.map((prod) => {
const orderItem = {
productId: prod._id,
quantity: +faker.commerce.price({ dec: 0, min: 1, max: 5 }),
};
const orderPrice = prod.price * orderItem.quantity;
let coupon = coupons[getRandomNumber(coupons.length + 20)] ?? null;
let discountedOrderPrice = orderPrice;
if (coupon && coupon.minimumCartValue <= orderPrice) {
discountedOrderPrice -= coupon.discountValue;
} else {
coupon = null;
}
return {
items: [orderItem],
orderPrice,
discountedOrderPrice,
coupon,
};
});

await EcomOrder.deleteMany({});
await EcomOrder.insertMany(
orders.map((order) => {
const customer = customers[getRandomNumber(customers.length)];
const orderData = orderItems[getRandomNumber(orderItems.length)];
return {
...order,
customer,
address:
addresses.find(
(add) => add.owner?.toString() === customer?._id.toString()
)?._id ?? addresses[getRandomNumber(addresses.length)],
items: orderData.items,
coupon: orderData.coupon,
orderPrice: orderData.orderPrice,
discountedOrderPrice: orderData.discountedOrderPrice,
};
})
);
};

const seedEcommerce = asyncHandler(async (req, res) => {
const owner = await User.findOne({
role: UserRolesEnum.ADMIN,
});

if (!owner) {
throw new ApiError(
500,
"Something went wrong while seeding the data. Please try again once"
);
}

await seedCategories(owner._id);
await seedAddresses();
await seedCoupons(owner._id);
await seedProducts();
await seedOrders();
return res
.status(201)
.json(
new ApiResponse(201, {}, "Database populated for ecommerce successfully")
);
});

export { seedEcommerce };
Empty file added src/seeds/social-media.seeds.js
Empty file.
Loading

0 comments on commit fee85b0

Please sign in to comment.