diff --git a/backend/package-lock.json b/backend/package-lock.json index f487c14820109de20d308db79ff8cd51cc21472f..0a9fd211c3caf4497d8a0e3c4918c7932dbf4f4c 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -19,6 +19,7 @@ "@nestjs/serve-static": "^4.0.2", "@nestjs/swagger": "^7.3.0", "@nestjs/typeorm": "^10.0.2", + "axios": "^1.7.7", "bcrypt": "^5.1.1", "class-transformer": "^0.5.1", "class-validator": "^0.14.1", @@ -31,7 +32,8 @@ "qrcode": "^1.5.4", "reflect-metadata": "^0.2.0", "rxjs": "^7.8.1", - "typeorm": "^0.3.20" + "typeorm": "^0.3.20", + "uuid": "^11.0.2" }, "devDependencies": { "@nestjs/cli": "^10.0.0", @@ -2426,6 +2428,18 @@ "typeorm": "^0.3.0" } }, + "node_modules/@nestjs/typeorm/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -3741,6 +3755,16 @@ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, + "node_modules/axios": { + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", + "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/b4a": { "version": "1.6.6", "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.6.tgz", @@ -6300,6 +6324,25 @@ "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", "dev": true }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/foreground-child": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", @@ -6369,7 +6412,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dev": true, "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -6546,6 +6588,18 @@ "node": ">= 14" } }, + "node_modules/gaxios/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/gcp-metadata": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.0.tgz", @@ -6792,6 +6846,18 @@ "node": ">=14" } }, + "node_modules/google-gax/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/gopd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", @@ -10447,6 +10513,19 @@ "node": ">=14" } }, + "node_modules/preview-email/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "optional": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -12271,6 +12350,18 @@ "node": ">=14" } }, + "node_modules/teeny-request/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/terser": { "version": "5.29.1", "resolved": "https://registry.npmjs.org/terser/-/terser-5.29.1.tgz", @@ -12885,6 +12976,18 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/typeorm/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/typescript": { "version": "5.4.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.2.tgz", @@ -13021,15 +13124,15 @@ } }, "node_modules/uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.0.2.tgz", + "integrity": "sha512-14FfcOJmqdjbBPdDjFQyk/SdT4NySW4eM0zcG+HqbHP5jzuH56xO3J1DGhgs/cEMCfwYi3HQI1gnTO62iaG+tQ==", "funding": [ "https://github.com/sponsors/broofa", "https://github.com/sponsors/ctavan" ], "bin": { - "uuid": "dist/bin/uuid" + "uuid": "dist/esm/bin/uuid" } }, "node_modules/v8-compile-cache-lib": { diff --git a/backend/package.json b/backend/package.json index 3c5b8991342b5193e029565921feb078bf43243a..7840e2ac8f2dac0dfc454431a3c19bbeeb6e0c52 100644 --- a/backend/package.json +++ b/backend/package.json @@ -30,6 +30,7 @@ "@nestjs/serve-static": "^4.0.2", "@nestjs/swagger": "^7.3.0", "@nestjs/typeorm": "^10.0.2", + "axios": "^1.7.7", "bcrypt": "^5.1.1", "class-transformer": "^0.5.1", "class-validator": "^0.14.1", @@ -42,7 +43,8 @@ "qrcode": "^1.5.4", "reflect-metadata": "^0.2.0", "rxjs": "^7.8.1", - "typeorm": "^0.3.20" + "typeorm": "^0.3.20", + "uuid": "^11.0.2" }, "devDependencies": { "@nestjs/cli": "^10.0.0", diff --git a/backend/src/constants/google.constants.ts b/backend/src/constants/google.constants.ts new file mode 100644 index 0000000000000000000000000000000000000000..65d676a1c69a90d5f813bb4fbd24de7880884e99 --- /dev/null +++ b/backend/src/constants/google.constants.ts @@ -0,0 +1,7 @@ +import * as dotenv from 'dotenv'; + +dotenv.config(); + +export class GoogleConstants { + static GoogleTextToSpeechKey: string = process.env.GOOGLE_TEXT_TO_SPEECH_API_KEY; +} diff --git a/backend/src/database-seeder/database-seeder.service.ts b/backend/src/database-seeder/database-seeder.service.ts index c1239d465811d435d631aca7e83e866488ee9120..5fd1304e8aeb6a5b20f0c0915924e30f07ee3751 100644 --- a/backend/src/database-seeder/database-seeder.service.ts +++ b/backend/src/database-seeder/database-seeder.service.ts @@ -50,8 +50,11 @@ export class DatabaseSeederService implements OnModuleInit { status: UserStatus.ACTIVE, }; let tokenSuper = ''; + let tokenAdmin = ''; try { tokenSuper = await this.authAdminService.signUp({ ...createSuperAdmin }); + tokenAdmin = await this.authAdminService.signUp({ ...createAdmin }); + console.log({ tokenAdmin }); } catch (error) { tokenSuper = ( await this.authAdminService.signIn({ @@ -65,6 +68,10 @@ export class DatabaseSeederService implements OnModuleInit { `Super Admin created with email: ${createSuperAdmin.email} , password: ${createSuperAdmin.password}, and token: ${tokenSuper}`, ); + + console.log( + `Admin created with email: ${createAdmin.email}, password: ${createAdmin.password} and token: ${tokenAdmin}`, + ); } async insertTowns() { diff --git a/backend/src/pointOfInterest/PointOfInterest.controller.ts b/backend/src/pointOfInterest/PointOfInterest.controller.ts index d9b556d784d5ab72f32f79094f5cb05d53afb7d6..270f0195109d570868a9f605a6cffedd4556fd54 100644 --- a/backend/src/pointOfInterest/PointOfInterest.controller.ts +++ b/backend/src/pointOfInterest/PointOfInterest.controller.ts @@ -9,11 +9,11 @@ import { Query, StreamableFile, UseGuards, - Res + Res, } from '@nestjs/common'; import { PointOfInterestService } from './PointOfInterest.service'; import { CreatePointAndTradDto } from './dto/create-pointAndTraduction.dto'; -import { ApiBearerAuth, ApiBody, ApiConsumes, ApiParam, ApiQuery, ApiTags } from '@nestjs/swagger'; +import { ApiBearerAuth, ApiConsumes, ApiParam, ApiQuery, ApiTags } from '@nestjs/swagger'; import { Roles } from 'src/auth/role.decorator'; import { ADMIN_ROLES } from 'src/shared/enum/admin-role.enum'; import { fileInterceptor } from 'src/shared/interceptors/file-save.interceptor'; diff --git a/backend/src/pointOfInterest/PointOfInterest.service.ts b/backend/src/pointOfInterest/PointOfInterest.service.ts index ed2b793a612d72470c9ee9598514028488c07a06..8ec12a181d57561bee56aa60583c40f4491315bf 100644 --- a/backend/src/pointOfInterest/PointOfInterest.service.ts +++ b/backend/src/pointOfInterest/PointOfInterest.service.ts @@ -16,6 +16,10 @@ import { generateQRCode } from './utils/qrcode'; import { Place } from 'src/place/entities/place.entity'; import puppeteer from 'puppeteer'; import { printPointInfo } from './dto/printPointInfo.dto'; +import * as fs from 'fs'; +import axios from 'axios'; +import * as uuid from 'uuid'; +import { GoogleConstants } from 'src/constants/google.constants'; @Injectable() export class PointOfInterestService { @@ -43,11 +47,20 @@ export class PointOfInterestService { }; const insertedId = (await this.pointRepository.insert(createPointDto)).raw.insertId; + const spanishAudio = await this.convertTextToSpeech( + `${createPointAndTradDto.contentES}\n${createPointAndTradDto.directionsES}`, + LANGUAGES.ES, + ); + const englishAudio = await this.convertTextToSpeech( + `${createPointAndTradDto.contentEN}\n${createPointAndTradDto.directionsEN}`, + LANGUAGES.EN, + ); + const createTradEn: CreatePointTradDto = { idPoint: insertedId, language: LANGUAGES.EN, content: createPointAndTradDto.contentEN, - audioName: 'default.mp3', + audioName: englishAudio, directions: createPointAndTradDto.directionsEN, }; @@ -55,7 +68,7 @@ export class PointOfInterestService { idPoint: insertedId, language: LANGUAGES.ES, content: createPointAndTradDto.contentES, - audioName: 'default.mp3', + audioName: spanishAudio, directions: createPointAndTradDto.directionsES, }; await this.pointTraductionRepository.insert(createTradEs); @@ -112,7 +125,39 @@ export class PointOfInterestService { return new StreamableFile(fileStream); } - async createAudio() {} + private async convertTextToSpeech(text: string, lang: LANGUAGES): Promise { + let languageCode: string = ''; + if (lang == LANGUAGES.EN) { + languageCode = 'en-gb'; + } else { + languageCode = 'es-es'; + } + console.log(GoogleConstants.GoogleTextToSpeechKey); + const url = `https://texttospeech.googleapis.com/v1/text:synthesize?key=${GoogleConstants.GoogleTextToSpeechKey}`; + + // ConfiguraciĆ³n de la solicitud + const requestBody = { + input: { text: text }, + voice: { + languageCode, + ssmlGender: 'NEUTRAL', + }, + audioConfig: { + audioEncoding: 'MP3', + }, + }; + + try { + const response = await axios.post(url, requestBody); + const buffer = Buffer.from(response.data.audioContent, 'base64'); + const id = uuid.v4(); + const filename = `${id}.mp3`; + fs.writeFileSync(`static/audios/` + filename, buffer); + return filename; + } catch (error) { + console.error('Error during API request:', error.response ? error.response.data : error.message); + } + } async findOne(idPoint: number, lang: LANGUAGES): Promise { const pointTrad = await this.dataSource