diff --git a/.env.example b/.env.example index 83dc1cc41ba53c4e75c11cc13300062dbdc3834e..cb44a376827dad87d6795279dc44e71d70326c90 100644 --- a/.env.example +++ b/.env.example @@ -2,6 +2,8 @@ NODE_ENV=development # db config DB_HOST=localhost +MAILER_HOST=smtp.c3sl.ufpr.br +MAILER_PORT=25 DB_USER=postgres DB_PASSWORD=postgres DB_NAME=apex_db diff --git a/bun.lockb b/bun.lockb index eb973bacbb595524c04f0abd0000e6b6fc16a0d4..ed664d4f36f4331d4d53938047c366426f3f22f2 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index a48e32787e71e333fbcc445633f981decbc48a56..361105960110ad49c8bb2e60cefc136a3d6f7b49 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "hono-backend", "scripts": { - "dev": "bun run --hot --watch src/index.ts", + "dev": "bun run --hot --watch src/index.ts --port=4000", "db:generate": "drizzle-kit generate", "db:migrate": "cross-env DB_MIGRATING=true bun run src/db/migrate.ts", "db:seed": "cross-env DB_SEEDING=true bun run src/db/seed.ts", @@ -20,6 +20,9 @@ "drizzle-zod": "^0.5.1", "file-type": "^20.1.0", "hono": "^4.4.8", + "hono-openapi": "^0.4.6", + "jwt-simple": "^0.5.6", + "nodemailer": "^6.10.0", "postgres": "^3.4.4", "reflect-metadata": "^0.2.2", "typedi": "^0.10.0", diff --git a/src/db/repo/password-recovery.repo.ts b/src/db/repo/password-recovery.repo.ts deleted file mode 100644 index bc2c4e9a0b86519ae9104c2f5fa33d0d73f13c96..0000000000000000000000000000000000000000 --- a/src/db/repo/password-recovery.repo.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { Service } from "typedi"; -import db from "@/db"; -import type { PasswordRecoveryInput, PasswordRecoveryModel, PasswordRecoveryUpdate } from "../schema/password-recovery.schema"; -import passwordRecoveryTable, { passwordRecoverySchemas } from "../schema/password-recovery.schema"; -import { eq } from "drizzle-orm"; -import { z } from "zod"; - -@Service() -export class PasswordRecoveryRepo{ - async create(passTicket: PasswordRecoveryInput, tx?: db): Promise<PasswordRecoveryModel> { - const repo = tx ?? db - - const [ret] = await repo - .insert(passwordRecoveryTable) - .values(passTicket) - .returning() - - return passwordRecoverySchemas.model.parse(ret) - } - async update(resetTicket: PasswordRecoveryUpdate): Promise<PasswordRecoveryModel>{ - const [ret] = await db - .update(passwordRecoveryTable) - .set(resetTicket) - .where(eq(passwordRecoveryTable.id, resetTicket.id)) - .returning() - - return passwordRecoverySchemas.model.parse(ret) - } - async findById(id: PasswordRecoveryModel['id']): Promise<PasswordRecoveryModel | null>{ - const passTicket = await db.query.passwordRecoveryTable.findFirst({ - where: eq(passwordRecoveryTable.id, id) - }) - if (!passTicket) return null - return passwordRecoverySchemas.model.parse(passTicket) - } - async findByUserId(userId: PasswordRecoveryModel['userId']): Promise<PasswordRecoveryModel[] | null>{ - const passTicket = await db.query.passwordRecoveryTable.findMany({ - where: eq(passwordRecoveryTable.userId, userId) - }) - if (!passTicket) return null - return z.array(passwordRecoverySchemas.model).parse(passTicket) - } - async delete(id: PasswordRecoveryModel['id']): Promise<PasswordRecoveryModel>{ - const [ret] = await db - .delete(passwordRecoveryTable) - .where(eq(passwordRecoveryTable.id, id)) - .returning() - - return passwordRecoverySchemas.model.parse(ret) - } -} \ No newline at end of file diff --git a/src/db/schema/index.ts b/src/db/schema/index.ts index 574a14ef65e5b206f8c72eb991a7c31aefbcdc77..797533280278d9379ff9a341309935b422265c6b 100644 --- a/src/db/schema/index.ts +++ b/src/db/schema/index.ts @@ -7,9 +7,6 @@ import submissionTable from './submission.schema' import licenseTable from './license.schema' import languageTable from './language.schema' import educationalStageTable from './educational-stage.schema' -import passwordRecoveryTable, { - passwordRecoveryTableRelations -} from './password-recovery.schema' import userStatsTable from './user-stats.schema' import userTable from './user.schema' import objectTypeTable from './object-type.schema' @@ -49,8 +46,6 @@ export { licenseTable, languageTable, educationalStageTable, - passwordRecoveryTable, - passwordRecoveryTableRelations, objectTypeTable, roleTable, complaintTable, @@ -79,7 +74,6 @@ export { export const tables = [ userTable, - passwordRecoveryTable, userStatsTable, resourceTable, resourceStatsTable, diff --git a/src/db/schema/password-recovery.schema.ts b/src/db/schema/password-recovery.schema.ts deleted file mode 100644 index 1a2c79c3bc3b7691d74de6b9210e69232bb5ffb3..0000000000000000000000000000000000000000 --- a/src/db/schema/password-recovery.schema.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { relations, sql } from "drizzle-orm"; -import { boolean, integer, pgTable, serial, timestamp, varchar } from "drizzle-orm/pg-core"; -import userTable from "./user.schema"; -import { createInsertSchema, createSelectSchema } from "drizzle-zod"; -import type { z } from "zod"; - -const passwordRecoveryTable = pgTable('reset_tickets',{ - id: serial('id').primaryKey() - .unique() - .notNull(), - userId: integer('user_id') - .references(() => userTable.id, {onDelete: 'cascade'}) - .notNull(), - tokenHash: varchar('token_hash', {length: 255}) - .notNull(), - expirationDate: timestamp('expiration_date', { mode: 'string'}) - .notNull(), - tokenUsed: boolean('token_used') - .notNull() - .default(false), - validToken: boolean('valid_token') - .notNull() - .default(true), - createdAt: timestamp('created_at', { - mode: 'string', - }) - .notNull() - .defaultNow(), - updatedAt: timestamp('updated_at', { - mode: 'string', - }) - .notNull() - .defaultNow() - .$onUpdate(() => sql`current_timestamp`), - -}) - -export const passwordRecoveryTableRelations = relations( - passwordRecoveryTable, - ({ one }) => ({ - user: one(userTable, { - fields: [passwordRecoveryTable.userId], - references: [userTable.id] - }) - }) -) - -const passwordRecoveryModelSchema = createSelectSchema(passwordRecoveryTable) -const passwordRecoveryDtoSchema = passwordRecoveryModelSchema.omit({ - tokenHash: true -}) -const passwordRecoveryInputSchema = createInsertSchema(passwordRecoveryTable) -const passwordRecoveryUpdateSchema = passwordRecoveryInputSchema - .partial() - .required({ id: true }) - -export type PasswordRecoveryModel = z.infer<typeof passwordRecoveryModelSchema> -export type PasswordRecoveryDto = z.infer<typeof passwordRecoveryDtoSchema> -export type PasswordRecoveryInput = z.infer<typeof passwordRecoveryInputSchema> -export type PasswordRecoveryUpdate = z.infer<typeof passwordRecoveryUpdateSchema> - -export const passwordRecoverySchemas = { - model: passwordRecoveryModelSchema, - dto: passwordRecoveryDtoSchema, - input: passwordRecoveryInputSchema, - update: passwordRecoveryInputSchema -} - -export default passwordRecoveryTable \ No newline at end of file diff --git a/src/db/schema/user.schema.ts b/src/db/schema/user.schema.ts index f0b89c6411745c0d49d4c9a958e6d109e77258b8..913f574ff97c6a964fc14e754df53e4730499eb7 100644 --- a/src/db/schema/user.schema.ts +++ b/src/db/schema/user.schema.ts @@ -76,7 +76,6 @@ const userProfileSchema = userModelSchema.omit({ deleted_at: true, reactivated_at: true, active: true, - }) export type UserInput = z.infer<typeof userInputSchema> diff --git a/src/db/seeds/resource.seed.ts b/src/db/seeds/resource.seed.ts index 98cf82ae540c35557d5e45903ddd549b91e41663..783883f04ecefc5828e44c5b6be281309e5d92eb 100644 --- a/src/db/seeds/resource.seed.ts +++ b/src/db/seeds/resource.seed.ts @@ -36,7 +36,7 @@ const resourceData: ResourceInput[] = [ link: 'link 2', thumbnail: 'thumbnail 2', user_id: 1, - resource_stats_id: 2, + resource_stats_id: 3, object_type_id: 1, license_id: 1 } diff --git a/src/db/seeds/resourceStats.seed.ts b/src/db/seeds/resourceStats.seed.ts index e5926180862772db025752675cd29ab8919b2a8b..db8ed38af6e53762f28cd920ebbed6de57f117a4 100644 --- a/src/db/seeds/resourceStats.seed.ts +++ b/src/db/seeds/resourceStats.seed.ts @@ -14,6 +14,12 @@ const statsResourceData: ResourceStatsInput[] = [ shares: 0, score: '0' }, + { + views: 0, + downloads: 0, + shares: 0, + score: '0' + }, { views: 0, downloads: 0, diff --git a/src/mailer.ts b/src/mailer.ts new file mode 100644 index 0000000000000000000000000000000000000000..f3881a15d9316bf01e4dc1d3a2e004438af8f09c --- /dev/null +++ b/src/mailer.ts @@ -0,0 +1,9 @@ +import nodemailer from 'nodemailer'; + +/* cria o transportador de email */ +const transporter = nodemailer.createTransport({ + host: process.env.MAILER_HOST, + port: +(process.env.MAILER_PORT as string) +}); + +export default transporter; diff --git a/src/routes/auth.route.ts b/src/routes/auth.route.ts index ce9b45b747d1b6e2dbc63aeb434385df61dbaffc..6c8b726496d5b50f44e6c68a594137392e80a889 100644 --- a/src/routes/auth.route.ts +++ b/src/routes/auth.route.ts @@ -1,20 +1,18 @@ import { authSchema } from '@/db/repo/auth.repo' -import type { PasswordRecoveryModel } from '@/db/schema/password-recovery.schema' import { userStatsSchemas } from '@/db/schema/user-stats.schema' import { userSchemas } from '@/db/schema/user.schema' import { AuthService } from '@/services/auth.service' import { HttpStatus, createApexError } from '@/services/error.service' -import { PasswordRecoveryService } from '@/services/password.service' import { UserStatsService } from '@/services/user-stats.service' import { UserService } from '@/services/user.service' import { zValidator } from '@hono/zod-validator' import { Hono } from 'hono' import Container from 'typedi' +import jwt from 'jwt-simple'; const authService = Container.get(AuthService) const userService = Container.get(UserService) const userStatsService = Container.get(UserStatsService) -const passwordRecoveryService = Container.get(PasswordRecoveryService) export const authRouter = new Hono().post( '/signin', @@ -57,114 +55,103 @@ export const authRouter = new Hono().post( return c.text('could not create') } }) -.post('/request/:email', +.post('/recoveryPassword/:id', async (c) => { - try{ - const email: string = c.req.param('email') - const user = userSchemas.userDtoSchema.parse( - await userService.findByEmail(email) - ) + try { + const id: number = +c.req.param('id') + + const user = await userService.findById(id) - /* - * When a ticket is generated, the last one is deleted - */ - const ticketList = await passwordRecoveryService.findByUserId(user.id) - if(ticketList?.length != 0){ - const lastTicket = ticketList![ticketList!.length - 1] - if(lastTicket.validToken){ - await passwordRecoveryService.delete(lastTicket.id) - } - + if (!user) { + return c.json( + createApexError({ + status: 'error', + message: 'Usuário não encontrado', + code: HttpStatus.NOT_FOUND, + path: c.req.routePath, + suggestion: 'Verifique se o e-mail está correto', + }), + HttpStatus.NOT_FOUND + ) } - const resetTicket = await passwordRecoveryService.create(user) - return c.json(resetTicket) - - /** - * To-do: send email - * const subject = "Plataforma MEC de Recursos Educacionais - Redefinição de senha" - * - * const content = passwordRecoveryService.emailTemplatePasswordRecovery(resetTicket); - * await emailService.sendEmail(user.email, subject, content) - * return c.json({ - * message: { - * status: 'success', - * message: 'email sent successfully', - * code: HttpStatus.OK - * } - * }) - */ + + var payload = { + id: id, + email: user.email + }; + + + var secret = user.password + '-' + user.createdAt; + //const resetTicket = await recoveryService.create(user) + + var token = jwt.encode(payload, secret); + + await authService.passwordRecoveryEmail(token, user.email, "Recuperação de senha", user.email) + return c.json({ message: 'E-mail de recuperação enviado com sucesso!' }) } catch (e) { + console.error(e) return c.json( createApexError({ status: 'error', - message: 'could not send recovery password email', - code: HttpStatus.BAD_REQUEST, + message: 'Erro ao processar solicitação', + code: HttpStatus.INTERNAL_SERVER_ERROR, path: c.req.routePath, - suggestion: 'check the input and try again', + suggestion: 'Tente novamente mais tarde', }), - HttpStatus.BAD_REQUEST + HttpStatus.INTERNAL_SERVER_ERROR ) } -}) + } +) .post( - '/reset', -//url preview: reset?email=admin%40admin.com&token=xEakn2HpEaH8Xvyz8fsntYvmHip8IVP0NZu7bWpjkEY&password= + '/reset/password-reset', + zValidator('json', userSchemas.userUpdateSchema), async (c) => { try { - /* - * Variable declaration section - */ - const email = c.req.queries('email') - const password = c.req.queries('password') - const token = c.req.queries('token') - if(email == null || password == null|| token == null) - throw new Error(); - const user = await userService.findByEmail(email[0]) - if(user == null) - throw new Error(); - - const resetTickets = await passwordRecoveryService.findByUserId(user.id) - if(resetTickets == null) - throw new Error(); - - /* - * Find reset ticket in database - */ - let resetTicket: PasswordRecoveryModel | any = null - resetTickets.forEach(element => { - if(element.tokenHash == token[0]) - resetTicket = element - }); - - /* - * Check if the ticket is valid (expired, used, new ticket generated) - */ - if(passwordRecoveryService.isNotValidTicket(resetTicket)) - throw new Error(); - - /* - * Set user new password and update it - */ - user.password = password[0] - - const ret = userSchemas.userDtoSchema.parse( - await userService.update(user) - ) - - await passwordRecoveryService.delete(resetTicket.id) + const input = await c.req.valid('json'); + const emailArray = c.req.queries('email'); + const token = c.req.queries('token')?.[0] ?? ''; + + if (!emailArray?.[0] || !token) { + throw new Error('Invalid request: missing email or token'); + } + + const email = emailArray[0]; + const user = await userService.findByEmail(email); + + if (!user) { + throw new Error('User not found'); + } - return c.json({ user: ret }) + const secret = user.password + '-' + String(user.createdAt); + + // Decodifica o token e verifica a assinatura + let payload; + try { + payload = jwt.decode(token, secret); + } catch (error) { + throw new Error('Invalid or expired token'); + } + + // Atualiza a senha do usuário + user.password = String(input.password); + const updatedUser = await userService.update(user); + + userSchemas.userDtoSchema.parse(updatedUser); + + return c.json({ message: 'Password updated successfully' }); } catch (e) { return c.json( createApexError({ status: 'error', - message: 'could not send recovery password email', + message: 'Token expired', code: HttpStatus.BAD_REQUEST, path: c.req.routePath, - suggestion: 'check the input and try again', + suggestion: 'Check the input and try again', }), HttpStatus.BAD_REQUEST - ) + ); } } -) \ No newline at end of file +); + diff --git a/src/routes/user.route.ts b/src/routes/user.route.ts index 333803ee97e9839cd07a599e5355b14966d5a19a..48372f88342440aca160819af17144e2f712e55d 100644 --- a/src/routes/user.route.ts +++ b/src/routes/user.route.ts @@ -8,6 +8,8 @@ import { createApexError, HttpStatus } from '@/services/error.service' import { followRelationSchemas } from '@/db/relations/followers.relation' import { UserStatsService } from '@/services/user-stats.service' import { FollowRelationService } from '@/services/follow.relation.service' +import { UserRepo } from '@/db/repo/user.repo' +import { PasswordRecoveryService } from '@/services/password.service' const service = Container.get(UserService) const followService = Container.get(FollowRelationService) @@ -288,4 +290,5 @@ export const userRouter = honoWithJwt() HttpStatus.BAD_REQUEST ) } - }) + }) + \ No newline at end of file diff --git a/src/services/auth.service.ts b/src/services/auth.service.ts index bf01c8bca738d3e6f296c2c1a31070d00e8bf759..d7c92e928779dc2a5066e58a1f4b25974357f9f2 100644 --- a/src/services/auth.service.ts +++ b/src/services/auth.service.ts @@ -8,6 +8,7 @@ import { type AuthInput, type AuthPayload, } from '@/db/repo/auth.repo' +import transporter from "@/mailer"; @Service() export class AuthService { @@ -31,4 +32,82 @@ export class AuthService { return token } + + async passwordRecoveryEmail(resetTicket: string, receiver: string, subject: string, userEmail: string) { + const token_link: string = `http://mecreddev:3002/novaSenha?token=${resetTicket}&email=${userEmail}`; + const text = ` + <head> + <style> + + .container { + display: block; + text-align: left; + background-color: rgb(240, 240, 240); + width: 90%; + margin: 0 auto; + padding: 30px 30px; + border-radius: 30%, + font-family: Arial, Helvetica, sans-serif; + } + + button { + background-color: #00bacc; + color: white; + border: none; + border-radius: 5px; + padding: 10px; + font-size: 16px; + cursor: pointer; + } + + .container_msg { + text-align: left; + padding: 0px 20px 0px 20px; + background-color: white; + } + + button:hover { + background-color: rgb(7, 29, 65, 0.8); + } + span { + color: #e74c3c; + font-weight: bold; + } + + </style> + </head> + + <body> + <div class="container" > + <a href="${process.env["URL"]}"></a> + <h1 style="color:#00bacc; font-size: 30px; font-weight: 800; margin-top: 5px; margin-bottom: 5px">Recuperação de senha</h1> + <br> + <p>Este e-mail foi enviado para confirmar que você é o proprietário do endereço de e-mail fornecido no cadastro da MECRED. + Essa etapa é importante para garantir a validade e segurança das informações enviadas. + </p> + <p> Para criar uma nova senha, acesse: </p> + <a href="${token_link}"><button>Recupere sua senha</button></a> + <p style="margin-top:5px"> + <br> + <span>Atenção:</span> caso você não tenha cadastrado uma conta na plataforma, por favor, ignore este e-mail. Nenhuma ação será realizada sem sua confirmação. + </p> + </div> + + </body> + ` + + try { + const response = await transporter.sendMail({ + from: "Não responda <no-reply@c3sl.ufpr.br>", + to: receiver, + subject: subject, + html: text + }); + console.log("E-mail enviado:", response); + console.log(userEmail) + } catch (error) { + console.error("Erro ao enviar e-mail:", error); + } + } + } diff --git a/src/services/password.service.ts b/src/services/password.service.ts deleted file mode 100644 index 9ed0a643053b488522fc4a9f02641360c6ef4ed6..0000000000000000000000000000000000000000 --- a/src/services/password.service.ts +++ /dev/null @@ -1,139 +0,0 @@ -/*/import * as nodemailer from 'nodemailer'; -import mailConfig from "./config";*/ -import { Inject, Service } from "typedi"; -import { PasswordRecoveryRepo } from '@/db/repo/password-recovery.repo'; -import type { PasswordRecoveryModel } from '@/db/schema/password-recovery.schema'; -import type db from '@/db'; -import type { UserDto } from '@/db/schema/user.schema'; - -@Service() -export class PasswordRecoveryService { - @Inject() - private readonly repo: PasswordRecoveryRepo - - isNotValidTicket(resetTicket: PasswordRecoveryModel): boolean { - - if(resetTicket == null) return true - const is_expired: boolean = Date.parse(resetTicket.expirationDate) < Date.now() - if(!resetTicket.validToken || is_expired || resetTicket.tokenUsed) - return true - return false - } - - generateToken(userEmail: string, tokenExpirationDate: string): string{ - const hasher = new Bun.CryptoHasher("sha256") - const hashKey = userEmail.concat(tokenExpirationDate) - return hasher.update(hashKey).digest("base64") - - } - - generateExpirationDate(): string { - const time_expire = new Date() - time_expire.setHours(time_expire.getHours() + 1) - - return time_expire.toLocaleString('pt-br', { timeZone: 'America/Sao_Paulo'}) - } - - - emailTemplatePasswordRecovery(resetTicket: PasswordRecoveryModel): string{ - const recover_link: string = process.env["URL"] + '/reset-password/'+resetTicket.tokenHash - const token_link: string = recover_link + "?token=" + resetTicket.tokenHash - - const expirationDate = new Date(resetTicket.expirationDate) - expirationDate.setHours(expirationDate.getHours() - 1) - const requestedDate = expirationDate.toLocaleString('pt-br', { timeZone: 'America/Sao_Paulo'}) - const text = ` - <head> - <style> - - .container { - display: block; - text-align: center; - background-color: rgb(240, 240, 240); - box-shadow: 0 7px 29px 0 rgba(100, 100, 111, .2); - width: 50%; - margin: 0 auto; - padding: 60px 20px; - font-family: Arial, Helvetica, sans-serif; - } - - button { - background-color: rgb(7, 29, 65); - color: white; - border: none; - border-radius: 5px; - padding: 10px; - font-size: 16px; - cursor: pointer; - } - - .container_msg { - text-align: center; - padding: 0px 20px 0px 20px; - background-color: white; - } - - button:hover { - background-color: rgb(7, 29, 65, 0.8); - } - </style> - </head> - - <body> - <div class="container" > - <a href="${process.env["URL"]}"></a> - <h1 style="color:rgb(7, 29, 65); font-size: 30px; font-weight: 800; margin-top: 10px;">Recuperação de senha</h1> - <p>Recebemos uma requisição de recuperação de senha em </p> - <strong>${requestedDate}</strong> - <br> - <p> Para criar uma nova senha, acesse: </p> - <br> - <a href="${token_link}"><button>Recupere sua senha </button></a> - </div> - </body> - - ` - // precisa pegar a logo do mecred - // <a href="${process.env["URL"]}"><img src="${process.env["URL"]}/static/media/PNLD_logo.c06d68e4.png" width="133" height="53" /></a> - return text - } - - /*async sendEmail(receiver: string, subject: string, content: string): Promise<object>{ - const transporter = nodemailer.createTransporter(mailConfig); - return await transporter.sendMail({ - from: "Não Responda <no-reply@c3sl.ufpr.br>", - to: receiver, - subject: subject, - html: content - }) - }*/ - - async create(user: UserDto, tx?: db): Promise<PasswordRecoveryModel>{ - const tokenExpirationDate = this.generateExpirationDate() - const newTokenHash = this.generateToken(user?.email, tokenExpirationDate) - const resetTicket = { - userId: user!.id, - tokenHash: newTokenHash, - expirationDate: tokenExpirationDate, - tokenUsed: false - } - return await this.repo.create(resetTicket, tx) - } - - async findById(id: PasswordRecoveryModel['id']): Promise<PasswordRecoveryModel | null>{ - return await this.repo.findById(id) - } - - async findByUserId(userId: PasswordRecoveryModel['userId']): Promise<PasswordRecoveryModel[] | null>{ - return await this.repo.findByUserId(userId) - } - - async update(resetTicket: PasswordRecoveryModel): Promise<PasswordRecoveryModel>{ - return await this.repo.update(resetTicket) - } - - async delete(id: PasswordRecoveryModel['id']): Promise<PasswordRecoveryModel>{ - return await this.repo.delete(id) - } - -} \ No newline at end of file diff --git a/src/static/mecred.svg b/src/static/mecred.svg new file mode 100644 index 0000000000000000000000000000000000000000..8ccfd675db024b435156a1373b67afd5600f99da --- /dev/null +++ b/src/static/mecred.svg @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 226.73 45.26"> + <defs> + <style> + .cls-1 { + fill: #ed6f24; + } + + .cls-1, .cls-2, .cls-3, .cls-4 { + stroke-width: 0px; + } + + .cls-2 { + fill: #56358c; + } + + .cls-3 { + fill: #e62954; + } + + .cls-4 { + fill: #00a3bb; + } + </style> + </defs> + <path class="cls-4" d="M68.21,25.82h-17.21v11.05h20.4v7.86h-30.6V.74h30.6v7.86h-20.4v9.56h17.21v7.65Z"/> + <path class="cls-4" d="M110.08,29.33h0c-.21,5.31-1.7,9.35-4.46,11.9-2.76,2.76-6.8,4.04-11.9,4.04s-9.56-1.7-12.75-5.1-4.89-7.65-4.89-12.96v-8.92c0-5.31,1.49-9.56,4.68-12.96,3.19-3.4,7.23-5.1,12.32-5.1s9.56,1.49,12.32,4.25,4.46,6.8,4.89,11.9v.21h-9.56c-.21-2.76-.85-4.89-1.91-6.38s-2.98-2.12-5.52-2.12c-2.34,0-4.04.85-5.31,2.76s-1.91,4.25-1.91,7.23v9.35c0,2.97.64,5.53,2.12,7.44s3.4,2.76,5.74,2.76,4.04-.64,5.1-1.91,1.49-3.4,1.7-6.16h9.35v-.21Z"/> + <path class="cls-1" d="M126.64,27.84v16.79h-10.2V.64h17c5.1,0,9.14,1.28,12.11,3.61s4.46,5.53,4.46,9.78c0,2.34-.64,4.25-1.7,5.95-1.28,1.7-2.97,2.97-5.31,4.04,2.76.85,4.67,2.12,5.74,3.82,1.28,1.7,1.7,4.04,1.7,6.59v2.76c0,1.06.21,2.55.43,3.82.43,1.49.85,2.55,1.7,3.19v.64h-10.62c-.85-.64-1.28-1.7-1.49-3.4-.21-1.49-.42-2.98-.42-4.46v-2.76c0-2.12-.64-3.61-1.7-4.89-1.06-1.06-2.76-1.7-4.89-1.7h-6.8v.21ZM126.64,19.98h6.8c2.12,0,3.61-.43,4.67-1.49,1.06-.85,1.7-2.34,1.7-4.04s-.64-3.19-1.7-4.25-2.76-1.7-4.67-1.7h-6.8v11.47Z"/> + <path class="cls-3" d="M184.87,25.82h-17.21v11.05h20.4v7.86h-30.6V.74h30.6v7.86h-20.4v9.56h17.21v7.65Z"/> + <path class="cls-2" d="M192.52,44.73V.74h15.94c5.1,0,9.56,1.7,12.96,5.1s5.31,7.65,5.31,12.96v7.86c0,5.31-1.7,9.56-5.31,12.96-3.4,3.4-7.86,5.1-12.96,5.1h-15.94ZM202.93,8.61v28.26h4.89c2.76,0,4.89-.85,6.38-2.76,1.49-1.91,2.34-4.25,2.34-7.44v-8.07c0-2.97-.85-5.53-2.34-7.44-1.49-1.91-3.61-2.76-6.38-2.76h-4.89v.21Z"/> + <polygon class="cls-4" points="25.93 .74 18.06 13.07 10.2 .74 0 .74 0 44.73 10.2 44.73 10.2 18.38 15.3 25.82 20.82 25.82 25.93 18.38 25.93 44.73 36.12 44.73 36.12 .74 25.93 .74"/> +</svg> \ No newline at end of file diff --git a/src/tests/preload-tests.ts b/src/tests/preload-tests.ts index f8838f56d966f5671cb426f6e3b7265455c9c1e6..7037d4c8c54fa704e0b1b51251be93dbf2358d5a 100644 --- a/src/tests/preload-tests.ts +++ b/src/tests/preload-tests.ts @@ -2,7 +2,7 @@ import 'reflect-metadata' console.log('preloaded for tests') -export const api = 'http://localhost:3000/api' +export const api = 'http://localhost:4000/api' export const token = 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImZlNzcwMWJjLWNhZDItNDNkYi05ZjFiLWE3ZDhjM2I1Zjc0YiIsInVzZXJuYW1lIjoiYWRtaW4ifQ.Cd8ixLFseKLRidLTpBfHA1QLolwBFO2pzXHq9UtclWk'