mirror of
https://github.com/verdaccio/verdaccio.git
synced 2025-01-20 22:52:46 -05:00
feat: add support for new search endpoint(#1732)
https://github.com/verdaccio/verdaccio/issues/310 * Add an incomplete implementation of the v1/search api * Use parseInt and || instead of ?? for processing numeric arguments * Remove res.end, as we already use response.json * Remove unused request parameters and add TODO comment * Fix eslint errors Co-authored-by: Joshua Jensch <j.jensch@hvs.de>
This commit is contained in:
parent
0a83d94819
commit
9ac307adc5
2 changed files with 103 additions and 0 deletions
98
src/api/endpoint/api/v1/search.ts
Normal file
98
src/api/endpoint/api/v1/search.ts
Normal file
|
@ -0,0 +1,98 @@
|
|||
import semver from 'semver'
|
||||
import { Package } from '@verdaccio/types';
|
||||
|
||||
function compileTextSearch(textSearch: string): ((pkg: Package) => boolean) {
|
||||
const personMatch = (person, search) => {
|
||||
if(typeof person === 'string')
|
||||
return person.includes(search);
|
||||
|
||||
if(typeof person === 'object')
|
||||
for(const field of Object.values(person))
|
||||
if(typeof field === 'string' && field.includes(search))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
const matcher = function(q) {
|
||||
const match = q.match(/author:(.*)/)
|
||||
if(match !== null)
|
||||
return (pkg) => personMatch(pkg.author, match[1])
|
||||
|
||||
// TODO: maintainer, keywords, not/is unstable insecure, boost-exact
|
||||
// TODO implement some scoring system for freetext
|
||||
return (pkg) => {
|
||||
return ['name', 'displayName', 'description']
|
||||
.map(k => pkg[k])
|
||||
.filter(x => x !== undefined)
|
||||
.some(txt => txt.includes(q))
|
||||
};
|
||||
}
|
||||
|
||||
const textMatchers = (textSearch || '').split(' ').map(matcher);
|
||||
return (pkg) => textMatchers.every(m => m(pkg));
|
||||
}
|
||||
|
||||
export default function(route, auth, storage): void {
|
||||
route.get('/-/v1/search', (req, res)=>{
|
||||
// TODO: implement proper result scoring weighted by quality, popularity and maintenance query parameters
|
||||
let [text, size, from /* , quality, popularity, maintenance */] =
|
||||
['text', 'size', 'from' /* , 'quality', 'popularity', 'maintenance' */]
|
||||
.map(k => req.query[k])
|
||||
|
||||
size = parseInt(size) || 20;
|
||||
from = parseInt(from) || 0;
|
||||
|
||||
const isInteresting = compileTextSearch(text);
|
||||
|
||||
const resultStream = storage.search(0, {req: {query: {local: true}}});
|
||||
const resultBuf = [] as any;
|
||||
let completed = false;
|
||||
|
||||
const sendResponse = (): void => {
|
||||
completed = true;
|
||||
resultStream.destroy()
|
||||
|
||||
const final = resultBuf.slice(from, size).map(pkg => {
|
||||
return {
|
||||
package: pkg,
|
||||
flags: {
|
||||
unstable:
|
||||
Object.keys(pkg.versions)
|
||||
.some(v => semver.satisfies(v, '^1.0.0'))
|
||||
? undefined
|
||||
: true
|
||||
},
|
||||
score: {
|
||||
final: 1,
|
||||
detail: {
|
||||
quality: 1,
|
||||
popularity: 1,
|
||||
maintenance: 0
|
||||
}
|
||||
},
|
||||
searchScore: 100000
|
||||
}
|
||||
})
|
||||
const response = {
|
||||
objects: final,
|
||||
total: final.length,
|
||||
time: new Date().toUTCString()
|
||||
}
|
||||
|
||||
res.status(200)
|
||||
.json(response)
|
||||
}
|
||||
|
||||
resultStream.on('data', (pkg)=>{
|
||||
if(!isInteresting(pkg))
|
||||
return;
|
||||
resultBuf.push(pkg)
|
||||
if(!completed && resultBuf.length >= size + from)
|
||||
sendResponse();
|
||||
})
|
||||
resultStream.on('end', ()=>{
|
||||
if(!completed)
|
||||
sendResponse()
|
||||
})
|
||||
})
|
||||
}
|
|
@ -15,6 +15,8 @@ import stars from './api/stars';
|
|||
import profile from './api/v1/profile';
|
||||
import token from './api/v1/token';
|
||||
|
||||
import v1Search from './api/v1/search'
|
||||
|
||||
const { match, validateName, validatePackage, encodeScopePackage, antiLoop } = require('../middleware');
|
||||
|
||||
export default function(config: Config, auth: IAuth, storage: IStorageHandler) {
|
||||
|
@ -54,6 +56,9 @@ export default function(config: Config, auth: IAuth, storage: IStorageHandler) {
|
|||
publish(app, auth, storage, config);
|
||||
ping(app);
|
||||
stars(app, storage);
|
||||
|
||||
v1Search(app, auth, storage)
|
||||
|
||||
if (_.get(config, 'experiments.token') === true) {
|
||||
token(app, auth, storage, config);
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue