diff --git a/apps/api/migrations/orgconfigs/v0tov1.py b/apps/api/migrations/orgconfigs/v0tov1.py new file mode 100644 index 00000000..d0a4c944 --- /dev/null +++ b/apps/api/migrations/orgconfigs/v0tov1.py @@ -0,0 +1,69 @@ +def migrate_v0_to_v1(v0_config): + v1_config = { + "config_version": "1.0", + "general": { + "enabled": v0_config["GeneralConfig"]["active"], + "color": v0_config["GeneralConfig"]["color"], + "watermark": True, # Default value as it's not present in v0 + }, + "features": { + "courses": { + "enabled": True, + "limit": ( + v0_config["GeneralConfig"]["limits"]["max_users"] + if v0_config["GeneralConfig"]["limits"]["limits_enabled"] + else 5 + ), + }, + "members": { + "enabled": True, + "signup_mode": ( + "open" + if v0_config["GeneralConfig"]["users"]["signup_mechanism"] == "open" + else "inviteOnly" + ), + "admin_limit": 5, + "limit": ( + v0_config["GeneralConfig"]["limits"]["max_users"] + if v0_config["GeneralConfig"]["limits"]["limits_enabled"] + else 10 + ), + }, + "usergroups": { + "enabled": True, + "limit": ( + v0_config["GeneralConfig"]["limits"]["max_staff"] + if v0_config["GeneralConfig"]["limits"]["limits_enabled"] + else 10 + ), + }, + "storage": { + "enabled": True, + "limit": ( + v0_config["GeneralConfig"]["limits"]["max_storage"] + if v0_config["GeneralConfig"]["limits"]["limits_enabled"] + else 10 + ), + }, + "ai": { + "enabled": v0_config["AIConfig"]["enabled"], + "limit": ( + v0_config["AIConfig"]["limits"]["max_asks"] + if v0_config["AIConfig"]["limits"]["limits_enabled"] + else 10 + ), + "model": v0_config["AIConfig"]["ai_model"], + }, + "assignments": {"enabled": True, "limit": 10}, + "payments": {"enabled": False, "stripe_key": ""}, + "discussions": {"enabled": False, "limit": 10}, + "analytics": {"enabled": False, "limit": 10}, + "collaboration": { + "enabled": v0_config["GeneralConfig"]["collaboration"], + "limit": 10, + }, + "api": {"enabled": False, "limit": 10}, + }, + } + + return v1_config diff --git a/apps/api/src/db/organization_config.py b/apps/api/src/db/organization_config.py index 5dcfcc7b..067e42e7 100644 --- a/apps/api/src/db/organization_config.py +++ b/apps/api/src/db/organization_config.py @@ -4,51 +4,91 @@ from sqlalchemy import JSON, BigInteger, Column, ForeignKey from sqlmodel import Field, SQLModel -# AI -class AILimitsSettings(BaseModel): - limits_enabled: bool = False - max_asks: int = 0 - - -class AIEnabledFeatures(BaseModel): - editor: bool = False - activity_ask: bool = False - course_ask: bool = False - global_ai_ask: bool = False - - -class AIConfig(BaseModel): +# Features +class CourseOrgConfig(BaseModel): enabled: bool = True - limits: AILimitsSettings = AILimitsSettings() - embeddings: Literal["text-embedding-ada-002"] = "text-embedding-ada-002" - ai_model: Literal["gpt-3.5-turbo", "gpt-4-1106-preview"] = "gpt-3.5-turbo" - features: AIEnabledFeatures = AIEnabledFeatures() + limit: int = 10 -class OrgUserConfig(BaseModel): - signup_mechanism: Literal["open", "inviteOnly"] = "open" +class MemberOrgConfig(BaseModel): + enabled: bool = True + signup_mode: Literal["open", "inviteOnly"] = "open" + admin_limit: int = 1 + limit: int = 10 -# Limits -class LimitSettings(BaseModel): - limits_enabled: bool = False - max_users: int = 0 - max_storage: int = 0 - max_staff: int = 0 +class UserGroupOrgConfig(BaseModel): + enabled: bool = True + limit: int = 10 + + +class StorageOrgConfig(BaseModel): + enabled: bool = True + limit: int = 10 + + +class AIOrgConfig(BaseModel): + enabled: bool = True + limit: int = 10 + model: str = "gpt-4o-mini" + + +class AssignmentOrgConfig(BaseModel): + enabled: bool = False + limit: int = 10 + + +class PaymentOrgConfig(BaseModel): + enabled: bool = True + stripe_key: str = "" + + +class DiscussionOrgConfig(BaseModel): + enabled: bool = True + limit: int = 10 + + +class AnalyticsOrgConfig(BaseModel): + enabled: bool = True + limit: int = 10 + + +class CollaborationOrgConfig(BaseModel): + enabled: bool = True + limit: int = 10 + + +class APIOrgConfig(BaseModel): + enabled: bool = True + limit: int = 10 + + +class OrgFeatureConfig(BaseModel): + courses: CourseOrgConfig = CourseOrgConfig() + members: MemberOrgConfig = MemberOrgConfig() + usergroups: UserGroupOrgConfig = UserGroupOrgConfig() + storage: StorageOrgConfig = StorageOrgConfig() + ai: AIOrgConfig = AIOrgConfig() + assignments: AssignmentOrgConfig = AssignmentOrgConfig() + payments: PaymentOrgConfig = PaymentOrgConfig() + discussions: DiscussionOrgConfig = DiscussionOrgConfig() + analytics: AnalyticsOrgConfig = AnalyticsOrgConfig() + collaboration: CollaborationOrgConfig = CollaborationOrgConfig() + api: APIOrgConfig = APIOrgConfig() # General -class GeneralConfig(BaseModel): - color: str = "" - limits: LimitSettings = LimitSettings() - users: OrgUserConfig = OrgUserConfig() - collaboration: bool = False - active: bool = True +class OrgGeneralConfig(BaseModel): + enabled: bool = True + color: str = "normal" + watermark: bool = True -class OrganizationConfigBase(SQLModel): - GeneralConfig: GeneralConfig - AIConfig: AIConfig +# Main Config +class OrganizationConfigBase(BaseModel): + config_version: str = "1.0" + general: OrgGeneralConfig + features: OrgFeatureConfig class OrganizationConfig(SQLModel, table=True): @@ -56,7 +96,6 @@ class OrganizationConfig(SQLModel, table=True): org_id: int = Field( sa_column=Column(BigInteger, ForeignKey("organization.id", ondelete="CASCADE")) ) - # TODO: fix this to use the correct type GeneralConfig config: dict = Field(default={}, sa_column=Column(JSON)) creation_date: Optional[str] update_date: Optional[str] diff --git a/apps/api/src/services/ai/ai.py b/apps/api/src/services/ai/ai.py index 17516d76..17edab1c 100644 --- a/apps/api/src/services/ai/ai.py +++ b/apps/api/src/services/ai/ai.py @@ -86,8 +86,8 @@ def ai_start_activity_chat_session( org_config = result.first() org_config = OrganizationConfig.model_validate(org_config) - embeddings = org_config.config["AIConfig"]["embeddings"] - ai_model = org_config.config["AIConfig"]["ai_model"] + embeddings = "text-embedding-ada-002" + ai_model = org_config.config["features"]["ai"]["model"] chat_session = get_chat_session_history() @@ -177,8 +177,8 @@ def ai_send_activity_chat_message( org_config = result.first() org_config = OrganizationConfig.model_validate(org_config) - embeddings = org_config.config["AIConfig"]["embeddings"] - ai_model = org_config.config["AIConfig"]["ai_model"] + embeddings = "text-embedding-ada-002" + ai_model = org_config.config["features"]["ai"]["model"] chat_session = get_chat_session_history(chat_session_object.aichat_uuid) diff --git a/apps/api/src/services/ai/utils.py b/apps/api/src/services/ai/utils.py index 4b708ec1..2fe9c367 100644 --- a/apps/api/src/services/ai/utils.py +++ b/apps/api/src/services/ai/utils.py @@ -78,7 +78,7 @@ def check_limits_and_config(db_session: Session, organization: Organization): ) # Check if the Organization has Limits enabled and if the max_asks limit has been reached - if org_config.config["AIConfig"]["limits"]["limits_enabled"] == True: + if org_config.config["features"]["ai"]["limit"] > 0: LH_CONFIG = get_learnhouse_config() redis_conn_string = LH_CONFIG.redis_config.redis_connection_string @@ -107,7 +107,7 @@ def check_limits_and_config(db_session: Session, organization: Organization): ai_asks = int(ai_asks) # Check if the Number of asks is less than the max_asks limit - if org_config.config["AIConfig"]["limits"]["max_asks"] <= ai_asks: + if org_config.config["features"]["ai"]["limit"] <= ai_asks: raise HTTPException( status_code=403, detail="Organization has reached the max number of AI asks", diff --git a/apps/api/src/services/install/install.py b/apps/api/src/services/install/install.py index c54580eb..366751d2 100644 --- a/apps/api/src/services/install/install.py +++ b/apps/api/src/services/install/install.py @@ -5,12 +5,28 @@ from fastapi import HTTPException, Request from sqlalchemy import desc from sqlmodel import Session, select from src.db.install import Install, InstallRead -from src.db.organization_config import AIEnabledFeatures, AILimitsSettings, LimitSettings, OrgUserConfig, OrganizationConfig, OrganizationConfigBase, GeneralConfig, AIConfig +from src.db.organization_config import ( + AIOrgConfig, + APIOrgConfig, + AnalyticsOrgConfig, + AssignmentOrgConfig, + CollaborationOrgConfig, + CourseOrgConfig, + DiscussionOrgConfig, + MemberOrgConfig, + OrgFeatureConfig, + OrgGeneralConfig, + OrganizationConfig, + OrganizationConfigBase, + PaymentOrgConfig, + StorageOrgConfig, + UserGroupOrgConfig, +) from src.db.organizations import Organization, OrganizationCreate from src.db.roles import Permission, Rights, Role, RoleTypeEnum from src.db.user_organizations import UserOrganization from src.db.users import User, UserCreate, UserRead -from config.config import get_learnhouse_config +from config.config import GeneralConfig, get_learnhouse_config from src.security.security import security_hash_password @@ -57,7 +73,7 @@ async def get_latest_install_instance(request: Request, db_session: Session): status_code=404, detail="No install instance found", ) - + install = InstallRead.model_validate(install) return install @@ -96,8 +112,7 @@ async def update_install_instance( # Install Default roles def install_default_elements(db_session: Session): - """ - """ + """ """ # remove all default roles statement = select(Role).where(Role.role_type == RoleTypeEnum.TYPE_GLOBAL) roles = db_session.exec(statement).all() @@ -300,9 +315,7 @@ def install_default_elements(db_session: Session): # Organization creation -def install_create_organization( - org_object: OrganizationCreate, db_session: Session -): +def install_create_organization(org_object: OrganizationCreate, db_session: Session): org = Organization.model_validate(org_object) # Complete the org object @@ -314,36 +327,28 @@ def install_create_organization( db_session.commit() db_session.refresh(org) - # Org Config + # Org Config org_config = OrganizationConfigBase( - GeneralConfig=GeneralConfig( - color="#000000", - limits=LimitSettings( - limits_enabled=False, - max_users=0, - max_storage=0, - max_staff=0, - ), - collaboration=False, - users=OrgUserConfig( - signup_mechanism="open", - ), - active=True, - ), - AIConfig=AIConfig( + config_version="1.0", + general=OrgGeneralConfig( enabled=True, - limits=AILimitsSettings( - limits_enabled=False, - max_asks=0, - ), - embeddings="text-embedding-ada-002", - ai_model="gpt-3.5-turbo", - features=AIEnabledFeatures( - editor=True, - activity_ask=True, - course_ask=True, - global_ai_ask=True, + color="normal", + watermark=True, + ), + features=OrgFeatureConfig( + courses=CourseOrgConfig(enabled=True, limit=0), + members=MemberOrgConfig( + enabled=True, signup_mode="open", admin_limit=0, limit=0 ), + usergroups=UserGroupOrgConfig(enabled=True, limit=0), + storage=StorageOrgConfig(enabled=True, limit=0), + ai=AIOrgConfig(enabled=True, limit=0, model="text-embedding-ada-002"), + assignments=AssignmentOrgConfig(enabled=True, limit=0), + payments=PaymentOrgConfig(enabled=True, stripe_key=""), + discussions=DiscussionOrgConfig(enabled=True, limit=0), + analytics=AnalyticsOrgConfig(enabled=True, limit=0), + collaboration=CollaborationOrgConfig(enabled=True, limit=0), + api=APIOrgConfig(enabled=True, limit=0), ), ) @@ -365,7 +370,7 @@ def install_create_organization( def install_create_organization_user( - user_object: UserCreate, org_slug: str, db_session: Session + user_object: UserCreate, org_slug: str, db_session: Session ): user = User.model_validate(user_object) @@ -418,8 +423,6 @@ def install_create_organization_user( db_session.commit() db_session.refresh(user) - - # get org id statement = select(Organization).where(Organization.slug == org_slug) org = db_session.exec(statement) diff --git a/apps/api/src/services/orgs/orgs.py b/apps/api/src/services/orgs/orgs.py index bab0add7..58edc7ea 100644 --- a/apps/api/src/services/orgs/orgs.py +++ b/apps/api/src/services/orgs/orgs.py @@ -5,14 +5,21 @@ from typing import Literal from uuid import uuid4 from sqlmodel import Session, select from src.db.organization_config import ( - AIConfig, - AIEnabledFeatures, - AILimitsSettings, - GeneralConfig, - LimitSettings, - OrgUserConfig, + AIOrgConfig, + APIOrgConfig, + AnalyticsOrgConfig, + AssignmentOrgConfig, + CollaborationOrgConfig, + CourseOrgConfig, + DiscussionOrgConfig, + MemberOrgConfig, + OrgFeatureConfig, + OrgGeneralConfig, OrganizationConfig, OrganizationConfigBase, + PaymentOrgConfig, + StorageOrgConfig, + UserGroupOrgConfig, ) from src.security.rbac.rbac import ( authorization_verify_based_on_org_admin_status, @@ -149,35 +156,27 @@ async def create_org( db_session.commit() db_session.refresh(user_org) - org_config = OrganizationConfigBase( - GeneralConfig=GeneralConfig( - color="#000000", - limits=LimitSettings( - limits_enabled=False, - max_users=0, - max_storage=0, - max_staff=0, - ), - collaboration=False, - users=OrgUserConfig( - signup_mechanism="open", - ), - active=True, - ), - AIConfig=AIConfig( + org_config = org_config = OrganizationConfigBase( + config_version="1.0", + general=OrgGeneralConfig( enabled=True, - limits=AILimitsSettings( - limits_enabled=False, - max_asks=0, - ), - embeddings="text-embedding-ada-002", - ai_model="gpt-3.5-turbo", - features=AIEnabledFeatures( - editor=True, - activity_ask=True, - course_ask=True, - global_ai_ask=True, + color="normal", + watermark=True, + ), + features=OrgFeatureConfig( + courses=CourseOrgConfig(enabled=True, limit=0), + members=MemberOrgConfig( + enabled=True, signup_mode="open", admin_limit=0, limit=0 ), + usergroups=UserGroupOrgConfig(enabled=True, limit=0), + storage=StorageOrgConfig(enabled=True, limit=0), + ai=AIOrgConfig(enabled=True, limit=0, model="text-embedding-ada-002"), + assignments=AssignmentOrgConfig(enabled=True, limit=0), + payments=PaymentOrgConfig(enabled=True, stripe_key=""), + discussions=DiscussionOrgConfig(enabled=True, limit=0), + analytics=AnalyticsOrgConfig(enabled=True, limit=0), + collaboration=CollaborationOrgConfig(enabled=True, limit=0), + api=APIOrgConfig(enabled=True, limit=0), ), ) @@ -210,8 +209,6 @@ async def create_org( return org - -# Temporary pre-alpha code async def create_org_with_config( request: Request, org_object: OrganizationCreate, @@ -477,7 +474,7 @@ async def update_org_signup_mechanism( # Update config updated_config = OrganizationConfigBase(**updated_config) - updated_config.GeneralConfig.users.signup_mechanism = signup_mechanism + updated_config.features.members.signup_mode = signup_mechanism # Update the database org_config.config = json.loads(updated_config.json()) @@ -527,7 +524,7 @@ async def get_org_join_mechanism( # Get the signup mechanism config = OrganizationConfigBase(**config) - signup_mechanism = config.GeneralConfig.users.signup_mechanism + signup_mechanism = config.features.members.signup_mode return signup_mechanism