Skip to content

Commit

Permalink
✨ Added search
Browse files Browse the repository at this point in the history
  • Loading branch information
mawoka-myblock committed Apr 14, 2022
1 parent 22b51e7 commit d43795b
Show file tree
Hide file tree
Showing 9 changed files with 170 additions and 22 deletions.
1 change: 1 addition & 0 deletions Caddyfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
reverse_proxy /openapi.json localhost:8000
reverse_proxy /socket.io* localhost:8000
reverse_proxy /socket.io/* localhost:8000
reverse_proxy /ms http://localhost:7700


}
Expand Down
1 change: 1 addition & 0 deletions Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ jinja2 = "*"
argon2-cffi = "*"
sentry-sdk = "*"
aiofiles = "*"
meilisearch = "*"

[dev-packages]
coverage = "*"
Expand Down
54 changes: 35 additions & 19 deletions Pipfile.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion classquiz/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

from classquiz.config import settings
from classquiz.db import database
from classquiz.routers import users, quiz, utils, stats, storage
from classquiz.routers import users, quiz, utils, stats, storage, search
from classquiz.socket_server import sio

settings = settings()
Expand Down Expand Up @@ -46,4 +46,5 @@ async def shutdown() -> None:
app.include_router(utils.router, tags=["utils"], prefix="/api/v1/utils")
app.include_router(stats.router, tags=["stats"], prefix="/api/v1/stats")
app.include_router(storage.router, tags=["storage"], prefix="/api/v1/storage")
app.include_router(search.router, tags=["search"], prefix="/api/v1/search")
app.mount("/", ASGIApp(sio))
5 changes: 5 additions & 0 deletions classquiz/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import redis.asyncio as redis_lib
from pydantic import BaseSettings, RedisDsn, PostgresDsn
import meilisearch as MeiliSearch

from classquiz.storage import Storage

Expand All @@ -25,6 +26,8 @@ class Settings(BaseSettings):
access_token_expire_minutes: int = 30
cache_expiry: int = 86400
sentry_dsn: str | None = "https://4981de1f72f24fd7b5e21b8913b93a02@o661934.ingest.sentry.io/6254641"
meilisearch_url: str
meilisearch_index: str = "classquiz"

# storage_backend
storage_backend: str | None = "deta"
Expand Down Expand Up @@ -52,3 +55,5 @@ def settings() -> Settings:
deta_id=settings().deta_project_id,
storage_path=settings().storage_path,
)

meilisearch = MeiliSearch.Client(settings().meilisearch_url)
16 changes: 14 additions & 2 deletions classquiz/routers/quiz.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,12 @@
from pydantic import ValidationError

from classquiz.auth import get_current_user, get_current_user_optional
from classquiz.config import redis, settings, storage
from classquiz.config import redis, settings, storage, meilisearch
from classquiz.db.models import Quiz, QuizInput, User, PlayGame
from classquiz.kahoot_importer.import_quiz import import_quiz

settings = settings()


router = APIRouter()


Expand All @@ -31,6 +30,12 @@ async def create_quiz_lol(quiz_input: QuizInput, user: User = Depends(get_curren
raise HTTPException(status_code=400, detail="image url is not valid")
quiz = Quiz(**quiz_input.dict(), user_id=user.id, id=uuid.uuid4())
await redis.delete("global_quiz_count")
meilisearch.index(settings.meilisearch_index).add_documents([{
"id": str(quiz.id),
"title": quiz.title,
"description": quiz.description,
"user": (await User.objects.filter(id=quiz.user_id).first()).username,
}])
return await quiz.save()


Expand Down Expand Up @@ -117,6 +122,12 @@ async def update_quiz(quiz_id: str, quiz_input: QuizInput, user: User = Depends(
quiz.description = quiz_input.description
quiz.updated_at = datetime.now()
quiz.questions = quiz_input.dict()["questions"]
meilisearch.index(settings.meilisearch_index).update_documents([{
"id": str(quiz.id),
"title": quiz.title,
"description": quiz.description,
"user": (await User.objects.filter(id=quiz.user_id).first()).username,
}])
return await quiz.update()


Expand Down Expand Up @@ -149,4 +160,5 @@ async def delete_quiz(quiz_id: str, user: User = Depends(get_current_user)):
pass
if len(pics_to_delete) != 0:
await storage.delete(pics_to_delete)
meilisearch.index(settings.meilisearch_index).delete_document(str(quiz.id))
return await quiz.delete()
86 changes: 86 additions & 0 deletions classquiz/routers/search.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import json
import re
import uuid
from datetime import datetime
from random import randint

import pydantic
from fastapi import APIRouter, Depends, HTTPException
from fastapi.responses import JSONResponse
from pydantic import ValidationError

from classquiz.auth import get_current_user, get_current_user_optional
from classquiz.config import redis, settings, storage, meilisearch
from uuid import UUID
from typing import Optional, List
from classquiz.db.models import Quiz, QuizInput, User, PlayGame
from classquiz.kahoot_importer.import_quiz import import_quiz

settings = settings()

router = APIRouter()


class Hit(pydantic.BaseModel):
id: UUID
title: str
description: str
user: str
formatted: Optional['Hit'] = pydantic.Field(None, alias='_formatted')


class SearchResponse(pydantic.BaseModel):
hits: List[Hit] | list[None]
nbHits: int
exhaustiveNbHits: bool
query: str
limit: int
offset: int
processingTimeMs: int


class SearchData(pydantic.BaseModel):
q: str
offset: Optional[int] = 0
limit: Optional[int] = 20
filter: Optional[str] = None
facetsDistribution: Optional[list[str]] = None
attributesToRetrieve: Optional[list[str]] = ["*"]
attributesToCrop: Optional[list[str]]= None
cropLength: Optional[int] = 200
attributesToHighlight: Optional[list[str]] = None
matches: Optional[bool] = False
sort: Optional[list[str]] = None


@router.post("/search", response_model=SearchResponse)
async def search(data: SearchData):
index = meilisearch.get_index(settings.meilisearch_index)
query = index.search(data.q, {
"offset": data.offset,
"limit": data.limit,
"filter": data.filter,
"cropLength": data.cropLength,
"matches": data.matches,
"facetsDistribution": data.facetsDistribution,
"attributesToRetrieve": data.attributesToRetrieve,
"attributesToCrop": data.attributesToCrop,
"sort": data.sort,
"attributesToHighlight": data.attributesToHighlight
})
return query.serialize()


@router.get("/search", response_model=SearchResponse)
async def search_get(q: str, offset: int = 0, limit: int = 20, filter: str | None = None,
cropLength: int = 200, matches: bool = False, attributesToHighlight: Optional[str] = "*"):
index = meilisearch.get_index(settings.meilisearch_index)
query = index.search(q, {
"offset": offset,
"limit": limit,
"filter": filter,
"cropLength": cropLength,
"matches": matches,
"attributesToHighlight": [attributesToHighlight]
})
return SearchResponse(**query)
Empty file added classquiz/scripts/__init__.py
Empty file.
26 changes: 26 additions & 0 deletions import_to_meili.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import meilisearch
from classquiz.db.models import Quiz, User
from pprint import pprint
from classquiz.config import settings
from asyncio import run

settings = settings()


async def __main__():
meili_data = []
questions = await Quiz.objects.filter(public=True).all()
for question in questions:
meili_data.append({
"id": str(question.id),
"title": question.title,
"description": question.description,
"user": (await User.objects.filter(id=question.user_id).first()).username,
})
print(meili_data)
client = meilisearch.Client(settings.meilisearch_url)
client.index(settings.meilisearch_index).add_documents(meili_data)


if __name__ == "__main__":
run(__main__())

0 comments on commit d43795b

Please sign in to comment.