diff --git a/prisma/schema.prisma b/prisma/schema.prisma
index d6547b1..6347045 100644
--- a/prisma/schema.prisma
+++ b/prisma/schema.prisma
@@ -91,7 +91,6 @@ model card {
questionVideo String? @db.VarChar(500)
comments String?
type String?
- rating Int @default(0)
moreId String?
created DateTime @default(now())
updated DateTime @updatedAt
@@ -110,7 +109,6 @@ model deck {
isPrivate Boolean @default(false)
shots Int @default(0)
cover String? @db.VarChar(500)
- rating Int @default(0)
isDeleted Boolean?
isBlocked Boolean?
created DateTime @default(now())
diff --git a/requests/decks.http b/requests/decks.http
new file mode 100644
index 0000000..e69de29
diff --git a/requests/openapi-spec.json b/requests/openapi-spec.json
new file mode 100644
index 0000000..3c4b6af
--- /dev/null
+++ b/requests/openapi-spec.json
@@ -0,0 +1,1548 @@
+{
+ "openapi": "3.0.0",
+ "paths": {
+ "/v1/users": {
+ "get": {
+ "operationId": "UsersController_findAll",
+ "parameters": [],
+ "responses": {
+ "200": {
+ "description": "",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object"
+ }
+ }
+ }
+ }
+ },
+ "tags": ["Admin"]
+ },
+ "post": {
+ "operationId": "UsersController_create",
+ "parameters": [],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/CreateUserDto"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object"
+ }
+ }
+ }
+ }
+ },
+ "tags": ["Admin"]
+ },
+ "delete": {
+ "operationId": "UsersController_removeAll",
+ "parameters": [],
+ "responses": {
+ "200": {
+ "description": ""
+ }
+ },
+ "tags": ["Admin"]
+ }
+ },
+ "/v1/users/{id}": {
+ "delete": {
+ "operationId": "UsersController_remove",
+ "parameters": [
+ {
+ "name": "id",
+ "required": true,
+ "in": "path",
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "boolean"
+ }
+ }
+ }
+ }
+ },
+ "tags": ["Admin"]
+ }
+ },
+ "/v1/auth/me": {
+ "get": {
+ "operationId": "AuthController_getUserData",
+ "summary": "Current user data",
+ "description": "Retrieve current user data.",
+ "parameters": [],
+ "responses": {
+ "200": {
+ "description": "",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UserEntity"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "User not found"
+ },
+ "401": {
+ "description": "Not logged in"
+ }
+ },
+ "tags": ["Auth"]
+ },
+ "patch": {
+ "operationId": "AuthController_updateUserData",
+ "summary": "Update user data",
+ "description": "Update current user data.",
+ "parameters": [],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "multipart/form-data": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserDataDto"
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UserEntity"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "User not found"
+ },
+ "401": {
+ "description": "Not logged in"
+ }
+ },
+ "tags": ["Auth"]
+ }
+ },
+ "/v1/auth/login": {
+ "post": {
+ "operationId": "AuthController_login",
+ "summary": "Sign in using email and password. Must have an account to do so.",
+ "description": "Sign in using email and password. Must have an account to do so.",
+ "parameters": [],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/LoginDto"
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/LoginResponse"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Invalid credentials"
+ }
+ },
+ "tags": ["Auth"]
+ }
+ },
+ "/v1/auth/sign-up": {
+ "post": {
+ "operationId": "AuthController_registration",
+ "summary": "Create a new user account",
+ "description": "Create a new user account",
+ "parameters": [],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/RegistrationDto"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UserEntity"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Email already exists"
+ }
+ },
+ "tags": ["Auth"]
+ }
+ },
+ "/v1/auth/verify-email": {
+ "post": {
+ "operationId": "AuthController_confirmRegistration",
+ "summary": "Verify user email",
+ "description": "Verify user email",
+ "parameters": [],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/EmailVerificationDto"
+ }
+ }
+ }
+ },
+ "responses": {
+ "204": {
+ "description": "Email verified successfully"
+ },
+ "400": {
+ "description": "Email has already been verified"
+ },
+ "404": {
+ "description": "User not found"
+ }
+ },
+ "tags": ["Auth"]
+ }
+ },
+ "/v1/auth/resend-verification-email": {
+ "post": {
+ "operationId": "AuthController_resendVerificationEmail",
+ "summary": "Send verification email again",
+ "description": "Send verification email again",
+ "parameters": [],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ResendVerificationEmailDto"
+ }
+ }
+ }
+ },
+ "responses": {
+ "204": {
+ "description": "Verification email sent successfully"
+ },
+ "400": {
+ "description": "Email has already been verified"
+ },
+ "404": {
+ "description": "User not found"
+ }
+ },
+ "tags": ["Auth"]
+ }
+ },
+ "/v1/auth/logout": {
+ "post": {
+ "operationId": "AuthController_logout",
+ "summary": "Sign current user out",
+ "description": "Sign current user out",
+ "parameters": [],
+ "responses": {
+ "204": {
+ "description": "Logged out successfully"
+ },
+ "401": {
+ "description": "Not logged in"
+ }
+ },
+ "tags": ["Auth"]
+ }
+ },
+ "/v1/auth/refresh-token": {
+ "post": {
+ "operationId": "AuthController_refreshToken",
+ "summary": "Get new access token using refresh token",
+ "description": "Get new access token using refresh token",
+ "parameters": [],
+ "responses": {
+ "204": {
+ "description": "New tokens generated successfully"
+ },
+ "401": {
+ "description": "Invalid or missing refreshToken"
+ }
+ },
+ "tags": ["Auth"]
+ }
+ },
+ "/v1/auth/recover-password": {
+ "post": {
+ "operationId": "AuthController_recoverPassword",
+ "summary": "Send password recovery email",
+ "description": "Send password recovery email",
+ "parameters": [],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/RecoverPasswordDto"
+ }
+ }
+ }
+ },
+ "responses": {
+ "204": {
+ "description": "Password recovery email sent successfully"
+ },
+ "400": {
+ "description": "Email has already been verified"
+ },
+ "404": {
+ "description": "User not found"
+ }
+ },
+ "tags": ["Auth"]
+ }
+ },
+ "/v1/auth/reset-password/{token}": {
+ "post": {
+ "operationId": "AuthController_resetPassword",
+ "summary": "Reset password",
+ "description": "Reset password",
+ "parameters": [
+ {
+ "name": "token",
+ "required": true,
+ "in": "path",
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ResetPasswordDto"
+ }
+ }
+ }
+ },
+ "responses": {
+ "204": {
+ "description": "Password reset successfully"
+ },
+ "400": {
+ "description": "Password is required"
+ },
+ "404": {
+ "description": "Incorrect or expired password reset token"
+ }
+ },
+ "tags": ["Auth"]
+ }
+ },
+ "/v1/decks": {
+ "get": {
+ "operationId": "DecksController_findAll",
+ "summary": "Paginated decks list",
+ "description": "Retrieve paginated decks list.",
+ "parameters": [
+ {
+ "name": "minCardsCount",
+ "required": false,
+ "in": "query",
+ "schema": {
+ "type": "number"
+ }
+ },
+ {
+ "name": "maxCardsCount",
+ "required": false,
+ "in": "query",
+ "schema": {
+ "type": "number"
+ }
+ },
+ {
+ "name": "name",
+ "required": false,
+ "in": "query",
+ "description": "Search by deck name",
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "authorId",
+ "required": false,
+ "in": "query",
+ "description": "Filter by deck authorId",
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "orderBy",
+ "required": false,
+ "in": "query",
+ "description": "A string that represents the name of the field to order by and the order direction.\nThe format is: \"field_name-order_direction\".\nAvailable directions: \"asc\" and \"desc\".",
+ "example": "name-desc",
+ "schema": {
+ "nullable": true,
+ "type": "string"
+ }
+ },
+ {
+ "name": "currentPage",
+ "required": false,
+ "in": "query",
+ "schema": {
+ "type": "number"
+ }
+ },
+ {
+ "name": "itemsPerPage",
+ "required": false,
+ "in": "query",
+ "schema": {
+ "type": "number"
+ }
+ }
+ ],
+ "responses": {
+ "206": {
+ "description": "",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/PaginatedDecks"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized"
+ }
+ },
+ "tags": ["Decks"]
+ },
+ "post": {
+ "operationId": "DecksController_create",
+ "summary": "Create a deck",
+ "description": "Create a deck",
+ "parameters": [],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "multipart/form-data": {
+ "schema": {
+ "$ref": "#/components/schemas/CreateDeckDto"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/DeckWithAuthor"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized"
+ }
+ },
+ "tags": ["Decks"]
+ }
+ },
+ "/v1/decks/{id}": {
+ "get": {
+ "operationId": "DecksController_findOne",
+ "summary": "Retrieve a deck by id",
+ "description": "Retrieve a deck by id",
+ "parameters": [
+ {
+ "name": "id",
+ "required": true,
+ "in": "path",
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/DeckWithAuthor"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized"
+ }
+ },
+ "tags": ["Decks"]
+ },
+ "patch": {
+ "operationId": "DecksController_update",
+ "summary": "Update a deck",
+ "description": "Update a deck",
+ "parameters": [
+ {
+ "name": "id",
+ "required": true,
+ "in": "path",
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "multipart/form-data": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateDeckDto"
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/DeckWithAuthor"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "404": {
+ "description": "Deck not found"
+ }
+ },
+ "tags": ["Decks"]
+ },
+ "delete": {
+ "operationId": "DecksController_remove",
+ "summary": "Delete a deck",
+ "description": "Delete a deck",
+ "parameters": [
+ {
+ "name": "id",
+ "required": true,
+ "in": "path",
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Deck deleted",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Deck"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "404": {
+ "description": "Deck not found"
+ }
+ },
+ "tags": ["Decks"]
+ }
+ },
+ "/v1/decks/{id}/cards": {
+ "get": {
+ "operationId": "DecksController_findCardsInDeck",
+ "summary": "Retrieve cards in a deck",
+ "description": "Retrieve paginated cards in a deck",
+ "parameters": [
+ {
+ "name": "id",
+ "required": true,
+ "in": "path",
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "question",
+ "required": false,
+ "in": "query",
+ "schema": {
+ "minLength": 1,
+ "maxLength": 30,
+ "type": "string"
+ }
+ },
+ {
+ "name": "answer",
+ "required": false,
+ "in": "query",
+ "schema": {
+ "minLength": 1,
+ "maxLength": 30,
+ "type": "string"
+ }
+ },
+ {
+ "name": "orderBy",
+ "required": false,
+ "in": "query",
+ "schema": {
+ "nullable": true,
+ "type": "string"
+ }
+ },
+ {
+ "name": "currentPage",
+ "required": false,
+ "in": "query",
+ "schema": {
+ "type": "number"
+ }
+ },
+ {
+ "name": "itemsPerPage",
+ "required": false,
+ "in": "query",
+ "schema": {
+ "type": "number"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/PaginatedCards"
+ }
+ }
+ }
+ }
+ },
+ "tags": ["Decks"]
+ },
+ "post": {
+ "operationId": "DecksController_createCardInDeck",
+ "summary": "Create a card",
+ "description": "Create card in a deck",
+ "parameters": [
+ {
+ "name": "id",
+ "required": true,
+ "in": "path",
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "multipart/form-data": {
+ "schema": {
+ "$ref": "#/components/schemas/CreateCardDto"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Card"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "404": {
+ "description": "Deck not found"
+ }
+ },
+ "tags": ["Decks"]
+ }
+ },
+ "/v1/decks/{id}/learn": {
+ "get": {
+ "operationId": "DecksController_findRandomCardInDeck",
+ "summary": "Retrieve a random card",
+ "description": "Retrieve a random card in a deck. The cards priority is based on the grade",
+ "parameters": [
+ {
+ "name": "id",
+ "required": true,
+ "in": "path",
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "previousCardId",
+ "required": false,
+ "in": "query",
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Card"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized"
+ }
+ },
+ "tags": ["Decks"]
+ },
+ "post": {
+ "operationId": "DecksController_saveGrade",
+ "summary": "Save the grade of a card",
+ "description": "Save the grade of a card",
+ "parameters": [
+ {
+ "name": "id",
+ "required": true,
+ "in": "path",
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/SaveGradeDto"
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object"
+ }
+ }
+ }
+ },
+ "204": {
+ "description": "Grade saved"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "404": {
+ "description": "Card not found"
+ }
+ },
+ "tags": ["Decks"]
+ }
+ },
+ "/v1/cards/{id}": {
+ "get": {
+ "operationId": "CardsController_findOne",
+ "summary": "Get card by id",
+ "description": "Get card by id",
+ "parameters": [
+ {
+ "name": "id",
+ "required": true,
+ "in": "path",
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Card"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "404": {
+ "description": "Card not found"
+ }
+ },
+ "tags": ["Cards"]
+ },
+ "patch": {
+ "operationId": "CardsController_update",
+ "summary": "Update card",
+ "description": "Update partial card data",
+ "parameters": [
+ {
+ "name": "id",
+ "required": true,
+ "in": "path",
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "multipart/form-data": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateCardDto"
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Card"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "404": {
+ "description": "Card not found"
+ }
+ },
+ "tags": ["Cards"]
+ },
+ "delete": {
+ "operationId": "CardsController_remove",
+ "summary": "Delete card by id",
+ "description": "Delete card by id",
+ "parameters": [
+ {
+ "name": "id",
+ "required": true,
+ "in": "path",
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "204": {
+ "description": "New tokens generated successfully"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "404": {
+ "description": "Card not found"
+ }
+ },
+ "tags": ["Cards"]
+ }
+ }
+ },
+ "info": {
+ "title": "Flashcards",
+ "description": "Flashcards API",
+ "version": "1.0",
+ "contact": {}
+ },
+ "tags": [
+ {
+ "name": "Auth",
+ "description": ""
+ },
+ {
+ "name": "Decks",
+ "description": ""
+ },
+ {
+ "name": "Cards",
+ "description": ""
+ },
+ {
+ "name": "Admin",
+ "description": ""
+ }
+ ],
+ "servers": [
+ {
+ "url": "https://api.flashcards.andrii.es"
+ }
+ ],
+ "components": {
+ "schemas": {
+ "CreateUserDto": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 3,
+ "maxLength": 10
+ },
+ "password": {
+ "type": "string",
+ "minLength": 6,
+ "maxLength": 20
+ },
+ "email": {
+ "type": "string",
+ "description": "User's email address",
+ "pattern": "/^[\\w-.]+@([\\w-]+\\.)+[\\w-]{2,4}$/"
+ }
+ },
+ "required": ["name", "password", "email"]
+ },
+ "UserEntity": {
+ "type": "object",
+ "properties": {
+ "avatar": {
+ "type": "string",
+ "format": "binary"
+ },
+ "id": {
+ "type": "string"
+ },
+ "email": {
+ "type": "string"
+ },
+ "isEmailVerified": {
+ "type": "boolean"
+ },
+ "name": {
+ "type": "string"
+ },
+ "created": {
+ "format": "date-time",
+ "type": "string"
+ },
+ "updated": {
+ "format": "date-time",
+ "type": "string"
+ }
+ },
+ "required": ["avatar", "id", "email", "isEmailVerified", "name", "created", "updated"]
+ },
+ "UpdateUserDataDto": {
+ "type": "object",
+ "properties": {
+ "avatar": {
+ "type": "string",
+ "format": "binary"
+ },
+ "name": {
+ "type": "string"
+ },
+ "email": {
+ "type": "string"
+ }
+ },
+ "required": ["avatar", "name", "email"]
+ },
+ "LoginDto": {
+ "type": "object",
+ "properties": {
+ "password": {
+ "type": "string",
+ "minLength": 3,
+ "maxLength": 30
+ },
+ "email": {
+ "type": "string"
+ },
+ "rememberMe": {
+ "type": "boolean"
+ }
+ },
+ "required": ["password", "email", "rememberMe"]
+ },
+ "LoginResponse": {
+ "type": "object",
+ "properties": {
+ "accessToken": {
+ "type": "string"
+ }
+ },
+ "required": ["accessToken"]
+ },
+ "RegistrationDto": {
+ "type": "object",
+ "properties": {
+ "html": {
+ "type": "string",
+ "description": "HTML template to be sent in the email;\n ##name## will be replaced with the user's name; \n ##token## will be replaced with the password recovery token",
+ "example": "Hello, ##name##!
Please confirm your email by clicking on the link below:
Confirm email. If it doesn't work, copy and paste the following link in your browser:
http://localhost:3000/confirm-email/##token##"
+ },
+ "name": {
+ "type": "string",
+ "minLength": 3,
+ "maxLength": 30
+ },
+ "password": {
+ "type": "string",
+ "minLength": 3,
+ "maxLength": 30
+ },
+ "email": {
+ "type": "string"
+ },
+ "subject": {
+ "type": "string",
+ "description": "Email subject"
+ },
+ "sendConfirmationEmail": {
+ "type": "boolean",
+ "description": "Whether to send a confirmation email or not.\nDefaults to false",
+ "example": false
+ }
+ },
+ "required": ["password", "email"]
+ },
+ "EmailVerificationDto": {
+ "type": "object",
+ "properties": {
+ "code": {
+ "type": "string"
+ }
+ },
+ "required": ["code"]
+ },
+ "ResendVerificationEmailDto": {
+ "type": "object",
+ "properties": {
+ "html": {
+ "type": "string",
+ "description": "HTML template to be sent in the email;\n ##name## will be replaced with the user's name; \n ##token## will be replaced with the password recovery token",
+ "example": "Hello, ##name##!
Please confirm your email by clicking on the link below:
Confirm email. If it doesn't work, copy and paste the following link in your browser:
http://localhost:3000/confirm-email/##token##"
+ },
+ "userId": {
+ "type": "string"
+ },
+ "subject": {
+ "type": "string",
+ "description": "Email subject"
+ }
+ },
+ "required": ["userId"]
+ },
+ "RecoverPasswordDto": {
+ "type": "object",
+ "properties": {
+ "html": {
+ "type": "string",
+ "description": "HTML template to be sent in the email;\n ##name## will be replaced with the user's name; \n ##token## will be replaced with the password recovery token",
+ "example": "
Click here to recover your password
" + }, + "email": { + "type": "string", + "description": "User's email address" + }, + "subject": { + "type": "string", + "description": "Email subject" + } + }, + "required": ["email"] + }, + "ResetPasswordDto": { + "type": "object", + "properties": { + "password": { + "type": "string", + "minLength": 3, + "maxLength": 30 + } + }, + "required": ["password"] + }, + "DeckAuthor": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": ["id", "name"] + }, + "DeckWithAuthor": { + "type": "object", + "properties": { + "author": { + "$ref": "#/components/schemas/DeckAuthor" + }, + "id": { + "type": "string" + }, + "userId": { + "type": "string" + }, + "name": { + "type": "string" + }, + "isPrivate": { + "type": "boolean" + }, + "shots": { + "type": "number" + }, + "cover": { + "type": "string", + "nullable": true + }, + "rating": { + "type": "number" + }, + "created": { + "format": "date-time", + "type": "string" + }, + "updated": { + "format": "date-time", + "type": "string" + }, + "cardsCount": { + "type": "number" + } + }, + "required": [ + "author", + "id", + "userId", + "name", + "isPrivate", + "shots", + "cover", + "rating", + "created", + "updated", + "cardsCount" + ] + }, + "Pagination": { + "type": "object", + "properties": { + "currentPage": { + "type": "number" + }, + "itemsPerPage": { + "type": "number" + }, + "totalPages": { + "type": "number" + }, + "totalItems": { + "type": "number" + } + }, + "required": ["currentPage", "itemsPerPage", "totalPages", "totalItems"] + }, + "PaginatedDecks": { + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/DeckWithAuthor" + } + }, + "pagination": { + "$ref": "#/components/schemas/Pagination" + }, + "maxCardsCount": { + "type": "number" + } + }, + "required": ["items", "pagination", "maxCardsCount"] + }, + "CreateDeckDto": { + "type": "object", + "properties": { + "cover": { + "type": "string", + "description": "Cover image (binary)", + "format": "binary" + }, + "name": { + "type": "string", + "minLength": 3, + "maxLength": 30 + }, + "isPrivate": { + "type": "boolean", + "description": "Private decks are not visible to other users" + } + }, + "required": ["name"] + }, + "UpdateDeckDto": { + "type": "object", + "properties": { + "cover": { + "type": "string", + "format": "binary" + }, + "name": { + "type": "string" + }, + "isPrivate": { + "type": "boolean" + } + } + }, + "Deck": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "userId": { + "type": "string" + }, + "name": { + "type": "string" + }, + "isPrivate": { + "type": "boolean" + }, + "shots": { + "type": "number" + }, + "cover": { + "type": "string", + "nullable": true + }, + "rating": { + "type": "number" + }, + "created": { + "format": "date-time", + "type": "string" + }, + "updated": { + "format": "date-time", + "type": "string" + }, + "cardsCount": { + "type": "number" + } + }, + "required": [ + "id", + "userId", + "name", + "isPrivate", + "shots", + "cover", + "rating", + "created", + "updated", + "cardsCount" + ] + }, + "CardWithoutRating": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "deckId": { + "type": "string" + }, + "userId": { + "type": "string" + }, + "question": { + "type": "string" + }, + "answer": { + "type": "string" + }, + "shots": { + "type": "number" + }, + "answerImg": { + "type": "string" + }, + "questionImg": { + "type": "string" + }, + "questionVideo": { + "type": "string" + }, + "answerVideo": { + "type": "string" + }, + "created": { + "format": "date-time", + "type": "string" + }, + "updated": { + "format": "date-time", + "type": "string" + } + }, + "required": [ + "id", + "deckId", + "userId", + "question", + "answer", + "shots", + "answerImg", + "questionImg", + "questionVideo", + "answerVideo", + "created", + "updated" + ] + }, + "PaginatedCards": { + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CardWithoutRating" + } + }, + "pagination": { + "$ref": "#/components/schemas/Pagination" + } + }, + "required": ["items", "pagination"] + }, + "CreateCardDto": { + "type": "object", + "properties": { + "question": { + "type": "string", + "minLength": 3, + "maxLength": 500 + }, + "answer": { + "type": "string", + "minLength": 3, + "maxLength": 500 + }, + "questionImg": { + "type": "string", + "minLength": 0, + "maxLength": 0 + }, + "answerImg": { + "type": "string", + "minLength": 0, + "maxLength": 0 + }, + "questionVideo": { + "type": "string", + "minLength": 3, + "maxLength": 500 + }, + "answerVideo": { + "type": "string", + "minLength": 3, + "maxLength": 500 + } + }, + "required": ["question", "answer"] + }, + "Card": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "deckId": { + "type": "string" + }, + "userId": { + "type": "string" + }, + "question": { + "type": "string" + }, + "answer": { + "type": "string" + }, + "shots": { + "type": "number" + }, + "answerImg": { + "type": "string" + }, + "questionImg": { + "type": "string" + }, + "questionVideo": { + "type": "string" + }, + "answerVideo": { + "type": "string" + }, + "rating": { + "type": "number" + }, + "created": { + "format": "date-time", + "type": "string" + }, + "updated": { + "format": "date-time", + "type": "string" + } + }, + "required": [ + "id", + "deckId", + "userId", + "question", + "answer", + "shots", + "answerImg", + "questionImg", + "questionVideo", + "answerVideo", + "rating", + "created", + "updated" + ] + }, + "SaveGradeDto": { + "type": "object", + "properties": { + "cardId": { + "type": "string" + }, + "grade": { + "type": "number", + "minimum": 1, + "maximum": 5 + } + }, + "required": ["cardId", "grade"] + }, + "UpdateCardDto": { + "type": "object", + "properties": { + "questionImg": { + "type": "string", + "minLength": 0, + "maxLength": 0, + "format": "binary" + }, + "answerImg": { + "type": "string", + "minLength": 0, + "maxLength": 0, + "format": "binary" + }, + "question": { + "type": "string", + "minLength": 3, + "maxLength": 500 + }, + "answer": { + "type": "string", + "minLength": 3, + "maxLength": 500 + }, + "questionVideo": { + "type": "string", + "minLength": 3, + "maxLength": 500 + }, + "answerVideo": { + "type": "string", + "minLength": 3, + "maxLength": 500 + } + } + } + } + } +} \ No newline at end of file diff --git a/src/modules/cards/entities/cards.entity.ts b/src/modules/cards/entities/cards.entity.ts index ea8fda3..8924adf 100644 --- a/src/modules/cards/entities/cards.entity.ts +++ b/src/modules/cards/entities/cards.entity.ts @@ -1,5 +1,3 @@ -import { OmitType } from '@nestjs/swagger' - import { Pagination } from '../../../infrastructure/common/pagination/pagination.dto' export class Card { @@ -13,15 +11,12 @@ export class Card { questionImg: string questionVideo: string answerVideo: string - rating: number created: Date updated: Date } -export class CardWithoutRating extends OmitType(Card, ['rating'] as const) {} - export class PaginatedCards { - items: CardWithoutRating[] + items: Card[] pagination: Pagination } diff --git a/src/modules/decks/entities/deck.entity.ts b/src/modules/decks/entities/deck.entity.ts index e1cb334..ab02c2c 100644 --- a/src/modules/decks/entities/deck.entity.ts +++ b/src/modules/decks/entities/deck.entity.ts @@ -7,7 +7,6 @@ export class Deck { isPrivate: boolean shots: number cover: string | null - rating: number created: Date updated: Date cardsCount: number