diff --git a/packages/server/folder/folder.schema.js b/packages/server/folder/folder.schema.js index 8a311f1b..729a9fdb 100644 --- a/packages/server/folder/folder.schema.js +++ b/packages/server/folder/folder.schema.js @@ -47,4 +47,6 @@ FolderSchema.virtual('childFolders', { justOne: false, }); +FolderSchema.index({ _parentFolder: 1 }); + module.exports = FolderSchema; diff --git a/packages/server/group/group.controller.js b/packages/server/group/group.controller.js index b347c619..06111ad9 100644 --- a/packages/server/group/group.controller.js +++ b/packages/server/group/group.controller.js @@ -238,12 +238,36 @@ async function readProfiles(req, res) { async function readMailings(req, res) { const { groupId } = req.params; - const [group, mailings] = await Promise.all([ - Groups.findById(groupId).select('_id'), - Mailings.findForApi({ _company: groupId }), + const { page: pageParam = 1, limit: limitParam = 10 } = req.query; + const page = parseInt(pageParam); + let limit = parseInt(limitParam); + + let skip = (page - 1) * limit; // Calculate the number of items to skip + if (limit === -1) { + limit = 0; + skip = 0; + } + const group = await Groups.findById(groupId).select('_id'); + if (!group) { + throw new NotFound(); + } + + // Retrieve mailings excluding the 'previewHtml' and 'data' fields and their total count + const [mailings, totalItems] = await Promise.all([ + Mailings.find({ _company: groupId }) + .select('-previewHtml -data') // Exclude the 'previewHtml' and data field + // in case limit = -1, we want to retrieve all mailings + .skip(skip) + .limit(limit), + Mailings.countDocuments({ _company: groupId }), // Count all mailings for this group ]); - if (!group) throw new NotFound(); - res.json({ items: mailings }); + + res.json({ + items: mailings, + totalItems, + currentPage: page, + totalPages: Math.ceil(totalItems / limit), // Calculate the total number of pages + }); } /** diff --git a/packages/server/mailing/mailing.schema.js b/packages/server/mailing/mailing.schema.js index 869fc06d..7185633e 100644 --- a/packages/server/mailing/mailing.schema.js +++ b/packages/server/mailing/mailing.schema.js @@ -165,6 +165,9 @@ MailingSchema.index({ tags: -1 }); MailingSchema.index({ _company: 1, tags: 1 }); MailingSchema.index({ _company: 1, _parentFolder: 1, updatedAt: -1 }); MailingSchema.index({ _company: 1, _workspace: 1, updatedAt: -1 }); +MailingSchema.index({ _user: 1 }); +MailingSchema.index({ _parentFolder: 1 }); + MailingSchema.statics.findForApi = async function findForApi(query = {}) { return this.find(query, { previewHtml: 0, data: 0 }); }; @@ -253,7 +256,9 @@ MailingSchema.statics.findForApiWithPagination = async function findForApiWithPa author: 1, userId: '$_user', tags: 1, - previewHtml: '$previewHtml', + hasHtmlPreview: { + $cond: { if: { $gt: ['$previewHtml', null] }, then: true, else: false }, + }, _workspace: 1, espIds: 1, updatedAt: 1, @@ -266,8 +271,7 @@ MailingSchema.statics.findForApiWithPagination = async function findForApiWithPa const { docs, ...restPaginationProperties } = result; const convertedResultMailingDocs = docs?.map( - ({ previewHtml, wireframe, author, ...doc }) => ({ - hasHtmlPreview: !!previewHtml, + ({ wireframe, author, ...doc }) => ({ templateName: wireframe, userName: author, ...doc, diff --git a/packages/server/template/template.schema.js b/packages/server/template/template.schema.js index c5814ae0..99a98012 100644 --- a/packages/server/template/template.schema.js +++ b/packages/server/template/template.schema.js @@ -97,58 +97,32 @@ TemplateSchema.plugin(mongooseHidden, { // }) TemplateSchema.virtual('hasMarkup').get(function () { - return this.markup != null; + return Boolean(this.markup); }); TemplateSchema.index({ name: 1 }); TemplateSchema.statics.findForApi = async function findForApi(query = {}) { - const templates = await this.find(query) - // we need to keep markup in order for the virtual `hasMarkup` to have the right result - // we also need all assets + const templates = await this.find(query, { + id: '$_id', + name: 1, + description: 1, + createdAt: 1, + updatedAt: 1, + _company: 1, + assets: 1, + hasMarkup: { + $cond: { if: { $gt: ['$markup', null] }, then: true, else: false }, + }, + }) .populate({ path: '_company', select: 'id name' }) - .sort({ name: 1 }); - // change some fields - // • we don't want the markup to be send => remove - // • we don't want all assets => remove - // • BUT we still want the cover image => add - return templates.map((template) => { - // pick is more performant than omit - const templateRes = _.pick(template.toJSON(), [ - 'id', - 'name', - 'description', - 'createdAt', - 'updatedAt', - 'hasMarkup', - 'group', - ]); - templateRes.coverImage = template.assets['_full.png']; - return templateRes; - }); -}; + .sort({ name: 1 }) + .lean(); -// TemplateSchema.virtual('url').get(function() { -// let userId = this._user && this._user._id ? this._user._id : this._user -// let userUrl = this._user ? `/users/${userId}` : '/users' -// let companyId = -// this._company && this._company._id ? this._company._id : this._company -// let companyUrl = this._company ? `/companies/${companyId}` : '/companies' -// // read should be `/companies/${this._company}/wireframes/${this._id}` -// return { -// read: `/users/${this._user}/wireframe/${this._id}`, -// show: `/wireframes/${this._id}`, -// backTo: this._company ? companyUrl : userUrl, -// user: userUrl, -// company: companyUrl, -// delete: `/wireframes/${this._id}/delete`, -// removeImages: `/wireframes/${this._id}/remove-images`, -// markup: `/wireframes/${this._id}/markup`, -// preview: `/wireframes/${this._id}/preview`, -// generatePreviews: `/wireframes/${this._id}/generate-previews`, -// imgCover: this.assets['_full.png'] -// ? `/img/${this.assets['_full.png']}` -// : false, -// } -// }) + const finalTemplates = templates.map(({ assets, ...template }) => ({ + ...template, + coverImage: JSON.parse(assets)?.['_full.png'] || null, + })); + return finalTemplates; +}; module.exports = TemplateSchema; diff --git a/packages/server/user/user.controller.js b/packages/server/user/user.controller.js index bd05985c..2b8562df 100644 --- a/packages/server/user/user.controller.js +++ b/packages/server/user/user.controller.js @@ -140,20 +140,39 @@ async function read(req, res) { * @apiGroup Users * * @apiParam {string} userId + * @apiParam {number} [page=1] + * @apiParam {number} [limit=10] * * @apiUse mailings * @apiSuccess {mailings[]} items */ - async function readMailings(req, res) { const { userId } = req.params; - const [user, mailings] = await Promise.all([ - Users.findById(userId).select({ _id: 1 }), - Mailings.findForApi({ _user: userId }), + const { page = 1, limit = 10 } = req.query; + const parsedPage = parseInt(page, 10); + const parsedLimit = parseInt(limit, 10); + const offset = (parsedPage - 1) * parsedLimit; + + const user = await Users.findById(userId).select('_id'); + if (!user) { + throw new createError.NotFound(); // Ensure this error is properly handled by your error middleware + } + + // Retrieve mailings and their total count + const [mailings, totalItems] = await Promise.all([ + Mailings.find({ _user: userId }) + .select('-previewHtml -data') + .skip(offset) + .limit(parsedLimit), + Mailings.countDocuments({ _user: userId }), // Count all mailings for this user ]); - if (!user) throw new createError.NotFound(); - res.json({ items: mailings }); + res.json({ + items: mailings, + totalItems, + currentPage: parsedPage, + totalPages: Math.ceil(totalItems / parsedLimit), // Calculate the total number of pages + }); } /** diff --git a/packages/ui/components/group/mailings-tab.vue b/packages/ui/components/group/mailings-tab.vue index e3756126..934aa369 100644 --- a/packages/ui/components/group/mailings-tab.vue +++ b/packages/ui/components/group/mailings-tab.vue @@ -6,24 +6,59 @@ export default { name: 'BsGroupMailingsTab', components: { BsMailingsAdminTable }, data() { - return { mailings: [], loading: false }; + return { + mailings: [], + loading: false, + pagination: { + page: 1, + itemsPerPage: 10, + itemsLength: 0, + pageCount: 0, + pageStart: 0, + pageStop: 0, + }, + }; }, - async mounted() { - const { - $axios, - $route: { params }, - } = this; - try { + watch: { + 'pagination.page': 'fetchMailings', + 'pagination.itemsPerPage': 'fetchMailings', + }, + mounted() { + this.fetchMailings(); + }, + methods: { + async fetchMailings() { + const { + $axios, + $route: { params }, + pagination, + } = this; this.loading = true; - const mailingsResponse = await $axios.$get( - apiRoutes.groupsItemMailings(params) - ); - this.mailings = mailingsResponse.items; - } catch (error) { - console.log(error); - } finally { - this.loading = false; - } + try { + const response = await $axios.$get( + apiRoutes.groupsItemMailings(params), + { + params: { page: pagination.page, limit: pagination.itemsPerPage }, + } + ); + this.mailings = response.items; + this.pagination.itemsLength = response.totalItems; + this.pagination.pageCount = response.totalPages; + // Calculate the range of items displayed on the current page + this.pagination.pageStart = + (this.pagination.page - 1) * this.pagination.itemsPerPage; + this.pagination.pageStop = + this.pagination.pageStart + this.pagination.itemsPerPage; + } catch (error) { + console.error('Error fetching mailings:', error); + } finally { + this.loading = false; + } + }, + handleItemsPerPageChange(itemsPerPage) { + this.pagination.page = 1; + this.pagination.itemsPerPage = itemsPerPage; + }, }, }; @@ -31,7 +66,32 @@ export default { diff --git a/packages/ui/components/mailings/admin-table.vue b/packages/ui/components/mailings/admin-table.vue index 130336fd..93e00b37 100644 --- a/packages/ui/components/mailings/admin-table.vue +++ b/packages/ui/components/mailings/admin-table.vue @@ -5,6 +5,7 @@ export default { mailings: { type: Array, default: () => [] }, hiddenCols: { type: Array, default: () => [] }, loading: { type: Boolean, default: false }, + pagination: { type: Object, default: () => ({}) }, }, computed: { tablesHeaders() { @@ -21,11 +22,22 @@ export default { ].filter((column) => !this.hiddenCols.includes(column.value)); }, }, + methods: { + handleItemsPerPageChange(itemsPerPage) { + this.$emit('update:items-per-page', itemsPerPage); + }, + }, };