Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 19 additions & 5 deletions src/app/api/movies/route.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,28 @@
import { NextResponse } from 'next/server'
import connectDB from '@/lib/db/connectDB'
import { getAllMovies, createMovieFromOmdbTitle, findMovieByTitle } from '@/lib/db/movieDbService'
import { getAllMovies, createMovieFromOmdbTitle, findMovieByTitle, findMoviesByTitle } from '@/lib/db/movieDbService'
import { requireAdminAccess } from '@/lib/auth/requireAdminAccess'

export async function GET() {

export async function GET(req) {
await connectDB()
const movies = await getAllMovies()
return NextResponse.json(movies)
}

//Backend search logic
const { searchParams } = new URL(req.url)
const query = searchParams.get('q')

try {
if (query) {
const result = await findMoviesByTitle(query)
return NextResponse.json(result)
}

const movies = await getAllMovies()
return NextResponse.json(movies)
} catch (error) {
return NextResponse.json({ error: error.message }, { status: 500 })
}
}
export async function POST(req) {
if (requireAdminAccess()) {
return NextResponse.json({ error: 'Endast tillgängligt för administratörer' }, { status: 403 })
Expand Down
63 changes: 62 additions & 1 deletion src/components/SearchField.jsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,72 @@
'use client'

import { useState, useEffect } from 'react'

//Updating the SearchField to handle a backend search logic
export default function SearchField() {
const [query, setQuery] = useState('')
const [results, setResults] = useState([])

const handleSearch = async () => {
if (query.trim() === '') return
try {
const res = await fetch(`/api/movies?q=${encodeURIComponent(query)}`)
if (!res.ok) throw new Error('Något gick fel vid sökning')
const data = await res.json()
setResults(data)
} catch (error) {
console.error('Sökfel:', error)
setResults([])
}
}

//Debounce for UX improvements
useEffect(() => {
const timeoutId = setTimeout(() => {
handleSearch()
}, 500)

return () => clearTimeout(timeoutId)
}, [query])

return (
<>
<div className="searchfield-container">
<h2>Sök efter film:</h2>
<input type="text" placeholder="skriv här..."></input>
<input
type="text"
placeholder="skriv här..."
value={query}
onChange={(e) => setQuery(e.target.value)}
aria-label="Sök efter film"
onKeyDown={(e) => {
if (e.key === 'Enter') {
handleSearch()
}
}}
></input>
<button className="search-button" onClick={handleSearch}>
Sök
</button>

<div className="moviecard__list-search">
{results.length === 0 && query !== '' && (
<p>
Inga resultat hittades för <strong>"{query}"</strong>
</p>
)}

{results.map((movie) => (
<a key={movie._id} href={`/movies/${movie._id}`}>
<div className="moviecard__container-search">
{movie.posterUrl && (
<img src={movie.posterUrl} alt={`${movie.title} poster`} className="moviecard__poster-search" />
)}
<p className="moviecard__title-search">{movie.title}</p>
</div>
</a>
))}
</div>
</div>
</>
)
Expand Down
13 changes: 13 additions & 0 deletions src/lib/db/movieDbService.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,16 @@ export async function createMovieFromOmdbTitle(title) {

return await movie.save()
}

//MongoDB searches all titles that contains the query, no matter if upper or lower case
function escapeRegex(string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
}

export async function findMoviesByTitle(query) {
await connectDB()
const safeQuery = escapeRegex(query)
return await Movie.find({
title: { $regex: safeQuery, $options: 'i' },
}).lean() //Lean improves performance, returns JS objects instead of mongoose documents.
}
33 changes: 33 additions & 0 deletions src/styles/MovieCard.scss
Original file line number Diff line number Diff line change
Expand Up @@ -98,3 +98,36 @@
text-align: center;
}
}

.moviecard__list-search {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 1rem;
margin-top: 1rem;
}

.moviecard__container-search {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
background: #f2f2f2;
padding: 1rem;
border-radius: 8px;
transition: transform 0.2s ease;
}

.moviecard__container-search:hover {
transform: scale(1.05);
}

.moviecard__poster-search {
width: 100%;
height: auto;
border-radius: 4px;
}

.moviecard__title-search {
margin-top: 0.5rem;
font-weight: bold;
}
16 changes: 16 additions & 0 deletions src/styles/moviePage.scss
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,22 @@
}
}

.search-button {
background-color: #03658c;
color: white;
border: none;
padding: 0.5rem 1rem;
margin-left: 0.2rem;
border-radius: 6px;
font-weight: bold;
cursor: pointer;
transition: background-color 0.2s ease;
}

.search-button:hover {
background-color: #02496a;
}

.moviecard__list {
list-style: none;

Expand Down
6 changes: 4 additions & 2 deletions src/tests/api/movies/movies.test.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// src/tests/admin/admin.test.js
import { FormData } from 'formdata-node'
import { expect, jest, test, describe, beforeEach, beforeAll } from '@jest/globals'
import { findMovieById } from '@/lib/db/movieDbService'
import { findMoviesByTitle } from '@/lib/db/movieDbService'

// Mock database and services
jest.unstable_mockModule('@/lib/db/connectDB', () => ({
Expand All @@ -16,6 +16,7 @@ jest.unstable_mockModule('@/lib/db/movieDbService', () => ({
getAllMovies: jest.fn(),
deleteMovieAndScreenings: jest.fn(),
findMovieById: jest.fn(),
findMoviesByTitle: jest.fn(),
}))

let POST, GET, DELETE
Expand Down Expand Up @@ -93,7 +94,8 @@ describe('GET /api/movies (mocked)', () => {
const mockMovies = [{ _id: '1', title: 'Interstellar' }]
movieService.getAllMovies.mockResolvedValue(mockMovies)

const res = await GET()
const req = { url: 'http://localhost/api/movies' }
const res = await GET(req)
const data = await res.json()

expect(res.status).toBe(200)
Expand Down
1 change: 0 additions & 1 deletion src/tests/api/rooms/rooms.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { getRoomById } from '@/lib/db/roomDbService'
import { expect, jest, test, describe, beforeAll, beforeEach } from '@jest/globals'

jest.unstable_mockModule('@/lib/db/connectDB', () => ({
Expand Down
1 change: 0 additions & 1 deletion src/tests/api/screenings/screenings.test.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// src/tests/screenings/screenings.test.js
import { FormData } from 'formdata-node'
import { expect, jest, test, describe, beforeEach, beforeAll } from '@jest/globals'
import { getScreeningById } from '@/lib/db/screeningDbService'

// Mock database and services
jest.unstable_mockModule('@/lib/db/connectDB', () => ({
Expand Down