Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add user preferences service #74

Merged
merged 46 commits into from
Nov 10, 2023
Merged
Show file tree
Hide file tree
Changes from 39 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
7b13d78
feat: add user preferences service
leafty Oct 30, 2023
b0768cd
add models and orm
leafty Oct 30, 2023
8baf48f
Add first route
leafty Oct 31, 2023
c839954
Add migrations
leafty Oct 31, 2023
4add884
404 on no object
leafty Oct 31, 2023
21f9681
Merge branch 'main' into leafty/add-user-preferences
leafty Oct 31, 2023
c3f305f
fix formating
leafty Oct 31, 2023
fe16809
update pyproject.toml
leafty Oct 31, 2023
934f419
update poetry (?)
leafty Oct 31, 2023
a6ff7a0
update spec file
leafty Oct 31, 2023
c323c51
update spec file again
leafty Oct 31, 2023
f935f61
add migration task to main()
leafty Oct 31, 2023
aa69835
refactor spec merge
leafty Nov 1, 2023
10d7528
Add endpoint to add pinned project
leafty Nov 1, 2023
f4c4bb0
Add endpoint to add pinned project >.<
leafty Nov 1, 2023
dd9d4c9
fix bug
leafty Nov 1, 2023
86d5d1b
fix bug
leafty Nov 1, 2023
825765f
fix bug
leafty Nov 1, 2023
47c6602
fix bug ???
leafty Nov 1, 2023
44fb5fb
add delete_pinned_projects endpoint
leafty Nov 1, 2023
3a00d51
fix bug
leafty Nov 1, 2023
3b5945b
use scalars()
leafty Nov 1, 2023
5b87330
fix bug
leafty Nov 1, 2023
a37d25f
fix bug
leafty Nov 1, 2023
5bb7b21
try this way
leafty Nov 1, 2023
0ea92cf
try this way
leafty Nov 1, 2023
568f11c
try this way
leafty Nov 1, 2023
31bb625
try this way
leafty Nov 1, 2023
10b6f3a
cleanup
leafty Nov 1, 2023
803a294
Merge branch 'main' into leafty/add-user-preferences
leafty Nov 1, 2023
4d1b8ca
update docker-compose
leafty Nov 2, 2023
ea43831
Merge remote-tracking branch 'refs/remotes/origin/leafty/add-user-pre…
leafty Nov 2, 2023
f0c5fef
add first test
floraliiie Nov 6, 2023
bf019e3
added more database tests
floraliiie Nov 6, 2023
718e89e
enable ci/cd
floraliiie Nov 6, 2023
c001dc3
added more tests
floraliiie Nov 6, 2023
ae73b2c
Merge remote-tracking branch 'origin/feat-add-user-preferences' into …
floraliiie Nov 6, 2023
0516f47
ignore pydoc on migrations
floraliiie Nov 6, 2023
fe73a55
update
floraliiie Nov 6, 2023
1bcf908
action on all PRs
leafty Nov 8, 2023
ec15eb7
update routes
leafty Nov 8, 2023
546cc26
Update components/renku_data_services/user_preferences/db.py
leafty Nov 9, 2023
c60f16d
Update the DELETE endpoint
leafty Nov 9, 2023
d7c84eb
use default
leafty Nov 9, 2023
2a41e08
fix tests
leafty Nov 9, 2023
2fbf667
use another primary key
floraliiie Nov 10, 2023
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
2 changes: 2 additions & 0 deletions .devcontainer/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ services:
POSTGRES_USER: renku
POSTGRES_DB: renku
POSTGRES_PASSWORD: renku
ports:
- 5432:5432

# Add "forwardPorts": ["5432"] to **devcontainer.json** to forward PostgreSQL locally.
# (Adding the "ports" property to this file will not forward from a Codespace.)
Expand Down
7 changes: 4 additions & 3 deletions .github/workflows/test_publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ on:
branches:
- main
tags:
- 'v*'
- "v*"
pull_request:
branches:
- 'main'
- 'hotfix/**'
- "main"
- "hotfix/**"
- "feat-add-user-preferences"
olevski marked this conversation as resolved.
Show resolved Hide resolved

jobs:
test:
Expand Down
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
schemas:
poetry run datamodel-codegen --input components/renku_data_services/crc/api.spec.yaml --input-file-type openapi --output-model-type pydantic_v2.BaseModel --output components/renku_data_services/crc/apispec.py --use-double-quotes --target-python-version 3.11 --collapse-root-models --field-constraints --strict-nullable --base-class renku_data_services.crc.apispec_base.BaseAPISpec
poetry run datamodel-codegen --input components/renku_data_services/storage/api.spec.yaml --input-file-type openapi --output-model-type pydantic_v2.BaseModel --output components/renku_data_services/storage/apispec.py --use-double-quotes --target-python-version 3.11 --collapse-root-models --field-constraints --strict-nullable --base-class renku_data_services.storage.apispec_base.BaseAPISpec
poetry run datamodel-codegen --input components/renku_data_services/user_preferences/api.spec.yaml --input-file-type openapi --output-model-type pydantic_v2.BaseModel --output components/renku_data_services/user_preferences/apispec.py --use-double-quotes --target-python-version 3.11 --collapse-root-models --field-constraints --strict-nullable --base-class renku_data_services.user_preferences.apispec_base.BaseAPISpec

style_checks:
poetry check
Expand Down
8 changes: 8 additions & 0 deletions bases/renku_data_services/data_api/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
)
from renku_data_services.data_api.config import Config
from renku_data_services.storage.blueprints import StorageBP, StorageSchemaBP
from renku_data_services.user_preferences.blueprints import UserPreferencesBP


def register_all_handlers(app: Sanic, config: Config) -> Sanic:
Expand Down Expand Up @@ -57,6 +58,12 @@ def register_all_handlers(app: Sanic, config: Config) -> Sanic:
authenticator=config.gitlab_authenticator,
)
storage_schema = StorageSchemaBP(name="storage_schema", url_prefix=url_prefix)
user_preferences = UserPreferencesBP(
name="user_preferences",
url_prefix=url_prefix,
user_preferences_repo=config.user_preferences_repo,
authenticator=config.authenticator,
)
misc = MiscBP(name="misc", url_prefix=url_prefix, apispec=config.spec, version=config.version)
app.blueprint(
[
Expand All @@ -68,6 +75,7 @@ def register_all_handlers(app: Sanic, config: Config) -> Sanic:
user_resource_pools.blueprint(),
storage.blueprint(),
storage_schema.blueprint(),
user_preferences.blueprint(),
misc.blueprint(),
]
)
Expand Down
14 changes: 13 additions & 1 deletion bases/renku_data_services/data_api/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import renku_data_services.base_models as base_models
import renku_data_services.crc
import renku_data_services.storage
import renku_data_services.user_preferences
from renku_data_services import errors
from renku_data_services.crc import models
from renku_data_services.crc.db import ResourcePoolRepository, UserRepository
Expand All @@ -25,6 +26,7 @@
from renku_data_services.k8s.quota import QuotaRepository
from renku_data_services.migrations.core import DataRepository
from renku_data_services.storage.db import StorageRepository
from renku_data_services.user_preferences.db import UserPreferencesRepository
from renku_data_services.users.dummy import DummyAuthenticator, DummyUserStore
from renku_data_services.users.gitlab import GitlabAuthenticator
from renku_data_services.users.keycloak import KcUserStore, KeycloakAuthenticator
Expand Down Expand Up @@ -73,6 +75,7 @@ class Config:
user_repo: UserRepository
rp_repo: ResourcePoolRepository
storage_repo: StorageRepository
user_preferences_repo: UserPreferencesRepository
user_store: base_models.UserStore
authenticator: base_models.Authenticator
gitlab_authenticator: base_models.Authenticator
Expand All @@ -94,7 +97,11 @@ def __post_init__(self):
with open(spec_file, "r") as f:
storage_spec = safe_load(f)

self.spec = merge_api_specs(crc_spec, storage_spec)
spec_file = Path(renku_data_services.user_preferences.__file__).resolve().parent / "api.spec.yaml"
with open(spec_file, "r") as f:
user_preferences_spec = safe_load(f)

self.spec = merge_api_specs(crc_spec, storage_spec, user_preferences_spec)

if self.default_resource_pool_file is not None:
with open(self.default_resource_pool_file, "r") as f:
Expand Down Expand Up @@ -190,10 +197,15 @@ def from_env(cls):
sync_sqlalchemy_url=sync_sqlalchemy_url,
async_sqlalchemy_url=async_sqlalchemy_url,
)
user_preferences_repo = UserPreferencesRepository(
sync_sqlalchemy_url=sync_sqlalchemy_url,
async_sqlalchemy_url=async_sqlalchemy_url,
)
return cls(
user_repo=user_repo,
rp_repo=rp_repo,
storage_repo=storage_repo,
user_preferences_repo=user_preferences_repo,
version=version,
authenticator=authenticator,
gitlab_authenticator=gitlab_authenticator,
Expand Down
2 changes: 2 additions & 0 deletions bases/renku_data_services/data_api/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ def create_app() -> Sanic:
# specified below with the main_process_start decorator do not run.
run_migrations_for_app("resource_pools", config.rp_repo)
run_migrations_for_app("storage", config.rp_repo)
run_migrations_for_app("user_preferences", config.user_preferences_repo)
config.rp_repo.initialize(config.default_resource_pool)
app = register_all_handlers(app, config)

Expand All @@ -30,6 +31,7 @@ async def do_migrations(*_):
logger.info("running migrations")
run_migrations_for_app("resource_pools", config.rp_repo)
run_migrations_for_app("storage", config.rp_repo)
run_migrations_for_app("user_preferences", config.user_preferences_repo)
config.rp_repo.initialize(config.default_resource_pool)

@app.before_server_start
Expand Down
4 changes: 4 additions & 0 deletions components/renku_data_services/migrations/alembic.ini
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ config_class = renku_data_services.data_api.config
script_location = renku_data_services:storage_migrations
config_class = renku_data_services.data_api.config

[user_preferences]
script_location = renku_data_services:user_preferences_migrations
config_class = renku_data_services.data_api.config

[post_write_hooks]
# post_write_hooks defines scripts or Python functions that are run
# on newly generated revision scripts. See the documentation for further
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Blueprints for user preferences."""
142 changes: 142 additions & 0 deletions components/renku_data_services/user_preferences/api.spec.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
---
openapi: 3.0.2
info:
title: Renku user preferences services
description: |
Services that provide information about user preferences.
version: v1
servers:
- url: /api/data
- url: /ui-server/api/data
paths:
/user_preferences:
olevski marked this conversation as resolved.
Show resolved Hide resolved
get:
summary: Get user preferences for the currently logged in user
responses:
"200":
description: The user preferences
content:
"application/json":
schema:
$ref: "#/components/schemas/UserPreferences"
"404":
description: The user has no preferences saved
content:
"application/json":
schema:
$ref: "#/components/schemas/ErrorResponse"
default:
$ref: "#/components/responses/Error"
tags:
- user_preferences
"/user_preferences/pinned_projects":
post:
summary: Add a pinned project
requestBody:
required: true
content:
"application/json":
schema:
$ref: "#/components/schemas/AddPinnedProject"
responses:
"200":
description: The updated user preferences
content:
"application/json":
schema:
$ref: "#/components/schemas/UserPreferences"
default:
$ref: "#/components/responses/Error"
tags:
- user_preferences
delete:
summary: Remove a pinned project
parameters:
- in: query
name: project_slug
olevski marked this conversation as resolved.
Show resolved Hide resolved
required: true
schema:
type: string
responses:
"200":
description: The updated user preferences
content:
"application/json":
schema:
$ref: "#/components/schemas/UserPreferences"
default:
$ref: "#/components/responses/Error"
tags:
- user_preferences

components:
schemas:
UserPreferences:
type: object
description: The object containing user preferences
additionalProperties: false
properties:
user_id:
$ref: "#/components/schemas/UserId"
pinned_projects:
$ref: "#/components/schemas/PinnedProjects"
required: ["user_id", "pinned_projects"]
UserId:
type: string
description: The unique identifier for a user
minLength: 3
example: "user-id-example"
PinnedProjects:
type: object
description: The list of projects a user has pinned on their dashboard
properties:
project_slugs:
type: array
items:
$ref: "#/components/schemas/ProjectSlug"
ProjectSlug:
type: string
description: The slug used to identify a project
minLength: 3
example: "user/my-project"
olevski marked this conversation as resolved.
Show resolved Hide resolved
AddPinnedProject:
type: object
additionalProperties: false
properties:
project_slug:
$ref: "#/components/schemas/ProjectSlug"
required: ["project_slug"]
ErrorResponse:
type: object
properties:
error:
type: object
properties:
code:
type: integer
minimum: 0
exclusiveMinimum: true
example: 1404
detail:
type: string
example: "A more detailed optional message showing what the problem was"
message:
type: string
example: "Something went wrong - please try again later"
required: ["code", "message"]
required: ["error"]

responses:
Error:
description: The schema for all 4xx and 5xx responses
content:
"application/json":
schema:
$ref: "#/components/schemas/ErrorResponse"
securitySchemes:
oidc:
type: openIdConnect
openIdConnectUrl: /auth/realms/Renku/.well-known/openid-configuration
security:
- oidc:
- openid
46 changes: 46 additions & 0 deletions components/renku_data_services/user_preferences/apispec.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# generated by datamodel-codegen:
# filename: api.spec.yaml
# timestamp: 2023-11-01T09:29:32+00:00

from __future__ import annotations

from typing import List, Optional

from pydantic import ConfigDict, Field, RootModel

from renku_data_services.user_preferences.apispec_base import BaseAPISpec


class ProjectSlug(RootModel):
root: str = Field(..., description="The slug used to identify a project", example="user/my-project", min_length=3)


class AddPinnedProject(BaseAPISpec):
model_config = ConfigDict(
extra="forbid",
)
project_slug: str = Field(
..., description="The slug used to identify a project", example="user/my-project", min_length=3
)


class Error(BaseAPISpec):
code: int = Field(..., example=1404, gt=0)
detail: Optional[str] = Field(None, example="A more detailed optional message showing what the problem was")
message: str = Field(..., example="Something went wrong - please try again later")


class ErrorResponse(BaseAPISpec):
error: Error


class PinnedProjects(BaseAPISpec):
project_slugs: Optional[List[ProjectSlug]] = None


class UserPreferences(BaseAPISpec):
model_config = ConfigDict(
extra="forbid",
)
user_id: str = Field(..., description="The unique identifier for a user", example="user-id-example", min_length=3)
pinned_projects: PinnedProjects
22 changes: 22 additions & 0 deletions components/renku_data_services/user_preferences/apispec_base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
"""Base models for API specifications."""
from pydantic import BaseModel, Extra, Field


class BaseAPISpec(BaseModel):
"""Base API specification."""

class Config:
"""Enables orm mode for pydantic."""

from_attributes = True


class PinnedProjectFilter(BaseAPISpec):
"""The schema for the query parameters used to filter pinned projects."""

class Config:
"""Configuration."""

extra = Extra.ignore

project_slug: str = Field()
Loading