1- import { validateSharePointSiteId } from '@/lib/core/security/input-validation'
1+ import {
2+ validateMicrosoftGraphId ,
3+ validateSharePointSiteId ,
4+ } from '@/lib/core/security/input-validation'
25import type { ServerSelectorKey } from '@/lib/selectors/manifest'
36import { resolveSelectorOAuthAccessToken } from '@/lib/selectors/server/credentials'
47import {
58 SelectorConnectionUnavailableError ,
69 SelectorContextUnavailableError ,
10+ SelectorOptionsUnavailableError ,
711} from '@/lib/selectors/server/errors'
812import { flatSelectorResult } from '@/lib/selectors/server/providers/flat-results'
913import { fetchProviderJson } from '@/lib/selectors/server/providers/provider-http'
10- import type {
11- ExecuteServerSelectorArgs ,
12- ServerSelectorAttachmentMap ,
14+ import {
15+ detailSelectorResult ,
16+ type ExecuteServerSelectorArgs ,
17+ type ServerSelectorAttachmentMap ,
1318} from '@/lib/selectors/server/types'
19+ import type { SafeSelectorOption } from '@/lib/selectors/types'
1420import { assertGraphNextPageUrl , getGraphNextPageUrl } from '@/tools/sharepoint/utils'
1521
1622type SharePointSelectorKey = Extract < ServerSelectorKey , 'sharepoint.lists' | 'sharepoint.sites' >
@@ -58,18 +64,79 @@ async function drainGraph<T>(
5864 return { values, truncated : Boolean ( nextUrl ) }
5965}
6066
67+ function requireSiteId ( value : string | undefined ) : string {
68+ const validation = validateSharePointSiteId ( value )
69+ if ( ! validation . isValid || ! validation . sanitized ) {
70+ throw new SelectorContextUnavailableError ( )
71+ }
72+ return validation . sanitized
73+ }
74+
75+ function requireListId ( value : string ) : string {
76+ const trimmed = value . trim ( )
77+ const validation = validateMicrosoftGraphId ( trimmed , 'listId' )
78+ if ( ! validation . isValid || trimmed . length > 512 ) {
79+ throw new SelectorContextUnavailableError ( )
80+ }
81+ return trimmed
82+ }
83+
84+ async function getGraphDetail < T > ( args : ExecuteServerSelectorArgs , url : string ) : Promise < T > {
85+ const token = await graphToken ( args )
86+ return fetchProviderJson < T > ( url , {
87+ headers : { Authorization : `Bearer ${ token } ` } ,
88+ signal : args . signal ,
89+ redirect : 'error' ,
90+ } )
91+ }
92+
93+ async function getList (
94+ args : ExecuteServerSelectorArgs ,
95+ listId : string
96+ ) : Promise < SafeSelectorOption > {
97+ const siteId = requireSiteId ( args . context . siteId )
98+ const requestedId = requireListId ( listId )
99+ const list = await getGraphDetail < { id ?: string ; displayName ?: string | null } > (
100+ args ,
101+ `https://graph.microsoft.com/v1.0/sites/${ encodeURIComponent ( siteId ) } /lists/${ encodeURIComponent ( requestedId ) } ?$select=id,displayName,list`
102+ )
103+ const providerId = typeof list . id === 'string' ? list . id . trim ( ) : ''
104+ const displayName = typeof list . displayName === 'string' ? list . displayName . trim ( ) : ''
105+ const label = displayName || providerId
106+ if ( ! providerId || ! label ) throw new SelectorOptionsUnavailableError ( )
107+ return { id : requestedId , label }
108+ }
109+
110+ async function getSite (
111+ args : ExecuteServerSelectorArgs ,
112+ siteId : string
113+ ) : Promise < SafeSelectorOption > {
114+ const requestedId = requireSiteId ( siteId )
115+ const site = await getGraphDetail < {
116+ id ?: string
117+ name ?: string | null
118+ displayName ?: string | null
119+ } > (
120+ args ,
121+ `https://graph.microsoft.com/v1.0/sites/${ encodeURIComponent ( requestedId ) } ?$select=id,name,displayName,webUrl`
122+ )
123+ const providerId = typeof site . id === 'string' ? site . id . trim ( ) : ''
124+ const displayName = typeof site . displayName === 'string' ? site . displayName . trim ( ) : ''
125+ const name = typeof site . name === 'string' ? site . name . trim ( ) : ''
126+ const label = displayName || name || providerId
127+ if ( ! providerId || ! label ) throw new SelectorOptionsUnavailableError ( )
128+ return { id : requestedId , label }
129+ }
130+
61131async function listLists ( args : ExecuteServerSelectorArgs ) {
62- const siteId = args . context . siteId
63- if ( ! siteId ) throw new SelectorContextUnavailableError ( )
64- const validation = validateSharePointSiteId ( siteId )
65- if ( ! validation . isValid ) throw new SelectorContextUnavailableError ( )
132+ const siteId = requireSiteId ( args . context . siteId )
66133 const result = await drainGraph < {
67134 id : string
68135 displayName : string
69136 list ?: { hidden ?: boolean }
70137 } > (
71138 args ,
72- `https://graph.microsoft.com/v1.0/sites/${ encodeURIComponent ( validation . sanitized ?? siteId ) } /lists?$select=id,displayName,description,webUrl,list&$top=999`
139+ `https://graph.microsoft.com/v1.0/sites/${ encodeURIComponent ( siteId ) } /lists?$select=id,displayName,description,webUrl,list&$top=999`
73140 )
74141 return {
75142 items : result . values
@@ -95,11 +162,14 @@ export const sharepointSelectorAttachments = {
95162 credential : sharepointCredential ,
96163 destination : 'fixed' ,
97164 execute : async ( args ) => {
165+ if ( args . request . kind === 'detail' ) {
166+ return detailSelectorResult ( await getList ( args , args . request . id ) )
167+ }
98168 const result = await listLists ( args )
99169 return flatSelectorResult (
100170 args . request ,
101171 result . items ,
102- true ,
172+ false ,
103173 result . truncated
104174 ? { truncated : { reason : 'provider-cap' , pages : MAX_GRAPH_PAGES } }
105175 : undefined
@@ -110,11 +180,14 @@ export const sharepointSelectorAttachments = {
110180 credential : siteCredential ,
111181 destination : 'fixed' ,
112182 execute : async ( args ) => {
183+ if ( args . request . kind === 'detail' ) {
184+ return detailSelectorResult ( await getSite ( args , args . request . id ) )
185+ }
113186 const result = await listSites ( args )
114187 return flatSelectorResult (
115188 args . request ,
116189 result . items ,
117- true ,
190+ false ,
118191 result . truncated
119192 ? { truncated : { reason : 'provider-cap' , pages : MAX_GRAPH_PAGES } }
120193 : undefined
0 commit comments