Skip to content
Closed
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
40 changes: 40 additions & 0 deletions auth/auth_server/alembic/versions/88f7bebcdcb9_add_user_options.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# pylint: disable=C0103
"""Add user option table

Revision ID: 88f7bebcdcb9
Revises: 998f27cb8c46
Create Date: 2026-03-10 16:27:40.340018

"""
from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision = '88f7bebcdcb9'
down_revision = '998f27cb8c46'
branch_labels = None
depends_on = None


def upgrade():
op.create_table(
'user_option',
sa.Column('id', sa.String(36), nullable=False),
sa.Column('created_at', sa.Integer(), nullable=False),
sa.Column('deleted_at', sa.Integer(), nullable=False),
sa.Column('user_id', sa.String(36), nullable=False),
sa.Column('name', sa.String(256), nullable=False),
sa.Column('value', sa.TEXT(), nullable=False),
sa.PrimaryKeyConstraint('id'),
sa.ForeignKeyConstraint(['user_id'], ['user.id']),
sa.UniqueConstraint('user_id', 'name', 'deleted_at',
name='uc_user_id_name_deleted_at')
)
op.create_index(op.f('ix_user_option_user_name'),
'user_option', ['user_id', 'name'],
unique=False)


def downgrade():
op.drop_table('user_option')
5 changes: 4 additions & 1 deletion auth/auth_server/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,10 @@ class Urls:
r"%s/users/(?P<user_id>[^/]+)/action_resources",
'bulk_action_resources': r"%s/bulk_action_resources",
'signin': r"%s/signin",
'verification_codes': r"%s/verification_codes"
'verification_codes': r"%s/verification_codes",
'user_options_collection': r"%s/users/(?P<user_id>[^/]+)/options",
'user_options':
r"%s/users/(?P<user_id>[^/]+)/options/(?P<option_name>[^/]+)"
}

def __init__(self):
Expand Down
93 changes: 93 additions & 0 deletions auth/auth_server/controllers/user_option.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import logging

from auth.auth_server.controllers.base import BaseController
from auth.auth_server.controllers.base_async import BaseAsyncControllerWrapper
from auth.auth_server.exceptions import Err
from auth.auth_server.models.models import UserOption, User
from auth.auth_server.utils import (strtobool, check_string_attribute,
check_valid_json)
from tools.optscale_exceptions.common_exc import (
WrongArgumentsException, ForbiddenException, NotFoundException)

LOG = logging.getLogger(__name__)


class UserOptionsController(BaseController):
def _get_model_type(self):
return UserOption

def check_user_access(self, user_id, token):
req_user = self.get_user_by_id(user_id)
if not req_user:
raise NotFoundException(Err.OA0003, [User.__name__, user_id])
if token:
token_user = self.get_user(token)
if token_user.id != user_id:
raise ForbiddenException(Err.OA0012, [])
return req_user

def get_by_name(self, user_id, option_name, **kwargs):
token = kwargs.get('token', None)
self.check_user_access(user_id, token)
user_options = super().list(user_id=user_id, name=option_name)
if len(user_options) > 1:
raise WrongArgumentsException(Err.OA0029, [])
elif len(user_options) == 0:
return '{}'
else:
return user_options[0].value

def list(self, user_id, **kwargs):
token = kwargs.get('token', None)
self.check_user_access(user_id, token)
with_values = kwargs.get('with_values', False)
try:
if not isinstance(with_values, bool):
with_values = strtobool(with_values)
except ValueError:
raise WrongArgumentsException(Err.OA0063, ['with_values'])
base_list = super().list(user_id=user_id)
result = [
{
'name': obj.name, 'value': obj.value
} if with_values else obj.name for obj in base_list
]
return result

def patch(self, user_id, option_name, value_data, **kwargs):
token = kwargs.get('token', None)
self.check_user_access(user_id, token)

options = super().list(user_id=user_id, name=option_name)
check_valid_json(value_data, 'value')
if len(options) > 1:
raise WrongArgumentsException(Err.OA0029, [])
elif len(options) == 0:
check_string_attribute('option_name', option_name,
max_length=256)
res = super().create(user_id=user_id, name=option_name,
deleted_at=0, value=value_data)
return res.value
else:
option = options[0]
res = super().edit(option.id, value=value_data)
return res.value

def delete(self, user_id, option_name, **kwargs):
token = kwargs.get('token', None)
self.check_user_access(user_id, token)

options = super().list(user_id=user_id, name=option_name)
if len(options) > 1:
raise WrongArgumentsException(Err.OA0029, [])
elif len(options) == 0:
raise NotFoundException(
Err.OA0003, [UserOption.__name__, option_name])
else:
option = options[0]
super().delete(option.id)


class UserOptionsAsyncController(BaseAsyncControllerWrapper):
def _get_controller_class(self):
return UserOptionsController
2 changes: 1 addition & 1 deletion auth/auth_server/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ class Err(enum.Enum):
"Invalid model type: %s",
]
OA0046 = [
"Payload is not a valid json",
"%s is not a valid json",
]
OA0047 = [
"Payload is malformed",
Expand Down
1 change: 1 addition & 0 deletions auth/auth_server/handlers/v2/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@
import auth.auth_server.handlers.v2.types
import auth.auth_server.handlers.v2.signin
import auth.auth_server.handlers.v2.verification_codes
import auth.auth_server.handlers.v2.user_options
6 changes: 3 additions & 3 deletions auth/auth_server/handlers/v2/base.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import json

from tools.optscale_exceptions.common_exc import WrongArgumentsException
from tools.optscale_exceptions.http_exc import OptHTTPError

from auth.auth_server.exceptions import Err
from auth.auth_server.handlers.v1.base import BaseHandler as BaseHandler_v1
Expand All @@ -17,13 +17,13 @@ def get_arg(self, name, type_, default=None, repeated=False):
if type_ == bool and isinstance(arg, str):
lowered = arg.lower()
if lowered not in ['true', 'false']:
raise WrongArgumentsException(Err.OA0063, [name])
raise OptHTTPError(400, Err.OA0063, [name])
return lowered == 'true'
return type_(arg)
else:
return arg
except ValueError:
raise WrongArgumentsException(Err.OA0060, [name])
raise OptHTTPError(400, Err.OA0060, [name])

def parse_url_params_into_payload(self, payload_map_params):
data = {}
Expand Down
Loading
Loading