diff --git a/README.md b/README.md index 2d8bd025..ff9cc4b6 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ $ docker-compose up -d # Use the precreated database dump from the server $ mkdir db $ curl https://plezanje.net/storage/db.sql --output ./db/db.sql -$ docker exec -it api_postgres_1 bash -c "psql -U plezanjenet -f /etc/db/db.sql" +$ docker exec -it api-postgres-1 bash -c "psql -U plezanjenet -f /etc/db/db.sql" ``` Copy the `.env.example` file to `.env` and set configuration parameters. diff --git a/package.json b/package.json index 63f97c40..4e24fe4c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "plezanje-graphql", - "version": "0.0.1", + "version": "0.0.2", "description": "", "author": "", "private": true, diff --git a/src/activities/resolvers/activity-routes.resolver.ts b/src/activities/resolvers/activity-routes.resolver.ts index 645167c5..7203c65a 100644 --- a/src/activities/resolvers/activity-routes.resolver.ts +++ b/src/activities/resolvers/activity-routes.resolver.ts @@ -34,6 +34,7 @@ import { Activity } from '../entities/activity.entity'; import { ActivityLoader } from '../loaders/activity.loader'; import { ActivityRoutesService } from '../services/activity-routes.service'; import { PaginatedActivityRoutes } from '../utils/paginated-activity-routes.class'; +import { StatsActivities } from '../utils/stats-activities.class'; import { GraphQLResolveInfo } from 'graphql'; import { CacheScope } from 'apollo-server-types'; import { CreateActivityRouteInput } from '../dtos/create-activity-route.input'; @@ -115,6 +116,22 @@ export class ActivityRoutesResolver { return this.activityRoutesService.paginate(input, currentUser); } + @UseGuards(UserAuthGuard) + @Query(() => [StatsActivities]) + myActivityStatistics( + @CurrentUser() currentUser: User, + @Args('input', { nullable: true }) input: FindActivityRoutesInput = {}, + @Info() info: GraphQLResolveInfo, + ) { + info.cacheControl.setCacheHint({ scope: CacheScope.Private }); + + // TODO: currentUser should serve as authorization filter (what is allowed to be returned) + // userId in input should be renamed to forUserId, and be used as a result filter (what is the client asking for) + input.userId = currentUser.id; + return this.activityRoutesService.getStats(input, currentUser); + } + + @UseGuards(UserAuthGuard) @Query(() => [ActivityRoute]) myCragSummary( diff --git a/src/activities/services/activity-routes.service.ts b/src/activities/services/activity-routes.service.ts index 3e30d3e9..9cdac0ae 100644 --- a/src/activities/services/activity-routes.service.ts +++ b/src/activities/services/activity-routes.service.ts @@ -44,6 +44,7 @@ import { calculateScore, recalculateActivityRoutesScores, } from '../../crags/utils/calculate-scores'; +import { StatsActivities } from '../utils/stats-activities.class'; @Injectable() export class ActivityRoutesService { @@ -488,7 +489,6 @@ export class ActivityRoutesService { currentUser: User = null, ): Promise { const query = this.buildQuery(params, currentUser); - const itemCount = await query.getCount(); const pagination = new PaginationMeta( @@ -507,6 +507,49 @@ export class ActivityRoutesService { }); } + async getStats( + params: FindActivityRoutesInput = {}, + currentUser: User = null, + ): Promise { + + const builder = this.activityRoutesRepository + .createQueryBuilder('ar') + .select('EXTRACT(YEAR FROM ar.date)', 'year') + .addSelect('r.difficulty', 'difficulty') + .addSelect('ar.ascent_type', 'ascent_type') + .addSelect('count(ar.id)', 'nr_ascents') + .addSelect('count(r.id)', 'nr_routes') + .addSelect('count(p.id)', 'nr_pitches') + .innerJoin('route', 'r', 'r.id = ar.route_id') + .leftJoin('pitch', 'p', 'p.id = ar.pitch_id') + .where('ar.user_id = :userId', { + userId: currentUser.id, + }) + .andWhere('ar.ascent_type IN (:...ascentType)', { + ascentType: ['onsight', 'redpoint', 'flash'], + }) + .andWhere( + "(r.publish_status IN ('published', 'in_review') OR (r.publish_status = 'draft' AND ar.user_id = :userId))", + { userId: currentUser.id }, + ) + .groupBy("r.difficulty").addGroupBy("EXTRACT(YEAR FROM ar.date)").addGroupBy("ar.ascent_type") + .orderBy('r.difficulty', 'ASC') + .addOrderBy('year', 'ASC'); + + setBuilderCache(builder, 'getRawAndEntities'); + + const raw = await builder.getRawMany() + const myStats = raw.map((element, index) => { + return { + year: element.year, + difficulty: element.difficulty, + ascent_type: element.ascent_type, + nr_routes: element.nr_routes, + }; + }); + return myStats; + } + async find( params: FindActivityRoutesInput = {}, currentUser: User = null, diff --git a/src/activities/utils/stats-activities.class.ts b/src/activities/utils/stats-activities.class.ts new file mode 100644 index 00000000..bf05128d --- /dev/null +++ b/src/activities/utils/stats-activities.class.ts @@ -0,0 +1,17 @@ +import { Field, Float, Int, ObjectType } from '@nestjs/graphql'; + +@ObjectType() +export class StatsActivities { + @Field(() => Int) + year: number; + + @Field(() => Float) + difficulty: number; + + @Field(() => String) + ascent_type: string; + + @Field(() => Int) + nr_routes: number; + +}