aboutsummaryrefslogtreecommitdiffhomepage
path: root/client/src/app/shared
diff options
context:
space:
mode:
Diffstat (limited to 'client/src/app/shared')
-rw-r--r--client/src/app/shared/shared-search/advanced-search.model.ts160
-rw-r--r--client/src/app/shared/shared-search/index.ts3
-rw-r--r--client/src/app/shared/shared-search/search.service.ts88
-rw-r--r--client/src/app/shared/shared-search/shared-search.module.ts20
4 files changed, 271 insertions, 0 deletions
diff --git a/client/src/app/shared/shared-search/advanced-search.model.ts b/client/src/app/shared/shared-search/advanced-search.model.ts
new file mode 100644
index 000000000..516854a8c
--- /dev/null
+++ b/client/src/app/shared/shared-search/advanced-search.model.ts
@@ -0,0 +1,160 @@
1import { NSFWQuery, SearchTargetType } from '@shared/models'
2
3export class AdvancedSearch {
4 startDate: string // ISO 8601
5 endDate: string // ISO 8601
6
7 originallyPublishedStartDate: string // ISO 8601
8 originallyPublishedEndDate: string // ISO 8601
9
10 nsfw: NSFWQuery
11
12 categoryOneOf: string
13
14 licenceOneOf: string
15
16 languageOneOf: string
17
18 tagsOneOf: string
19 tagsAllOf: string
20
21 durationMin: number // seconds
22 durationMax: number // seconds
23
24 sort: string
25
26 searchTarget: SearchTargetType
27
28 // Filters we don't want to count, because they are mandatory
29 private silentFilters = new Set([ 'sort', 'searchTarget' ])
30
31 constructor (options?: {
32 startDate?: string
33 endDate?: string
34 originallyPublishedStartDate?: string
35 originallyPublishedEndDate?: string
36 nsfw?: NSFWQuery
37 categoryOneOf?: string
38 licenceOneOf?: string
39 languageOneOf?: string
40 tagsOneOf?: string
41 tagsAllOf?: string
42 durationMin?: string
43 durationMax?: string
44 sort?: string
45 searchTarget?: SearchTargetType
46 }) {
47 if (!options) return
48
49 this.startDate = options.startDate || undefined
50 this.endDate = options.endDate || undefined
51 this.originallyPublishedStartDate = options.originallyPublishedStartDate || undefined
52 this.originallyPublishedEndDate = options.originallyPublishedEndDate || undefined
53
54 this.nsfw = options.nsfw || undefined
55 this.categoryOneOf = options.categoryOneOf || undefined
56 this.licenceOneOf = options.licenceOneOf || undefined
57 this.languageOneOf = options.languageOneOf || undefined
58 this.tagsOneOf = options.tagsOneOf || undefined
59 this.tagsAllOf = options.tagsAllOf || undefined
60 this.durationMin = parseInt(options.durationMin, 10)
61 this.durationMax = parseInt(options.durationMax, 10)
62
63 this.searchTarget = options.searchTarget || undefined
64
65 if (isNaN(this.durationMin)) this.durationMin = undefined
66 if (isNaN(this.durationMax)) this.durationMax = undefined
67
68 this.sort = options.sort || '-match'
69 }
70
71 containsValues () {
72 const exceptions = new Set([ 'sort', 'searchTarget' ])
73
74 const obj = this.toUrlObject()
75 for (const k of Object.keys(obj)) {
76 if (this.silentFilters.has(k)) continue
77
78 if (obj[k] !== undefined && obj[k] !== '') return true
79 }
80
81 return false
82 }
83
84 reset () {
85 this.startDate = undefined
86 this.endDate = undefined
87 this.originallyPublishedStartDate = undefined
88 this.originallyPublishedEndDate = undefined
89 this.nsfw = undefined
90 this.categoryOneOf = undefined
91 this.licenceOneOf = undefined
92 this.languageOneOf = undefined
93 this.tagsOneOf = undefined
94 this.tagsAllOf = undefined
95 this.durationMin = undefined
96 this.durationMax = undefined
97
98 this.sort = '-match'
99 }
100
101 toUrlObject () {
102 return {
103 startDate: this.startDate,
104 endDate: this.endDate,
105 originallyPublishedStartDate: this.originallyPublishedStartDate,
106 originallyPublishedEndDate: this.originallyPublishedEndDate,
107 nsfw: this.nsfw,
108 categoryOneOf: this.categoryOneOf,
109 licenceOneOf: this.licenceOneOf,
110 languageOneOf: this.languageOneOf,
111 tagsOneOf: this.tagsOneOf,
112 tagsAllOf: this.tagsAllOf,
113 durationMin: this.durationMin,
114 durationMax: this.durationMax,
115 sort: this.sort,
116 searchTarget: this.searchTarget
117 }
118 }
119
120 toAPIObject () {
121 return {
122 startDate: this.startDate,
123 endDate: this.endDate,
124 originallyPublishedStartDate: this.originallyPublishedStartDate,
125 originallyPublishedEndDate: this.originallyPublishedEndDate,
126 nsfw: this.nsfw,
127 categoryOneOf: this.intoArray(this.categoryOneOf),
128 licenceOneOf: this.intoArray(this.licenceOneOf),
129 languageOneOf: this.intoArray(this.languageOneOf),
130 tagsOneOf: this.intoArray(this.tagsOneOf),
131 tagsAllOf: this.intoArray(this.tagsAllOf),
132 durationMin: this.durationMin,
133 durationMax: this.durationMax,
134 sort: this.sort,
135 searchTarget: this.searchTarget
136 }
137 }
138
139 size () {
140 let acc = 0
141
142 const obj = this.toUrlObject()
143 for (const k of Object.keys(obj)) {
144 if (this.silentFilters.has(k)) continue
145
146 if (obj[k] !== undefined && obj[k] !== '') acc++
147 }
148
149 return acc
150 }
151
152 private intoArray (value: any) {
153 if (!value) return undefined
154 if (Array.isArray(value)) return value
155
156 if (typeof value === 'string') return value.split(',')
157
158 return [ value ]
159 }
160}
diff --git a/client/src/app/shared/shared-search/index.ts b/client/src/app/shared/shared-search/index.ts
new file mode 100644
index 000000000..f687f6767
--- /dev/null
+++ b/client/src/app/shared/shared-search/index.ts
@@ -0,0 +1,3 @@
1export * from './advanced-search.model'
2export * from './search.service'
3export * from './shared-search.module'
diff --git a/client/src/app/shared/shared-search/search.service.ts b/client/src/app/shared/shared-search/search.service.ts
new file mode 100644
index 000000000..96b954c99
--- /dev/null
+++ b/client/src/app/shared/shared-search/search.service.ts
@@ -0,0 +1,88 @@
1import { Observable } from 'rxjs'
2import { catchError, map, switchMap } from 'rxjs/operators'
3import { HttpClient, HttpParams } from '@angular/common/http'
4import { Injectable } from '@angular/core'
5import { ComponentPaginationLight, RestExtractor, RestPagination, RestService } from '@app/core'
6import { peertubeLocalStorage } from '@app/helpers'
7import { Video, VideoChannel, VideoChannelService, VideoService } from '@app/shared/shared-main'
8import { ResultList, SearchTargetType, Video as VideoServerModel, VideoChannel as VideoChannelServerModel } from '@shared/models'
9import { environment } from '../../../environments/environment'
10import { AdvancedSearch } from './advanced-search.model'
11
12@Injectable()
13export class SearchService {
14 static BASE_SEARCH_URL = environment.apiUrl + '/api/v1/search/'
15
16 constructor (
17 private authHttp: HttpClient,
18 private restExtractor: RestExtractor,
19 private restService: RestService,
20 private videoService: VideoService
21 ) {
22 // Add ability to override search endpoint if the user updated this local storage key
23 const searchUrl = peertubeLocalStorage.getItem('search-url')
24 if (searchUrl) SearchService.BASE_SEARCH_URL = searchUrl
25 }
26
27 searchVideos (parameters: {
28 search: string,
29 componentPagination?: ComponentPaginationLight,
30 advancedSearch?: AdvancedSearch
31 }): Observable<ResultList<Video>> {
32 const { search, componentPagination, advancedSearch } = parameters
33
34 const url = SearchService.BASE_SEARCH_URL + 'videos'
35 let pagination: RestPagination
36
37 if (componentPagination) {
38 pagination = this.restService.componentPaginationToRestPagination(componentPagination)
39 }
40
41 let params = new HttpParams()
42 params = this.restService.addRestGetParams(params, pagination)
43
44 if (search) params = params.append('search', search)
45
46 if (advancedSearch) {
47 const advancedSearchObject = advancedSearch.toAPIObject()
48 params = this.restService.addObjectParams(params, advancedSearchObject)
49 }
50
51 return this.authHttp
52 .get<ResultList<VideoServerModel>>(url, { params })
53 .pipe(
54 switchMap(res => this.videoService.extractVideos(res)),
55 catchError(err => this.restExtractor.handleError(err))
56 )
57 }
58
59 searchVideoChannels (parameters: {
60 search: string,
61 searchTarget?: SearchTargetType,
62 componentPagination?: ComponentPaginationLight
63 }): Observable<ResultList<VideoChannel>> {
64 const { search, componentPagination, searchTarget } = parameters
65
66 const url = SearchService.BASE_SEARCH_URL + 'video-channels'
67
68 let pagination: RestPagination
69 if (componentPagination) {
70 pagination = this.restService.componentPaginationToRestPagination(componentPagination)
71 }
72
73 let params = new HttpParams()
74 params = this.restService.addRestGetParams(params, pagination)
75 params = params.append('search', search)
76
77 if (searchTarget) {
78 params = params.append('searchTarget', searchTarget as string)
79 }
80
81 return this.authHttp
82 .get<ResultList<VideoChannelServerModel>>(url, { params })
83 .pipe(
84 map(res => VideoChannelService.extractVideoChannels(res)),
85 catchError(err => this.restExtractor.handleError(err))
86 )
87 }
88}
diff --git a/client/src/app/shared/shared-search/shared-search.module.ts b/client/src/app/shared/shared-search/shared-search.module.ts
new file mode 100644
index 000000000..134300d88
--- /dev/null
+++ b/client/src/app/shared/shared-search/shared-search.module.ts
@@ -0,0 +1,20 @@
1import { NgModule } from '@angular/core'
2import { SharedMainModule } from '../shared-main'
3import { SearchService } from './search.service'
4
5@NgModule({
6 imports: [
7 SharedMainModule
8 ],
9
10 declarations: [
11 ],
12
13 exports: [
14 ],
15
16 providers: [
17 SearchService
18 ]
19})
20export class SharedSearchModule { }