mirror of
https://github.com/rzmk/learnhouse.git
synced 2025-12-19 04:19:25 +00:00
Merge pull request #463 from learnhouse/feat/perf-improvements
Misc Perf improvements
This commit is contained in:
commit
8d2e61ff39
12 changed files with 181 additions and 158 deletions
|
|
@ -35,7 +35,11 @@ learnhouse_config = get_learnhouse_config()
|
||||||
engine = create_engine(
|
engine = create_engine(
|
||||||
learnhouse_config.database_config.sql_connection_string, # type: ignore
|
learnhouse_config.database_config.sql_connection_string, # type: ignore
|
||||||
echo=False,
|
echo=False,
|
||||||
pool_pre_ping=True # type: ignore
|
pool_pre_ping=True, # type: ignore
|
||||||
|
pool_size=10,
|
||||||
|
max_overflow=20,
|
||||||
|
pool_recycle=300, # Recycle connections after 5 minutes
|
||||||
|
pool_timeout=30
|
||||||
)
|
)
|
||||||
|
|
||||||
# Create all tables after importing all models
|
# Create all tables after importing all models
|
||||||
|
|
|
||||||
|
|
@ -92,24 +92,21 @@ async def get_activity(
|
||||||
current_user: PublicUser,
|
current_user: PublicUser,
|
||||||
db_session: Session,
|
db_session: Session,
|
||||||
):
|
):
|
||||||
statement = select(Activity).where(Activity.activity_uuid == activity_uuid)
|
# Optimize by joining Activity with Course in a single query
|
||||||
activity = db_session.exec(statement).first()
|
statement = (
|
||||||
|
select(Activity, Course)
|
||||||
|
.join(Course)
|
||||||
|
.where(Activity.activity_uuid == activity_uuid)
|
||||||
|
)
|
||||||
|
result = db_session.exec(statement).first()
|
||||||
|
|
||||||
if not activity:
|
if not result:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=404,
|
status_code=404,
|
||||||
detail="Activity not found",
|
detail="Activity not found",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Get course from that activity
|
activity, course = result
|
||||||
statement = select(Course).where(Course.id == activity.course_id)
|
|
||||||
course = db_session.exec(statement).first()
|
|
||||||
|
|
||||||
if not course:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=404,
|
|
||||||
detail="Course not found",
|
|
||||||
)
|
|
||||||
|
|
||||||
# RBAC check
|
# RBAC check
|
||||||
await rbac_check(request, course.course_uuid, current_user, "read", db_session)
|
await rbac_check(request, course.course_uuid, current_user, "read", db_session)
|
||||||
|
|
@ -124,9 +121,8 @@ async def get_activity(
|
||||||
|
|
||||||
activity_read = ActivityRead.model_validate(activity)
|
activity_read = ActivityRead.model_validate(activity)
|
||||||
activity_read.content = activity_read.content if has_paid_access else { "paid_access": False }
|
activity_read.content = activity_read.content if has_paid_access else { "paid_access": False }
|
||||||
activity = activity_read
|
|
||||||
|
|
||||||
return activity
|
return activity_read
|
||||||
|
|
||||||
async def get_activityby_id(
|
async def get_activityby_id(
|
||||||
request: Request,
|
request: Request,
|
||||||
|
|
@ -134,31 +130,26 @@ async def get_activityby_id(
|
||||||
current_user: PublicUser,
|
current_user: PublicUser,
|
||||||
db_session: Session,
|
db_session: Session,
|
||||||
):
|
):
|
||||||
statement = select(Activity).where(Activity.id == activity_id)
|
# Optimize by joining Activity with Course in a single query
|
||||||
activity = db_session.exec(statement).first()
|
statement = (
|
||||||
|
select(Activity, Course)
|
||||||
|
.join(Course)
|
||||||
|
.where(Activity.id == activity_id)
|
||||||
|
)
|
||||||
|
result = db_session.exec(statement).first()
|
||||||
|
|
||||||
if not activity:
|
if not result:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=404,
|
status_code=404,
|
||||||
detail="Activity not found",
|
detail="Activity not found",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Get course from that activity
|
activity, course = result
|
||||||
statement = select(Course).where(Course.id == activity.course_id)
|
|
||||||
course = db_session.exec(statement).first()
|
|
||||||
|
|
||||||
if not course:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=404,
|
|
||||||
detail="Course not found",
|
|
||||||
)
|
|
||||||
|
|
||||||
# RBAC check
|
# RBAC check
|
||||||
await rbac_check(request, course.course_uuid, current_user, "read", db_session)
|
await rbac_check(request, course.course_uuid, current_user, "read", db_session)
|
||||||
|
|
||||||
activity = ActivityRead.model_validate(activity)
|
return ActivityRead.model_validate(activity)
|
||||||
|
|
||||||
return activity
|
|
||||||
|
|
||||||
|
|
||||||
async def update_activity(
|
async def update_activity(
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ from src.security.rbac.rbac import (
|
||||||
from src.services.courses.thumbnails import upload_thumbnail
|
from src.services.courses.thumbnails import upload_thumbnail
|
||||||
from fastapi import HTTPException, Request, UploadFile
|
from fastapi import HTTPException, Request, UploadFile
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
|
||||||
async def get_course(
|
async def get_course(
|
||||||
|
|
@ -106,6 +107,7 @@ async def get_course_meta(
|
||||||
# Avoid circular import
|
# Avoid circular import
|
||||||
from src.services.courses.chapters import get_course_chapters
|
from src.services.courses.chapters import get_course_chapters
|
||||||
|
|
||||||
|
# Get course with a single query
|
||||||
course_statement = select(Course).where(Course.course_uuid == course_uuid)
|
course_statement = select(Course).where(Course.course_uuid == course_uuid)
|
||||||
course = db_session.exec(course_statement).first()
|
course = db_session.exec(course_statement).first()
|
||||||
|
|
||||||
|
|
@ -118,36 +120,51 @@ async def get_course_meta(
|
||||||
# RBAC check
|
# RBAC check
|
||||||
await rbac_check(request, course.course_uuid, current_user, "read", db_session)
|
await rbac_check(request, course.course_uuid, current_user, "read", db_session)
|
||||||
|
|
||||||
# Get course authors
|
# Start async tasks concurrently
|
||||||
|
tasks = []
|
||||||
|
|
||||||
|
# Task 1: Get course authors
|
||||||
|
async def get_authors():
|
||||||
authors_statement = (
|
authors_statement = (
|
||||||
select(User)
|
select(User)
|
||||||
.join(ResourceAuthor)
|
.join(ResourceAuthor)
|
||||||
.where(ResourceAuthor.resource_uuid == course.course_uuid)
|
.where(ResourceAuthor.resource_uuid == course.course_uuid)
|
||||||
)
|
)
|
||||||
authors = db_session.exec(authors_statement).all()
|
return db_session.exec(authors_statement).all()
|
||||||
|
|
||||||
# convert from User to UserRead
|
# Task 2: Get course chapters
|
||||||
authors = [UserRead.model_validate(author) for author in authors]
|
async def get_chapters():
|
||||||
|
# Ensure course.id is not None
|
||||||
course = CourseRead(**course.model_dump(), authors=authors)
|
if course.id is None:
|
||||||
|
return []
|
||||||
# Get course chapters
|
return await get_course_chapters(request, course.id, db_session, current_user)
|
||||||
chapters = await get_course_chapters(request, course.id, db_session, current_user)
|
|
||||||
|
|
||||||
# Trail
|
|
||||||
trail = None
|
|
||||||
|
|
||||||
|
# Task 3: Get user trail (only for authenticated users)
|
||||||
|
async def get_trail():
|
||||||
if isinstance(current_user, AnonymousUser):
|
if isinstance(current_user, AnonymousUser):
|
||||||
trail = None
|
return None
|
||||||
else:
|
return await get_user_trail_with_orgid(
|
||||||
trail = await get_user_trail_with_orgid(
|
|
||||||
request, current_user, course.org_id, db_session
|
request, current_user, course.org_id, db_session
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Add tasks to the list
|
||||||
|
tasks.append(get_authors())
|
||||||
|
tasks.append(get_chapters())
|
||||||
|
tasks.append(get_trail())
|
||||||
|
|
||||||
|
# Run all tasks concurrently
|
||||||
|
authors_raw, chapters, trail = await asyncio.gather(*tasks)
|
||||||
|
|
||||||
|
# Convert authors from User to UserRead
|
||||||
|
authors = [UserRead.model_validate(author) for author in authors_raw]
|
||||||
|
|
||||||
|
# Create course read model
|
||||||
|
course_read = CourseRead(**course.model_dump(), authors=authors)
|
||||||
|
|
||||||
return FullCourseReadWithTrail(
|
return FullCourseReadWithTrail(
|
||||||
**course.model_dump(),
|
**course_read.model_dump(),
|
||||||
chapters=chapters,
|
chapters=chapters,
|
||||||
trail=trail if trail else None,
|
trail=trail,
|
||||||
)
|
)
|
||||||
|
|
||||||
async def get_courses_orgslug(
|
async def get_courses_orgslug(
|
||||||
|
|
@ -197,18 +214,33 @@ async def get_courses_orgslug(
|
||||||
|
|
||||||
courses = db_session.exec(query).all()
|
courses = db_session.exec(query).all()
|
||||||
|
|
||||||
# Fetch authors for each course
|
if not courses:
|
||||||
|
return []
|
||||||
|
|
||||||
|
# Get all course UUIDs
|
||||||
|
course_uuids = [course.course_uuid for course in courses]
|
||||||
|
|
||||||
|
# Fetch all authors for all courses in a single query
|
||||||
|
authors_query = (
|
||||||
|
select(ResourceAuthor, User)
|
||||||
|
.join(User, ResourceAuthor.user_id == User.id) # type: ignore
|
||||||
|
.where(ResourceAuthor.resource_uuid.in_(course_uuids)) # type: ignore
|
||||||
|
)
|
||||||
|
|
||||||
|
author_results = db_session.exec(authors_query).all()
|
||||||
|
|
||||||
|
# Create a dictionary mapping course_uuid to list of authors
|
||||||
|
course_authors = {}
|
||||||
|
for resource_author, user in author_results:
|
||||||
|
if resource_author.resource_uuid not in course_authors:
|
||||||
|
course_authors[resource_author.resource_uuid] = []
|
||||||
|
course_authors[resource_author.resource_uuid].append(UserRead.model_validate(user))
|
||||||
|
|
||||||
|
# Create CourseRead objects with authors
|
||||||
course_reads = []
|
course_reads = []
|
||||||
for course in courses:
|
for course in courses:
|
||||||
authors_query = (
|
|
||||||
select(User)
|
|
||||||
.join(ResourceAuthor, ResourceAuthor.user_id == User.id) # type: ignore
|
|
||||||
.where(ResourceAuthor.resource_uuid == course.course_uuid)
|
|
||||||
)
|
|
||||||
authors = db_session.exec(authors_query).all()
|
|
||||||
|
|
||||||
course_read = CourseRead.model_validate(course)
|
course_read = CourseRead.model_validate(course)
|
||||||
course_read.authors = [UserRead.model_validate(author) for author in authors]
|
course_read.authors = course_authors.get(course.course_uuid, [])
|
||||||
course_reads.append(course_read)
|
course_reads.append(course_read)
|
||||||
|
|
||||||
return course_reads
|
return course_reads
|
||||||
|
|
|
||||||
|
|
@ -529,39 +529,31 @@ async def get_orgs_by_user_admin(
|
||||||
page: int = 1,
|
page: int = 1,
|
||||||
limit: int = 10,
|
limit: int = 10,
|
||||||
) -> list[OrganizationRead]:
|
) -> list[OrganizationRead]:
|
||||||
|
# Join Organization, UserOrganization and OrganizationConfig in a single query
|
||||||
statement = (
|
statement = (
|
||||||
select(Organization)
|
select(Organization, OrganizationConfig)
|
||||||
.join(UserOrganization)
|
.join(UserOrganization)
|
||||||
|
.outerjoin(OrganizationConfig)
|
||||||
.where(
|
.where(
|
||||||
UserOrganization.user_id == user_id,
|
UserOrganization.user_id == user_id,
|
||||||
UserOrganization.role_id == 1, # Only where the user is admin
|
UserOrganization.role_id == 1, # Only where the user is admin
|
||||||
|
UserOrganization.org_id == Organization.id,
|
||||||
|
OrganizationConfig.org_id == Organization.id
|
||||||
)
|
)
|
||||||
.offset((page - 1) * limit)
|
.offset((page - 1) * limit)
|
||||||
.limit(limit)
|
.limit(limit)
|
||||||
)
|
)
|
||||||
|
|
||||||
# Get organizations where the user is an admin
|
# Execute single query to get all data
|
||||||
result = db_session.exec(statement)
|
result = db_session.exec(statement)
|
||||||
orgs = result.all()
|
org_data = result.all()
|
||||||
|
|
||||||
|
# Process results in memory
|
||||||
orgsWithConfig = []
|
orgsWithConfig = []
|
||||||
|
for org, org_config in org_data:
|
||||||
for org in orgs:
|
|
||||||
|
|
||||||
# Get org config
|
|
||||||
statement = select(OrganizationConfig).where(
|
|
||||||
OrganizationConfig.org_id == org.id
|
|
||||||
)
|
|
||||||
result = db_session.exec(statement)
|
|
||||||
|
|
||||||
org_config = result.first()
|
|
||||||
|
|
||||||
config = OrganizationConfig.model_validate(org_config) if org_config else {}
|
config = OrganizationConfig.model_validate(org_config) if org_config else {}
|
||||||
|
org_read = OrganizationRead(**org.model_dump(), config=config)
|
||||||
org = OrganizationRead(**org.model_dump(), config=config)
|
orgsWithConfig.append(org_read)
|
||||||
|
|
||||||
orgsWithConfig.append(org)
|
|
||||||
|
|
||||||
return orgsWithConfig
|
return orgsWithConfig
|
||||||
|
|
||||||
|
|
@ -573,36 +565,30 @@ async def get_orgs_by_user(
|
||||||
page: int = 1,
|
page: int = 1,
|
||||||
limit: int = 10,
|
limit: int = 10,
|
||||||
) -> list[OrganizationRead]:
|
) -> list[OrganizationRead]:
|
||||||
|
# Join Organization, UserOrganization and OrganizationConfig in a single query
|
||||||
statement = (
|
statement = (
|
||||||
select(Organization)
|
select(Organization, OrganizationConfig)
|
||||||
.join(UserOrganization)
|
.join(UserOrganization)
|
||||||
.where(UserOrganization.user_id == user_id)
|
.outerjoin(OrganizationConfig)
|
||||||
|
.where(
|
||||||
|
UserOrganization.user_id == user_id,
|
||||||
|
UserOrganization.org_id == Organization.id,
|
||||||
|
OrganizationConfig.org_id == Organization.id
|
||||||
|
)
|
||||||
.offset((page - 1) * limit)
|
.offset((page - 1) * limit)
|
||||||
.limit(limit)
|
.limit(limit)
|
||||||
)
|
)
|
||||||
|
|
||||||
# Get organizations where the user is an admin
|
# Execute single query to get all data
|
||||||
result = db_session.exec(statement)
|
result = db_session.exec(statement)
|
||||||
orgs = result.all()
|
org_data = result.all()
|
||||||
|
|
||||||
|
# Process results in memory
|
||||||
orgsWithConfig = []
|
orgsWithConfig = []
|
||||||
|
for org, org_config in org_data:
|
||||||
for org in orgs:
|
|
||||||
|
|
||||||
# Get org config
|
|
||||||
statement = select(OrganizationConfig).where(
|
|
||||||
OrganizationConfig.org_id == org.id
|
|
||||||
)
|
|
||||||
result = db_session.exec(statement)
|
|
||||||
|
|
||||||
org_config = result.first()
|
|
||||||
|
|
||||||
config = OrganizationConfig.model_validate(org_config) if org_config else {}
|
config = OrganizationConfig.model_validate(org_config) if org_config else {}
|
||||||
|
org_read = OrganizationRead(**org.model_dump(), config=config)
|
||||||
org = OrganizationRead(**org.model_dump(), config=config)
|
orgsWithConfig.append(org_read)
|
||||||
|
|
||||||
orgsWithConfig.append(org)
|
|
||||||
|
|
||||||
return orgsWithConfig
|
return orgsWithConfig
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -499,6 +499,7 @@ function ActivityChapterDropdown(props: {
|
||||||
<Link
|
<Link
|
||||||
key={activity.id}
|
key={activity.id}
|
||||||
href={getUriWithOrg(props.orgslug, '') + `/course/${cleanCourseUuid}/activity/${cleanActivityUuid}`}
|
href={getUriWithOrg(props.orgslug, '') + `/course/${cleanCourseUuid}/activity/${cleanActivityUuid}`}
|
||||||
|
prefetch={false}
|
||||||
onClick={() => setIsOpen(false)}
|
onClick={() => setIsOpen(false)}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
|
|
|
||||||
|
|
@ -11,25 +11,36 @@ type MetadataProps = {
|
||||||
searchParams: Promise<{ [key: string]: string | string[] | undefined }>
|
searchParams: Promise<{ [key: string]: string | string[] | undefined }>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Session = {
|
||||||
|
tokens?: {
|
||||||
|
access_token?: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add this function at the top level to avoid duplicate fetches
|
||||||
|
async function fetchCourseMetadata(courseuuid: string, access_token: string | null | undefined) {
|
||||||
|
return await getCourseMetadata(
|
||||||
|
courseuuid,
|
||||||
|
{ revalidate: 1800, tags: ['courses'] },
|
||||||
|
access_token || null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export async function generateMetadata(props: MetadataProps): Promise<Metadata> {
|
export async function generateMetadata(props: MetadataProps): Promise<Metadata> {
|
||||||
const params = await props.params;
|
const params = await props.params;
|
||||||
const session = await getServerSession(nextAuthOptions)
|
const session = await getServerSession(nextAuthOptions as any) as Session
|
||||||
const access_token = session?.tokens?.access_token
|
const access_token = session?.tokens?.access_token || null
|
||||||
|
|
||||||
// Get Org context information
|
// Get Org context information
|
||||||
const org = await getOrganizationContextInfo(params.orgslug, {
|
const org = await getOrganizationContextInfo(params.orgslug, {
|
||||||
revalidate: 1800,
|
revalidate: 1800,
|
||||||
tags: ['organizations'],
|
tags: ['organizations'],
|
||||||
})
|
})
|
||||||
const course_meta = await getCourseMetadata(
|
const course_meta = await fetchCourseMetadata(params.courseuuid, access_token)
|
||||||
params.courseuuid,
|
|
||||||
{ revalidate: 0, tags: ['courses'] },
|
|
||||||
access_token ? access_token : null
|
|
||||||
)
|
|
||||||
const activity = await getActivityWithAuthHeader(
|
const activity = await getActivityWithAuthHeader(
|
||||||
params.activityid,
|
params.activityid,
|
||||||
{ revalidate: 0, tags: ['activities'] },
|
{ revalidate: 1800, tags: ['activities'] },
|
||||||
access_token ? access_token : null
|
access_token || null
|
||||||
)
|
)
|
||||||
|
|
||||||
// SEO
|
// SEO
|
||||||
|
|
@ -57,24 +68,22 @@ export async function generateMetadata(props: MetadataProps): Promise<Metadata>
|
||||||
}
|
}
|
||||||
|
|
||||||
const ActivityPage = async (params: any) => {
|
const ActivityPage = async (params: any) => {
|
||||||
const session = await getServerSession(nextAuthOptions)
|
const session = await getServerSession(nextAuthOptions as any) as Session
|
||||||
const access_token = session?.tokens?.access_token
|
const access_token = session?.tokens?.access_token || null
|
||||||
const activityid = (await params.params).activityid
|
const activityid = (await params.params).activityid
|
||||||
const courseuuid = (await params.params).courseuuid
|
const courseuuid = (await params.params).courseuuid
|
||||||
const orgslug = (await params.params).orgslug
|
const orgslug = (await params.params).orgslug
|
||||||
|
|
||||||
const course_meta = await getCourseMetadata(
|
const [course_meta, activity] = await Promise.all([
|
||||||
courseuuid,
|
fetchCourseMetadata(courseuuid, access_token),
|
||||||
{ revalidate: 0, tags: ['courses'] },
|
getActivityWithAuthHeader(
|
||||||
access_token ? access_token : null
|
|
||||||
)
|
|
||||||
const activity = await getActivityWithAuthHeader(
|
|
||||||
activityid,
|
activityid,
|
||||||
{ revalidate: 0, tags: ['activities'] },
|
{ revalidate: 1800, tags: ['activities'] },
|
||||||
access_token ? access_token : null
|
access_token || null
|
||||||
)
|
)
|
||||||
|
])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
|
||||||
<ActivityClient
|
<ActivityClient
|
||||||
activityid={activityid}
|
activityid={activityid}
|
||||||
courseuuid={courseuuid}
|
courseuuid={courseuuid}
|
||||||
|
|
@ -82,7 +91,6 @@ const ActivityPage = async (params: any) => {
|
||||||
activity={activity}
|
activity={activity}
|
||||||
course={course_meta}
|
course={course_meta}
|
||||||
/>
|
/>
|
||||||
</>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -223,6 +223,7 @@ const CourseClient = (props: any) => {
|
||||||
)}`
|
)}`
|
||||||
}
|
}
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
|
prefetch={false}
|
||||||
>
|
>
|
||||||
<p>{activity.name}</p>
|
<p>{activity.name}</p>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
@ -240,6 +241,7 @@ const CourseClient = (props: any) => {
|
||||||
)}`
|
)}`
|
||||||
}
|
}
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
|
prefetch={false}
|
||||||
>
|
>
|
||||||
<div className="text-xs bg-gray-100 text-gray-400 font-bold px-2 py-1 rounded-full flex space-x-1 items-center">
|
<div className="text-xs bg-gray-100 text-gray-400 font-bold px-2 py-1 rounded-full flex space-x-1 items-center">
|
||||||
<p>Page</p>
|
<p>Page</p>
|
||||||
|
|
@ -260,6 +262,7 @@ const CourseClient = (props: any) => {
|
||||||
)}`
|
)}`
|
||||||
}
|
}
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
|
prefetch={false}
|
||||||
>
|
>
|
||||||
<div className="text-xs bg-gray-100 text-gray-400 font-bold px-2 py-1 rounded-full flex space-x-1 items-center">
|
<div className="text-xs bg-gray-100 text-gray-400 font-bold px-2 py-1 rounded-full flex space-x-1 items-center">
|
||||||
<p>Video</p>
|
<p>Video</p>
|
||||||
|
|
@ -281,6 +284,7 @@ const CourseClient = (props: any) => {
|
||||||
)}`
|
)}`
|
||||||
}
|
}
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
|
prefetch={false}
|
||||||
>
|
>
|
||||||
<div className="text-xs bg-gray-100 text-gray-400 font-bold px-2 py-1 rounded-full flex space-x-1 items-center">
|
<div className="text-xs bg-gray-100 text-gray-400 font-bold px-2 py-1 rounded-full flex space-x-1 items-center">
|
||||||
<p>Document</p>
|
<p>Document</p>
|
||||||
|
|
@ -302,6 +306,7 @@ const CourseClient = (props: any) => {
|
||||||
)}`
|
)}`
|
||||||
}
|
}
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
|
prefetch={false}
|
||||||
>
|
>
|
||||||
<div className="text-xs bg-gray-100 text-gray-400 font-bold px-2 py-1 rounded-full flex space-x-1 items-center">
|
<div className="text-xs bg-gray-100 text-gray-400 font-bold px-2 py-1 rounded-full flex space-x-1 items-center">
|
||||||
<p>Assignment</p>
|
<p>Assignment</p>
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ export async function generateMetadata(props: MetadataProps): Promise<Metadata>
|
||||||
})
|
})
|
||||||
const course_meta = await getCourseMetadata(
|
const course_meta = await getCourseMetadata(
|
||||||
params.courseuuid,
|
params.courseuuid,
|
||||||
{ revalidate: 0, tags: ['courses'] },
|
{ revalidate: 1800, tags: ['courses'] },
|
||||||
access_token ? access_token : null
|
access_token ? access_token : null
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -66,24 +66,23 @@ export async function generateMetadata(props: MetadataProps): Promise<Metadata>
|
||||||
}
|
}
|
||||||
|
|
||||||
const CoursePage = async (params: any) => {
|
const CoursePage = async (params: any) => {
|
||||||
const courseuuid = (await params.params).courseuuid
|
|
||||||
const orgslug = (await params.params).orgslug
|
|
||||||
const session = await getServerSession(nextAuthOptions)
|
const session = await getServerSession(nextAuthOptions)
|
||||||
const access_token = session?.tokens?.access_token
|
const access_token = session?.tokens?.access_token
|
||||||
|
|
||||||
|
// Fetch course metadata once
|
||||||
const course_meta = await getCourseMetadata(
|
const course_meta = await getCourseMetadata(
|
||||||
courseuuid,
|
params.params.courseuuid,
|
||||||
{ revalidate: 0, tags: ['courses'] },
|
{ revalidate: 1800, tags: ['courses'] },
|
||||||
access_token ? access_token : null
|
access_token ? access_token : null
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
|
||||||
<CourseClient
|
<CourseClient
|
||||||
courseuuid={courseuuid}
|
courseuuid={params.params.courseuuid}
|
||||||
orgslug={orgslug}
|
orgslug={params.params.orgslug}
|
||||||
course={course_meta}
|
course={course_meta}
|
||||||
|
access_token={access_token}
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -191,7 +191,6 @@ function ActivityElement(props: ActivitiyElementProps) {
|
||||||
''
|
''
|
||||||
)}`
|
)}`
|
||||||
}
|
}
|
||||||
prefetch
|
|
||||||
className="p-1 px-2 sm:px-3 bg-linear-to-bl text-cyan-800 from-sky-400/50 to-cyan-200/80 border border-cyan-600/10 shadow-md rounded-md font-bold text-xs flex items-center space-x-1 transition-colors duration-200 hover:from-sky-500/50 hover:to-cyan-300/80"
|
className="p-1 px-2 sm:px-3 bg-linear-to-bl text-cyan-800 from-sky-400/50 to-cyan-200/80 border border-cyan-600/10 shadow-md rounded-md font-bold text-xs flex items-center space-x-1 transition-colors duration-200 hover:from-sky-500/50 hover:to-cyan-300/80"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
>
|
>
|
||||||
|
|
@ -296,7 +295,6 @@ const ActivityElementOptions = ({ activity, isMobile }: { activity: any; isMobil
|
||||||
''
|
''
|
||||||
)}/edit`
|
)}/edit`
|
||||||
}
|
}
|
||||||
prefetch
|
|
||||||
className={`hover:cursor-pointer p-1 ${isMobile ? 'px-2' : 'px-3'} bg-sky-700 rounded-md items-center`}
|
className={`hover:cursor-pointer p-1 ${isMobile ? 'px-2' : 'px-3'} bg-sky-700 rounded-md items-center`}
|
||||||
target='_blank'
|
target='_blank'
|
||||||
>
|
>
|
||||||
|
|
@ -313,7 +311,6 @@ const ActivityElementOptions = ({ activity, isMobile }: { activity: any; isMobil
|
||||||
getUriWithOrg(org.slug, '') +
|
getUriWithOrg(org.slug, '') +
|
||||||
`/dash/assignments/${assignmentUUID}`
|
`/dash/assignments/${assignmentUUID}`
|
||||||
}
|
}
|
||||||
prefetch
|
|
||||||
className={`hover:cursor-pointer p-1 ${isMobile ? 'px-2' : 'px-3'} bg-teal-700 rounded-md items-center`}
|
className={`hover:cursor-pointer p-1 ${isMobile ? 'px-2' : 'px-3'} bg-teal-700 rounded-md items-center`}
|
||||||
>
|
>
|
||||||
<div className="text-sky-100 font-bold text-xs flex items-center space-x-1">
|
<div className="text-sky-100 font-bold text-xs flex items-center space-x-1">
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,7 @@ function ActivityIndicators(props: Props) {
|
||||||
key={activity.activity_uuid}
|
key={activity.activity_uuid}
|
||||||
>
|
>
|
||||||
<Link
|
<Link
|
||||||
prefetch={true}
|
prefetch={false}
|
||||||
href={
|
href={
|
||||||
getUriWithOrg(orgslug, '') +
|
getUriWithOrg(orgslug, '') +
|
||||||
`/course/${courseid}/activity/${activity.activity_uuid.replace(
|
`/course/${courseid}/activity/${activity.activity_uuid.replace(
|
||||||
|
|
|
||||||
|
|
@ -111,11 +111,11 @@ export async function deleteActivity(activity_uuid: any, access_token: string) {
|
||||||
export async function getActivityWithAuthHeader(
|
export async function getActivityWithAuthHeader(
|
||||||
activity_uuid: any,
|
activity_uuid: any,
|
||||||
next: any,
|
next: any,
|
||||||
access_token: string
|
access_token: string | null | undefined
|
||||||
) {
|
) {
|
||||||
const result = await fetch(
|
const result = await fetch(
|
||||||
`${getAPIUrl()}activities/activity_${activity_uuid}`,
|
`${getAPIUrl()}activities/activity_${activity_uuid}`,
|
||||||
RequestBodyWithAuthHeader('GET', null, next, access_token)
|
RequestBodyWithAuthHeader('GET', null, next, access_token || undefined)
|
||||||
)
|
)
|
||||||
const res = await result.json()
|
const res = await result.json()
|
||||||
return res
|
return res
|
||||||
|
|
|
||||||
|
|
@ -41,13 +41,13 @@ export async function searchOrgCourses(
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getCourseMetadata(
|
export async function getCourseMetadata(
|
||||||
course_uuid: any,
|
course_uuid: string,
|
||||||
next: any,
|
next: any,
|
||||||
access_token: string
|
access_token: string | null | undefined
|
||||||
) {
|
) {
|
||||||
const result = await fetch(
|
const result = await fetch(
|
||||||
`${getAPIUrl()}courses/course_${course_uuid}/meta`,
|
`${getAPIUrl()}courses/course_${course_uuid}/meta`,
|
||||||
RequestBodyWithAuthHeader('GET', null, next, access_token)
|
RequestBodyWithAuthHeader('GET', null, next, access_token || undefined)
|
||||||
)
|
)
|
||||||
const res = await errorHandling(result)
|
const res = await errorHandling(result)
|
||||||
return res
|
return res
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue