From 630c31cfc9e059aaffe96edd499f80cb1e9bca67 Mon Sep 17 00:00:00 2001 From: Andreas Knuth Date: Thu, 29 Aug 2024 17:13:24 +0200 Subject: [PATCH] Favorites #19, Social Media Start #53 --- .../src/listings/business-listing.service.ts | 20 +- .../listings/business-listings.controller.ts | 14 +- ...commercial-property-listings.controller.ts | 12 +- .../listings/commercial-property.service.ts | 21 +- bizmatch/package.json | 3 +- bizmatch/src/app/app.component.html | 21 +- bizmatch/src/app/app.config.ts | 9 + .../components/footer/footer.component.html | 2 +- .../components/header/header.component.html | 9 +- .../details-business-listing.component.html | 212 ++++-------------- .../details-business-listing.component.ts | 10 +- bizmatch/src/app/pages/details/details.scss | 19 ++ .../business-listings.component.html | 52 ++++- .../favorites/favorites.component.html | 113 +++++++--- .../favorites/favorites.component.ts | 53 ++++- .../my-listing/my-listing.component.html | 60 +---- bizmatch/src/app/services/listings.service.ts | 8 +- bizmatch/src/index.html | 5 +- bizmatch/src/styles.scss | 1 + 19 files changed, 346 insertions(+), 298 deletions(-) diff --git a/bizmatch-server/src/listings/business-listing.service.ts b/bizmatch-server/src/listings/business-listing.service.ts index 8f01120..ae9fdaa 100644 --- a/bizmatch-server/src/listings/business-listing.service.ts +++ b/bizmatch-server/src/listings/business-listing.service.ts @@ -1,5 +1,5 @@ import { BadRequestException, Inject, Injectable } from '@nestjs/common'; -import { and, count, eq, gte, ilike, inArray, lte, ne, or, SQL, sql } from 'drizzle-orm'; +import { and, arrayContains, count, eq, gte, ilike, inArray, lte, ne, or, SQL, sql } from 'drizzle-orm'; import { NodePgDatabase } from 'drizzle-orm/node-postgres'; import { WINSTON_MODULE_PROVIDER } from 'nest-winston'; import { Logger } from 'winston'; @@ -186,7 +186,14 @@ export class BusinessListingService { return listings.map(l => convertDrizzleBusinessToBusiness(l)); } - + // #### Find Favorites ######################################## + async findFavoriteListings(user: JwtUser): Promise { + const userFavorites = await this.conn + .select() + .from(businesses) + .where(arrayContains(businesses.favoritesForUser, [user.username])); + return userFavorites.map(l => convertDrizzleBusinessToBusiness(l)); + } // #### CREATE ######################################## async createListing(data: BusinessListing): Promise { try { @@ -236,6 +243,15 @@ export class BusinessListingService { async deleteListing(id: string): Promise { await this.conn.delete(businesses).where(eq(businesses.id, id)); } + // #### DELETE Favorite ################################### + async deleteFavorite(id: string, user: JwtUser): Promise { + await this.conn + .update(businesses) + .set({ + favoritesForUser: sql`array_remove(${businesses.favoritesForUser}, ${user.username})`, + }) + .where(sql`${businesses.id} = ${id}`); + } // ############################################################## // States // ############################################################## diff --git a/bizmatch-server/src/listings/business-listings.controller.ts b/bizmatch-server/src/listings/business-listings.controller.ts index 43d2110..7f27b68 100644 --- a/bizmatch-server/src/listings/business-listings.controller.ts +++ b/bizmatch-server/src/listings/business-listings.controller.ts @@ -1,8 +1,9 @@ import { Body, Controller, Delete, Get, Inject, Param, Post, Put, Request, UseGuards } from '@nestjs/common'; import { WINSTON_MODULE_PROVIDER } from 'nest-winston'; -import { BusinessListing } from 'src/models/db.model.js'; import { Logger } from 'winston'; +import { JwtAuthGuard } from '../jwt-auth/jwt-auth.guard.js'; import { OptionalJwtAuthGuard } from '../jwt-auth/optional-jwt-auth.guard.js'; +import { BusinessListing } from '../models/db.model.js'; import { BusinessListingCriteria, JwtUser } from '../models/main.model.js'; import { BusinessListingService } from './business-listing.service.js'; @@ -18,7 +19,11 @@ export class BusinessListingsController { findById(@Request() req, @Param('id') id: string): any { return this.listingsService.findBusinessesById(id, req.user as JwtUser); } - + @UseGuards(JwtAuthGuard) + @Get('favorites/all') + findFavorites(@Request() req): any { + return this.listingsService.findFavoriteListings(req.user as JwtUser); + } @UseGuards(OptionalJwtAuthGuard) @Get('user/:userid') findByUserId(@Request() req, @Param('userid') userid: string): Promise { @@ -54,4 +59,9 @@ export class BusinessListingsController { getStates(): any { return this.listingsService.getStates(); } + @UseGuards(JwtAuthGuard) + @Delete('favorites/:id') + deleteFavorite(@Request() req, @Param('id') id: string) { + this.listingsService.deleteFavorite(id, req.user as JwtUser); + } } diff --git a/bizmatch-server/src/listings/commercial-property-listings.controller.ts b/bizmatch-server/src/listings/commercial-property-listings.controller.ts index a681474..a8ab9ed 100644 --- a/bizmatch-server/src/listings/commercial-property-listings.controller.ts +++ b/bizmatch-server/src/listings/commercial-property-listings.controller.ts @@ -2,6 +2,7 @@ import { Body, Controller, Delete, Get, Inject, Param, Post, Put, Request, UseGu import { WINSTON_MODULE_PROVIDER } from 'nest-winston'; import { Logger } from 'winston'; import { FileService } from '../file/file.service.js'; +import { JwtAuthGuard } from '../jwt-auth/jwt-auth.guard.js'; import { OptionalJwtAuthGuard } from '../jwt-auth/optional-jwt-auth.guard.js'; import { CommercialPropertyListing } from '../models/db.model'; import { CommercialPropertyListingCriteria, JwtUser } from '../models/main.model.js'; @@ -20,7 +21,11 @@ export class CommercialPropertyListingsController { findById(@Request() req, @Param('id') id: string): any { return this.listingsService.findCommercialPropertiesById(id, req.user as JwtUser); } - + @UseGuards(JwtAuthGuard) + @Get('favorites/all') + findFavorites(@Request() req): any { + return this.listingsService.findFavoriteListings(req.user as JwtUser); + } @UseGuards(OptionalJwtAuthGuard) @Get('user/:email') findByEmail(@Request() req, @Param('email') email: string): Promise { @@ -55,4 +60,9 @@ export class CommercialPropertyListingsController { this.listingsService.deleteListing(id); this.fileService.deleteDirectoryIfExists(imagePath); } + @UseGuards(JwtAuthGuard) + @Delete('favorites/:id') + deleteFavorite(@Request() req, @Param('id') id: string) { + this.listingsService.deleteFavorite(id, req.user as JwtUser); + } } diff --git a/bizmatch-server/src/listings/commercial-property.service.ts b/bizmatch-server/src/listings/commercial-property.service.ts index 6872fc0..29f0293 100644 --- a/bizmatch-server/src/listings/commercial-property.service.ts +++ b/bizmatch-server/src/listings/commercial-property.service.ts @@ -1,5 +1,5 @@ import { BadRequestException, Inject, Injectable } from '@nestjs/common'; -import { and, count, eq, gte, ilike, inArray, lte, ne, or, SQL, sql } from 'drizzle-orm'; +import { and, arrayContains, count, eq, gte, ilike, inArray, lte, ne, or, SQL, sql } from 'drizzle-orm'; import { NodePgDatabase } from 'drizzle-orm/node-postgres'; import { WINSTON_MODULE_PROVIDER } from 'nest-winston'; import { Logger } from 'winston'; @@ -10,7 +10,7 @@ import { FileService } from '../file/file.service.js'; import { GeoService } from '../geo/geo.service.js'; import { CommercialPropertyListing, CommercialPropertyListingSchema } from '../models/db.model.js'; import { CommercialPropertyListingCriteria, emailToDirName, JwtUser } from '../models/main.model.js'; -import { convertCommercialToDrizzleCommercial, convertDrizzleCommercialToCommercial, getDistanceQuery } from '../utils.js'; +import { convertCommercialToDrizzleCommercial, convertDrizzleBusinessToBusiness, convertDrizzleCommercialToCommercial, getDistanceQuery } from '../utils.js'; @Injectable() export class CommercialPropertyService { @@ -123,6 +123,14 @@ export class CommercialPropertyService { .where(and(...conditions))) as CommercialPropertyListing[]; return listings.map(l => convertDrizzleCommercialToCommercial(l)) as CommercialPropertyListing[]; } + // #### Find Favorites ######################################## + async findFavoriteListings(user: JwtUser): Promise { + const userFavorites = await this.conn + .select() + .from(commercials) + .where(arrayContains(commercials.favoritesForUser, [user.username])); + return userFavorites.map(l => convertDrizzleBusinessToBusiness(l)); + } // #### Find by imagePath ######################################## async findByImagePath(imagePath: string, serial: string): Promise { const result = await this.conn @@ -202,6 +210,15 @@ export class CommercialPropertyService { async deleteListing(id: string): Promise { await this.conn.delete(commercials).where(eq(commercials.id, id)); } + // #### DELETE Favorite ################################### + async deleteFavorite(id: string, user: JwtUser): Promise { + await this.conn + .update(commercials) + .set({ + favoritesForUser: sql`array_remove(${commercials.favoritesForUser}, ${user.username})`, + }) + .where(sql`${commercials.id} = ${id}`); + } // ############################################################## // States // ############################################################## diff --git a/bizmatch/package.json b/bizmatch/package.json index 7bc9866..3d1d311 100644 --- a/bizmatch/package.json +++ b/bizmatch/package.json @@ -46,6 +46,7 @@ "ngx-image-cropper": "^8.0.0", "ngx-mask": "^18.0.0", "ngx-quill": "^26.0.5", + "ngx-sharebuttons": "^15.0.3", "ngx-stripe": "^18.1.0", "on-change": "^5.0.1", "rxjs": "~7.8.1", @@ -73,4 +74,4 @@ "tailwindcss": "^3.4.4", "typescript": "~5.4.5" } -} \ No newline at end of file +} diff --git a/bizmatch/src/app/app.component.html b/bizmatch/src/app/app.component.html index b9cdd21..52ea5a1 100644 --- a/bizmatch/src/app/app.component.html +++ b/bizmatch/src/app/app.component.html @@ -1,5 +1,5 @@ -
+
@if (actualRoute !=='home'){
} @@ -32,25 +32,6 @@
} - diff --git a/bizmatch/src/app/app.config.ts b/bizmatch/src/app/app.config.ts index fb5c86f..f334fee 100644 --- a/bizmatch/src/app/app.config.ts +++ b/bizmatch/src/app/app.config.ts @@ -5,6 +5,8 @@ import { HTTP_INTERCEPTORS, provideHttpClient, withInterceptorsFromDi } from '@a import { provideAnimations } from '@angular/platform-browser/animations'; import { KeycloakBearerInterceptor, KeycloakService } from 'keycloak-angular'; import { provideQuillConfig } from 'ngx-quill'; +import { provideShareButtonsOptions, SharerMethods, withConfig } from 'ngx-sharebuttons'; +import { shareIcons } from 'ngx-sharebuttons/icons'; import { provideNgxStripe } from 'ngx-stripe'; import { environment } from '../environments/environment'; import { customKeycloakAdapter } from '../keycloak'; @@ -54,6 +56,13 @@ export const appConfig: ApplicationConfig = { provide: 'TIMEOUT_DURATION', useValue: 5000, // Standard-Timeout von 5 Sekunden }, + provideShareButtonsOptions( + shareIcons(), + withConfig({ + debug: true, + sharerMethod: SharerMethods.Anchor, + }), + ), provideRouter( routes, withEnabledBlockingInitialNavigation(), diff --git a/bizmatch/src/app/components/footer/footer.component.html b/bizmatch/src/app/components/footer/footer.component.html index 1319435..381a636 100644 --- a/bizmatch/src/app/components/footer/footer.component.html +++ b/bizmatch/src/app/components/footer/footer.component.html @@ -1,4 +1,4 @@ -