fix: filter search records based on roles

This commit is contained in:
Jannat Patel
2025-12-10 11:17:21 +05:30
parent 552b5845ea
commit 7e3c5beaea
8 changed files with 81 additions and 47 deletions

View File

@@ -90,6 +90,8 @@ declare module 'vue' {
LucideArrowDown: typeof import('~icons/lucide/arrow-down')['default'] LucideArrowDown: typeof import('~icons/lucide/arrow-down')['default']
LucideArrowUp: typeof import('~icons/lucide/arrow-up')['default'] LucideArrowUp: typeof import('~icons/lucide/arrow-up')['default']
LucideCornerDownLeft: typeof import('~icons/lucide/corner-down-left')['default'] LucideCornerDownLeft: typeof import('~icons/lucide/corner-down-left')['default']
LucideSearch: typeof import('~icons/lucide/search')['default']
LucideX: typeof import('~icons/lucide/x')['default']
Members: typeof import('./src/components/Settings/Members.vue')['default'] Members: typeof import('./src/components/Settings/Members.vue')['default']
MobileLayout: typeof import('./src/components/MobileLayout.vue')['default'] MobileLayout: typeof import('./src/components/MobileLayout.vue')['default']
MultiSelect: typeof import('./src/components/Controls/MultiSelect.vue')['default'] MultiSelect: typeof import('./src/components/Controls/MultiSelect.vue')['default']

View File

@@ -18,11 +18,17 @@
<div class="max-h-96 overflow-auto mb-2"> <div class="max-h-96 overflow-auto mb-2">
<div v-if="query.length" class="mt-5 space-y-5"> <div v-if="query.length" class="mt-5 space-y-5">
<CommandPaletteGroup :list="searchResults" /> <CommandPaletteGroup
:list="searchResults"
@navigateTo="navigateTo"
/>
</div> </div>
<div v-else class="mt-5 space-y-5"> <div v-else class="mt-5 space-y-5">
<CommandPaletteGroup :list="jumpToOptions" /> <CommandPaletteGroup
:list="jumpToOptions"
@navigateTo="navigateTo"
/>
</div> </div>
</div> </div>
@@ -61,7 +67,8 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { createResource, debounce, Dialog } from 'frappe-ui' import { createResource, debounce, Dialog } from 'frappe-ui'
import { computed, inject, nextTick, onMounted, ref, watch } from 'vue' import { nextTick, onMounted, ref, watch } from 'vue'
import { useRouter } from 'vue-router'
import { import {
BookOpen, BookOpen,
Briefcase, Briefcase,
@@ -72,7 +79,6 @@ import {
Search, Search,
Users, Users,
} from 'lucide-vue-next' } from 'lucide-vue-next'
import { useRouter } from 'vue-router'
import CommandPaletteGroup from './CommandPaletteGroup.vue' import CommandPaletteGroup from './CommandPaletteGroup.vue'
const show = defineModel<boolean>({ required: true, default: false }) const show = defineModel<boolean>({ required: true, default: false })
@@ -146,6 +152,13 @@ watch(
{ immediate: true } { immediate: true }
) )
watch(show, () => {
if (!show.value) {
query.value = ''
searchResults.value = []
}
})
onMounted(() => { onMounted(() => {
addKeyboardShortcuts() addKeyboardShortcuts()
}) })

View File

@@ -8,6 +8,7 @@
v-for="item in result.items" v-for="item in result.items"
class="flex items-center justify-between p-2 rounded hover:bg-surface-gray-2 cursor-pointer" class="flex items-center justify-between p-2 rounded hover:bg-surface-gray-2 cursor-pointer"
:class="{ 'bg-surface-gray-2': item.isActive }" :class="{ 'bg-surface-gray-2': item.isActive }"
@click="emit('navigateTo', item.route)"
> >
<div class="flex items-center space-x-3"> <div class="flex items-center space-x-3">
<component <component
@@ -18,7 +19,7 @@
<div v-html="item.title"></div> <div v-html="item.title"></div>
</div> </div>
<div v-if="item.modified" class="text-ink-gray-5"> <div v-if="item.modified" class="text-ink-gray-5">
{{ item.modified }} {{ dayjs.unix(item.modified).fromNow(true) }} {{ dayjs.unix(item.modified).fromNow(true) }}
</div> </div>
</div> </div>
</div> </div>
@@ -26,9 +27,9 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { inject } from 'vue' import { inject } from 'vue'
import { dayjsLocal } from 'frappe-ui'
const dayjs = inject<any>('$dayjs') const dayjs = inject<any>('$dayjs')
const emit = defineEmits(['navigateTo'])
const props = defineProps<{ const props = defineProps<{
list: Array<{ list: Array<{

View File

@@ -192,7 +192,6 @@ import PageModal from '@/components/Modals/PageModal.vue'
import { capture } from '@/telemetry' import { capture } from '@/telemetry'
import LMSLogo from '@/components/Icons/LMSLogo.vue' import LMSLogo from '@/components/Icons/LMSLogo.vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import InviteIcon from '@/components/Icons/InviteIcon.vue'
import { import {
ref, ref,
onMounted, onMounted,
@@ -225,10 +224,10 @@ import {
minimize, minimize,
IntermediateStepModal, IntermediateStepModal,
} from 'frappe-ui/frappe' } from 'frappe-ui/frappe'
import InviteIcon from './Icons/InviteIcon.vue' import InviteIcon from '@/components/Icons/InviteIcon.vue'
import UserDropdown from '@/components/UserDropdown.vue' import UserDropdown from '@/components/Sidebar/UserDropdown.vue'
import CollapseSidebar from '@/components/Icons/CollapseSidebar.vue' import CollapseSidebar from '@/components/Icons/CollapseSidebar.vue'
import SidebarLink from '@/components/SidebarLink.vue' import SidebarLink from '@/components/Sidebar/SidebarLink.vue'
import CommandPalette from '@/components/CommandPalette/CommandPalette.vue' import CommandPalette from '@/components/CommandPalette/CommandPalette.vue'
const { user } = sessionStore() const { user } = sessionStore()

View File

@@ -48,7 +48,7 @@ const apps = createResource({
name: 'frappe', name: 'frappe',
logo: '/assets/lms/images/desk.png', logo: '/assets/lms/images/desk.png',
title: __('Desk'), title: __('Desk'),
route: '/app', route: '/desk/lms',
}, },
] ]
data.map((app) => { data.map((app) => {

View File

@@ -65,6 +65,6 @@ export default defineConfig(({ mode }) => ({
'highlight.js', 'highlight.js',
'plyr', 'plyr',
], ],
exclude: mode === 'production' ? [] : ['frappe-ui'], //exclude: mode === 'production' ? [] : ['frappe-ui'],
}, },
})) }))

View File

@@ -1,4 +1,5 @@
import frappe import frappe
from frappe.utils import nowdate
@frappe.whitelist() @frappe.whitelist()
@@ -12,14 +13,20 @@ def search_sqlite(query: str):
except LearningSearchIndexMissingError: except LearningSearchIndexMissingError:
return [] return []
return prepare_search_results(result)
def prepare_search_results(result):
roles = frappe.get_roles()
groups = {} groups = {}
print(result)
for r in result["results"]: for r in result["results"]:
doctype = r["doctype"] doctype = r["doctype"]
if doctype == "LMS Course" and can_access_course(r, roles):
if doctype == "LMS Course": r["author_info"] = get_author_info(r.get("author"))
groups.setdefault("Courses", []).append(r) groups.setdefault("Courses", []).append(r)
elif doctype == "LMS Batch": elif doctype == "LMS Batch" and can_access_batch(r, roles):
r["author_info"] = get_author_info(r.get("author"))
groups.setdefault("Batches", []).append(r) groups.setdefault("Batches", []).append(r)
out = [] out = []
@@ -27,3 +34,31 @@ def search_sqlite(query: str):
out.append({"title": key, "items": groups[key]}) out.append({"title": key, "items": groups[key]})
return out return out
def can_access_course(course, roles):
if can_create_course(roles):
return True
elif course.get("published"):
return True
return False
def can_access_batch(batch, roles):
if can_create_batch(roles):
return True
elif batch.get("published") and batch.get("start_date") >= nowdate():
return True
return False
def can_create_course(roles):
return "Course Creator" in roles or "Moderator" in roles
def can_create_batch(roles):
return "Batch Evaluator" in roles or "Moderator" in roles
def get_author_info(owner):
return frappe.db.get_value("User", owner, ["full_name", "user_image", "username", "email"], as_dict=True)

View File

@@ -2,15 +2,14 @@ from contextlib import suppress
import frappe import frappe
from frappe.search.sqlite_search import SQLiteSearch, SQLiteSearchIndexMissingError from frappe.search.sqlite_search import SQLiteSearch, SQLiteSearchIndexMissingError
from frappe.utils import update_progress_bar from frappe.utils import nowdate
from redis.exceptions import ResponseError
class LearningSearch(SQLiteSearch): class LearningSearch(SQLiteSearch):
INDEX_NAME = "learning.db" INDEX_NAME = "learning.db"
INDEX_SCHEMA = { INDEX_SCHEMA = {
"metadata_fields": ["category", "owner", "published"], "metadata_fields": ["category", "owner", "published", "published_on", "start_date"],
"tokenizer": "unicode61 remove_diacritics 2 tokenchars '-_'", "tokenizer": "unicode61 remove_diacritics 2 tokenchars '-_'",
} }
@@ -64,31 +63,6 @@ class LearningSearch(SQLiteSearch):
], ],
} }
def can_create_course(self, roles):
return "Course Creator" in roles or "Moderator" in roles
def can_create_batch(self, roles):
return "Batch Evaluator" in roles or "Moderator" in roles
def get_records(self, doctype):
records = []
roles = frappe.get_roles()
filters = {}
if doctype == "LMS Course":
if not self.can_create_course(roles):
filters = {"published": 1}
if doctype == "LMS Batch":
if not self.can_create_batch(roles):
filters = {"published": 1}
records = frappe.db.get_all(doctype, filters=filters, fields=self.DOCTYPE_FIELDS[doctype])
for record in records:
record["doctype"] = doctype
return records
def build_index(self): def build_index(self):
try: try:
super().build_index() super().build_index()
@@ -96,11 +70,21 @@ class LearningSearch(SQLiteSearch):
frappe.throw(e) frappe.throw(e)
def get_search_filters(self): def get_search_filters(self):
roles = frappe.get_roles()
if not (self.can_create_course(roles) and self.can_create_batch(roles)):
return {"published": 1}
return {} return {}
@SQLiteSearch.scoring_function
def get_doctype_boost(self, row, query, query_words):
doctype = row["doctype"]
if doctype == "LMS Course":
if row["published"]:
return 1.3
elif doctype == "LMS Batch":
if row["published"] and row["start_date"] >= nowdate():
return 1.3
elif row["published"]:
return 1.2
return 1.0
class LearningSearchIndexMissingError(SQLiteSearchIndexMissingError): class LearningSearchIndexMissingError(SQLiteSearchIndexMissingError):
pass pass