wiki:UseCaseImplementations

UseCaseImplementations

  • кратка презентација на финалниот изглед на прототипот на овој линк

Кратки инструкции како да се компајлира

docker compose up

  • впишете ја адресата localhost во прелистувачот
  • регистрирајте се
  • логирајте се

проектот содржи README.md

Едно од поважните сценарија Search

search by genre

Откако ќе се логираме, треба да притиснеме на копчето Search заокружено и означено со број 1. Тоа ќе не пренасочи на рутата !/search. Тука може да забележиме компонента за пребарување на музика и подкасти според жанра/категорија или според име.

На примерот со број 2 е означен табот со кој одбираме да пребаруваме музика. Одбираме жанра Pop означена со број 3. И на крај притискаме на копчето Search кое праќа GET барање до серверот.

search by genre 2

Резултатот е прикажан во табелите Songs и Albums. Taбелите ги прикажуваат само песните/албумите филтрирани според жанрата што ја одбравме. Жанрата на албумот е дознаена индиректно преку кои се жанрите на песните што се дел од еден албум.

Доколку не одбереме да пребаруваме по нешто специфично се враќаат сите песни и албуми. Ова е прикажано во следните две слики.

if no genre 1

every album if no genre

@app.get("/albums", response_model=Union[List[schemas.Album] | schemas.Album])
def get_albums(
    album_id: Optional[int] = Query(None),
    genres: Optional[List[str]] = Query(None),
    search_string: Optional[str] = None,
    db: Session = Depends(get_db),
    buzzer: models.Buzzer = Depends(validate_session)
):
    albums_query = db.query(models.Album)

    if album_id:
        try:
            album = albums_query.filter(models.Album.album_id == album_id).one()
        except NoResultFound:
            raise HTTPException(status_code=404, detail="Album not found.")
        return album

    if genres:
        print("Filtering by genre..")
        albums_query = albums_query.join(models.Song, models.Album.album_id == models.Song.album_id)\
                                        .join(models.IsOfGenre, models.IsOfGenre.creation_id == models.Song.song_id)\
                                        .join(models.Genre, models.Genre.genre_id == models.IsOfGenre.genre_id)\
                                        .filter(models.Genre.genre_name.in_(genres))\
                                        .distinct()

    if search_string:
        print("Filtering by album name..")
        albums_query = albums_query.filter(models.Album.album_title.ilike(f"%{search_string}%"))

    albums = albums_query.all()

    return albums

Овој ендпоинт е дизајниран за да ги земе албумите од базата на податоци врз основа на различни query параметри.

(1) /albums

Ендпоинтот е пристаплив преку HTTP GET барање на "/albums" рутата.

(2) Query параметри:

  • album_id: Опционален целоброен параметар кој се користи за да се земе одреден албум според неговиот идентификатор.
  • genres: Опционална листа од стрингови која се користи за да се филтрираат албумите според жанри.
  • search_string: Опционален стринг кој се користи за да се филтрираат албумите според нивните наслови.

(3) Зависности:

db: за сесијата на базата на податоци. buzzer: за валидација на сесијата.

(4) Функционалност:

Ако е даден album_id, се обидува да го земе албумот со тоа id и го враќа. Доколку не е пронајден, повикува 404 HTTP exception. Ако се дадени genres, филтрира албуми според тие жанрови со поврзување на Album, Song, IsOfGenre, и Genre табелите и филтрирање според имињата на жанровите. Се враќаат само различни албуми. Ако е даден search_string, филтрира албуми според насловите на албумите безразлика на големи и мали букви. На крај, ги зема сите албуми кои одговараат на специфицираните критериуми и ги враќа.

(5) Одговор:

Одговорот може да биде или еден албум (ако е даден album_id) или листа од албуми. Одговорот е серијализиран според шемата Album дефинирана во модулот schemas.

@app.get("/songs", response_model=List[schemas.Song])
def get_songs(
    genres: Optional[List[str]] = Query(None),
    search_string: Optional[str] = None,
    db: Session = Depends(get_db),
    buzzer: models.Buzzer = Depends(validate_session)
):
    songs_query = db.query(models.Song).join(models.Creation, models.Song.song_id == models.Creation.creation_id)

    if genres:
        songs_query = songs_query.join(models.IsOfGenre, models.IsOfGenre.creation_id == models.Song.song_id)\
                                 .join(models.Genre, models.Genre.genre_id == models.IsOfGenre.genre_id)\
                                 .filter(models.Genre.genre_name.in_(genres))

    if search_string:
        songs_query = songs_query.filter(models.Song.song_title.contains(search_string))

    songs = songs_query.all()

    result = []
    for song in songs:
        song_data = {
            "song_id": song.song_id,
            "album_id": song.album_id,
            "rlabel_id": song.rlabel_id,
            "lyrics": song.lyrics,
            "song_title": song.song_title,
            "song_file": song.song_file,
            "song_duration": song.creation.creation_duration,
            "song_date": song.creation.creation_date,
            "created_by": song.creation.created_by,
            "genres": get_genres_for_song(song.song_id, db)
        }
        result.append(schemas.Song(**song_data))

    return result

Ова е ендпоинтот "/songs" за пребарување на песни според жанра и име. На сличен начин функционира како и претходната функција.

class Song(Base):
    __tablename__ = 'song'
    __table_args__ = {'schema': 'IND0_185022'}

    song_id = Column(Integer, ForeignKey('IND0_185022.creation.creation_id'), primary_key=True, autoincrement=True)
    album_id = Column(Integer, ForeignKey('IND0_185022.album.album_id'), nullable=True)
    rlabel_id = Column(Integer, ForeignKey('IND0_185022.record_label.rlabel_id'), nullable=True)
    lyrics = Column(TEXT, nullable=True)
    song_title = Column(String(255))
    song_file = Column(TEXT)


    creation= relationship("Creation", back_populates="song")
    genres = relationship("IsOfGenre", back_populates="song")

Ова е моделот Song кој наследува од Base моделот на SQLAlchemy библиотеката. Го дефинираме името на табелата заедно со колоните на табелата.

Исто така потребна ни е и следната шема SongBase, заедно со Song и SongCreate, за да можеме полесно да серијализираме објекти и да имаме дата валидација.

class SongBase(BaseModel):
    album_id: Optional[int] = None
    rlabel_id: Optional[int] = None
    lyrics: Optional[str] = None
    song_title: str
    song_file: str
    song_duration: int
    song_date: date
    genres: Optional[List[str]]


class Song(SongBase):
    song_id: int

    class Config:
        orm_mode = True


class SongCreate(SongBase):
    pass

Истото го имаме направено и за Album.

class Album(Base):
    __tablename__ = 'album'
    __table_args__ = {'schema': 'IND0_185022'}

    album_id = Column(Integer, primary_key=True, autoincrement=True)
    album_title = Column(String(255))
    album_date = Column(TIMESTAMP, default=func.now())
    al_created_by = Column(Integer, ForeignKey('IND0_185022.artist.user_id'))
    rlabel_id = Column(Integer, ForeignKey('IND0_185022.record_label.rlabel_id'))
class AlbumBase(BaseModel):
    album_title: str
    al_created_by: int
    rlabel_id: int


class AlbumCreate(AlbumBase):
    pass


class Album(AlbumBase):
    album_id: int

    class Config:
        orm_mode = True

Следува кодот кој е дел од frontend-от и тоа е компонентата Search. Импортира зависности и компоненти од надворешни датотеки/модули, вклучувајќи React hooks, API функции, контекстни провајдери, енумерации и други компоненти.

Користи useState hook за менаџирање на локалната состојба за categories и genres. Овие состојби ги чуваат податоците превземени од серверот (категории и жанрови).

Користи useContext hook за пристап до податоци поврзани со автентикацијата од контекстниот провајдер AuthContext.

Користи useEffect hook за извршување на странични ефекти (како преземање на податоци) откако компонентата ќе се рендерира. Презема категории и жанрови кога activeTab се менува или кога компонентата се монтира.

Рендира кориснички интерфејс за страницата за пребарување, вклучувајќи ја компонентата Header, компонентата SearchTab и две табели кои прикажуваат песни/албуми или епизоди/подкасти во зависност од активниот таб.

Условно рендира табели врз основа на тоа дали постоjaт податоци за creations или collections. Доколку податоците постојат, ги предава на компонентата Таble заедно со наслов кој го покажува видот на креации кои се прикажани.

Се користат CSS класи од библиотеката tailwind, за да се дефинира изгледот на различните елементи во страницата за пребарување.

import { ChangeEvent, useContext, useEffect, useState } from "react"
import { get_categories, get_genres } from "./api/creation";
import { AuthContext } from "./AuthContext";
import { CreationType } from "./enums";
import Header from "./Header"
import InputField from "./inputField"
import { useSearchContext } from "./SearchContext";
import SearchTab from "./SearchTab";
import Table from "./Table.new"

const Search = () => {
    const { creations, collections, activeTab } = useSearchContext()
    const [categories, setCategories] = useState<Category[]>([])
    const [genres, setGenres] = useState<string[]>([])
    const { sessionUuid } = useContext(AuthContext)
    useEffect(() => {
        if (sessionUuid) {
            get_categories(sessionUuid).then(res => setCategories(res))
            get_genres(sessionUuid).then(res => setGenres(res))
        }
    }, [activeTab])

    return (
        <div className="bg-black text-purple-300 min-h-screen  h-fit p-10">
            <Header />
            <div>
                <h1 className="my-3 text-white text-bold text-5xl">Search</h1>
                <SearchTab categories={categories} genres={genres} />
            </div>
            {
                creations ?
                    <Table rows={creations} title={activeTab == CreationType.Song ? "Songs" : "Episodes"} />
                    : null
            }
            {
                collections ?
                    <Table rows={collections} title={activeTab == CreationType.Song ? "Albums" : "Podcasts"} />
                    : null
            }
        </div>
    )
}

export default Search;

Oва е функција која е дел од api-то што комуницира со eндпоинтот на backend-oт. Функцијата прави GET барање со две заглавја меѓу кои е и идентификаторот на сесијата. Тоа ни е потребно бидејќи рутата е заштитена и корисници кои не се логирани, не можат да пристапат до неа.

async function get_genres(sessionUuid: string) {
    const options = {
        method: "GET",
        headers: {
            "Content-Type": "application/json",
            "session-uuid": sessionUuid
        }
    }
    return fetch("http://localhost:8000/genres", options)
        .then((res) => res.json())
        .catch((err) => {
            console.log(err)
        })
}

Oткако ќе се притисне копчето Search, кое е дел од SearchTab компонента чиј код не е прикажан тука но е дел од претходната компонента, се повикува следната функција која ги формира соодветните параметри за пребарување.

async function get_songs(genres: Set<string>, searchString: string, sessionUuid: string): Promise<SongRes[]> {
    let genres_query = ''
    if (genres) {
        genres_query = Array.from(genres).map(genre => `genres=${genre}&`).join("")
    }
    let query = genres_query
    if (searchString) {
        query = query + "search_string=" + searchString
    }

    return await fetch(`http://localhost:8000/songs?${query}`,
        {
            method: "GET",
            headers: {
                'Content-Type': 'application/json',
                'Session-Uuid': sessionUuid
            }
        }).then(response => {
            if (!response.ok) {
                throw new Error('Network response was not ok');
            }
            return response.json();
        })
}

Низ апликацијата се користени и типови за олеснување на процесот на развивање на апликацијата. Еден од тие типови е SongRes.

interface SongRes {
    album_id?: number,
    rlabel_id: number,
    lyrics: string,
    song_title: string,
    song_file: string,
    song_duration: number,
    song_date: string | Date,
    genres: string[],
    song_id?: number
}
Last modified 6 weeks ago Last modified on 04/09/24 18:38:53

Attachments (9)

Download all attachments as: .zip

Note: See TracWiki for help on using the wiki.