Skip to content

Commit

Permalink
173 get user favorite resources (IT-Academy-BCN#234)
Browse files Browse the repository at this point in the history
  • Loading branch information
danimorera authored May 23, 2023
1 parent 2fb4dde commit e9e6d15
Show file tree
Hide file tree
Showing 11 changed files with 349 additions and 6 deletions.
95 changes: 91 additions & 4 deletions back/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,96 @@ paths:
- No category found with this slug
required:
- message
/api/v1/favorites/by-user/:userId:
get:
tags:
- favorites
description: Takes a user Id and returns the list of favorites saved by that user
summary: Returns favorite resources by user
parameters:
- name: userId
in: path
required: true
description: ID of the user for which to retrieve favorite resources
- name: categorySlug
in: path
required: true
description: Slug of the category for which to retrieve favorite resources
example: node
responses:
"200":
description: Favorite resources retireved successfully.
content:
application/json:
schema:
type: array
items:
type: object
properties:
resource:
type: object
properties:
id:
type: string
title:
type: string
example: My Resource in Javascript
slug:
type: string
example: my-resource-in-javascript
description:
type: string
example: Lorem ipsum javascript
url:
type: string
format: uri
example: https://tutorials.cat/learn/javascript
resourceType:
type: string
enum: &a1
- BLOG
- VIDEO
- TUTORIAL
userId:
type: string
createdAt:
type: string
updatedAt:
type: string
required:
- id
- title
- url
- resourceType
- userId
- createdAt
- updatedAt
required:
- resource
"400":
description: User ID is required
content:
application/json:
schema:
type: object
properties:
error:
type: string
example: UserId is required
required:
- error
"404":
description: User not found
content:
application/json:
schema:
type: object
properties:
error:
type: string
example: User not found
required:
- error
/api/v1/resources:
get:
tags:
Expand Down Expand Up @@ -430,10 +520,7 @@ paths:
example: https://tutorials.cat/learn/javascript
resourceType:
type: string
enum: &a1
- BLOG
- VIDEO
- TUTORIAL
enum: *a1
createdAt:
type: string
updatedAt:
Expand Down
4 changes: 2 additions & 2 deletions back/prisma/seed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ async function seedDB() {
{
topicId: listasTopic?.id || '',
resourceId: secondResource?.id || '',
}
},
]

await prisma.topicsOnResources.createMany({
Expand All @@ -99,4 +99,4 @@ async function seedDB() {
})
}

seedDB()
seedDB()
128 changes: 128 additions & 0 deletions back/src/__tests__/favorites/favorites.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import supertest from 'supertest'
import { RESOURCE_TYPE, User, Topic } from '@prisma/client'
import { expect, test, describe, beforeAll, afterAll } from 'vitest'
import { server, testUserData } from '../globalSetup'
import { prisma } from '../../prisma/client'

let testUser: User
let testTopic: Topic

beforeAll(async () => {
testUser = (await prisma.user.findUnique({
where: { dni: testUserData.admin.dni },
})) as User

const resourceData = [
{
title: 'test-resource-1-favorites',
slug: 'test-resource-1-favorites',
description: 'random description',
url: 'https://sample.com',
userId: testUser.id,
resourceType: 'BLOG' as RESOURCE_TYPE,
},
{
title: 'test-resource-2-favorites',
slug: 'test-resource-2-favorites',
description: 'random description',
url: 'https://sample.com',
userId: testUser.id,
resourceType: 'VIDEO' as RESOURCE_TYPE,
},
]

const resources = await prisma.$transaction(
resourceData.map((resource) => prisma.resource.create({ data: resource }))
)

const favoritesData = [
{
userId: testUser.id,
resourceId: resources[0].id,
},
]

await prisma.favorites.createMany({
data: favoritesData,
})

testTopic = (await prisma.topic.findUnique({
where: { slug: 'testing' },
})) as Topic

const topicsOnResources = [
{
topicId: testTopic.id,
resourceId: resources[0].id,
},
]

await prisma.topicsOnResources.createMany({
data: topicsOnResources,
})
})

afterAll(async () => {
await prisma.topicsOnResources.deleteMany({
where: { topicId: testTopic.id },
})

await prisma.favorites.deleteMany({
where: { userId: testUser.id },
})

await prisma.resource.deleteMany({
where: { userId: testUser.id },
})
})

describe('Testing /favorites/ endpoint', () => {
describe('Testing GET /by-user/:userId?/:categorySlug?', () => {
test('Should respond OK status', async () => {
const userId = testUser.id
const response = await supertest(server).get(
`/api/v1/favorites/by-user/${userId}`
)
expect(response.status).toBe(200)
})
test('Should respond 400 status without userId', async () => {
const response = await supertest(server).get(`/api/v1/favorites/by-user/`)
expect(response.status).toBe(400)
})
test('Should respond 404 status without valid userId', async () => {
const userId = 'invalidUserId'
const response = await supertest(server).get(
`/api/v1/favorites/by-user/${userId}/testing`
)
expect(response.status).toBe(404)
})

test('Should return favorites as an array of objects.', async () => {
const userId = testUser.id
const categorySlug = 'testing'
const response = await supertest(server).get(
`/api/v1/favorites/by-user/${userId}/${categorySlug}`
)

expect(response.body).toBeInstanceOf(Array)
expect(response.body.length).toBeGreaterThan(0)
expect(response.body).toEqual(
expect.arrayContaining([
expect.objectContaining({
resource: expect.objectContaining({
id: expect.any(String),
title: expect.any(String),
slug: expect.any(String),
description: expect.any(String),
url: expect.any(String),
resourceType: expect.any(String),
userId: expect.any(String),
createdAt: expect.any(String),
updatedAt: expect.any(String),
}),
}),
])
)
})
})
})
44 changes: 44 additions & 0 deletions back/src/controllers/favoriteController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import Koa, { Middleware } from 'koa'
import { prisma } from '../prisma/client'
import { NotFoundError, DefaultError } from '../helpers/errors'

export const getUserFavoriteResources: Middleware = async (
ctx: Koa.Context
) => {
const { userId, categorySlug } = ctx.params
if (!userId) throw new DefaultError(400, 'User is needed')

const user = await prisma.user.findUnique({ where: { id: userId } })
if (!user) throw new NotFoundError('User not found')
let favorites
if (categorySlug) {
favorites = await prisma.favorites.findMany({
where: {
userId,
resource: {
topics: {
some: {
topic: {
category: { slug: categorySlug },
},
},
},
},
},
select: {
resource: true,
},
})
} else {
favorites = await prisma.favorites.findMany({
where: {
userId,
},
select: {
resource: true,
},
})
}
ctx.status = 200
ctx.body = favorites
}
1 change: 1 addition & 0 deletions back/src/controllers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ export { getVote, putVote } from './vote/voteController'
export { getTopics } from './topicController'
export { getCategories } from './categoryController'
export { postMedia } from './media/postMediaController'
export { getUserFavoriteResources } from './favoriteController'
65 changes: 65 additions & 0 deletions back/src/openapi/routes/favorites.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { pathRoot } from '../../routes/routes'
import { registry } from '../registry'
import { resourceSchema } from '../../schemas'
import { z } from '../zod'

registry.registerPath({
method: 'get',
tags: ['favorites'],
path: `${pathRoot.v1.favorites}/by-user/:userId`,
description:
'Takes a user Id and returns the list of favorites saved by that user',
summary: 'Returns favorite resources by user',
parameters: [
{
name: 'userId',
in: 'path',
required: true,
description: 'ID of the user for which to retrieve favorite resources',
},
{
name: 'categorySlug',
in: 'path',
required: true,
description:
'Slug of the category for which to retrieve favorite resources',
example: 'node',
},
],
responses: {
200: {
description: 'Favorite resources retireved successfully.',
content: {
'application/json': {
schema: z.array(
z.object({
resource: resourceSchema.omit({
topics: true,
}),
})
),
},
},
},
400: {
description: 'User ID is required',
content: {
'application/json': {
schema: z.object({
error: z.string().openapi({ example: 'UserId is required' }),
}),
},
},
},
404: {
description: 'User not found',
content: {
'application/json': {
schema: z.object({
error: z.string().openapi({ example: 'User not found' }),
}),
},
},
},
},
})
1 change: 1 addition & 0 deletions back/src/openapi/routes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import './auth/register'
import './auth/me'
import './categories'
import './topics/topics'
import './favorites'
import './resources/get'
import './resources/create'
import './resources/topic'
Expand Down
14 changes: 14 additions & 0 deletions back/src/routes/favoritesRouter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import Router from '@koa/router'
import { getUserFavoriteResources } from '../controllers'
import { pathRoot } from './routes'

const favoritesRouter = new Router()

favoritesRouter.prefix(pathRoot.v1.favorites)

favoritesRouter.get(
'/by-user/:userId?/:categorySlug?',
getUserFavoriteResources
)

export { favoritesRouter }
1 change: 1 addition & 0 deletions back/src/routes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@ export { authRouter } from './authRouter'
export { resourcesRouter } from './resourcesRouter'
export { topicsRouter } from './topicsRouter'
export { categoriesRouter } from './categoriesRouter'
export { favoritesRouter } from './favoritesRouter'
export { mediaRouter } from './mediaRouter'
export { voteRouter } from './voteRouter'
Loading

0 comments on commit e9e6d15

Please sign in to comment.