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

Feature/1428 pagination #1492

Merged
merged 20 commits into from
Dec 5, 2024
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
e0ca4d7
Add pagination backend
Nov 18, 2024
ca50fda
Fix types
Nov 18, 2024
baefe60
Fix types
Nov 18, 2024
f82a53d
Предварительная версия пагинации
toprogramm Nov 25, 2024
d192b89
добавлены inject
toprogramm Nov 25, 2024
9d17836
Refactor api request (tmp)
Nov 25, 2024
ff701da
Add city and country pagination
Nov 25, 2024
b480d51
Fix typo
Nov 25, 2024
db526c3
Фиксы стилей и количества ивентов на странице в зависимости от разреш…
toprogramm Nov 26, 2024
71fdd95
Добавление, фиксы иконок и картинок
toprogramm Nov 26, 2024
5ea44ed
Стилизация, и расположение элементов под дизайн
toprogramm Nov 26, 2024
19c7f69
фикс на предупреждения ?? ts
toprogramm Nov 26, 2024
d1d8beb
fixed naming
toprogramm Nov 28, 2024
756aa1c
лишние надписи снизу перенесены в пагинацию
toprogramm Nov 28, 2024
8cfeb20
добавлена и обновлена документация
toprogramm Nov 28, 2024
bfec27f
Fix bug with controls shown on mobile|tablet pages when no events wer…
Dec 3, 2024
73e84f1
Добавлены иконки исправлены стили
toprogramm Dec 3, 2024
5e2ef0e
style changes
toprogramm Dec 4, 2024
35ea00e
небольшие изменения стилей
toprogramm Dec 4, 2024
3dedbe7
Merge branch 'main' into feature/1428-pagination
toprogramm Dec 5, 2024
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
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,6 @@ npm run dev:frontend
```bash
npm run dev:backend
```

# Documentation
- [Перейти к русскоязычной версии документации](documentation/ru-documentation.md)
19 changes: 19 additions & 0 deletions backend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"@jest/globals": "^29.7.0",
"@octokit/rest": "^20.0.1",
"@shelf/jest-mongodb": "^4.2.0",
"@types/mongoose-aggregate-paginate-v2": "^1.0.12",
"axios": "^1.4.0",
"city-timezones": "^1.2.1",
"dotenv": "^16.1.4",
Expand All @@ -33,6 +34,7 @@
"jsonwebtoken": "^9.0.0",
"moment-timezone": "^0.5.43",
"mongoose": "^7.2.3",
"mongoose-aggregate-paginate-v2": "^1.1.2",
"nodemon": "^2.0.22",
"octokit": "^3.1.0",
"public-google-sheets-parser": "^1.3.2",
Expand Down
99 changes: 97 additions & 2 deletions backend/src/controllers/events-state-controller.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { v4 as uuid } from 'uuid';
import { FilterQuery, PipelineStage, SortValues } from 'mongoose';
import { FilterQuery, PipelineStage, SortValues, AggregatePaginateResult } from 'mongoose';
import { EventDbEntity, EventOnPoster, SearchEventPayload } from '@common/types/event';
import { PaginationOptions } from '@common/types/pagination';
import { CommonErrorsEnum, SupportedLanguages } from '../../../common/const';
import { EventModel } from '../models/event.model';
import { EventModel, IEventDocument } from '../models/event.model';
import { imageController } from './image-controller';
import { countriesAndCitiesController } from './countries-and-cities.controller';

Expand Down Expand Up @@ -140,6 +141,100 @@ class EventsStateController {
);
}

async getPaginatedEvents(
lang: SupportedLanguages,
isOnline: boolean,
paginationOptions: PaginationOptions,
query?: SearchEventPayload | undefined
): Promise<AggregatePaginateResult<EventDbEntity>> {
const queryObject: FilterQuery<EventOnPoster> = {
$and: [],
$expr: {
$and: [
{
$gte: [
{
$add: ['$date', { $multiply: [1000, '$durationInSeconds'] }]
},
{
$toDouble: '$$NOW'
}
]
}
]
}
};
const sortObject: string | Record<string, SortValues> | PipelineStage.Sort['$sort'] = {};
if (query?.searchLine) {
queryObject.$text = { $search: query.searchLine };
}
if (query?.country) {
const englishCountryName = await countriesAndCitiesController.getLocalizedCountryName(
query?.country,
SupportedLanguages.ENGLISH
);
const countryQuery = isOnline
? { $and: [{ 'location.country': englishCountryName }, { isOnline }] }
: { 'location.country': englishCountryName };
queryObject.$and?.push(countryQuery);
sortObject.isOnline = 'ascending';
}
if (query?.city) {
const englishCityName = await countriesAndCitiesController.getLocalizedCityName(
query?.city,
SupportedLanguages.ENGLISH
);
const cityQuery = isOnline
? { $or: [{ 'location.city': englishCityName }, { isOnline }] }
: { 'location.city': englishCityName };
queryObject.$and?.push(cityQuery);
sortObject.isOnline = 'ascending';
}
if (query?.startDate) {
const startDateQuery = {
$lte: [
query.startDate,
{ $add: ['$date', { $multiply: [1000, '$durationInSeconds'] }] }
]
};
queryObject.$expr.$and.push(startDateQuery);
}
if (query?.endDate) {
const endDateQuery = {
$gte: [query.endDate, '$date']
};
queryObject.$expr.$and.push(endDateQuery);
}
if (query?.tags && query?.tags.length !== 0) {
queryObject.tags = { $in: query?.tags };
}
if (queryObject.$and?.length === 0) {
delete queryObject.$and;
}
queryObject['meta.moderation.status'] = { $nin: ['declined', 'in-progress'] };

sortObject.date = 'ascending';

const pipeline = [
{
$match: {
...queryObject
}
}
];

const aggregate = EventModel.aggregate<IEventDocument>(pipeline).sort(sortObject);
const futureEvents = await EventModel.aggregatePaginate<EventDbEntity>(
aggregate,
paginationOptions
);
const localizedDocs = await Promise.all(
futureEvents.docs.map(async (event) => this.localizeEventLocation(event, lang))
);
futureEvents.docs = localizedDocs;
return futureEvents;
}

async getEvent(id: string, lang: SupportedLanguages = SupportedLanguages.ENGLISH) {
const event = await EventModel.findOne(
{
Expand Down
17 changes: 6 additions & 11 deletions backend/src/models/event.model.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import mongoose, { Schema, Document } from 'mongoose';
import mongoose, { AggregatePaginateModel, Schema } from 'mongoose';
import mongooseAggregatePaginate from 'mongoose-aggregate-paginate-v2';
import { EventDbEntity, IEventMeta } from '@common/types/event';

export type IEventDocument = EventDbEntity & IEventMeta & Document;
export type IEventDocument = EventDbEntity & IEventMeta;

const schema = new Schema<IEventDocument>(
const schema = new Schema(
{
id: {
type: String,
Expand Down Expand Up @@ -105,12 +106,6 @@ const schema = new Schema<IEventDocument>(
}
);

schema.index({
title: 'text',
'description.ru': 'text',
'description.en': 'text',
'location.city': 'text',
'location.country': 'text'
});
schema.plugin(mongooseAggregatePaginate);

export const EventModel = mongoose.model<IEventDocument>('Event', schema);
export const EventModel: AggregatePaginateModel<IEventDocument> = mongoose.model('Event', schema);
53 changes: 53 additions & 0 deletions backend/src/rest/v1/events/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@ import {
IAddEventHandler,
IDeleteEventHandler,
IFindEventHandler,
IFindEventPaginatedHandler,
IFindEventsCityHandler,
IFindEventsCityPaginatedHandler,
IFindEventsCountryHandler,
IFindEventsCountryPaginatedHandler,
IGetEventHandler,
IGetEventsHandler,
IGetMyEventsHandler,
Expand Down Expand Up @@ -82,6 +85,56 @@ export const updateEvent: IUpdateEventHandler = async (request) => {
return undefined;
};

export const findEventsPaginated: IFindEventPaginatedHandler = async (request) => {
const lang =
(request.headers['accept-language'] as SupportedLanguages) || SupportedLanguages.ENGLISH;
return eventsStateController.getPaginatedEvents(
lang,
true,
request.body.options,
request.body.query
);
};

export const findEventsByCityPaginated: IFindEventsCityPaginatedHandler = async (request) => {
const city = transformFromQuery(request.params.cityName);
const queryObj: SearchEventPayload = {
city,
...request.body.query
};
const lang =
(request.headers['accept-language'] as SupportedLanguages) || SupportedLanguages.ENGLISH;
const events = await eventsStateController.getPaginatedEvents(
lang,
false,
request.body.options,
queryObj
);
// eslint-disable-next-line @typescript-eslint/no-throw-literal
if (events.length === 0) throw { statusCode: 404, message: CommonErrorsEnum.NO_EVENTS_IN_CITY };
return events;
};

export const findEventsByCountryPaginated: IFindEventsCountryPaginatedHandler = async (request) => {
const country = transformFromQuery(request.params.countryName);
const queryObj: SearchEventPayload = {
country,
...request.body.query
};
const lang =
(request.headers['accept-language'] as SupportedLanguages) || SupportedLanguages.ENGLISH;
const events = await eventsStateController.getPaginatedEvents(
lang,
false,
request.body.options,
queryObj
);
if (events.length === 0)
// eslint-disable-next-line @typescript-eslint/no-throw-literal
throw { statusCode: 404, message: CommonErrorsEnum.NO_EVENTS_IN_COUNTRY };
return events;
};

export const findEvents: IFindEventHandler = async (request) => {
const lang =
(request.headers['accept-language'] as SupportedLanguages) || SupportedLanguages.ENGLISH;
Expand Down
23 changes: 23 additions & 0 deletions backend/src/rest/v1/events/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ import {
deleteEvent,
findEvents,
findEventsByCity,
findEventsByCityPaginated,
findEventsByCountry,
findEventsByCountryPaginated,
findEventsPaginated,
getEvent,
getEvents,
getMyEvents,
Expand All @@ -13,8 +16,11 @@ import {
import {
IAddEventRoute,
IDeleteEventRoute,
IFindEventPaginatedRoute,
IFindEventRoute,
IFindEventsCityPaginatedRoute,
IFindEventsCityRoute,
IFindEventsCountryPaginatedRoute,
IFindEventsCountryRoute,
IGetEventRoute,
IGetEventsRoute,
Expand All @@ -24,8 +30,11 @@ import {
import {
addEventSchema,
deleteEventSchema,
findEventsByCityPaginatedSchema,
findEventsByCitySchema,
findEventsByCountryPaginatedSchema,
findEventsByCountrySchema,
findEventsPaginatedSchema,
findEventsSchema,
getEventSchema,
getEventsSchema,
Expand Down Expand Up @@ -98,4 +107,18 @@ export const eventsApi = async (fastify: FastifyInstance) => {
schema: findEventsByCountrySchema,
handler: findEventsByCountry
});
fastify.post<IFindEventPaginatedRoute>('/find/pagination', {
schema: findEventsPaginatedSchema,
handler: findEventsPaginated
});

fastify.post<IFindEventsCityPaginatedRoute>('/city/:cityName/pagination', {
schema: findEventsByCityPaginatedSchema,
handler: findEventsByCityPaginated
});

fastify.post<IFindEventsCountryPaginatedRoute>('/country/:countryName/pagination', {
schema: findEventsByCountryPaginatedSchema,
handler: findEventsByCountryPaginated
});
};
Loading
Loading