-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
17 changed files
with
541 additions
and
38 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
drop table if exists public.emojis; | ||
|
||
create table public.emojis ( | ||
emoji text not null primary key, | ||
alias text null | ||
references public.emojis (emoji) | ||
on update cascade | ||
on delete cascade, | ||
owner bigint null | ||
references public.users (user_id) | ||
on update cascade | ||
on delete set null, | ||
post_id bigint null | ||
references public.posts (post_id) | ||
on update cascade | ||
on delete cascade, | ||
alt text null, | ||
filename text not null, | ||
unique (alias), | ||
unique (post_id) | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
alter table public.reports | ||
add column parent bigint null | ||
references public.reports (report_id) | ||
on update cascade | ||
on delete cascade; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
from typing import Self | ||
|
||
from shared.auth import KhUser | ||
from shared.exceptions.http_error import Conflict, HttpErrorHandler, NotFound | ||
|
||
from .models import AliasRequest, CreateRequest, Emoji, InternalEmoji | ||
from .repository import EmojiRepository, users | ||
from psycopg2.errors import UniqueViolation | ||
|
||
|
||
class Emojis(EmojiRepository) : | ||
|
||
@HttpErrorHandler('creating emoji', | ||
handlers={ | ||
UniqueViolation: (Conflict, 'emoji or alias already exists'), | ||
}, | ||
) | ||
async def create(self: Self, user: KhUser, req: CreateRequest) -> Emoji : | ||
iemoji = InternalEmoji( | ||
emoji = req.emoji, | ||
owner = await users._handle_to_user_id(req.owner) if req.owner else None, | ||
post_id = req.post_id.int() if req.post_id else None, | ||
filename = req.filename, | ||
) | ||
await super().create(iemoji) | ||
return await self.emoji(user, iemoji) | ||
|
||
|
||
async def read(self: Self, user: KhUser, emoji: str) -> Emoji : | ||
iemoji = await self._read(emoji) | ||
|
||
if not iemoji : | ||
raise NotFound('emoji does not exist') | ||
|
||
return await self.emoji(user, iemoji) | ||
|
||
|
||
@HttpErrorHandler('creating emoji alias', | ||
handlers={ | ||
UniqueViolation: (Conflict, 'emoji or alias already exists'), | ||
}, | ||
) | ||
async def alias(self: Self, user: KhUser, req: AliasRequest) -> Emoji : | ||
return await self.emoji(user, await super().alias(req.alias_of, req.emoji)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
from typing import Optional | ||
|
||
from pydantic import BaseModel, Field, validator | ||
|
||
from shared.models._shared import PostId, UserPortable, _post_id_converter | ||
from shared.sql.query import Table | ||
|
||
|
||
class InternalEmoji(BaseModel) : | ||
_post_id_converter = validator('post_id', pre=True, always=True, allow_reuse=True)(_post_id_converter) | ||
__table_name__: Table = Table('kheina.public.emojis') | ||
|
||
emoji: str = Field(description='orm:"pk"') | ||
alt: Optional[str] = None | ||
alias: Optional[str] = None | ||
owner: Optional[int] = None | ||
post_id: Optional[int] = None | ||
filename: str | ||
|
||
|
||
class Emoji(BaseModel) : | ||
_post_id_converter = validator('post_id', pre=True, always=True, allow_reuse=True)(_post_id_converter) | ||
|
||
class Config: | ||
validate_assignment = True | ||
|
||
emoji: str | ||
alt: Optional[str] = None | ||
owner: Optional[UserPortable] = None | ||
post_id: Optional[PostId] = None | ||
filename: str | ||
url: str = '' | ||
|
||
@validator('url', pre=True, always=True) | ||
def validate_url(cls, v, values) : | ||
return 'emoji/' + values['filename'] | ||
|
||
|
||
class CreateRequest(BaseModel) : | ||
_post_id_converter = validator('post_id', pre=True, always=True, allow_reuse=True)(_post_id_converter) | ||
|
||
emoji: str | ||
owner: Optional[str] = None | ||
post_id: Optional[PostId] = None | ||
filename: str | ||
|
||
|
||
class AliasRequest(BaseModel) : | ||
emoji: str | ||
alias_of: str |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,160 @@ | ||
from typing import Optional, Self | ||
|
||
from shared.auth import KhUser | ||
from shared.caching import AerospikeCache | ||
from shared.caching.key_value_store import KeyValueStore | ||
from shared.exceptions.http_error import BadRequest, NotFound | ||
from shared.models import PostId | ||
from shared.sql import SqlInterface | ||
from users.repository import Users | ||
|
||
from .models import Emoji, InternalEmoji | ||
|
||
|
||
kvs: KeyValueStore = KeyValueStore('kheina', 'emojis', local_TTL=60) | ||
aliaskvs: KeyValueStore = KeyValueStore('kheina', 'emoji_alias', local_TTL=3600) | ||
users = Users() | ||
|
||
|
||
class EmojiRepository(SqlInterface) : | ||
|
||
async def create(self: Self, emoji: InternalEmoji) -> None : | ||
if emoji.alias : | ||
raise BadRequest('cannot create an emoji with an alias') | ||
|
||
await self.insert(emoji) | ||
|
||
|
||
@AerospikeCache('kheina', 'emojis', '{emoji}') | ||
async def _read(self: Self, emoji: str) -> Optional[InternalEmoji] : | ||
data: Optional[tuple[str, Optional[str], Optional[str], Optional[int], Optional[int], str]] = await self.query_async(""" | ||
select | ||
emojis.emoji, | ||
emojis.alt, | ||
emojis.alias, | ||
emojis.owner, | ||
emojis.post_id, | ||
emojis.filename | ||
from kheina.public.emojis | ||
where emojis.emoji = %s | ||
limit 1; | ||
""", ( | ||
emoji, | ||
), | ||
fetch_one=True, | ||
) | ||
|
||
if not data : | ||
return None | ||
|
||
return InternalEmoji( | ||
emoji = data[0], | ||
alt = data[1], | ||
alias = data[2], | ||
owner = data[3], | ||
post_id = data[4], | ||
filename = data[5], | ||
) | ||
|
||
|
||
async def emoji(self: Self, user: KhUser, iemoji: InternalEmoji) -> Emoji : | ||
return Emoji( | ||
emoji = iemoji.alias or iemoji.emoji, | ||
alt = iemoji.alt, | ||
owner = await users.portable(user, await users._get_user(iemoji.owner)) if iemoji.owner else None, | ||
post_id = PostId(iemoji.post_id) if iemoji.post_id else None, | ||
filename = iemoji.filename, | ||
) | ||
|
||
|
||
@AerospikeCache('kheina', 'emoji_alias', '{emoji}', _kvs=aliaskvs) | ||
async def aliases(self: Self, emoji: str) -> list[str] : | ||
data: list[tuple[str]] = await self.query_async(""" | ||
select emoji | ||
from kheina.public.emojis | ||
where alias = %s; | ||
""", ( | ||
emoji, | ||
), | ||
fetch_all = True, | ||
) | ||
|
||
return list(map(lambda x : x[0], data)) | ||
|
||
|
||
async def alias(self: Self, emoji: str, alias: str) -> InternalEmoji : | ||
""" | ||
creates a new emoji alias from the given emoji. alias will be a clone of emoji | ||
an alias cannot be created of another alias. Use the original emoji instead. | ||
""" | ||
|
||
if not emoji : | ||
raise BadRequest('empty emoji given') | ||
|
||
if not alias : | ||
raise BadRequest('empty alias given') | ||
|
||
iemoji = await self._read(emoji) | ||
|
||
if not iemoji : | ||
raise BadRequest('emoji not found') | ||
|
||
if iemoji.alias : | ||
raise BadRequest('cannot create an alias of another alias') | ||
|
||
iemoji.alias = iemoji.emoji | ||
iemoji.emoji = alias | ||
aliases = await self.aliases(iemoji.alias) | ||
aliases.append(iemoji.emoji) | ||
await kvs.put_async(iemoji.emoji, iemoji) | ||
await aliaskvs.put_async(iemoji.alias, aliases) | ||
return await self.insert(iemoji) | ||
|
||
|
||
async def update(self: Self, emoji: InternalEmoji) -> None : | ||
""" | ||
updates an emoji and all of its aliases | ||
aliases cannot be updated | ||
""" | ||
raise NotImplementedError("doesn't exist yet") | ||
iemoji = await self._read(emoji.emoji) | ||
|
||
if not iemoji : | ||
raise NotFound('emoji does not exist') | ||
|
||
if iemoji.alias : | ||
raise BadRequest('cannot edit an alias') | ||
|
||
aliases = await self.aliases(iemoji.emoji) | ||
|
||
await self.query_async(""" | ||
update kheina.public.emojis | ||
set | ||
where emoji = any(%s); | ||
""", ( | ||
aliases + [iemoji.emoji], | ||
), | ||
commit = True, | ||
) | ||
|
||
# update all caches | ||
# await | ||
|
||
|
||
async def delete(self: Self, emoji: str) -> None : | ||
raise NotImplementedError("doesn't exist yet") | ||
|
||
|
||
@AerospikeCache('kheina', 'emoji_search', '{emoji_substring}', TTL_hours=1) | ||
async def list(self: Self, emoji_substring: str) -> list[str] : | ||
data: list[tuple[str]] = await self.query_async(""" | ||
select emojis.emoji | ||
from kheina.public.emojis | ||
where emojis.emoji like '%%' || %s || '%%'; | ||
""", ( | ||
emoji_substring, | ||
), | ||
fetch_all = True, | ||
) | ||
|
||
return list(map(lambda x : x[0], data)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
from fastapi import APIRouter | ||
|
||
from shared.models.auth import Scope | ||
from shared.server import Request | ||
from shared.timing import timed | ||
|
||
from .emoji import Emojis | ||
from .models import AliasRequest, CreateRequest, Emoji | ||
|
||
|
||
emojiRouter = APIRouter( | ||
prefix='/emoji', | ||
) | ||
emojisRouter = APIRouter( | ||
prefix='/emojis', | ||
) | ||
|
||
emojis = Emojis() | ||
|
||
|
||
@emojiRouter.put('') | ||
@timed.root | ||
async def v1CreateEmoji(req: Request, body: CreateRequest) -> Emoji : | ||
await req.user.verify_scope(Scope.admin) | ||
return await emojis.create(req.user, body) | ||
|
||
|
||
@emojiRouter.put('/alias') | ||
@timed.root | ||
async def v1CreateAlias(req: Request, body: AliasRequest) -> Emoji : | ||
await req.user.verify_scope(Scope.admin) | ||
return await emojis.alias(req.user, body) | ||
|
||
|
||
@emojiRouter.get('/{emoji}') | ||
@timed.root | ||
async def v1GetEmoji(req: Request, emoji: str) -> Emoji : | ||
return await emojis.read(req.user, emoji) | ||
|
||
|
||
@emojisRouter.get('/{emoji}') | ||
@timed.root | ||
async def v1ListEmojis(emoji: str) -> list[str] : | ||
return await emojis.list(emoji) | ||
|
||
|
||
app = APIRouter( | ||
prefix='/v1', | ||
tags=['emoji'], | ||
) | ||
|
||
app.include_router(emojiRouter) | ||
app.include_router(emojisRouter) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
. |
Oops, something went wrong.