11import { AccessLevel , Inject , SingletonProto } from '@eggjs/tegg' ;
2+ import type { estypes } from '@elastic/elasticsearch' ;
3+ import dayjs from 'dayjs' ;
4+
25import { AbstractService } from '../../common/AbstractService' ;
3- import { getScopeAndName } from '../../common/PackageUtil' ;
6+ import { formatAuthor , getScopeAndName } from '../../common/PackageUtil' ;
47import { PackageManagerService } from './PackageManagerService' ;
5- import { SearchManifestType , SearchRepository } from '../../repository/SearchRepository' ;
8+ import { SearchManifestType , SearchMappingType , SearchRepository } from '../../repository/SearchRepository' ;
9+ import { PackageVersionDownloadRepository } from '../../repository/PackageVersionDownloadRepository' ;
10+ import { PackageRepository } from '../../repository/PackageRepository' ;
11+
612
713@SingletonProto ( {
814 accessLevel : AccessLevel . PUBLIC ,
@@ -12,7 +18,10 @@ export class PackageSearchService extends AbstractService {
1218 private readonly packageManagerService : PackageManagerService ;
1319 @Inject ( )
1420 private readonly searchRepository : SearchRepository ;
15-
21+ @Inject ( )
22+ private packageVersionDownloadRepository : PackageVersionDownloadRepository ;
23+ @Inject ( )
24+ protected packageRepository : PackageRepository ;
1625
1726 async syncPackage ( fullname : string , isSync = true ) {
1827 const [ scope , name ] = getScopeAndName ( fullname ) ;
@@ -22,26 +31,68 @@ export class PackageSearchService extends AbstractService {
2231 this . logger . warn ( '[PackageSearchService.syncPackage] save package:%s not found' , fullname ) ;
2332 return ;
2433 }
34+
35+ const pkg = await this . packageRepository . findPackage ( scope , name ) ;
36+ if ( ! pkg ) {
37+ this . logger . warn ( '[PackageSearchService.syncPackage] findPackage:%s not found' , fullname ) ;
38+ return ;
39+ }
40+
41+ // get last year download data
42+ const startDate = dayjs ( ) . subtract ( 1 , 'year' ) ;
43+ const endDate = dayjs ( ) ;
44+
45+ const entities = await this . packageVersionDownloadRepository . query ( pkg . packageId , startDate . toDate ( ) , endDate . toDate ( ) ) ;
46+ let downloadsAll = 0 ;
47+ for ( const entity of entities ) {
48+ for ( let i = 1 ; i <= 31 ; i ++ ) {
49+ const day = String ( i ) . padStart ( 2 , '0' ) ;
50+ const field = `d${ day } ` ;
51+ const counter = entity [ field ] ;
52+ if ( ! counter ) continue ;
53+ downloadsAll += counter ;
54+ }
55+ }
56+
57+ const { data : manifest } = fullManifests ;
58+
59+ const latestVersion = manifest [ 'dist-tags' ] . latest ;
60+
61+ const packageDoc : SearchMappingType = {
62+ name : manifest . name ,
63+ version : latestVersion ,
64+ _rev : manifest . _rev ,
65+ scope : scope ? scope . replace ( '@' , '' ) : 'unscoped' ,
66+ keywords : manifest . keywords || [ ] ,
67+ versions : Object . keys ( manifest . versions ) ,
68+ description : manifest . description ,
69+ license : manifest . license ,
70+ maintainers : manifest . maintainers ,
71+ author : formatAuthor ( manifest . author ) ,
72+ 'dist-tags' : manifest [ 'dist-tags' ] ,
73+ date : manifest . time ?. [ latestVersion ] ,
74+ created : manifest . time . created ,
75+ modified : manifest . time . modified ,
76+ } ;
77+
2578 const document : SearchManifestType = {
26- package : fullManifests . data ,
27- // TODO get download data from internal data
79+ package : packageDoc ,
2880 downloads : {
29- all : 0 ,
81+ all : downloadsAll ,
3082 } ,
3183 } ;
3284
3385 return await this . searchRepository . upsertPackage ( document ) ;
3486 }
3587
36- async searchPackage ( text : string | undefined , from : number , size : number ) : Promise < ( SearchManifestType | undefined ) [ ] > {
88+ async searchPackage ( text : string | undefined , from : number , size : number ) : Promise < { objects : ( SearchManifestType | undefined ) [ ] , total : number } > {
3789 const matchQueries = this . _buildMatchQueries ( text ) ;
3890 const scriptScore = this . _buildScriptScore ( {
3991 text,
4092 scoreEffect : 0.25 ,
4193 } ) ;
4294
4395 const res = await this . searchRepository . searchPackage ( {
44- type : 'score' ,
4596 body : {
4697 size,
4798 from,
@@ -59,14 +110,17 @@ export class PackageSearchService extends AbstractService {
59110 } ,
60111 } ,
61112 } ) ;
62- const data = res . hits . map ( item => {
63- return item . _source ;
64- } ) ;
65- return data ;
113+ const { hits, total } = res ;
114+ return {
115+ objects : hits ?. map ( item => {
116+ return item . _source ;
117+ } ) ,
118+ total : ( total as estypes . SearchTotalHits ) . value ,
119+ } ;
66120 }
67121
68122 async removePackage ( fullname : string ) {
69- return await this . searchRepository . remotePackage ( fullname ) ;
123+ return await this . searchRepository . removePackage ( fullname ) ;
70124 }
71125
72126 // https://github.com/npms-io/queries/blob/master/lib/search.js#L8C1-L78C2
@@ -145,8 +199,7 @@ export class PackageSearchService extends AbstractService {
145199 private _buildScriptScore ( params : { text : string | undefined , scoreEffect : number } ) {
146200 // keep search simple, only download(popularity)
147201 const downloads = 'doc["downloads.all"].value' ;
148- const source = `doc["package.name.raw"].value.equals(params.text) ? 100000 + ${ downloads } : _score * Math.pow(${ downloads } , params.scoreEffect)` ;
149-
202+ const source = `doc["package.name.raw"].value.equals("${ params . text } ") ? 100000 + ${ downloads } : _score * Math.pow(${ downloads } , ${ params . scoreEffect } )` ;
150203 return {
151204 script : {
152205 source,
0 commit comments